diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index f2548bd8702..00000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.pb filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93d40f8b043..7036da0df5f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,16 +1,38 @@ # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners # Each line is a file pattern followed by one or more owners. +# Order is important; the last matching pattern takes the most +# precedence. # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -# @openconfig/featureprofiles-maintainers will be requested for +# @openconfig/featureprofiles-approvers will be requested for # review when someone opens a pull request. -* @openconfig/featureprofiles-maintainers @openconfig/featureprofiles-quattro-tl +* @openconfig/featureprofiles-approvers -# Tests which are ported from ate_tests to otg_tests may be reviewed by this team. -**/otg_tests/** @openconfig/featureprofiles-maintainers-otg @openconfig/featureprofiles-maintainers @openconfig/featureprofiles-quattro-tl -/internal/otgutils/ @openconfig/featureprofiles-maintainers-otg @openconfig/featureprofiles-maintainers @openconfig/featureprofiles-quattro-tl +# /feature folders each have owners who are auto requested for review and may merge PR's +/feature/acl/ @openconfig/featureprofiles-owner-acl +/feature/aft/ @openconfig/featureprofiles-owner-aft +/feature/bgp/ @openconfig/featureprofiles-owner-bgp +/feature/dhcp/ @alokmtri-g +/feature/ethernet/ @ram-mac +/feature/gribi/ @nflath @nachikethas @xw-g +/feature/interface/ @openconfig/featureprofiles-owner-interface +/feature/isis/ @openconfig/featureprofiles-owner-isis +/feature/lldp/ @openconfig/featureprofiles-owner-lldp +/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/routing_policy/ @swetha-haridasula +/feature/sampling/ @sudhinj +/feature/security @openconfig/featureprofiles-owner-security +/feature/staticroute/ @openconfig/featureprofiles-owner-staticroute +/feature/stp/ @alokmtri-g +/feature/system @openconfig/featureprofiles-owner-system +/feature/vrrp @amrindrr -# Order is important; the last matching pattern takes the most -# precedence. +# Common OTG utilities +/internal/otgutils/ @openconfig/featureprofiles-maintainers-otg diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index aa390f695ff..ff20539b0a0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,43 +11,41 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@dc323e67f16fb5f7663d20ff7941f27f5809e9b6 - name: Set up Go - uses: actions/setup-go@v2.1.3 + uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: go-version: 1.21.x - name: Cache - uses: actions/cache@v3 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ github.job }}-${{ runner.os }}-go-build- - name: Build run: go build -v ./... test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@dc323e67f16fb5f7663d20ff7941f27f5809e9b6 - name: Set up Go - uses: actions/setup-go@v2.1.3 + uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: go-version: 1.21.x - name: Cache - uses: actions/cache@v3 + uses: actions/cache@f4b3439a656ba812b8cb417d2d49f9c810103092 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ github.job }}-${{ runner.os }}-go-build- # Dependency for Go module github.com/google/gopacket - name: Install libpcap-dev run: sudo apt-get -y install libpcap-dev - run: go test -v -coverprofile=profile.cov $(go list ./... | grep -v /.*test.*) - name: Send coverage - uses: shogo82148/actions-goveralls@v1 + uses: shogo82148/actions-goveralls@7b1bd2871942af030d707d6574e5f684f9891fb2 with: path-to-profile: profile.cov static_analysis: @@ -55,7 +53,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install go - uses: actions/setup-go@v2 + uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: go-version: '1.21' # Go & staticcheck build cache require a lot of disk space. Reclaim extra @@ -65,17 +63,18 @@ jobs: sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo mv "${HOME}/.cache" /mnt/cache + ln -s /mnt/cache "${HOME}/.cache" - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 with: path: | ~/go/pkg/mod ~/.cache/go-build ~/.cache/staticcheck key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ github.job }}-${{ runner.os }}-go-build- # Dependency for Go module github.com/google/gopacket - name: Install libpcap-dev run: sudo apt-get -y install libpcap-dev @@ -99,11 +98,13 @@ jobs: # # goimports does not support "gofmt -s" so both goimports and gofmt are # required. - if goimports -d . | grep '^'; then - exit 1 - fi + find . -name "*.go" | egrep -v "pb.go$" | while read l; do + if goimports -d $l | grep '^'; then + exit 1; + fi; + done - name: Get revive - run: go install github.com/mgechev/revive@latest + run: go install github.com/mgechev/revive@v1.3.4 - name: Run revive run: revive ./... - name: Get staticcheck diff --git a/.github/workflows/nosimage.yml b/.github/workflows/nosimage.yml new file mode 100644 index 00000000000..45a6e276b46 --- /dev/null +++ b/.github/workflows/nosimage.yml @@ -0,0 +1,42 @@ +name: NOSImage validation script + +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 + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe + with: + go-version: stable + cache: false + - name: Generate Examples and Check No Diff + run: | + cd tools/nosimage + go generate ./example + + git diff --exit-code --ignore-all-space --ignore-blank-lines + + - name: Validate Good Example + run: | + cd tools/nosimage + go run validate/validate.go -file example/valid_example_nosimageprofile.textproto; rm -rf tmp + + - name: Validate Bad Example + run: | + cd tools/nosimage + for file in example/invalid-*.textproto; do + if go run validate/validate.go -file "$file"; then + echo "Validation passed for $file, but failure expected" + exit 1 + fi + done + rm -rf tmp diff --git a/.github/workflows/protobufs.yml b/.github/workflows/protobufs.yml index eb720c83e01..99326ed33a6 100644 --- a/.github/workflows/protobufs.yml +++ b/.github/workflows/protobufs.yml @@ -13,99 +13,57 @@ jobs: runs-on: ubuntu-latest steps: - name: Install go - uses: actions/setup-go@v2 + uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: go-version: '1.21' - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ github.job }}-${{ runner.os }}-go-build- - name: Install protobuf - uses: arduino/setup-protoc@v1 + uses: arduino/setup-protoc@149f6c87b92550901b26acd1632e11c3662e381f with: version: '3.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Lint protobufs run: | go install github.com/googleapis/api-linter/cmd/api-linter@latest - # Set directory to hold symlink - readonly PROTOBUF_IMPORT_DIR='protobuf-import' - mkdir -p "${PROTOBUF_IMPORT_DIR}" - # Remove any existing symlinks & empty directories - find "${PROTOBUF_IMPORT_DIR}" -type l -delete - find "${PROTOBUF_IMPORT_DIR}" -type d -empty -delete - # Download the required dependencies - go mod download - # Copy all of the proto files into the right directory. - fp_proto_dir="${PROTOBUF_IMPORT_DIR}/github.com/openconfig/featureprofiles" - mkdir -p "${fp_proto_dir}" - cp --parents `find -name \*.proto` "${fp_proto_dir}" - # Get ondatra modules we use and create required directory structure - go list -f "${PROTOBUF_IMPORT_DIR}/{{ .Path }}" -m github.com/openconfig/ondatra | xargs -L1 dirname | sort | uniq | xargs mkdir -p - # Create symlink - go list -f "{{ .Dir }} "${PROTOBUF_IMPORT_DIR}"/{{ .Path }}" -m github.com/openconfig/ondatra | xargs -L1 -- ln -s - cd "${fp_proto_dir}" - find . -name \*.proto -exec api-linter -I"${OLDPWD}"/"${PROTOBUF_IMPORT_DIR}" --disable-rule all --enable-rule core {} \+ - - name: Compile topology binding textprotos + make protoimports + cd protobuf-import + find github.com/openconfig/featureprofiles/ -name \*.proto -exec api-linter --disable-rule all --enable-rule core {} \+ + - name: Validate textprotos run: | - fail=0 - # Set directory to hold symlink - readonly PROTOBUF_IMPORT_DIR='protobuf-import' - mkdir -p "${PROTOBUF_IMPORT_DIR}" - # Remove any existing symlinks & empty directories - find "${PROTOBUF_IMPORT_DIR}" -type l -delete - find "${PROTOBUF_IMPORT_DIR}" -type d -empty -delete - # Download the required dependencies - go mod download - # Get ondatra modules we use and create required directory structure - go list -f "${PROTOBUF_IMPORT_DIR}/{{ .Path }}" -m github.com/openconfig/ondatra | xargs -L1 dirname | sort | uniq | xargs mkdir -p - # Create symlink - go list -f "{{ .Dir }} \"${PROTOBUF_IMPORT_DIR}\"/{{ .Path }}" -m github.com/openconfig/ondatra | xargs -L1 -- ln -s - for i in `find topologies/ -type f -name "*.binding"`; do - if ! output=$(protoc -I="${PROTOBUF_IMPORT_DIR}" --proto_path=topologies/proto --encode=openconfig.testing.Binding topologies/proto/binding.proto < $i 2>&1 >/dev/null); then - fail=1 - echo -e "Compile $i failed:\n$output\n" - fi - done - if [ "$fail" == "1" ]; then exit 1; fi - - name: Compile feature profile textprotos - run: | - fail=0 - for i in `find feature/ -type f -name "feature.textproto"`; do - if ! output=$(protoc --encode=openconfig.profiles.FeatureProfile proto/feature.proto < $i 2>&1 >/dev/null); then - fail=1 - echo -e "Compile $i failed:\n$output\n" - fi + go install github.com/bstoll/textproto-validator@15e24d0eb567d63615f0aa70940bc073ab674fe7 + make protoimports + for i in `find . -name \*.textproto`; do + textproto-validator -I ./protobuf-import $i done - if [ "$fail" == "1" ]; then exit 1; fi validate_oc_paths: name: Validate OpenConfig Paths runs-on: ubuntu-latest steps: - name: Install go - uses: actions/setup-go@v2 + uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: go-version: '1.21' - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 with: fetch-depth: 0 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ github.job }}-${{ runner.os }}-go-build- - - name: Fetch Openconfig Models + - name: Fetch OpenConfig Models run: make openconfig_public - name: Validate Paths run: | diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 141a200e01d..a90c1025bbd 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,22 +1,38 @@ name: Pull Request on: [pull_request] jobs: - check_style: - name: Check style against CONTRIBUTING.md + check_ips: runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - name: Setup Perl - uses: perl-actions/install-with-cpanm@v1 + uses: perl-actions/install-with-cpanm@10d60f00b4073f484fc29d45bfbe2f776397ab3d with: install: | Net::IP - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - name: IP Addresses Assignment run: | - find . -name \*.go -exec ./tools/check_ip_addresses.pl \{} + + git diff --name-only main | while read l; do + ./tools/check_ip_addresses.pl $l; + done + + check_style: + name: Check style against CONTRIBUTING.md + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - name: Allowed File Types run: ./tools/allowed_file_types.sh + - name: Block hyphenated directory names + run: | + if ! find ./feature -type d -name '*-*' -print -exec false {} +; then + echo "Hyphenated directories are not allowed. Please use a different separator like underscore." + exit 1 + fi - name: Enum run: | fail=0 @@ -49,9 +65,9 @@ jobs: name: OTG Changes Required runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - name: Check if OTG changes required - uses: actions/github-script@v6 + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 with: script: | const script = require('./.github/workflows/required_otg_changes_check.js') diff --git a/.github/workflows/readme_oc_path_and_rpc.yml b/.github/workflows/readme_oc_path_and_rpc.yml new file mode 100644 index 00000000000..23215b74d7a --- /dev/null +++ b/.github/workflows/readme_oc_path_and_rpc.yml @@ -0,0 +1,99 @@ +name: README OpenConfig Path and RPC Coverage + +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: stable + cache: false + + - name: Validate Validation Script + run: | + cd tools/validate_readme_spec + ./validate_readme_spec_test.sh + + - name: Validate Template README + run: | + go install ./tools/validate_readme_spec + validate_readme_spec --alsologtostderr doc/test-requirements-template.md + + - name: Validate Test READMEs + 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[@]}" + } + + ##### 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 f in $(git diff --name-only "${BASE}" "${HEAD}" | grep -E '^\W*feature' | xargs -r dirname | sort -u | sed 's/$/\/README.md/'); do + if [ -f "$f" ]; then + affected_readmes+=("$f") + 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:" + for f in "${affected_readmes[@]}"; do + validate_readme_spec --alsologtostderr "${exemption_flags[@]}" "${f}" + done + fi + + ##### END: Validate Changed Test READMEs ##### + + echo "########## Validating already-converted READMEs:" + validate feature/aft + validate feature/bgp/policybase/otg_tests/import_export_multi_test + validate feature/gnmi + validate feature/gnoi + validate feature/isis + validate feature/mtu + validate feature/networkinstance + validate feature/security + validate feature/staticroute + validate feature/system/management + validate feature/system/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/rebase_check.yml b/.github/workflows/rebase_check.yml index 76e330acb1a..69f825aef51 100644 --- a/.github/workflows/rebase_check.yml +++ b/.github/workflows/rebase_check.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 with: fetch-depth: 0 - name: Check if dependencies and checks are up to date. diff --git a/.github/workflows/required_approvals.yml b/.github/workflows/required_approvals.yml new file mode 100644 index 00000000000..4a2123d17d6 --- /dev/null +++ b/.github/workflows/required_approvals.yml @@ -0,0 +1,24 @@ +# 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: + branches: + - main +jobs: + check-approvals: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: read + steps: + - name: Check for required approvals + id: check-approvals + uses: skymoore/required-approvals@57612e00c501132dfb35ddaff54615b363f8e076 + with: + token: ${{ secrets.GITHUB_TOKEN }} + read_org_scoped_token: ${{ secrets.READ_ORG_SCOPED_TOKEN }} + org_name: openconfig + min_approvals: 2 + approval_mode: ALL + pr_number: ${{ github.event.number }} diff --git a/.github/workflows/rundata_check.yml b/.github/workflows/rundata_check.yml index 65657a213ee..33373f14d70 100644 --- a/.github/workflows/rundata_check.yml +++ b/.github/workflows/rundata_check.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - name: Check that addrundata is up to date. run: go run ./tools/addrundata diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index c12394a9d6e..a0124455fb2 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -9,26 +9,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: path: featureprofiles - name: Checkout wiki - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: "openconfig/featureprofiles.wiki" path: featureprofiles.wiki - name: Set up Go - uses: actions/setup-go@v2.1.3 + uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: go-version: 1.21.x - name: Cache - uses: actions/cache@v3 + uses: actions/cache@f4b3439a656ba812b8cb417d2d49f9c810103092 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ github.job }}-${{ runner.os }}-go-build- - name: Build Wiki run: | pushd featureprofiles.wiki diff --git a/.gitignore b/.gitignore index 93acb89f3cb..07ebd5a939c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *~ topologies/kne/testbed.kne.yml .vscode/ +.idea/ # used by `make validate_paths` openconfig_public/ # used by `make proto/...` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75f51a7cac6..2290141cb5a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,15 +62,13 @@ The directory tree is organized as follows: * `cloudbuild/` contains google cloud build scripts for running virtual routers in containers on [KNE](https://github.com/openconfig/kne) * `feature/` contains definition and tests of feature profiles. -* `feature/experimental` contains new features and tests which are not yet - categorized or not confirmed to pass on any hardware platform or software - release. When the test is deemed more mature, it is moved to the `feature/` - directory. * `internal/` contains packages used by feature profile tests. * `proto/` contains protobuf files for feature profiles. * `tools/` contains code used for CI checks. * `topologies/` contains the testbed topology definitions. +Directory names are not allowed to contain hyphen (-) characters. + ## Allowed File Types Regular files should be plain text in either ASCII or UTF-8 encoding. Please @@ -94,7 +92,7 @@ allowed file types, please file an issue for discussion. ## Test Suite Organization Test suites should be placed in subdirectories formatted like -`feature//[/]//.go`. +`feature//[/]//.go`. For example: * `feature/interface/` is the collection of interface feature profiles. @@ -105,7 +103,8 @@ For example: * `feature/interface/singleton/feature.textproto` - defines the singleton interface feature profile in machine readable format. * `feature/interface/singleton/ate_tests/` contains the singleton interfaces - test suite using ATE traffic generation API. + test suite using ATE traffic generation API. Note, use of the ATE API is + deprecated and should not be used for any new test development. * `feature/interface/singleton/otg_tests/` contains the singleton interfaces test suite using OTG traffic generation API. * `feature/interface/singleton/kne_tests/` contains the singleton interfaces @@ -123,8 +122,8 @@ in the [project](https://github.com/orgs/openconfig/projects/2/views/1) item. Each test must also be accompanied by a `metadata.textproto` file that supplies the metadata for annotating the JUnit XML test results. This file can be -generated or updated using the command: `go run ./tools/addrundata --fix`. -See [addrundata](/tools/addrundata/README.md) for more info. +generated or updated using the command: `go run ./tools/addrundata --fix`. See +[addrundata](/tools/addrundata/README.md) for more info. For example: @@ -175,7 +174,7 @@ were discovered when implementing the code. ## Test Structure Generally, a Feature Profiles ONDATRA test has the following stages: configure -DUT, configure ATE, generate and verify traffic, verify telemetry. The +DUT, configure OTG, generate and verify traffic, verify telemetry. The configuration stages should be factored out to their own functions, and any subtests should be run under `t.Run` so the test output clearly reflects which parts of the test passed and which parts failed. @@ -187,13 +186,13 @@ occurred. ``` func TestFoo(t *testing.T) { configureDUT(t) // calls t.Fatal() on error. - configureATE(t) // calls t.Fatal() on error. + configureOTG(t) // calls t.Fatal() on error. t.Run("Traffic", func(t *testing.T) { ... }) t.Run("Telemetry", func(t *testing.T) { ... }) } ``` -In the above example, `configureDUT` and `configureATE` should not be subtests, +In the above example, `configureDUT` and `configureOTG` should not be subtests, otherwise they could be skipped when someone specifies a test filter. The "Traffic" and "Telemetry" subtests will both run even if there is a fatal condition during `t.Run()`. @@ -217,7 +216,7 @@ func TestTableDriven(t *testing.T) { t.Run(c.name, func(t *testing.T) { t.Log("Description: ", c.desc) configureDUT(t, /* parameterized by c */) - configureATE(t, /* parameterized by c */) + configureOTG(t, /* parameterized by c */) t.Run("Traffic", func(t *testing.T) { ... }) t.Run("Telemetry", func(t *testing.T) { ... }) }) @@ -338,28 +337,29 @@ a1v4.Protocol, _ = a1v4.To_Acl_AclSet_AclEntry_Ipv4_Protocol_Union(6) ## IP Addresses Assignment -Netblocks used in the test topology should follow IPv4 Address Blocks Reserved -for Documentation ([RFC 5737]), IPv4 reserved for Benchmarking Methodology -([RFC 2544]), and IPv6 Address Prefix Reserved for Documentation ([RFC 3849]). -In particular: - -[RFC 5737]: https://datatracker.ietf.org/doc/html/rfc5737 -[RFC 2544]: https://datatracker.ietf.org/doc/html/rfc2544 -[RFC 3849]: https://datatracker.ietf.org/doc/html/rfc3849 +> **Warning:** Though we are trying to use RFC defined non-globally routable +> space in tests, there might be tests (e.g. scaling tests) that are still using +> public routable ranges. Users who run the tests own the responsibility of not +> leaking test traffic to internet. ### IPv4 -* `TEST-NET-1`: (192.0.2.0/24): control plane addresses split into /30 subnets - for each ATE/DUT port pair. -* `TEST-NET-2`: (198.51.100.0/24): data plane source network addresses used - for traffic testing; split as needed. -* `TEST-NET-3`: (203.0.113.0/24): data plane destination network addresses - used for traffic testing; split as needed. -* `BMWG`: (198.18.0.0/15): additional data plane networks. +* 192.0.2.0/24 ([TEST-NET-1](https://www.iana.org/go/rfc5737)): control plane + addresses split into /30 subnets for each ATE/DUT port pair. +* 198.51.100.0/24 ([TEST-NET-2](https://www.iana.org/go/rfc5737)): data plane + source network addresses used for traffic testing; split as needed. +* 203.0.113.0/24 ([TEST-NET-3](https://www.iana.org/go/rfc5737)): data plane + destination network addresses used for traffic testing; split as needed. +* 100.64.0.0/10 ([CGN Shared Space](https://www.iana.org/go/rfc6598)): + additional network address; split as needed. +* 198.18.0.0/15 ([Device Benchmark Testing](https://www.iana.org/go/rfc2544)): + additional network address; split as needed. ### IPv6 -2001:DB8::/32 is a very large space, so we divide them as follows. +2001:DB8::/32 +([Reserved for Documentation](https://datatracker.ietf.org/doc/html/rfc3849)) is +a very large space, so we divide them as follows. * 2001:DB8:0::/64: control plane addresses split into /126 subnets for each ATE/DUT port pair. @@ -368,6 +368,9 @@ In particular: * 2001:DB8:2::/64: data plane addresses used for traffic testing as the destination address; split as needed. +Link local addresses (FE80::/10) addresses are allowed in contexts where link +local is being tested. + ### Rationale The properties being tested in the test plan are agnostic to the IP addresses @@ -426,8 +429,6 @@ To contribute a pull request: [GitHub Quickstart](https://docs.github.com/en/get-started/quickstart) guide. - * New contributions should be in the feature/experimental directory. - 1. When opening a pull request, use a descriptive title and detail. See [Pull Request Title](#pull-request-title) below. @@ -474,7 +475,7 @@ preferred format is: ``` * (M) internal/fptest/* - Add a helper for referencing a keychain from other modules. - * (M) feature/experimental/isis/ate_tests/base_adjacencies_test + * (M) feature/isis/otg_tests/base_adjacencies_test - Fix testing of hello-authentication to reference a specific keychain. - Fix authentication of *SNP packets, referencing a keychain diff --git a/Makefile b/Makefile index 45756fbd5f5..79898629989 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,16 @@ # 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. + +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +GO_PROTOS:=proto/feature_go_proto/feature.pb.go proto/metadata_go_proto/metadata.pb.go proto/ocpaths_go_proto/ocpaths.pb.go proto/ocrpcs_go_proto/ocrpcs.pb.go proto/nosimage_go_proto/nosimage.pb.go topologies/proto/binding/binding.pb.go + +.PHONY: all clean protos validate_paths protoimports +all: openconfig_public protos validate_paths + openconfig_public: tools/clone_oc_public.sh openconfig_public -.PHONY: validate_paths validate_paths: openconfig_public proto/feature_go_proto/feature.pb.go go run -v tools/validate_paths.go \ -alsologtostderr \ @@ -23,12 +29,9 @@ validate_paths: openconfig_public proto/feature_go_proto/feature.pb.go --yang_skip_roots=$(CURDIR)/openconfig_public/release/models/wifi \ --feature_files=${FEATURE_FILES} -proto/feature_go_proto/feature.pb.go: proto/feature.proto - mkdir -p proto/feature_go_proto - protoc --proto_path=proto --go_out=./ --go_opt=Mfeature.proto=proto/feature_go_proto feature.proto +protos: $(GO_PROTOS) -proto/metadata_go_proto/metadata.pb.go: proto/metadata.proto - mkdir -p proto/metadata_go_proto +protoimports: # Set directory to hold symlink mkdir -p protobuf-import # Remove any existing symlinks & empty directories @@ -36,10 +39,21 @@ proto/metadata_go_proto/metadata.pb.go: proto/metadata.proto find protobuf-import -type d -empty -delete # Download the required dependencies go mod download - # Get ondatra modules we use and create required directory structure + # Get ondatra & kne modules we use and create required directory structure go list -f 'protobuf-import/{{ .Path }}' -m github.com/openconfig/ondatra | xargs -L1 dirname | sort | uniq | xargs mkdir -p - # Create symlink + 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/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 + +proto/feature_go_proto/feature.pb.go: proto/feature.proto + mkdir -p proto/feature_go_proto + protoc --proto_path=proto --go_out=./ --go_opt=Mfeature.proto=proto/feature_go_proto feature.proto + goimports -w proto/feature_go_proto/feature.pb.go + +proto/metadata_go_proto/metadata.pb.go: proto/metadata.proto protoimports + mkdir -p proto/metadata_go_proto protoc -I='protobuf-import' --proto_path=proto --go_out=./ --go_opt=Mmetadata.proto=proto/metadata_go_proto metadata.proto goimports -w proto/metadata_go_proto/metadata.pb.go @@ -53,7 +67,21 @@ proto/ocrpcs_go_proto/ocrpcs.pb.go: proto/ocrpcs.proto protoc --proto_path=proto --go_out=./ --go_opt=Mocrpcs.proto=proto/ocrpcs_go_proto ocrpcs.proto goimports -w proto/ocrpcs_go_proto/ocrpcs.pb.go -proto/nosimage_go_proto/nosimage.pb.go: proto/nosimage.proto +proto/nosimage_go_proto/nosimage.pb.go: proto/nosimage.proto protoimports mkdir -p proto/nosimage_go_proto - protoc -I="${GOPATH}/src" --proto_path=proto --go_out=./proto/nosimage_go_proto --go_opt=paths=source_relative --go_opt=Mnosimage.proto=proto/nosimage_go_proto --go_opt=Mgithub.com/openconfig/featureprofiles/proto/ocpaths.proto=github.com/openconfig/featureprofiles/proto/ocpaths_go_proto --go_opt=Mgithub.com/openconfig/featureprofiles/proto/ocrpcs.proto=github.com/openconfig/featureprofiles/proto/ocrpcs_go_proto nosimage.proto + protoc -I='protobuf-import' --proto_path=proto --go_out=./proto/nosimage_go_proto --go_opt=paths=source_relative --go_opt=Mnosimage.proto=proto/nosimage_go_proto --go_opt=Mgithub.com/openconfig/featureprofiles/proto/ocpaths.proto=github.com/openconfig/featureprofiles/proto/ocpaths_go_proto --go_opt=Mgithub.com/openconfig/featureprofiles/proto/ocrpcs.proto=github.com/openconfig/featureprofiles/proto/ocrpcs_go_proto nosimage.proto goimports -w proto/nosimage_go_proto/nosimage.pb.go + +proto/testregistry_go_proto/testregistry.pb.go: proto/testregistry.proto protoimports + mkdir -p proto/testregistry_go_proto + protoc -I='protobuf-import' --proto_path=proto --go_out=./proto/testregistry_go_proto --go_opt=paths=source_relative --go_opt=Mtestregistry.proto=proto/testregistry_go_proto testregistry.proto + goimports -w proto/testregistry_go_proto/testregistry.pb.go + +topologies/proto/binding/binding.pb.go: topologies/proto/binding.proto protoimports + mkdir -p topologies/proto/binding + protoc -I='protobuf-import' --proto_path=topologies/proto --go_out=. --go_opt=Mbinding.proto=topologies/proto/binding binding.proto + goimports -w topologies/proto/binding/binding.pb.go + +clean: + rm -f $(GO_PROTOS) + rm -rf protobuf-import openconfig_public diff --git a/README.md b/README.md index c160d39beb6..0524c430c01 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ or by opening a GitHub # Running Tests on Virtual Devices +> **Warning:** Though we are trying to use RFC defined non-globally routable +> space in tests, there might be tests (e.g. scaling tests) that are still using +> public routable ranges. Users who run the tests own the responsibility of not +> leaking test traffic to internet. + Tests may be run on virtual devices using the [Kubernetes Network Emulation](https://github.com/openconfig/kne) binding. @@ -50,7 +55,7 @@ kne create topologies/kne/arista/ceos/topology.textproto 2. Run a sample test: ``` -go test ./feature/system/tests/... -kne-topo $PWD/topologies/kne/arista/ceos/topology.textproto -vendor_creds ARISTA/admin/admin +go test ./feature/example/tests/... -kne-topo $PWD/topologies/kne/arista/ceos/topology.textproto -vendor_creds ARISTA/admin/admin ``` 3. Cleanup: @@ -109,30 +114,6 @@ kne delete topologies/kne/cisco/xrd/topology.textproto ## Juniper -### cPTX - -> NOTE: `cPTX` images require the host supports nested virtualization. - -Juniper `cPTX` images can be obtained by contacting Juniper. - -1. Create the topology: - -``` -kne create topologies/kne/juniper/cptx/topology.textproto -``` - -2. Run a sample test: - -``` -go test ./feature/example/tests/... -kne-topo $PWD/topologies/kne/juniper/cptx/topology.textproto -vendor_creds JUNIPER/root/Google123 -``` - -3. Cleanup: - -``` -kne delete topologies/kne/juniper/cptx/topology.textproto -``` - ### ncPTX Juniper `ncPTX` images can be obtained by contacting Juniper. diff --git a/cloudbuild/virtual.sh b/cloudbuild/virtual.sh index 1d9cc0f0b03..87991cc3f6f 100755 --- a/cloudbuild/virtual.sh +++ b/cloudbuild/virtual.sh @@ -50,9 +50,6 @@ case ${platform} in arista_ceos) vendor_creds=ARISTA/admin/admin ;; - juniper_cptx) - vendor_creds=JUNIPER/root/Google123 - ;; juniper_ncptx) vendor_creds=JUNIPER/root/Google123 ;; @@ -86,6 +83,7 @@ function metadata_kne_topology() { kne_topology_file["TESTBED_DUT_ATE_4LINKS"]="${topo_prefix}/dutate.textproto" kne_topology_file["TESTBED_DUT_ATE_9LINKS_LAG"]="${topo_prefix}/dutate_lag.textproto" kne_topology_file["TESTBED_DUT_DUT_ATE_2LINKS"]="${topo_prefix}/dutdutate.textproto" + kne_topology_file["TESTBED_DUT_ATE_8LINKS"]="${topo_prefix}/dutate.textproto" for p in "${!kne_topology_file[@]}"; do if grep -q "testbed.*${p}$" "${metadata_test_path}"/metadata.textproto; then echo "${kne_topology_file[${p}]}" @@ -113,7 +111,6 @@ for dut_test in ${dut_tests}; do test_badge=$(echo "${dut_test}" | awk '{split($0,a,",");print a[2]}') kne_topology=$(metadata_kne_topology "${test_path}") sed -i "s/ceos:latest/us-west1-docker.pkg.dev\/gep-kne\/arista\/ceos:ga/g" /tmp/kne/"${kne_topology}" - sed -i "s/cptx:latest/us-west1-docker.pkg.dev\/gep-kne\/juniper\/cptx:ga/g" /tmp/kne/"${kne_topology}" sed -i "s/ncptx:latest/us-west1-docker.pkg.dev\/gep-kne\/juniper\/ncptx:ga/g" /tmp/kne/"${kne_topology}" sed -i "s/8000e:latest/us-west1-docker.pkg.dev\/gep-kne\/cisco\/8000e:ga/g" /tmp/kne/"${kne_topology}" sed -i "s/xrd:latest/us-west1-docker.pkg.dev\/gep-kne\/cisco\/xrd:ga/g" /tmp/kne/"${kne_topology}" diff --git a/cloudbuild/virtual.yaml b/cloudbuild/virtual.yaml index 0636b762bf3..68f8f565378 100644 --- a/cloudbuild/virtual.yaml +++ b/cloudbuild/virtual.yaml @@ -1,12 +1,17 @@ steps: + - id: fetch-secrets + name: gcr.io/cloud-builders/gcloud + script: | + gcloud secrets versions access latest --secret=featureprofiles-ci-ssh > builder-key + gcloud secrets versions access latest --secret=featureprofiles-ci-ssh-pub > builder-key.pub - id: fp-presubmit - name: gcr.io/${PROJECT_ID}/remote-builder + name: us-west1-docker.pkg.dev/${PROJECT_ID}/featureprofiles-ci/remote-builder waitFor: ["-"] env: - USERNAME=user - - SSH_ARGS=--internal-ip --ssh-key-expire-after=1d + - SSH_ARGS=--internal-ip - INSTANCE_NAME=fp-presubmit-${BUILD_ID} - - INSTANCE_ARGS=--network cloudbuild-workers --image-project gep-kne --image-family kne --machine-type ${_MACHINE_TYPE} ${_MACHINE_ARGS} --boot-disk-size 200GB --scopes=default,compute-rw + - INSTANCE_ARGS=--network cloudbuild-workers --image-project gep-kne --image-family kne --machine-type ${_MACHINE_TYPE} ${_MACHINE_ARGS} --boot-disk-size 200GB --service-account=fp-kne@disco-idea-817.iam.gserviceaccount.com --scopes=default,compute-rw - ZONE=us-west1-a - REMOTE_WORKSPACE=/tmp/featureprofiles - COMMAND=sudo su -c "echo 'user ALL=(ALL) NOPASSWD:ALL' | sudo EDITOR='tee -a' visudo"; sudo -iu user /tmp/featureprofiles/cloudbuild/virtual.sh "${_DUT_PLATFORM}" "${_DUT_TESTS}" diff --git a/doc/test-requirements-template.md b/doc/test-requirements-template.md index b42c08871ef..4b65d1d866e 100644 --- a/doc/test-requirements-template.md +++ b/doc/test-requirements-template.md @@ -4,68 +4,115 @@ about: Use this template to document the requirements for a new test to be imple title: '' labels: enhancement assignees: '' - --- # Instructions for this template -Below is the required template for writing test requirements. Good examples of test -requirements include: +Below is the required template for writing test requirements. Good examples of +test requirements include: -* [TE-3.7: Base Hierarchical NHG Update](/feature/gribi/otg_tests/base_hierarchical_nhg_update/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) -* [RT-5.1: Singleton Interface](https://github.com/openconfig/featureprofiles/blob/main/feature/interface/singleton/otg_tests/singleton_test/README.md) +* [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) +* [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. +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 +* Specify the .testbed topology file from the + [topologies](https://github.com/openconfig/featureprofiles/tree/main/topologies) + folder to be used with this test ## Procedure -* TestID-x.y.z - Name of subtest - * Step 1 - * Step 2 - * Validation and pass fail criteria - -* TestID-x.y.z - Name of subtest - * Step 1 - * Step 2 - * Validation and pass fail criteria - -## Config Parameter Coverage - -Add list of OpenConfig 'config' paths used in this test, if any. - -## Telemetry Parameter Coverage - -Add list of OpenConfig 'state' paths used in this test, if any. - -## Protocol/RPC Parameter Coverage - -Add list of OpenConfig RPC's (gNMI, gNOI, gNSI, gRIBI) used in the list, if any. - -For example: - -* gNMI - * Set - * Subscribe -* gNOI - * System - * KillProcess - * Healthz - * Get - * Check - * Artifact +### 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. + +```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" + } + } + }, + "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. + +## 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 + +```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"] + +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 + * 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/feature/acl/feature.textproto b/feature/acl/feature.textproto index d29f33b888a..a85969cdf78 100644 --- a/feature/acl/feature.textproto +++ b/feature/acl/feature.textproto @@ -11,6 +11,8 @@ # 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" diff --git a/feature/acl/otg_tests/acl_update_test/README.md b/feature/acl/otg_tests/acl_update_test/README.md index b1f79cb014c..1bcfa5175c2 100644 --- a/feature/acl/otg_tests/acl_update_test/README.md +++ b/feature/acl/otg_tests/acl_update_test/README.md @@ -2,7 +2,15 @@ ## Summary -Configure an IP ACL, then test changing the ACL configuration to ensure a make-before-break behavior is performed. Make before break for ACL is defined as +Test configuration of an IP ACL. +Test changing the ACL configuration to ensure no packets are dropped due to +the configuration change, when the rule added or removed is not intended to +affect the traffic (make before break). + + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) ## ACL-1 Layer 3 terms @@ -47,36 +55,64 @@ Configure an IP ACL, then test changing the ACL configuration to ensure a make-b * Repeat the same test by moving ACLs to the DUT egress interface. -## Config Parameter coverage - -``` -acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/destination-address -acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/protocol -acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/source-address - -acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/destination-address -acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/protocol -acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/source-address - -acl/interfaces/interface/ingress-acl-sets/ingress-acl-set -acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/acl-entries -acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/acl-entries/acl-entry - -acl/interfaces/interface/egress-acl-sets/egress-acl-set -acl/interfaces/interface/egress-acl-sets/egress-acl-set/acl-entries -acl/interfaces/interface/egress-acl-sets/egress-acl-set/acl-entries/acl-entry -``` - -## Telemetry Parameter coverage - +### Sub Test 4 + +* Repeat sub tests 1 through 4 using a port where [/interfaces/interface/state/management](https://github.com/openconfig/public/blob/daf73c37e9062b458bb9eab645840e5d3835c74d/release/models/interfaces/openconfig-interfaces.yang#L719-L727) + is true and in the case of a modular form factor device (MFF), provided by a `CONTROLLER_CARD` component. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # base acl paths + /acl/acl-sets/acl-set/config/name: + /acl/acl-sets/acl-set/config/type: + /acl/acl-sets/acl-set/acl-entries/acl-entry/config/sequence-id: + /acl/acl-sets/acl-set/acl-entries/acl-entry/config/description: + + # ipv4 address match + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/destination-address: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/destination-address-prefix-set: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/protocol: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/source-address: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/source-address-prefix-set: + + # icmpv4 match + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/icmpv4/config/type: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/icmpv4/config/code: + + # ipv6 address match + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/destination-address: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/destination-address-prefix-set: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/protocol: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/source-address: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/source-address-prefix-set: + + # paths for tcp/udp port and port-range + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/source-port: + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/source-port-set: + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/destination-port: + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/destination-port-set: + + # paths needed to match IP fragments + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/detail-mode: + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/explicit-detail-match-mode: + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/explicit-tcp-flags: + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/builtin-detail: + + # state paths for management port and ACL counters + /interfaces/interface/state/management: + /acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/acl-entries/acl-entry/state/matched-packets: + /acl/interfaces/interface/egress-acl-sets/egress-acl-set/acl-entries/acl-entry/state/matched-packets: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true ``` -acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/acl-entries/acl-entry/state/matched-packets -acl/interfaces/interface/egress-acl-sets/egress-acl-set/acl-entries/acl-entry/state/matched-packets -``` - -## Protocol/RPC Parameter coverage - -None ## Minimum DUT platform requirement diff --git a/feature/aft/aft_base/otg_tests/afts_base/README.md b/feature/aft/aft_base/otg_tests/afts_base/README.md new file mode 100644 index 00000000000..b2e0d5a2792 --- /dev/null +++ b/feature/aft/aft_base/otg_tests/afts_base/README.md @@ -0,0 +1,177 @@ +# 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 new file mode 100644 index 00000000000..9fce154e4a1 --- /dev/null +++ b/feature/aft/aft_base/otg_tests/afts_prefix_counters/README.md @@ -0,0 +1,178 @@ +# 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/afts_summary/otg_tests/route_summary_counters_test/README.md b/feature/aft/afts_summary/otg_tests/route_summary_counters_test/README.md new file mode 100644 index 00000000000..5b675caf6ce --- /dev/null +++ b/feature/aft/afts_summary/otg_tests/route_summary_counters_test/README.md @@ -0,0 +1,41 @@ +# RT-4.10: AFTs Route Summary + +## Summary + +IPv4/IPv6 unicast AFTs route summary for ISIS and BGP protocol + +## Procedure + +Configure DUT:port1 for an IS-IS session with ATE:port1 +* Validate total number of entries of AFT for IPv4 and IPv6 + +Establish eBGP sessions between ATE:port1 and DUT:port1 and another between ATE:port2 and DUT:port2 +* Configure Route-policy under BGP peer-group address-family +* Advertise prefixes from ATE port-1, observe received prefixes at ATE port-2 for IPv4 and IPv6 +* Validate total number of entries of AFT for IPv4 and IPv6 + +## 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/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: + +rpcs: + gnmi: + gNMI.Subscribe: +``` + +## Control Protocol Coverage + +BGP +IS-IS + +## Minimum DUT Platform Requirement + +vRX diff --git a/feature/aft/afts_summary/otg_tests/route_summary_counters_test/metadata.textproto b/feature/aft/afts_summary/otg_tests/route_summary_counters_test/metadata.textproto new file mode 100644 index 00000000000..617ae227d00 --- /dev/null +++ b/feature/aft/afts_summary/otg_tests/route_summary_counters_test/metadata.textproto @@ -0,0 +1,55 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ef34466c-37da-4133-8d03-40ebe2a5168a" +plan_id: "RT-4.10" +description: "AFTs Route Summary" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + isis_multi_topology_unsupported: true + isis_interface_level1_disable_required: true + missing_isis_interface_afi_safi_enable: true + isis_restart_suppress_unsupported: true + explicit_port_speed: true + explicit_interface_in_default_vrf: true + missing_value_for_defaults: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + isis_interface_level1_disable_required: true + isis_single_topology_required: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + route_policy_under_afi_unsupported: true + } +} + diff --git a/feature/aft/afts_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go b/feature/aft/afts_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go new file mode 100644 index 00000000000..e0a77d6d5e1 --- /dev/null +++ b/feature/aft/afts_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go @@ -0,0 +1,414 @@ +// 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 route_summary_counters_test + +import ( + "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/isissession" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +var ( + dutPort1 = attrs.Attributes{ + Name: "port1", + Desc: "To ATE", + IPv4: "192.0.2.1", + IPv4Len: 30, + IPv6: "2001:db8::1", + IPv6Len: 126, + } + dutPort2 = attrs.Attributes{ + Name: "port2", + Desc: "To ATE", + IPv4: "192.0.2.5", + IPv4Len: 30, + IPv6: "2001:db8::5", + IPv6Len: 126, + } + + otgPort1 = attrs.Attributes{ + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + } + otgPort2 = attrs.Attributes{ + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + } + + pathID = 1 + prefixesCount = 4 + bgpPeerGroup = "BGP-PEER-GROUP" + + targetNetwork = &attrs.Attributes{ + Desc: "External network (simulated by ATE)", + IPv4: "198.51.100.0", + IPv4Len: 24, + IPv6: "2001:db8::198:51:100:0", + IPv6Len: 112, + } +) + +const ( + rplPermitAll = "PERMIT-ALL" + dutAS = uint32(65537) + ateAS1 = uint32(65536) + ateAS2 = uint32(65538) +) + +type ip struct { + v4 string + v6 string +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureOTG(t *testing.T, ts *isissession.TestSession) { + // netv4 is a simulated network containing the ipv4 addresses specified by targetNetwork + netv4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName("netv4").SetLinkMetric(10).SetOriginType(gosnappi.IsisV4RouteRangeOriginType.EXTERNAL) + netv4.Addresses().Add().SetAddress(targetNetwork.IPv4).SetPrefix(uint32(targetNetwork.IPv4Len)).SetCount(uint32(prefixesCount)) + + // netv6 is a simulated network containing the ipv6 addresses specified by targetNetwork + netv6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName("netv6").SetLinkMetric(10).SetOriginType(gosnappi.IsisV6RouteRangeOriginType.EXTERNAL) + netv6.Addresses().Add().SetAddress(targetNetwork.IPv6).SetPrefix(uint32(targetNetwork.IPv6Len)).SetCount(uint32(prefixesCount)) + + t.Log("Starting protocols on ATE...") + ts.PushAndStart(t) + ts.MustAdjacency(t) +} + +func TestRouteSummaryWithISIS(t *testing.T) { + ts := isissession.MustNew(t).WithISIS() + otg := ts.ATE.OTG() + + 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) + + configureOTG(t, ts) + gnmi.Watch(t, otg, gnmi.OTG().IsisRouter("devIsis").Counters().Level2().InLsp().State(), 30*time.Second, func(v *ygnmi.Value[uint64]) bool { + time.Sleep(5 * time.Second) + val, present := v.Val() + return present && val >= 1 + }).Await(t) + + dni := deviations.DefaultNetworkInstance(ts.DUT) + if got, ok := gnmi.Await(t, ts.DUT, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv4Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS).Counters().AftEntries().State(), 1*time.Minute, uint64(prefixesCount)).Val(); !ok { + t.Errorf("ipv4 isis entries, got: %d, want: %d", got, prefixesCount) + } + + if got, ok := gnmi.Await(t, ts.DUT, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv6Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS).Counters().AftEntries().State(), 1*time.Minute, uint64(prefixesCount)).Val(); !ok { + t.Errorf("ipv6 isis entries, got: %d, want: %d", got, prefixesCount) + } +} + +func TestRouteSummaryWithBGP(t *testing.T) { + dut := ondatra.DUT(t, "dut") + tests := []struct { + desc string + dut dutData + ate ateData + }{{ + desc: "propagate IPv4 over IPv4", + dut: dutData{ + routerID: dutPort1.IPv4, + neighborConfig: []*cfgplugins.NeighborConfig{ + { + IPv4Neighbor: otgPort1.IPv4, + PeerGroup: bgpPeerGroup, + AS: ateAS1, + }, + { + IPv4Neighbor: otgPort2.IPv4, + PeerGroup: bgpPeerGroup, + AS: ateAS2, + }, + }, + ipv4: true, + }, + ate: ateData{ + Port1: ip{v4: "192.0.2.2/30"}, + Port1Neighbor: dutPort1.IPv4, + Port2: ip{v4: "192.0.2.6/30"}, + Port2Neighbor: dutPort2.IPv4, + prefixesStart: ip{v4: "198.51.100.0/32"}, + }, + }, { + desc: "propagate IPv6 over IPv6", + dut: dutData{ + routerID: dutPort1.IPv4, + neighborConfig: []*cfgplugins.NeighborConfig{ + { + IPv6Neighbor: otgPort1.IPv6, + PeerGroup: bgpPeerGroup, + AS: ateAS1, + }, + { + IPv6Neighbor: otgPort2.IPv6, + PeerGroup: bgpPeerGroup, + AS: ateAS2, + }, + }, + ipv4: false, + }, + ate: ateData{ + Port1: ip{v6: "2001:db8::2/126"}, + Port1Neighbor: dutPort1.IPv6, + Port2: ip{v6: "2001:db8::6/126"}, + Port2Neighbor: dutPort2.IPv6, + prefixesStart: ip{v6: "2001:db8:1::1/128"}, + }, + }} + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + tc.dut.Configure(t, dut) + + ate := ondatra.ATE(t, "ate") + otgConfig := tc.ate.ConfigureOTG(t, ate.OTG(), []string{"port1", "port2"}) + + t.Logf("Verify DUT BGP sessions up") + neighbors := []string{otgPort1.IPv6, otgPort2.IPv6} + if tc.dut.ipv4 { + neighbors = []string{otgPort1.IPv4, otgPort2.IPv4} + } + tc.dut.AwaitBGPEstablished(t, dut, neighbors) + + t.Logf("Verify OTG BGP sessions up") + verifyOTGBGPTelemetry(t, ate.OTG(), otgConfig, "ESTABLISHED") + + dni := deviations.DefaultNetworkInstance(dut) + if tc.dut.ipv4 { + if got, ok := gnmi.Await(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv4Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP).Counters().AftEntries().State(), 1*time.Minute, uint64(prefixesCount)).Val(); !ok { + t.Errorf("ipv4 BGP entries, got: %d, want: %d", got, prefixesCount) + } + } else { + if got, ok := gnmi.Await(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv6Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP).Counters().AftEntries().State(), 1*time.Minute, uint64(prefixesCount)).Val(); !ok { + t.Errorf("ipv4 BGP entries, got: %d, want: %d", got, prefixesCount) + } + } + }) + } +} + +type ateData struct { + Port1 ip + Port2 ip + Port1Neighbor string + Port2Neighbor string + prefixesStart ip +} + +func (ad *ateData) ConfigureOTG(t *testing.T, otg *otg.OTG, ateList []string) gosnappi.Config { + config := gosnappi.NewConfig() + bgp4ObjectMap := make(map[string]gosnappi.BgpV4Peer) + bgp6ObjectMap := make(map[string]gosnappi.BgpV6Peer) + ipv4ObjectMap := make(map[string]gosnappi.DeviceIpv4) + ipv6ObjectMap := make(map[string]gosnappi.DeviceIpv6) + for ateIndex, v := range []struct { + iface attrs.Attributes + ip ip + neighbor string + as uint32 + }{ + {otgPort1, ad.Port1, ad.Port1Neighbor, ateAS1}, + {otgPort2, ad.Port2, ad.Port2Neighbor, ateAS2}, + } { + + devName := ateList[ateIndex] + ".dev" + port := config.Ports().Add().SetName(ateList[ateIndex]) + dev := config.Devices().Add().SetName(devName) + + eth := dev.Ethernets().Add().SetName(devName + ".Eth").SetMac(v.iface.MAC) + eth.Connection().SetPortName(port.Name()) + bgp := dev.Bgp().SetRouterId(v.iface.IPv4) + if v.ip.v4 != "" { + address := strings.Split(v.ip.v4, "/")[0] + prefixInt4, _ := strconv.Atoi(strings.Split(v.ip.v4, "/")[1]) + ipv4 := eth.Ipv4Addresses().Add().SetName(devName + ".IPv4").SetAddress(address).SetGateway(v.neighbor).SetPrefix(uint32(prefixInt4)) + bgp4Name := devName + ".BGP4.peer" + bgp4Peer := bgp.Ipv4Interfaces().Add().SetIpv4Name(ipv4.Name()).Peers().Add().SetName(bgp4Name).SetPeerAddress(ipv4.Gateway()).SetAsNumber(uint32(v.as)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgp4ObjectMap[bgp4Name] = bgp4Peer + ipv4ObjectMap[devName+".IPv4"] = ipv4 + } + if v.ip.v6 != "" { + address := strings.Split(v.ip.v6, "/")[0] + prefixInt6, _ := strconv.Atoi(strings.Split(v.ip.v6, "/")[1]) + ipv6 := eth.Ipv6Addresses().Add().SetName(devName + ".IPv6").SetAddress(address).SetGateway(v.neighbor).SetPrefix(uint32(prefixInt6)) + bgp6Name := devName + ".BGP6.peer" + bgp6Peer := bgp.Ipv6Interfaces().Add().SetIpv6Name(ipv6.Name()).Peers().Add().SetName(bgp6Name).SetPeerAddress(ipv6.Gateway()).SetAsNumber(uint32(v.as)).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true).SetExtendedNextHopEncoding(true) + bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgp6ObjectMap[bgp6Name] = bgp6Peer + ipv6ObjectMap[devName+".IPv6"] = ipv6 + } + } + if ad.prefixesStart.v4 != "" { + bgpName := ateList[0] + ".dev.BGP4.peer" + bgpPeer := bgp4ObjectMap[bgpName] + ip := ipv4ObjectMap[ateList[0]+".dev.IPv4"] + firstAdvAddr := strings.Split(ad.prefixesStart.v4, "/")[0] + firstAdvPrefix, _ := strconv.Atoi(strings.Split(ad.prefixesStart.v4, "/")[1]) + bgp4PeerRoutes := bgpPeer.V4Routes().Add().SetName(bgpName + ".rr4").SetNextHopIpv4Address(ip.Address()).SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4).SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgp4PeerRoutes.Addresses().Add().SetAddress(firstAdvAddr).SetPrefix(uint32(firstAdvPrefix)).SetCount(uint32(prefixesCount)) + bgp4PeerRoutes.AddPath().SetPathId(uint32(pathID)) + } + if ad.prefixesStart.v6 != "" { + bgp6Name := ateList[0] + ".dev.BGP6.peer" + bgp6Peer := bgp6ObjectMap[bgp6Name] + firstAdvAddr := strings.Split(ad.prefixesStart.v6, "/")[0] + firstAdvPrefix, _ := strconv.Atoi(strings.Split(ad.prefixesStart.v6, "/")[1]) + bgp6PeerRoutes := bgp6Peer.V6Routes().Add().SetName(bgp6Name + ".rr6") + bgp6PeerRoutes.Addresses().Add().SetAddress(firstAdvAddr).SetPrefix(uint32(firstAdvPrefix)).SetCount(uint32(prefixesCount)) + bgp6PeerRoutes.AddPath().SetPathId(uint32(pathID)) + } + + t.Logf("Pushing config to ATE and starting protocols...") + otg.PushConfig(t, config) + otg.StartProtocols(t) + return config + +} + +type dutData struct { + routerID string + neighborConfig []*cfgplugins.NeighborConfig + ipv4 bool +} + +func configureRoutingPolicy(d *oc.Root) (*oc.RoutingPolicy, error) { + rp := d.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(rplPermitAll) + stmt, err := pdef.AppendNewStatement("20") + if err != nil { + return nil, err + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + return rp, nil +} + +func (d *dutData) Configure(t *testing.T, dut *ondatra.DUTDevice) { + aftType := oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST + if !d.ipv4 { + aftType = oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST + } + bgpOC := cfgplugins.BuildBGPOCConfig(t, dut, d.routerID, []oc.E_BgpTypes_AFI_SAFI_TYPE{aftType}, d.neighborConfig) + + for _, a := range []attrs.Attributes{dutPort1, dutPort2} { + ocName := dut.Port(t, a.Name).Name() + gnmi.Replace(t, dut, gnmi.OC().Interface(ocName).Config(), a.NewOCInterface(ocName, dut)) + } + + t.Log("Configure Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.ExplicitPortSpeed(dut) { + for _, a := range []attrs.Attributes{dutPort1, dutPort2} { + fptest.SetPortSpeed(t, dut.Port(t, a.Name)) + } + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + for _, a := range []attrs.Attributes{dutPort1, dutPort2} { + ocName := dut.Port(t, a.Name).Name() + fptest.AssignToNetworkInstance(t, dut, ocName, deviations.DefaultNetworkInstance(dut), 0) + } + } + + dutProto := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + niProtocol := &oc.NetworkInstance_Protocol{ + Identifier: oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + Name: ygot.String("BGP"), + Bgp: bgpOC, + } + rpl, err := configureRoutingPolicy(&oc.Root{}) + if err != nil { + t.Fatalf("Failed to configure routing policy: %v", err) + } + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rpl) + gnmi.Replace(t, dut, dutProto.Config(), niProtocol) +} + +func (d *dutData) 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, "BGP"). + Bgp(). + Neighbor(neighbor). + SessionState().State(), time.Second*120, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + } + t.Log("BGP sessions established") +} + +func verifyOTGBGPTelemetry(t *testing.T, otg *otg.OTG, c gosnappi.Config, state string) { + for _, d := range c.Devices().Items() { + for _, ip := range d.Bgp().Ipv4Interfaces().Items() { + for _, configPeer := range ip.Peers().Items() { + nbrPath := gnmi.OTG().BgpPeer(configPeer.Name()) + _, ok := gnmi.Watch(t, otg, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + currState, ok := val.Val() + return ok && currState.String() == state + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, otg, nbrPath.State())) + t.Errorf("No BGP neighbor formed for peer %s", configPeer.Name()) + } + } + } + for _, ip := range d.Bgp().Ipv6Interfaces().Items() { + for _, configPeer := range ip.Peers().Items() { + nbrPath := gnmi.OTG().BgpPeer(configPeer.Name()) + _, ok := gnmi.Watch(t, otg, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + currState, ok := val.Val() + return ok && currState.String() == state + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, otg, nbrPath.State())) + t.Errorf("No BGP neighbor formed for peer %s", configPeer.Name()) + } + } + } + } +} diff --git a/feature/aft/afts_summary/otg_tests/scale_aft_summary/README.md b/feature/aft/afts_summary/otg_tests/scale_aft_summary/README.md new file mode 100644 index 00000000000..e7e416b40e7 --- /dev/null +++ b/feature/aft/afts_summary/otg_tests/scale_aft_summary/README.md @@ -0,0 +1,39 @@ +# RT-4.11: AFTs Route Summary + +## Summary + +IPv4/IPv6 scale unicast AFTs route summary for ISIS and BGP protocol + +## Procedure + +* Configure DUT:port1 for an IS-IS session with ATE:port1 +* Establish eBGP sessions between ATE:port1 and DUT:port1 and another between ATE:port2 and DUT:port2 +* Configure Route-policy under BGP peer-group address-family +* Advertise scale prefixes from ATE port-1, port-2 observe received prefixes at DUT for IPv4 and IPv6 +* Validate total number of entries of AFT for IPv4 and IPv6 + +## 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/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: + +rpcs: + gnmi: + gNMI.Subscribe: +``` + +## Control Protocol Coverage + +BGP +IS-IS + +## Minimum DUT Platform Requirement + +vRX diff --git a/feature/experimental/isis/ate_tests/base_adjacencies_test/metadata.textproto b/feature/aft/afts_summary/otg_tests/scale_aft_summary/metadata.textproto similarity index 73% rename from feature/experimental/isis/ate_tests/base_adjacencies_test/metadata.textproto rename to feature/aft/afts_summary/otg_tests/scale_aft_summary/metadata.textproto index 22ddf4a534b..d61d4590b2b 100644 --- a/feature/experimental/isis/ate_tests/base_adjacencies_test/metadata.textproto +++ b/feature/aft/afts_summary/otg_tests/scale_aft_summary/metadata.textproto @@ -1,9 +1,9 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "a5b892e1-192d-45ff-89b5-a84a2865fdb2" -plan_id: "RT-2.1" -description: "Base IS-IS Process and Adjacencies" +uuid: "89da0b4c-9a16-44f8-9757-d98ccdd6aaf4" +plan_id: "RT-4.11" +description: "AFTs Route Summary" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { @@ -30,6 +30,14 @@ platform_exceptions: { isis_single_topology_required: true } } +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} platform_exceptions: { platform: { vendor: ARISTA @@ -39,7 +47,8 @@ platform_exceptions: { missing_value_for_defaults: true interface_enabled: true default_network_instance: "default" - isis_instance_enabled_required: true - isis_interface_afi_unsupported: true + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + route_policy_under_afi_unsupported: true } } diff --git a/feature/aft/afts_summary/otg_tests/scale_aft_summary/route_test.go b/feature/aft/afts_summary/otg_tests/scale_aft_summary/route_test.go new file mode 100644 index 00000000000..e283663711e --- /dev/null +++ b/feature/aft/afts_summary/otg_tests/scale_aft_summary/route_test.go @@ -0,0 +1,566 @@ +// 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 route_test + +import ( + "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/isissession" + "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" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// The testbed consists of ate:port1 -> dut:port1 and +// dut:port2 -> ate:port2. The first pair is called the "source" +// pair, and the second the "destination" pair. +// +// * Source: ate:port1 -> dut:port1 subnet 192.0.2.0/30 2001:db8::192:0:2:0/126 +// * Destination: dut:port2 -> ate:port2 subnet 192.0.2.4/30 2001:db8::192:0:2:4/126 +// +// Note that the first (.0, .3) and last (.4, .7) IPv4 addresses are +// reserved from the subnet for broadcast, so a /30 leaves exactly 2 +// usable addresses. This does not apply to IPv6 which allows /127 +// for point to point links, but we use /126 so the numbering is +// consistent with IPv4. + +const ( + advertisedRoutesv4Prefix = 32 + advertisedRoutesv6Prefix = 128 + dutAS = 65501 + ate1AS = 64501 + ate2AS = 200 + plenIPv4 = 30 + plenIPv6 = 126 + rplType = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + rplName = "ALLOW" + peerGrpNamev4 = "BGP-PEER-GROUP-V4" + peerGrpNamev6 = "BGP-PEER-GROUP-V6" + peerGrpNamev4P1 = "BGP-PEER-GROUP-V4-P1" + peerGrpNamev6P1 = "BGP-PEER-GROUP-V6-P1" + peerGrpNamev4P2 = "BGP-PEER-GROUP-V4-P2" + peerGrpNamev6P2 = "BGP-PEER-GROUP-V6-P2" + isisRoute = "199.0.0.1" + bgpRoute = "203.0.113.0" + isisRoutev6 = "2001:db8::203:0:113:1" + bgpRoutev6 = "2001:DB8:2::1" + RouteCount = uint32(1000) +) + +var ( + dutP1 = attrs.Attributes{ + 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{ + Desc: "DUT to ATE destination", + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + ateP2 = attrs.Attributes{ + Name: "ateP2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } +) + +// configureDUT configures all the interfaces and BGP on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dc := gnmi.OC() + p1 := dut.Port(t, "port1").Name() + i1 := dutP1.NewOCInterface(p1, dut) + gnmi.Replace(t, dut, dc.Interface(p1).Config(), i1) + + p2 := dut.Port(t, "port2").Name() + i2 := dutP2.NewOCInterface(p2, dut) + gnmi.Replace(t, dut, dc.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, "port1")) + fptest.SetPortSpeed(t, dut.Port(t, "port2")) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p1, deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p2, deviations.DefaultNetworkInstance(dut), 0) + } + configureRoutePolicy(t, dut, rplName, rplType) + + dutConfPath := dc.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + dutConf := createBGPNeighborP1(dutAS, ate1AS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + dutConf = createBGPNeighborP2(dutAS, ate2AS, dut) + gnmi.Update(t, dut, dutConfPath.Config(), dutConf) + 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) + ts.PushAndStart(t) +} + +type BGPNeighbor struct { + as uint32 + neighborip string + isV4 bool +} + +func createBGPNeighborP1(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + nbrs := []*BGPNeighbor{ + {as: peerAs, neighborip: ateP1.IPv4, isV4: true}, + {as: peerAs, neighborip: ateP1.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.As = ygot.Uint32(localAs) + global.RouterId = ygot.String(dutP1.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) + + // 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(peerGrpNamev4P1) + pgv4.PeerAs = ygot.Uint32(peerAs) + pgv4.PeerGroupName = ygot.String(peerGrpNamev4P1) + pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNamev6P1) + pgv6.PeerAs = ygot.Uint32(peerAs) + pgv6.PeerGroupName = ygot.String(peerGrpNamev6P1) + + for _, nbr := range nbrs { + if nbr.isV4 { + nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.PeerAs = ygot.Uint32(nbr.as) + nv4.Enabled = ygot.Bool(true) + nv4.PeerGroup = ygot.String(peerGrpNamev4P1) + 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) + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pgv4.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } else { + pgafv4 := pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pgafv4.Enabled = ygot.Bool(true) + rpl := pgafv4.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } + } else { + nv6 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv6.PeerAs = ygot.Uint32(nbr.as) + nv6.Enabled = ygot.Bool(true) + nv6.PeerGroup = ygot.String(peerGrpNamev6P1) + 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) + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pgv6.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } else { + pgafv6 := pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + pgafv6.Enabled = ygot.Bool(true) + rpl := pgafv6.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + + } + } + } + return niProto +} + +func createBGPNeighborP2(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.As = ygot.Uint32(localAs) + global.RouterId = ygot.String(dutP1.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) + + // 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(peerGrpNamev4P2) + pgv4.PeerAs = ygot.Uint32(peerAs) + pgv4.PeerGroupName = ygot.String(peerGrpNamev4P2) + pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNamev6P2) + pgv6.PeerAs = ygot.Uint32(peerAs) + pgv6.PeerGroupName = ygot.String(peerGrpNamev6P2) + + for _, nbr := range nbrs { + if nbr.isV4 { + nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.PeerAs = ygot.Uint32(nbr.as) + nv4.Enabled = ygot.Bool(true) + nv4.PeerGroup = ygot.String(peerGrpNamev4P2) + 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) + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pgv4.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } else { + pgafv4 := pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pgafv4.Enabled = ygot.Bool(true) + rpl := pgafv4.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } + } else { + nv6 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv6.PeerAs = ygot.Uint32(nbr.as) + nv6.Enabled = ygot.Bool(true) + nv6.PeerGroup = ygot.String(peerGrpNamev6P2) + 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) + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pgv6.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } else { + pgafv6 := pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + pgafv6.Enabled = ygot.Bool(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 { + t.Logf("BGP session state: %s", state.String()) + 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 verifyBGPTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Log("Waiting for BGPv4 neighbor to establish...") + waitForBGPSession(t, dut, true) + +} + +func configureATE(t *testing.T) gosnappi.Config { + ate := ondatra.ATE(t, "ate") + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + 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.d1") + d2 := config.Devices().Add().SetName("p2.d1") + // Configuration on port1. + d1Eth1 := d1.Ethernets(). + Add(). + SetName("p1.d1.eth1"). + SetMac("00:00:02:02:02:02"). + SetMtu(1500) + d1Eth1. + Connection(). + SetPortName(p1.Name()) + + d1ipv41 := d1Eth1. + Ipv4Addresses(). + Add(). + SetName("p1.d1.eth1.ipv4"). + SetAddress("192.0.2.2"). + SetGateway("192.0.2.1"). + SetPrefix(30) + + d1ipv61 := d1Eth1. + Ipv6Addresses(). + Add(). + SetName("p1.d1.eth1.ipv6"). + SetAddress("2001:db8::2"). + SetGateway("2001:db8::1"). + SetPrefix(126) + + // isis router + d1isis := d1.Isis(). + SetName("p1.d1.isis"). + SetSystemId("650000000001") + d1isis.Basic(). + SetIpv4TeRouterId(d1ipv41.Address()). + SetHostname("ixia-c-port1") + d1isis.Advanced().SetAreaAddresses([]string{"49"}) + d1isisint := d1isis.Interfaces(). + Add(). + SetName("p1.d1.isis.intf"). + SetEthName(d1Eth1.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) + + d1IsisRoute1 := d1isis.V4Routes().Add().SetName("p1.d1.isis.rr1") + d1IsisRoute1.Addresses(). + Add(). + SetAddress(isisRoute). + SetPrefix(32).SetCount(RouteCount) + + d1IsisRoute1v6 := d1isis.V6Routes().Add().SetName("p1.d1.isis.rr1.v6") + d1IsisRoute1v6.Addresses(). + Add(). + SetAddress(isisRoutev6). + SetPrefix(126).SetCount(RouteCount) + + configureBGPDev(d1, d1ipv41, d1ipv61, ate1AS) + + // configuration on port2 + d2Eth1 := d2.Ethernets(). + Add(). + SetName("p2.d1.eth1"). + SetMac("00:00:03:03:03:03"). + SetMtu(1500) + d2Eth1. + Connection(). + SetPortName(p2.Name()) + d2ipv41 := d2Eth1.Ipv4Addresses(). + Add(). + SetName("p2.d1.eth1.ipv4"). + SetAddress("192.0.2.6"). + SetGateway("192.0.2.5"). + SetPrefix(30) + + d2ipv61 := d2Eth1. + Ipv6Addresses(). + Add(). + SetName("p2.d1.eth1.ipv6"). + SetAddress("2001:db8::6"). + SetGateway("2001:db8::5"). + SetPrefix(126) + + // isis router + d2isis := d2.Isis(). + SetName("p2.d1.isis"). + SetSystemId("650000000001") + d2isis.Basic(). + SetIpv4TeRouterId(d2ipv41.Address()). + SetHostname("ixia-c-port2") + d2isis.Advanced().SetAreaAddresses([]string{"49"}) + d2isisint := d2isis.Interfaces(). + Add(). + SetName("p2.d1.isis.intf"). + SetEthName(d2Eth1.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) + + d2IsisRoute1 := d2isis.V4Routes().Add().SetName("p2.d1.isis.rr1") + d2IsisRoute1.Addresses(). + Add(). + SetAddress(isisRoute). + SetPrefix(32). + SetCount(RouteCount) + + d2IsisRoute1V6 := d2isis.V6Routes().Add().SetName("p2.d1.isis.rr1.v6") + d2IsisRoute1V6.Addresses(). + Add(). + SetAddress(isisRoutev6). + SetPrefix(126). + SetCount(RouteCount) + + configureBGPDev(d2, d2ipv41, d2ipv61, ate2AS) + + return config +} + +// configureBGPDev configures the BGP on the OTG dev +func configureBGPDev(dev gosnappi.Device, Ipv4 gosnappi.DeviceIpv4, Ipv6 gosnappi.DeviceIpv6, as int) { + + 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(as)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + Bgp6Peer := Bgp.Ipv6Interfaces().Add().SetIpv6Name(Ipv6.Name()).Peers().Add().SetName(dev.Name() + ".BGP6.peer") + Bgp6Peer.SetPeerAddress(Ipv6.Gateway()).SetAsNumber(uint32(as)).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + configureBGPv4Routes(Bgp4Peer, Ipv4.Address(), Bgp4Peer.Name()+"v4route", bgpRoute, RouteCount) + configureBGPv6Routes(Bgp6Peer, Ipv6.Address(), Bgp6Peer.Name()+"v6route", bgpRoutev6, RouteCount) + +} + +func configureBGPv4Routes(peer gosnappi.BgpV4Peer, ipv4 string, name string, prefix string, count uint32) { + routes := peer.V4Routes().Add().SetName(name) + routes.SetNextHopIpv4Address(ipv4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + routes.Addresses().Add(). + SetAddress(prefix). + SetPrefix(advertisedRoutesv4Prefix). + SetCount(count) +} + +func configureBGPv6Routes(peer gosnappi.BgpV6Peer, ipv6 string, name string, prefix string, count uint32) { + routes := peer.V6Routes().Add().SetName(name) + routes.SetNextHopIpv6Address(ipv6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + routes.Addresses().Add(). + SetAddress(prefix). + SetPrefix(advertisedRoutesv6Prefix). + SetCount(count) +} +func VerifyDUT(t *testing.T, dut *ondatra.DUTDevice) { + + dni := deviations.DefaultNetworkInstance(dut) + if got, ok := gnmi.Await(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv4Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP).Counters().AftEntries().State(), 1*time.Minute, uint64(RouteCount)).Val(); !ok { + t.Errorf("ipv4 BGP entries, got: %d, want: %d", got, RouteCount) + } else { + t.Logf("Test case Passed: ipv4 BGP entries, got: %d, want: %d", got, RouteCount) + } + if got, ok := gnmi.Await(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv6Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP).Counters().AftEntries().State(), 1*time.Minute, uint64(RouteCount)).Val(); !ok { + t.Errorf("ipv6 BGP entries, got: %d, want: %d", got, RouteCount) + } else { + t.Logf("Test case Passed:ipv6 BGP entries, got: %d, want: %d", got, RouteCount) + } + + if got, ok := gnmi.Await(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv4Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS).Counters().AftEntries().State(), 1*time.Minute, uint64(RouteCount)).Val(); !ok { + t.Errorf("ipv4 isis entries, got: %d, want: %d", got, RouteCount) + } else { + t.Logf("Test case Passed: ipv4 isis entries, got: %d, want: %d", got, RouteCount) + + } + + if got, ok := gnmi.Await(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv6Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS).Counters().AftEntries().State(), 1*time.Minute, uint64(RouteCount)).Val(); !ok { + t.Errorf("ipv6 isis entries, got: %d, want: %d", got, RouteCount) + } else { + t.Logf("Test case Passed: ipv6 isis entries, got: %d, want: %d", got, RouteCount) + } +} + +func TestBGP(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + // DUT Configuration + t.Log("Start DUT interface Config") + configureDUT(t, dut) + // ATE Configuration. + t.Log("Start ATE Config") + config := configureATE(t) + ate.OTG().PushConfig(t, config) + time.Sleep(time.Second * 20) + ate.OTG().StartProtocols(t) + time.Sleep(time.Second * 20) + verifyBGPTelemetry(t, dut) + VerifyDUT(t, dut) +} diff --git a/feature/aft/feature.textproto b/feature/aft/feature.textproto index f85d65be8d0..b8ec741be44 100644 --- a/feature/aft/feature.textproto +++ b/feature/aft/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/bgp/addpath/feature.textproto b/feature/bgp/addpath/feature.textproto index 8500d916f14..36293f0cccd 100644 --- a/feature/bgp/addpath/feature.textproto +++ b/feature/bgp/addpath/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/bgp/addpath/otg_tests/route_propagation_test/README.md b/feature/bgp/addpath/otg_tests/route_propagation_test/README.md index 502d71ee974..6c790232e3b 100644 --- a/feature/bgp/addpath/otg_tests/route_propagation_test/README.md +++ b/feature/bgp/addpath/otg_tests/route_propagation_test/README.md @@ -4,49 +4,91 @@ BGP Route Propagation +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + ## Procedure Establish eBGP sessions between: * ATE port-1 and DUT port-1 * ATE port-2 and DUT port-2 -* Configure Route-policy under BGP peer-group address-family - -For IPv4 and IPv6: - -* Advertise prefixes from ATE port-1, observe received prefixes at ATE port-2. -* TODO: Specify default accept for received prefixes on DUT. -* TODO: Specify table based neighbor configuration to cover - validating the - supported capabilities from the DUT. - * TODO: MRAI (minimum route advertisement interval), ensuring routes are - advertised within specified time. - * IPv4 routes with an IPv6 next-hop when negotiating RFC5549 - validating - that routes are accepted and advertised with the specified values. - * TODO: With ADD-PATH enabled, ensure that multiple routes are accepted - from a neighbor when advertised with individual path IDs, and that these - routes are advertised to ATE port-2. - -## Config Parameter Coverage - -For prefix: -/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor - -Parameters: - -* afi-safis/afi-safi/add-paths/config/receive -* afi-safis/afi-safi/add-paths/config/send -* afi-safis/afi-safi/add-paths/config/send-max - -## Telemetry Parameter Coverage - -/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/supported-capabilities - -## Protocol/RPC Parameter Coverage -BGP -* OPEN - * Capabilities (Extended nexthop encoding capability (5), ADD-PATH (69)) -* UPDATE - * Extended NLRI Encodings (RFC7911) - * Nexthop AFI (RFC5549) +### RT-1.3.1: MRAI: [TODO: https://github.com/openconfig/featureprofiles/issues/3035] +* DUT: Configure the Minimum Route Advertisement Interval (MRAI) for desired behavior. +* ATE Port 2: Verify received routes adhere to the MRAI timing. + +### RT-1.3.2: RFC5549 +* DUT: Enable RFC5549 support: + * Update the BGP peer group configuration to enable extended next hop encoding using `/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding` +* ATE Port 1: Advertise IPv4 routes with IPv6 next-hops. +* ATE Port 2: Validate correct acceptance and propagation of routes with IPv6 next-hops. + +### RT-1.3.3: Add-Path (Initial State): [TODO: https://github.com/openconfig/featureprofiles/issues/3037] +* ATE Port 1: Advertise multiple routes with distinct path IDs for the same prefix. +* ATE Port 2: Confirm that all advertised routes are accepted and propagated by the DUT due to the initially enabled Add-Path. +* Verification (Telemetry): Verify that the DUT's telemetry output reflects the enabled Add-Path capabilities. + +### RT-1.3.4: Disabling Add-Path Send: [TODO: https://github.com/openconfig/featureprofiles/issues/3037] +* DUT: Disable Add-Path send for the neighbor connected to ATE Port 2 for both IPv4 and IPv6. +* Verification (Telemetry): Confirm that the DUT's telemetry reflects the disabled Add-Path send status. +* ATE Port 1: Readvertise multiple paths. +* ATE Port 2: Verify that only a single best path is received by ATE Port 2 due to disabled Add-Path send on the DUT. + +### RT-1.3.5: Disabling Add-Path Receive: [TODO: https://github.com/openconfig/featureprofiles/issues/3037] +* DUT: Disable Add-Path receive for the neighbor connected to ATE Port 1 for both IPv4 and IPv6. +* Verification (Telemetry): Confirm the disabled Add-Path receive status in telemetry. +* ATE Port 1: Advertise BGP routes to the DUT via Port 1. +* ATE Port 2: Verify that the DUT has received and propagated only one single path. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config paths + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/config/receive: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/config/send: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/config/send-max: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/config/receive: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/config/send: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/config/send-max: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/minimum-advertisement-interval: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/config/receive: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/config/send: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/config/send-max: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/minimum-advertisement-interval: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding: + + ## State paths + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/state/receive: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/state/send: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/state/send-max: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/state/extended-next-hop-encoding: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/state/receive: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/state/send: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/state/send-max: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/minimum-advertisement-interval: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/state/extended-next-hop-encoding: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/state/receive: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/state/send: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/state/send-max: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/minimum-advertisement-interval: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/state/extended-next-hop-encoding: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/supported-capabilities: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* MFF - A modular form factor device containing LINECARDs, FABRIC and redundant CONTROLLER_CARD components +* FFF - fixed form factor 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 6d85dad51f2..29ce31ff7da 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 @@ -122,7 +122,7 @@ func (ad *ateData) ConfigureOTG(t *testing.T, otg *otg.OTG, ateList []string) go ateIndex++ eth := dev.Ethernets().Add().SetName(devName + ".Eth").SetMac(v.iface.mac) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port.Name()) + eth.Connection().SetPortName(port.Name()) bgp := dev.Bgp().SetRouterId(v.iface.routerId) if v.ip.v4 != "" { address := strings.Split(v.ip.v4, "/")[0] 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 new file mode 100644 index 00000000000..464703ecf20 --- /dev/null +++ b/feature/bgp/admin_distance/otg_tests/admin_distance_test/README.md @@ -0,0 +1,92 @@ +# RT-1.34: BGP route-distance configuration + +## Summary + +BGP default-route-distance, external-route-distance and internal-route-distance (administrative distance) configuration. + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed + +## Procedure + +### Applying configuration + +For each section of configuration below, prepare a gnmi.SetBatch with all the configuration items appended to one SetBatch. Then apply the configuration to the DUT in one gnmi.Set using the `replace` option + +#### Initial Setup: + +* Connect DUT port-1, 2 and 3 to ATE port-1, 2 and 3 +* Configure IPv4/IPv6 addresses on the ports +* Create an IPv4 network i.e. ```ipv4-network-1 = 192.168.10.0/24``` attached to ATE port-1 and port-2 +* Create an IPv6 network i.e. ```ipv6-network-1 = 2024:db8:64:64::/64``` attached to ATE port-1 and port-2 +* Configure IPv4 and IPv6 IS-IS between DUT Port-1 and ATE Port-1 + * /network-instances/network-instance/protocols/protocol/isis/global/config + * Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` from ATE to DUT over the IPv4 and IPv6 IS-IS session on port-1 + +### RT-1.34.1 [TODO:https://github.com/openconfig/featureprofiles/issues/3050] +#### Validate traffic with modified eBGP Route-Distance of 5 +* Configure IPv4 and IPv6 eBGP between DUT Port-2 and ATE Port-2 + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-2 +* Configure Route-Distance of eBGP session on port-2 to 5 + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/external-route-distance +* Validate using gNMI Subscribe with mode 'ONCE' that the correct Route-Distance value of 5 is reported: + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/external-route-distance +* Generate traffic from ATE port-3 towards ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` +* Verify that the traffic is received on port-2 of the ATE + +### RT-1.34.2 [TODO:https://github.com/openconfig/featureprofiles/issues/3050] +#### Validate traffic with modified eBGP Route-Distance of 250 +* Configure Route-Distance of eBGP session on port-2 to 250 + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/external-route-distance +* Validate using gNMI Subscribe with mode 'ONCE' that the correct Route-Distance value of 250 is reported: + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/external-route-distance +* Generate traffic from ATE port-3 towards ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` +* Verify that the traffic is received on port-1 of the ATE + +### RT-1.34.3 [TODO:https://github.com/openconfig/featureprofiles/issues/3050] +#### Validate traffic with modified iBGP Route-Distance of 5 +* Replace IPv4 and IPv6 eBGP with IPv4 and IPv6 iBGP between DUT Port-2 and ATE Port-2 + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` from ATE to DUT over the IPv4 and IPv6 iBGP session on port-2 +* Configure Route-Distance of iBGP session on port-2 to 5 + * /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 5 is reported: + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/internal-route-distance +* 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-2 of the ATE + +### RT-1.34.4 [TODO:https://github.com/openconfig/featureprofiles/issues/3050] +#### Validate traffic with modified iBGP Route-Distance of 250 +* Configure Route-Distance of iBGP session on port-2 to 250 + * /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 250 is reported: + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/internal-route-distance +* 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 + +## 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 + ### Route-Distance + /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/external-route-distance: + /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/internal-route-distance: + + ## State paths + ### Route-Distance + /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/internal-route-distance: + /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/external-route-distance: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/bgp/admin_distance/otg_tests/admin_distance_test/admin_distance_test.go b/feature/bgp/admin_distance/otg_tests/admin_distance_test/admin_distance_test.go new file mode 100644 index 00000000000..19398b463b4 --- /dev/null +++ b/feature/bgp/admin_distance/otg_tests/admin_distance_test/admin_distance_test.go @@ -0,0 +1,330 @@ +// 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 admin_distance_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/isissession" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + prefixV4Len = uint32(24) + prefixV6Len = uint32(64) + v4Network = "192.168.10.0" + v6Network = "2024:db8:64:64::" + prefixesCount = 1 + bgpName = "BGP" + dutAS = uint32(64656) + ateAS = uint32(64657) + peerGrpNameV4 = "BGP-PEER-GROUP-V4" + peerGrpNameV6 = "BGP-PEER-GROUP-V6" + lossTolerance = 1 +) + +var ( + dutPort3 = &attrs.Attributes{ + Desc: "DUT to ATE link", + IPv4: "192.0.2.9", + IPv6: "2001:db8::9", + IPv4Len: 30, + IPv6Len: 126, + } + + atePort3 = &attrs.Attributes{ + Name: "port3", + Desc: "ATE to DUT link", + MAC: "02:12:01:00:00:01", + IPv4: "192.0.2.10", + IPv6: "2001:db8::a", + IPv4Len: 30, + IPv6Len: 126, + } + + advertisedIPv4 = ipAddr{address: v4Network, prefix: prefixV4Len} + advertisedIPv6 = ipAddr{address: v6Network, prefix: prefixV6Len} +) + +type ipAddr struct { + address string + prefix uint32 +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// isis on port1 and eBGP on port2 +func TestAdminDistance(t *testing.T) { + ts := isissession.MustNew(t).WithISIS() + configurePort3(t, ts) + advertisePrefixFromISISPort(ts) + t.Run("ISIS Setup", func(t *testing.T) { + ts.PushAndStart(t) + ts.MustAdjacency(t) + }) + + setupEBGPAndAdvertise(t, ts) + t.Run("BGP Setup", func(t *testing.T) { + t.Log("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, ts.DUT) + + t.Log("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, ts.ATE) + }) + + testCases := []struct { + desc string + rd uint8 + port string + bgp string + }{ + { + desc: "EBGP RD value 5", + rd: 5, + port: "port2", + bgp: "eBGP", + }, + { + desc: "EBGP RD value 250", + rd: 250, + port: "port1", + bgp: "eBGP", + }, + { + desc: "IBGP RD value 5", + rd: 5, + port: "port2", + bgp: "iBGP", + }, + { + desc: "IBGP RD value 250", + rd: 250, + port: "port1", + bgp: "iBGP", + }, + } + + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp() + t.Run("Test Admin Distance", func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + if tc.bgp == "iBGP" { + changeProtocolToIBGP(t, ts) + gnmi.Update(t, ts.DUT, bgpPath.Global().DefaultRouteDistance().InternalRouteDistance().Config(), tc.rd) + gnmi.Await(t, ts.DUT, bgpPath.Global().DefaultRouteDistance().InternalRouteDistance().State(), 30*time.Second, tc.rd) + } else { + gnmi.Update(t, ts.DUT, bgpPath.Global().DefaultRouteDistance().ExternalRouteDistance().Config(), tc.rd) + gnmi.Await(t, ts.DUT, bgpPath.Global().DefaultRouteDistance().ExternalRouteDistance().State(), 30*time.Second, tc.rd) + } + + ts.ATETop.Flows().Clear() + createFlow(t, ts.ATETop, false) + createFlow(t, ts.ATETop, true) + ts.ATE.OTG().PushConfig(t, ts.ATETop) + ts.ATE.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv4") + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv6") + + // b/374639328 #3 30 sec delay added for bgp and isis to come up + time.Sleep(30 * time.Second) + ts.ATE.OTG().StartTraffic(t) + // added 30 seconds for sleep for traffic flow + time.Sleep(30 * time.Second) + ts.ATE.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, ts.ATE.OTG(), ts.ATETop) + otgutils.LogPortMetrics(t, ts.ATE.OTG(), ts.ATETop) + + txPkts := gnmi.Get[uint64](t, ts.ATE.OTG(), gnmi.OTG().Port(ts.ATE.Port(t, "port3").ID()).Counters().OutFrames().State()) + rxPkts := gnmi.Get[uint64](t, ts.ATE.OTG(), gnmi.OTG().Port(ts.ATE.Port(t, tc.port).ID()).Counters().InFrames().State()) + if got := (math.Abs(float64(txPkts)-float64(rxPkts)) * 100) / float64(txPkts); got > lossTolerance { + t.Errorf("Packet loss percentage for flow: got %v, want %v", got, lossTolerance) + } + }) + } + }) +} + +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 changeProtocolToIBGP(t *testing.T, ts *isissession.TestSession) { + root := &oc.Root{} + dni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) + bgpP := dni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName) + bgpP.GetOrCreateBgp().GetOrCreateNeighbor(isissession.ATETrafficAttrs.IPv4).SetPeerAs(dutAS) + bgpP.GetOrCreateBgp().GetOrCreateNeighbor(isissession.ATETrafficAttrs.IPv6).SetPeerAs(dutAS) + gnmi.Update(t, ts.DUT, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Config(), dni) + + bgp4Peer := ts.ATEIntf2.Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + bgp4Peer.SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + bgp6Peer := ts.ATEIntf2.Bgp().Ipv6Interfaces().Items()[0].Peers().Items()[0] + bgp6Peer.SetAsNumber(dutAS).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) +} + +func configurePort3(t *testing.T, ts *isissession.TestSession) { + t.Helper() + dc := gnmi.OC() + + dp3 := ts.DUT.Port(t, "port3") + i3 := dutPort3.ConfigOCInterface(ts.DUTConf.GetOrCreateInterface(dp3.Name()), ts.DUT) + gnmi.Replace(t, ts.DUT, dc.Interface(i3.GetName()).Config(), i3) + if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { + fptest.AssignToNetworkInstance(t, ts.DUT, dp3.Name(), deviations.DefaultNetworkInstance(ts.DUT), 0) + } + if deviations.ExplicitPortSpeed(ts.DUT) { + fptest.SetPortSpeed(t, dp3) + } + ap3 := ts.ATE.Port(t, "port3") + atePort3.AddToOTG(ts.ATETop, ap3, dutPort3) +} + +func createFlow(t *testing.T, config gosnappi.Config, isV6 bool) { + t.Helper() + + flowName := "flowV4" + if isV6 { + flowName = "flowV6" + } + flow := config.Flows().Add().SetName(flowName) + flow.Metrics().SetEnable(true) + if isV6 { + flow.TxRx().Device(). + SetTxNames([]string{"port3.IPv6"}). + SetRxNames([]string{"port1.IPv6", "port2.IPv6"}) + } else { + flow.TxRx().Device(). + SetTxNames([]string{"port3.IPv4"}). + SetRxNames([]string{"port1.IPv4", "port2.IPv4"}) + } + flow.Size().SetFixed(512) + flow.Rate().SetPps(100) + flow.Duration().Continuous() + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort3.MAC) + if isV6 { + ipHeader := flow.Packet().Add().Ipv6() + ipHeader.Src().SetValue(atePort3.IPv6) + ipHeader.Dst().SetValue(advertisedIPv6.address) + } else { + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(atePort3.IPv4) + ipHeader.Dst().SetValue(advertisedIPv4.address) + } +} + +// setupEBGPAndAdvertise setups eBGP on DUT port1 and ATE port1 +func setupEBGPAndAdvertise(t *testing.T, ts *isissession.TestSession) { + t.Helper() + + // setup eBGP on DUT port1 and iBGP on port2 + root := &oc.Root{} + dni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) + dni.SetType(oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + + bgpP := dni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName) + bgpP.SetEnabled(true) + bgp := bgpP.GetOrCreateBgp() + + g := bgp.GetOrCreateGlobal() + g.SetAs(dutAS) + g.SetRouterId(isissession.DUTTrafficAttrs.IPv4) + 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) + + pgv4 := bgp.GetOrCreatePeerGroup(peerGrpNameV4) + pgv4.PeerAs = ygot.Uint32(dutAS) + pgv4.PeerGroupName = ygot.String(peerGrpNameV4) + pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNameV6) + pgv6.PeerAs = ygot.Uint32(dutAS) + pgv6.PeerGroupName = ygot.String(peerGrpNameV6) + + nV4 := bgp.GetOrCreateNeighbor(isissession.ATETrafficAttrs.IPv4) + nV4.SetPeerAs(ateAS) + nV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV4.PeerGroup = ygot.String(peerGrpNameV4) + nV6 := bgp.GetOrCreateNeighbor(isissession.ATETrafficAttrs.IPv6) + nV6.SetPeerAs(ateAS) + nV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nV6.PeerGroup = ygot.String(peerGrpNameV6) + + // Configure Import Allow-All policy + configureRoutePolicy(t, ts.DUT, "ALLOW", oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + pg1af4 := pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pg1af4.Enabled = ygot.Bool(true) + + pg1rpl4 := pg1af4.GetOrCreateApplyPolicy() + pg1rpl4.SetImportPolicy([]string{"ALLOW"}) + + pg1af6 := pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + pg1af6.Enabled = ygot.Bool(true) + pg1rpl6 := pg1af6.GetOrCreateApplyPolicy() + pg1rpl6.SetImportPolicy([]string{"ALLOW"}) + + gnmi.Update(t, ts.DUT, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Config(), dni) + + // setup eBGP on ATE port1 + devBGP := ts.ATEIntf2.Bgp().SetRouterId(isissession.ATETrafficAttrs.IPv4) + + ipv4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := devBGP.Ipv4Interfaces().Add().SetIpv4Name(ipv4.Name()).Peers().Add().SetName(ts.ATEIntf2.Name() + ".BGP4.peer") + bgp4Peer.SetPeerAddress(isissession.DUTTrafficAttrs.IPv4).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + ipv6 := ts.ATEIntf2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := devBGP.Ipv6Interfaces().Add().SetIpv6Name(ipv6.Name()).Peers().Add().SetName(ts.ATEIntf2.Name() + ".BGP6.peer") + bgp6Peer.SetPeerAddress(isissession.DUTTrafficAttrs.IPv6).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + // configure emulated IPv4 and IPv6 networks + netV4 := bgp4Peer.V4Routes().Add().SetName("v4-bgpNet-dev") + netV4.Addresses().Add().SetAddress(advertisedIPv4.address).SetPrefix(advertisedIPv4.prefix).SetCount(uint32(prefixesCount)) + + netV6 := bgp6Peer.V6Routes().Add().SetName("v6-bgpNet-dev") + netV6.Addresses().Add().SetAddress(advertisedIPv6.address).SetPrefix(advertisedIPv6.prefix).SetCount(uint32(prefixesCount)) + + ts.ATE.OTG().PushConfig(t, ts.ATETop) + ts.ATE.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv4") + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv6") +} + +func advertisePrefixFromISISPort(ts *isissession.TestSession) { + netV4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName("netv4").SetLinkMetric(10).SetOriginType(gosnappi.IsisV4RouteRangeOriginType.EXTERNAL) + netV4.Addresses().Add().SetAddress(advertisedIPv4.address).SetPrefix(advertisedIPv4.prefix).SetCount(uint32(prefixesCount)) + + netV6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName("netv6").SetLinkMetric(10).SetOriginType(gosnappi.IsisV6RouteRangeOriginType.EXTERNAL) + netV6.Addresses().Add().SetAddress(advertisedIPv6.address).SetPrefix(advertisedIPv6.prefix).SetCount(uint32(prefixesCount)) +} diff --git a/feature/bgp/admin_distance/otg_tests/admin_distance_test/metadata.textproto b/feature/bgp/admin_distance/otg_tests/admin_distance_test/metadata.textproto new file mode 100644 index 00000000000..42c8e91175e --- /dev/null +++ b/feature/bgp/admin_distance/otg_tests/admin_distance_test/metadata.textproto @@ -0,0 +1,50 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "00e08af7-9b22-4b0e-bf1b-c84e0af5a896" +plan_id: "RT-1.34" +description: "BGP route-distance configuration" +testbed: TESTBED_DUT_ATE_4LINKS +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 + skip_isis_set_level: true + skip_set_rp_match_set_options: true + skip_setting_disable_metric_propagation: true + skip_setting_statement_for_policy: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_rib_oc_path_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + bgp_rib_oc_path_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} + diff --git a/feature/bgp/aspath/feature.textproto b/feature/bgp/aspath/feature.textproto index 1faad483376..e82b142eb54 100644 --- a/feature/bgp/aspath/feature.textproto +++ b/feature/bgp/aspath/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/README.md b/feature/bgp/ate_tests/bgp_long_lived_graceful_restart/README.md similarity index 97% rename from feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/README.md rename to feature/bgp/ate_tests/bgp_long_lived_graceful_restart/README.md index 5d9f48a4f5a..ba3a85b48d4 100644 --- a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/README.md +++ b/feature/bgp/ate_tests/bgp_long_lived_graceful_restart/README.md @@ -76,9 +76,15 @@ BGP Long-Lived Graceful Restart * /neighbors/neighbor/afi-safis/afi-safi/graceful-restart/state/received * /neighbors/neighbor/afi-safis/afi-safi/graceful-restart/state/advertised -## Protocol/RPC Parameter coverage +## OpenConfig Path and RPC Coverage -N/A +```yaml +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + gNMI.Subscribe: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go b/feature/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go similarity index 82% rename from feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go rename to feature/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go index 341903baae9..9fc12263244 100644 --- a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go +++ b/feature/bgp/ate_tests/bgp_long_lived_graceful_restart/bgp_long_lived_graceful_restart_test.go @@ -16,7 +16,6 @@ package bgp_long_lived_graceful_restart_test import ( "context" - "encoding/json" "fmt" "testing" "time" @@ -24,14 +23,12 @@ import ( "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" gpb "github.com/openconfig/gnmi/proto/gnmi" - gnps "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnoigo/system" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ondatra/gnmi/oc/acl" - "github.com/openconfig/ondatra/gnoi" "github.com/openconfig/ondatra/ixnet" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" @@ -193,12 +190,6 @@ var ( IPv4Len: plenIPv4, IPv6Len: plenIPv6, } - routingDaemon = map[ondatra.Vendor]string{ - ondatra.JUNIPER: "rpd", - ondatra.ARISTA: "Bgp-main", - ondatra.CISCO: "emsd", - ondatra.NOKIA: "sr_bgp_mgr", - } ) func configureRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, name string, pr oc.E_RoutingPolicy_PolicyResultType) { @@ -612,215 +603,6 @@ func configACLInterface(iFace *oc.Acl_Interface, ifName string) *acl.Acl_Interfa return aclConf } -// Helper function to replicate configACL() configs in native model. -// Define the values for each ACL entry and marshal for json encoding. -// Then craft a gNMI set Request to update the changes. -func configACLNative(t testing.TB, d *ondatra.DUTDevice, name string) { - t.Helper() - switch d.Vendor() { - case ondatra.NOKIA: - var aclEntry10Val = []any{ - map[string]any{ - "action": map[string]any{ - "drop": map[string]any{}, - }, - "match": map[string]any{ - "destination-ip": map[string]any{ - "prefix": ateDstCIDR, - }, - "source-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - }, - }, - } - entry10Update, err := json.Marshal(aclEntry10Val) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - - var aclEntry20Val = []any{ - map[string]any{ - "action": map[string]any{ - "drop": map[string]any{}, - }, - "match": map[string]any{ - "source-ip": map[string]any{ - "prefix": ateDstCIDR, - }, - "destination-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - }, - }, - } - entry20Update, err := json.Marshal(aclEntry20Val) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - - var aclEntry30Val = []any{ - map[string]any{ - "action": map[string]any{ - "accept": map[string]any{}, - }, - "match": map[string]any{ - "source-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - "destination-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - }, - }, - } - entry30Update, err := json.Marshal(aclEntry30Val) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - gpbSetRequest := &gpb.SetRequest{ - Prefix: &gpb.Path{ - Origin: "srl", - }, - Update: []*gpb.Update{ - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "10"}}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: entry10Update, - }, - }, - }, - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "20"}}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: entry20Update, - }, - }, - }, - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "30"}}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: entry30Update, - }, - }, - }, - }, - } - gnmiClient := d.RawAPIs().GNMI(t) - if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { - t.Fatalf("Unexpected error configuring SRL ACL: %v", err) - } - default: - t.Fatalf("Unsupported vendor %s for deviation 'UseVendorNativeACLConfiguration'", d.Vendor()) - } -} - -// Helper function to replicate AdmitAllACL() configs in native model, -// then craft a gNMI set Request to update the changes. -func configAdmitAllACLNative(t testing.TB, d *ondatra.DUTDevice, name string) { - t.Helper() - switch d.Vendor() { - case ondatra.NOKIA: - gpbDelRequest := &gpb.SetRequest{ - Prefix: &gpb.Path{ - Origin: "srl", - }, - Delete: []*gpb.Path{ - { - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "10"}}, - }, - }, - { - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "20"}}, - }, - }, - }, - } - gnmiClient := d.RawAPIs().GNMI(t) - if _, err := gnmiClient.Set(context.Background(), gpbDelRequest); err != nil { - t.Fatalf("Unexpected error removing SRL ACL: %v", err) - } - default: - t.Fatalf("Unsupported vendor %s for deviation 'UseVendorNativeACLConfiguration'", d.Vendor()) - } -} - -// Helper function to replicate configACLInterface in native model. -// Set ACL at interface ingress, -// then craft a gNMI set Request to update the changes. -func configACLInterfaceNative(t *testing.T, d *ondatra.DUTDevice, ifName string) { - t.Helper() - switch d.Vendor() { - case ondatra.NOKIA: - var interfaceAclVal = []any{ - map[string]any{ - "ipv4-filter": []any{ - aclName, - }, - }, - } - interfaceAclUpdate, err := json.Marshal(interfaceAclVal) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - gpbSetRequest := &gpb.SetRequest{ - Prefix: &gpb.Path{ - Origin: "srl", - }, - Update: []*gpb.Update{ - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "interface", Key: map[string]string{"name": ifName}}, - {Name: "subinterface", Key: map[string]string{"index": "0"}}, - {Name: "acl"}, - {Name: "input"}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: interfaceAclUpdate, - }, - }, - }, - }, - } - gnmiClient := d.RawAPIs().GNMI(t) - if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { - t.Fatalf("Unexpected error configuring interface ACL: %v", err) - } - default: - t.Fatalf("Unsupported vendor %s for deviation 'UseVendorNativeACLConfiguration'", d.Vendor()) - } -} - func disableLLGRConf(dut *ondatra.DUTDevice, as int) string { switch dut.Vendor() { case ondatra.ARISTA: @@ -862,56 +644,6 @@ func removeNewPeers(t *testing.T, dut *ondatra.DUTDevice, nbrs []*bgpNeighbor) { fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) } -func restartRoutingProcess(t *testing.T, dut *ondatra.DUTDevice) { - t.Helper() - if _, ok := routingDaemon[dut.Vendor()]; !ok { - t.Fatalf("Please add support for vendor %v in var routingDaemon", dut.Vendor()) - } - t.Run("KillGRIBIDaemon", func(t *testing.T) { - // Find the PID of routing Daemon. - var pId uint64 - pName := routingDaemon[dut.Vendor()] - t.Run("FindroutingDaemonPid", func(t *testing.T) { - pId = findProcessByName(t, dut, pName) - if pId == 0 { - t.Fatalf("Couldn't find pid of routing daemon '%s'", pName) - } else { - t.Logf("Pid of routing daemon '%s' is '%d'", pName, pId) - } - }) - - // Kill routing daemon through gNOI Kill Request. - t.Run("ExecuteGnoiKill", func(t *testing.T) { - // TODO - pid type is uint64 in oc-system model, but uint32 in gNOI Kill Request proto. - // Until the models are brought in line, typecasting the uint64 to uint32. - gNOIKillProcess(t, dut, pName, uint32(pId)) - // Wait for a bit for routing daemon on the DUT to restart. - time.Sleep(30 * time.Second) - }) - }) -} - -// findProcessByName uses telemetry to find out the PID of a process -func findProcessByName(t *testing.T, dut *ondatra.DUTDevice, pName string) uint64 { - t.Helper() - pList := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - var pID uint64 - for _, proc := range pList { - if proc.GetName() == pName { - pID = proc.GetPid() - t.Logf("Pid of daemon '%s' is '%d'", pName, pID) - } - } - return pID -} - -// gNOIKillProcess kills a daemon on the DUT, given its name and pid. -func gNOIKillProcess(t *testing.T, dut *ondatra.DUTDevice, pName string, pID uint32) { - t.Helper() - killResponse := gnoi.Execute(t, dut, system.NewKillProcessOperation().Name(pName).PID(pID).Signal(gnps.KillProcessRequest_SIGNAL_TERM).Restart(true)) - t.Logf("Got kill process response: %v\n\n", killResponse) -} - // setBgpPolicy is used to configure routing policy on DUT. func setBgpPolicy(t *testing.T, dut *ondatra.DUTDevice, d *oc.Root) { t.Helper() @@ -1106,14 +838,9 @@ func TestTrafficWithGracefulRestartLLGR(t *testing.T) { startTime := time.Now() t.Log("Trigger graceful restart on ATE") ate.Actions().NewBGPGracefulRestart().WithRestartTime(grRestartTime * time.Second).WithPeers(bgpPeer).Send(t) - if deviations.UseVendorNativeACLConfig(dut) { - configACLNative(t, dut, aclName) - configACLInterfaceNative(t, dut, ifName) - } else { - gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configACL(d, aclName)) - aclConf := configACLInterface(iFace, ifName) - gnmi.Replace(t, dut, aclConf.Config(), iFace) - } + gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configACL(d, aclName)) + aclConf := configACLInterface(iFace, ifName) + gnmi.Replace(t, dut, aclConf.Config(), iFace) t.Run("Verify graceful restart telemetry", func(t *testing.T) { verifyGracefulRestart(t, dut) @@ -1158,7 +885,7 @@ func TestTrafficWithGracefulRestartLLGR(t *testing.T) { }) t.Run("Restart routing", func(t *testing.T) { - restartRoutingProcess(t, dut) + gnoi.KillProcess(t, dut, gnoi.ROUTING, gnoi.SigTerm, true, true) }) var bgpIxPeer []*ixnet.BGP @@ -1203,14 +930,9 @@ func TestTrafficWithGracefulRestartLLGR(t *testing.T) { t.Run("RemoveAclInterface", func(t *testing.T) { t.Log("Removing ACL on the interface to restore BGP GR. Traffic should now pass!") - if deviations.UseVendorNativeACLConfig(dut) { - configAdmitAllACLNative(t, dut, aclName) - configACLInterfaceNative(t, dut, ifName) - } else { - gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configAdmitAllACL(d, aclName)) - aclPath := configACLInterface(iFace, ifName) - gnmi.Replace(t, dut, aclPath.Config(), iFace) - } + gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configAdmitAllACL(d, aclName)) + aclPath := configACLInterface(iFace, ifName) + gnmi.Replace(t, dut, aclPath.Config(), iFace) }) t.Run("VerifyBGPEstablished", func(t *testing.T) { @@ -1292,14 +1014,9 @@ func TestTrafficWithGracefulRestart(t *testing.T) { startTime := time.Now() t.Log("Trigger graceful restart on ATE") ate.Actions().NewBGPGracefulRestart().WithRestartTime(grRestartTime * time.Second).WithPeers(bgpPeer).Send(t) - if deviations.UseVendorNativeACLConfig(dut) { - configACLNative(t, dut, aclName) - configACLInterfaceNative(t, dut, ifName) - } else { - gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configACL(d, aclName)) - aclConf := configACLInterface(iFace, ifName) - gnmi.Replace(t, dut, aclConf.Config(), iFace) - } + gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configACL(d, aclName)) + aclConf := configACLInterface(iFace, ifName) + gnmi.Replace(t, dut, aclConf.Config(), iFace) t.Run("Verify graceful restart telemetry", func(t *testing.T) { verifyGracefulRestart(t, dut) @@ -1336,14 +1053,9 @@ func TestTrafficWithGracefulRestart(t *testing.T) { t.Run("RemoveAclInterface", func(t *testing.T) { t.Log("Removing Acl on the interface to restore BGP GR. Traffic should now pass!") - if deviations.UseVendorNativeACLConfig(dut) { - configAdmitAllACLNative(t, dut, aclName) - configACLInterfaceNative(t, dut, ifName) - } else { - gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configAdmitAllACL(d, aclName)) - aclPath := configACLInterface(iFace, ifName) - gnmi.Replace(t, dut, aclPath.Config(), iFace) - } + gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configAdmitAllACL(d, aclName)) + aclPath := configACLInterface(iFace, ifName) + gnmi.Replace(t, dut, aclPath.Config(), iFace) }) t.Run("VerifyBGPEstablished", func(t *testing.T) { diff --git a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto b/feature/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto similarity index 85% rename from feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto rename to feature/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto index 29d9912c585..004e3e5a3e6 100644 --- a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto +++ b/feature/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto @@ -1,9 +1,9 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "9f319fa6-d197-48fa-84fe-ea075c422190" -plan_id: "RT-1.14" -description: "BGP Long-Lived Graceful Restart" +uuid: "9f319fa6-d197-48fa-84fe-ea075c422190" +plan_id: "RT-1.14" +description: "BGP Long-Lived Graceful Restart" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { @@ -18,7 +18,6 @@ platform_exceptions: { vendor: NOKIA } deviations: { - use_vendor_native_acl_config: true explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true @@ -39,11 +38,11 @@ platform_exceptions: { deviations: { route_policy_under_afi_unsupported: true omit_l2_mtu: true - interface_enabled: true - default_network_instance: "default" + interface_config_vrf_before_address: true deprecated_vlan_id: true + interface_enabled: true require_routed_subinterface_0: true - interface_config_vrf_before_address: true + default_network_instance: "default" bgp_llgr_oc_undefined: true } } diff --git a/feature/bgp/bestpath/feature.textproto b/feature/bgp/bestpath/feature.textproto index 05e2c3ed899..48d03763a8f 100644 --- a/feature/bgp/bestpath/feature.textproto +++ b/feature/bgp/bestpath/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/README.md b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/README.md new file mode 100644 index 00000000000..5a9fe642af5 --- /dev/null +++ b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/README.md @@ -0,0 +1,411 @@ +# RT-1.28: BGP to IS-IS redistribution + +## Summary + +- Source-protocol: BGP, destination-protocol: ISIS L2 +- Using Community (Source-protocol: BGP) +- Using defined prefix set + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed + +## Procedure + +#### Initial Setup: + +* Connect DUT port-1, 2 to ATE port-1, 2 +* Configure IPv4/IPv6 addresses on the ports +* Create an IPv4 networks i.e. ```ipv4-network = 192.168.10.0/24``` attached to ATE port-2 +* Create an IPv6 networks i.e. ```ipv6-network = 2024:db8:128:128::/64``` attached to ATE port-2 +* Configure IPv4 and IPv6 eBGP between DUT Port-2 and ATE Port-2 + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network = 192.168.10.0/24``` and ```ipv6-network = 2024:db8:128:128::/64``` from ATE to DUT with community ```64512:100``` +* Configure IPv4 and IPv6 IS-IS L2 adjacency between ATE port-1 and DUT port-1 + * /network-instances/network-instance/protocols/protocol/isis/global/afi-safi + * Set level-capability to ```LEVEL_2``` + * /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability + * Set metric-style to ```WIDE_METRIC``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style + +### RT-1.28.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### Non matching IPv4 BGP prefixes in a prefix-set should not be redistributed to IS-IS +--- +##### Configure a route-policy +* Configure an IPv4 route-policy definition with the name ```route-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```route-policy-v4``` configure a statement with the name ```statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set IS-IS level to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-level +##### Configure a prefix-set with a prefix that does not match BGP route for ```ipv4-network = 192.168.10.0/24``` +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```192.168.20.0/24``` and masklength to ```exact``` + * /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 +##### Attach prefix-set to route-policy +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set +##### Configure BGP to IS-IS L2 redistribution +* Set address-family to ```IPV4``` + * /network-instances/network-instance/table-connections/table-connection/config/address-family +* Configure source protocol to ```BGP``` + * /network-instances/network-instance/table-connections/table-connection/config/src-protocol +* Configure destination protocol to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/config/dst-protocol +* Disable metric propogation by setting it to ```false``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +##### Set default-import-policy to reject +* Configure default import policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +##### Attach the route-policy to import-policy +* Apply routing policy ```route-policy-v4``` for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +##### Verification +* Verify IPv4 route-policy definition is configured with the name ```route-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/state/name +* Verify for routing-policy ```route-policy-v4``` a statement with the name ```statement-v4``` is configured + * /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` policy-result is set to ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` IS-IS level is set to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-level +* Verify prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` is configured + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* Verify for prefix-set ```prefix-set-v4``` the ip-prefix is set to ```192.168.20.0/24``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` match options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` prefix-set is set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set +* Verify routing policy ```route-policy-v4``` is applied as import policy for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/state/import-policy +* Verify the address-family is set to ```IPV4``` + * /network-instances/network-instance/table-connections/table-connection/state/address-family +* Verify source protocol is set to ```BGP``` + * /network-instances/network-instance/table-connections/table-connection/state/src-protocol +* Verify destination protocol is set to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/state/dst-protocol +* Verify disable metric propogation is set to ```false``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +* Verify default import policy is set to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +* Verify routing policy ```route-policy-v4``` is applied for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +##### Validate test results +* Validate that the IS-IS on ATE does not receives the redistributed BGP route for network ```ipv4-network``` i.e. ```192.168.10.0/24``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix + +### RT-1.28.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### Matching IPv4 BGP prefixes in a prefix-set should be redistributed to IS-IS +--- +##### Replace the previously configured prefix and mask in prefix-set configured in RT-1.28.1 +* For prefix-set ```prefix-set-v4``` replace the ip-prefix to ```192.168.10.0/24``` and masklength is set to ```exact``` + * /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 +##### Verification +* Verify for prefix-set ```prefix-set-v4``` the ip-prefix is set to ```192.168.10.0/24``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +##### Validate test results +* Validate that the IS-IS on ATE receives the redistributed BGP route for network ```ipv4-network``` i.e. ```192.168.10.0/24``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` +* Validate that the traffic is received on ATE port-2 + +### RT-1.28.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### IPv4: Non matching BGP community in a community-set should not be redistributed to IS-IS +--- +##### Configure a community-set +* Configure a community-set with name ```community-set-v4``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name +* For community set ```community-set-v4``` configure a community member value to ```64599:200``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Attach community-set to the route-policy +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` reference the community set ```community-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set +##### Verification +* Verity a community set with name ```community-set-v4``` exists + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name +* Verify for community set ```community-set-v4``` a community member value of ```64599:200``` is configured + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +##### Validate test results +* Validate that the IS-IS on ATE does not receives the redistributed BGP route for network ```ipv4-network``` i.e. ```192.168.10.0/24``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix + +### RT-1.28.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### IPv4: Matching BGP community in a community-set should be redistributed to IS-IS +--- +##### Replace the previously configured community member value in RT-1.28.3 +* For community set ```community-set-v4``` replece the community member value to ```64512:100``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Verification +* Verify for community set ```community-set-v4``` a community member value of ```64512:100``` is configured + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +##### Validate test results +* Validate that the IS-IS on ATE receives the redistributed BGP route for network ```ipv4-network``` i.e. ```192.168.10.0/24``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/metric +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` +* Validate that the traffic is received on ATE port-2 + +### RT-1.28.5 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### Non matching IPv6 BGP prefixes in a prefix-set should not be redistributed to IS-IS +--- +##### Configure a route-policy +* Configure an IPv6 route-policy definition with the name ```route-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```route-policy-v6``` configure a statement with the name ```statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set IS-IS level to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-level +##### Configure a prefix-set with a prefix that does not match BGP route for ```ipv6-network = 2024:db8:128:128::/64``` +* Configure a prefix-set with the name ```prefix-set-v6``` and mode ```IPv6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v6``` set the ip-prefix to ```2024:db8:64:64::/64``` and masklength to ```exact``` + * /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 +##### Attach prefix-set to route-policy +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set prefix set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set +##### Configure BGP to IS-IS L2 redistribution +* Set address-family to ```IPV6``` + * /network-instances/network-instance/table-connections/table-connection/config/address-family +* Configure source protocol to ```BGP``` + * /network-instances/network-instance/table-connections/table-connection/config/src-protocol +* Configure destination protocol to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/config/dst-protocol +* Disable metric propogation by setting it to ```false``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +##### Set default-import-policy to reject +* Configure default import policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +##### Attach the route-policy to import-policy +* Apply routing policy ```route-policy-v6``` for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +##### Verification +* Verify IPv6 route-policy definition is configured with the name ```route-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/state/name +* Verify for routing-policy ```route-policy-v6``` a statement with the name ```statement-v6``` is configured + * /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` policy-result is set to ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` IS-IS level is set to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-level +* Verify prefix-set with the name ```prefix-set-v6``` and mode ```IPv6``` is configured + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* Verify for prefix-set ```prefix-set-v6``` the ip-prefix is set to ```2024:db8:64:64::/64``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` match options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` prefix-set is set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set +* Verify routing policy ```route-policy-v6``` is applied as import policy for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/state/import-policy +* Verify the address-family is set to ```IPV6``` + * /network-instances/network-instance/table-connections/table-connection/state/address-family +* Verify source protocol is set to ```BGP``` + * /network-instances/network-instance/table-connections/table-connection/state/src-protocol +* Verify destination protocol is set to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/state/dst-protocol +* Verify disable metric propogation is set to ```false``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +* Verify default import policy is set to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +* Verify routing policy ```route-policy-v6``` is applied for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +##### Validate test results +* Validate that the IS-IS on ATE does not receives the redistributed BGP route for network ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv6-reachability/prefixes/prefix/state/prefix + +### RT-1.28.6 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### Matching IPv6 BGP prefixes in a prefix-set should be redistributed to IS-IS +--- +##### Replace the previously configured prefix and mask in prefix-set configured in RT-1.28.5 +* For prefix-set ```prefix-set-v6``` replace the ip-prefix to ```2024:db8:128:128::/64``` and masklength is set to ```exact``` + * /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 +##### Verification +* Verify for prefix-set ```prefix-set-v6``` the ip-prefix is set to ```2024:db8:128:128::/64``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +##### Validate test results +* Validate that the IS-IS on ATE receives the redistributed BGP route for network ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv6-reachability/prefixes/prefix/state/prefix +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` +* Validate that the traffic is received on ATE port-2 + +### RT-1.28.7 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### IPv6: Non matching BGP community in a community-set should not be redistributed to IS-IS +--- +##### Configure a community-set +* Configure a community-set with name ```community-set-v6``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name +* For community set ```community-set-v6``` configure a community member value to ```64599:200``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Attach community-set to the route-policy +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` reference the community set ```community-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set +##### Verification +* Verity a community set with name ```community-set-v6``` exists + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name +* Verify for community set ```community-set-v6``` a community member value of ```64599:200``` is configured + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +##### Validate test results +* Validate that the IS-IS on ATE does not receives the redistributed BGP route for network ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv6-reachability/prefixes/prefix/state/prefix + +### RT-1.28.8 [TODO: https://github.com/openconfig/featureprofiles/issues/2570] +#### IPv6: Matching BGP community in a community-set should be redistributed to IS-IS +--- +##### Replace the previously configured community member value in RT-1.28.7 +* For community set ```community-set-v6``` replece the community member value to ```64512:100``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Verification +* Verify for community set ```community-set-v6``` a community member value of ```64512:100``` is configured + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +##### Validate test results +* Validate that the IS-IS on ATE receives the redistributed BGP route for network ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv6-reachability/prefixes/prefix/state/prefix + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv6-reachability/prefixes/prefix/state/metric +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/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/ + +* /network-instances/network-instance/protocols/protocol/isis/global/afi-safi +* /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability +* /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style + +* /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/actions/isis-actions/config/set-level + +* /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/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/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref + +* /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 + +## Telemetry parameter coverage + +* /routing-policy/policy-definitions/policy-definition/state/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-level + +* /routing-policy/defined-sets/prefix-sets/prefix-set/state/name +* /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix +* /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range + +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set + +* /network-instances/network-instance/table-connections/table-connection/state/import-policy +* /network-instances/network-instance/table-connections/table-connection/state/address-family +* /network-instances/network-instance/table-connections/table-connection/state/src-protocol +* /network-instances/network-instance/table-connections/table-connection/state/dst-protocol +* /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation + +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member + +* /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix +* /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv6-reachability/prefixes/prefix/state/prefix + +## Protocol/RPC Parameter Coverage + +* gNMI + * Get + * Set + +## Required DUT platform + +* FFF + +## 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/config/level-capability: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style: + /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/actions/isis-actions/config/set-level: + /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/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/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref: + /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: + + ## State paths + /routing-policy/policy-definitions/policy-definition/state/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/state/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-level: + /routing-policy/defined-sets/prefix-sets/prefix-set/state/name: + /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set: + /network-instances/network-instance/table-connections/table-connection/state/import-policy: + /network-instances/network-instance/table-connections/table-connection/state/address-family: + /network-instances/network-instance/table-connections/table-connection/state/src-protocol: + /network-instances/network-instance/table-connections/table-connection/state/dst-protocol: + /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/bgp_isis_redistribution_test.go b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/bgp_isis_redistribution_test.go new file mode 100644 index 00000000000..c54fe7f044d --- /dev/null +++ b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/bgp_isis_redistribution_test.go @@ -0,0 +1,907 @@ +// 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_isis_redistribution_test + +import ( + "fmt" + "net" + "strconv" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "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/isissession" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + bgpName = "BGP" + maskLenExact = "exact" + dummyAS = uint32(64655) + dutAS = uint32(64656) + ateAS = uint32(64657) + v4Route = "203.10.113.0" + v4TrafficStart = "203.10.113.1" + v4DummyRoute = "192.51.100.0" + v4RoutePrefix = uint32(24) + v6Route = "2001:db8:128:128:0:0:0:0" + v6TrafficStart = "2001:db8:128:128:0:0:0:1" + v6DummyRoute = "2001:db8:128:129:0:0:0:0" + v6RoutePrefix = uint32(64) + v4RoutePolicy = "route-policy-v4" + v4Statement = "statement-v4" + v4PrefixSet = "prefix-set-v4" + v4FlowName = "flow-v4" + v4CommunitySet = "community-set-v4" + v6RoutePolicy = "route-policy-v6" + v6Statement = "statement-v6" + v6PrefixSet = "prefix-set-v6" + v6FlowName = "flow-v6" + v6CommunitySet = "community-set-v6" + peerGrpNamev4 = "BGP-PEER-GROUP-V4" + peerGrpNamev6 = "BGP-PEER-GROUP-V6" + allowAllPolicy = "ALLOWAll" + tablePolicyMatchCommunitySetTag = "TablePolicyMatchCommunitySetTag" + matchTagRedistributionPolicy = "MatchTagRedistributionPolicy" + nonMatchingCommunityVal = "64655:200" + matchingCommunityVal = "64657:100" + routeTagVal = 10000 +) + +var ( + advertisedIPv4 ipAddr = ipAddr{address: v4Route, prefix: v4RoutePrefix} + advertisedIPv6 ipAddr = ipAddr{address: v6Route, prefix: v6RoutePrefix} + nonAdvertisedIPv4 ipAddr = ipAddr{address: v4DummyRoute, prefix: v4RoutePrefix} + nonAdvertisedIPv6 ipAddr = ipAddr{address: v6DummyRoute, prefix: v6RoutePrefix} +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type ipAddr struct { + address string + prefix uint32 +} + +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() +} + +type testCase struct { + name string + desc string + applyPolicyFunc func(t *testing.T, dut *ondatra.DUTDevice) + verifyTelemetryFunc func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) + testTraffic bool + ipv4 bool +} + +func TestBGPToISISRedistribution(t *testing.T) { + ts := isissession.MustNew(t).WithISIS() + t.Run("ISIS Setup", func(t *testing.T) { + ts.PushAndStart(t) + ts.MustAdjacency(t) + }) + configureRoutePolicyAllow(t, ts.DUT, allowAllPolicy, oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + setupEBGPAndAdvertise(t, ts) + t.Run("BGP Setup", func(t *testing.T) { + t.Log("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, ts.DUT) + + t.Log("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, ts.ATE) + }) + + testCases := []testCase{ + { + name: "NonMatchingPrefix", + desc: "Non matching IPv4 BGP prefixes in a prefix-set should not be redistributed to IS-IS", + applyPolicyFunc: nonMatchingPrefixRoutePolicy, + verifyTelemetryFunc: verifyNonMatchingPrefixTelemetry, + testTraffic: false, + ipv4: true, + }, + { + name: "MatchingPrefix", + desc: "Matching IPv4 BGP prefixes in a prefix-set should be redistributed to IS-IS", + applyPolicyFunc: matchingPrefixRoutePolicy, + verifyTelemetryFunc: verifyMatchingPrefixTelemetry, + testTraffic: true, + ipv4: true, + }, + { + name: "NonMatchingCommunity", + desc: "IPv4: Non matching BGP community in a community-set should not be redistributed to IS-IS", + applyPolicyFunc: nonMatchingCommunityRoutePolicy, + verifyTelemetryFunc: verifyNonMatchingCommunityTelemetry, + testTraffic: false, + ipv4: true, + }, + { + name: "MatchingCommunity", + desc: "IPv4: Matching BGP community in a community-set should be redistributed to IS-IS", + applyPolicyFunc: matchingCommunityRoutePolicy, + verifyTelemetryFunc: verifyMatchingCommunityTelemetry, + testTraffic: true, + ipv4: true, + }, + { + name: "NonMatchingPrefixV6", + desc: "Non matching IPv6 BGP prefixes in a prefix-set should not be redistributed to IS-IS", + applyPolicyFunc: nonMatchingPrefixRoutePolicyV6, + verifyTelemetryFunc: verifyNonMatchingPrefixTelemetryV6, + testTraffic: false, + ipv4: false, + }, + { + name: "MatchingPrefixV6", + desc: "Matching IPv6 BGP prefixes in a prefix-set should be redistributed to IS-IS", + applyPolicyFunc: matchingPrefixRoutePolicyV6, + verifyTelemetryFunc: verifyMatchingPrefixTelemetryV6, + testTraffic: true, + ipv4: false, + }, + { + name: "NonMatchingCommunityV6", + desc: "IPv6: Non matching BGP community in a community-set should not be redistributed to IS-IS", + applyPolicyFunc: nonMatchingCommunityRoutePolicyV6, + verifyTelemetryFunc: verifyNonMatchingCommunityTelemetryV6, + testTraffic: false, + ipv4: false, + }, + { + name: "MatchingCommunityV6", + desc: "IPv6: Matching BGP community in a community-set should be redistributed to IS-IS", + applyPolicyFunc: matchingCommunityRoutePolicyV6, + verifyTelemetryFunc: verifyMatchingCommunityTelemetryV6, + testTraffic: true, + ipv4: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Logf("Description: %s", tc.desc) + tc.applyPolicyFunc(t, ts.DUT) + if tc.ipv4 { + bgpISISRedistribution(t, ts.DUT, "set") + defer bgpISISRedistribution(t, ts.DUT, "delete") + } else { + bgpISISRedistributionV6(t, ts.DUT, "set") + defer bgpISISRedistributionV6(t, ts.DUT, "delete") + } + tc.verifyTelemetryFunc(t, ts.DUT, ts.ATE) + if tc.testTraffic { + if tc.ipv4 { + createFlow(t, ts) + checkTraffic(t, ts, v4FlowName) + } else { + createFlowV6(t, ts) + checkTraffic(t, ts, v6FlowName) + } + } + }) + } +} + +// setupEBGPAndAdvertise setups eBGP on DUT port1 and ATE port1 +func setupEBGPAndAdvertise(t *testing.T, ts *isissession.TestSession) { + t.Helper() + dut := ondatra.DUT(t, "dut") + // setup eBGP on DUT port2 + root := &oc.Root{} + dni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) + dni.SetType(oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + + bgpP := dni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName) + bgpP.SetEnabled(true) + bgp := bgpP.GetOrCreateBgp() + + g := bgp.GetOrCreateGlobal() + g.SetAs(dutAS) + g.SetRouterId(isissession.DUTTrafficAttrs.IPv4) + 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) + + pgv4 := bgp.GetOrCreatePeerGroup(peerGrpNamev4) + pgv4.PeerGroupName = ygot.String(peerGrpNamev4) + pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNamev6) + pgv6.PeerGroupName = ygot.String(peerGrpNamev6) + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pgv4.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{allowAllPolicy}) + rpl.SetImportPolicy([]string{allowAllPolicy}) + rplv6 := pgv6.GetOrCreateApplyPolicy() + rplv6.SetExportPolicy([]string{"ALLOW"}) + rplv6.SetImportPolicy([]string{"ALLOW"}) + } else { + pg1af4 := pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pg1af4.Enabled = ygot.Bool(true) + pg1rpl4 := pg1af4.GetOrCreateApplyPolicy() + pg1rpl4.SetExportPolicy([]string{allowAllPolicy}) + pg1rpl4.SetImportPolicy([]string{allowAllPolicy}) + pg1af6 := pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + pg1af6.Enabled = ygot.Bool(true) + pg1rpl6 := pg1af6.GetOrCreateApplyPolicy() + pg1rpl6.SetExportPolicy([]string{allowAllPolicy}) + pg1rpl6.SetImportPolicy([]string{allowAllPolicy}) + } + + nV4 := bgp.GetOrCreateNeighbor(isissession.ATETrafficAttrs.IPv4) + nV4.SetPeerAs(ateAS) + nV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV4.PeerGroup = ygot.String(peerGrpNamev4) + + nV6 := bgp.GetOrCreateNeighbor(isissession.ATETrafficAttrs.IPv6) + nV6.SetPeerAs(ateAS) + nV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nV6.PeerGroup = ygot.String(peerGrpNamev6) + gnmi.Update(t, ts.DUT, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Config(), dni) + + // setup eBGP on ATE port2 + dev2BGP := ts.ATEIntf2.Bgp().SetRouterId(isissession.ATETrafficAttrs.IPv4) + + ipv4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := dev2BGP.Ipv4Interfaces().Add().SetIpv4Name(ipv4.Name()).Peers().Add().SetName(ts.ATEIntf2.Name() + ".BGP4.peer") + bgp4Peer.SetPeerAddress(isissession.DUTTrafficAttrs.IPv4).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + ipv6 := ts.ATEIntf2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := dev2BGP.Ipv6Interfaces().Add().SetIpv6Name(ipv6.Name()).Peers().Add().SetName(ts.ATEIntf2.Name() + ".BGP6.peer") + bgp6Peer.SetPeerAddress(isissession.DUTTrafficAttrs.IPv6).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + // configure emulated IPv4 and IPv6 networks + netv4 := bgp4Peer.V4Routes().Add().SetName("v4-bgpNet-dev1") + netv4.Addresses().Add().SetAddress(advertisedIPv4.address).SetPrefix(advertisedIPv4.prefix) + commv4 := netv4.Communities().Add() + commv4.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv4.SetAsNumber(ateAS) + commv4.SetAsCustom(100) + + netv6 := bgp6Peer.V6Routes().Add().SetName("v6-bgpNet-dev1") + netv6.Addresses().Add().SetAddress(advertisedIPv6.address).SetPrefix(advertisedIPv6.prefix) + commv6 := netv6.Communities().Add() + commv6.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv6.SetAsNumber(ateAS) + commv6.SetAsCustom(100) + + ts.ATE.OTG().PushConfig(t, ts.ATETop) + ts.ATE.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv4") + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv6") +} + +func nonMatchingPrefixRoutePolicy(t *testing.T, dut *ondatra.DUTDevice) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(v4RoutePolicy) + stmt, err := pdef.AppendNewStatement(v4Statement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4Statement, err) + } + stmt.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + if !deviations.SkipIsisSetLevel(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetLevel(2) + } + if !deviations.SkipIsisSetMetricStyleType(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetMetricStyleType(oc.IsisPolicy_MetricStyle_WIDE_METRIC) + } + + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v4PrefixSet) + prefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + prefixSet.GetOrCreatePrefix(nonAdvertisedIPv4.cidr(t), maskLenExact) + + if !deviations.SkipSetRpMatchSetOptions(dut) { + stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v4PrefixSet) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + +} + +func matchingPrefixRoutePolicy(t *testing.T, dut *ondatra.DUTDevice) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v4PrefixSet) + prefixSet.GetOrCreatePrefix(advertisedIPv4.cidr(t), maskLenExact) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(v4PrefixSet).Config(), prefixSet) +} + +func nonMatchingCommunityRoutePolicy(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.CommunityMatchWithRedistributionUnsupported(dut) { + configureBGPTablePolicyWithSetTag(t, v4PrefixSet, advertisedIPv4.cidr(t), v4CommunitySet, dummyAS, 200, true) + bgpISISRedistributionWithRouteTagPolicy(t, dut, oc.Types_ADDRESS_FAMILY_IPV4) + } else { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(v4RoutePolicy) + stmt, err := pdef.AppendNewStatement(v4Statement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4Statement, err) + } + stmt.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + if !deviations.SkipIsisSetLevel(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetLevel(2) + } + if !deviations.SkipIsisSetMetricStyleType(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetMetricStyleType(oc.IsisPolicy_MetricStyle_WIDE_METRIC) + } + + communitySet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(v4CommunitySet) + communitySet.SetCommunityMember([]oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString(fmt.Sprintf("%d:%d", dummyAS, 200))}) + communitySet.SetMatchSetOptions(oc.BgpPolicy_MatchSetOptionsType_ANY) + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(v4CommunitySet) + } else { + stmt.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(v4CommunitySet) + } + // Configure ALLOWAll policy + pdef = rp.GetOrCreatePolicyDefinition(allowAllPolicy) + stmt, err = pdef.AppendNewStatement("id-1") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "id-1", err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } +} + +func matchingCommunityRoutePolicy(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.CommunityMatchWithRedistributionUnsupported(dut) { + configureBGPTablePolicyWithSetTag(t, v4PrefixSet, advertisedIPv4.cidr(t), v4CommunitySet, ateAS, 100, true) + bgpISISRedistributionWithRouteTagPolicy(t, dut, oc.Types_ADDRESS_FAMILY_IPV4) + } else { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + communitySet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(v4CommunitySet) + communitySet.SetCommunityMember([]oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString(fmt.Sprintf("%d:%d", ateAS, 100))}) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().BgpDefinedSets().CommunitySet(v4CommunitySet).Config(), communitySet) + } +} + +func verifyNonMatchingPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + rPolicy := gnmi.Get[*oc.RoutingPolicy](t, dut, gnmi.OC().RoutingPolicy().State()) + + rPolicyDef := rPolicy.GetPolicyDefinition(v4RoutePolicy) + if rpName := rPolicyDef.GetName(); rpName != v4RoutePolicy { + t.Errorf("Routing policy name: %s, want: %s", rpName, v4RoutePolicy) + } + if stmtName := rPolicyDef.GetStatement(v4Statement).GetName(); stmtName != v4Statement { + t.Errorf("Routing policy statement name: %s, want: %s", stmtName, v4Statement) + } + if polResult := rPolicyDef.GetStatement(v4Statement).GetActions().GetPolicyResult(); polResult != oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE { + t.Errorf("Routing policy statement result: %s, want: %s", polResult, oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + if !deviations.SkipIsisSetLevel(dut) { + if isisLevel := rPolicyDef.GetStatement(v4Statement).GetActions().GetIsisActions().GetSetLevel(); isisLevel != 2 { + t.Errorf("IS-IS level: %d, want: %d", isisLevel, 2) + } + } + + prefixSet := rPolicy.GetDefinedSets().GetPrefixSet(v4PrefixSet) + if pName := prefixSet.GetName(); pName != v4PrefixSet { + t.Errorf("Prefix set name: %s, want: %s", pName, v4PrefixSet) + } + if pMode := prefixSet.GetMode(); pMode != oc.PrefixSet_Mode_IPV4 { + t.Errorf("Prefix set mode: %s, want: %s", pMode, oc.PrefixSet_Mode_IPV4) + } + if prefix := prefixSet.GetPrefix(nonAdvertisedIPv4.cidr(t), maskLenExact); prefix == nil { + t.Errorf("Prefix is nil, want: %s", nonAdvertisedIPv4.cidr(t)) + } + + stmt := rPolicyDef.GetStatement(v4Statement) + if matchSetOpts := stmt.GetConditions().GetMatchPrefixSet().GetMatchSetOptions(); matchSetOpts != oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY { + t.Errorf("Match prefix set options: %s, want: %s", matchSetOpts, oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + if prefixSet := stmt.GetConditions().GetMatchPrefixSet().GetPrefixSet(); prefixSet != v4PrefixSet { + t.Errorf("Match prefix set prefix set: %s, want: %s", prefixSet, v4PrefixSet) + } + + tableConn := gnmi.Get[*oc.NetworkInstance_TableConnection](t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV4).State()) + if tableConn == nil { + t.Errorf("Table connection is nil, want non-nil") + } + if metricProp := tableConn.GetDisableMetricPropagation(); metricProp != false { + t.Errorf("Metric propagation: %t, want: %t", metricProp, false) + } + if defaultImportPolicy := tableConn.GetDefaultImportPolicy(); defaultImportPolicy != oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE { + t.Errorf("Default import policy: %s, want: %s", defaultImportPolicy, oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + if importPolicy := tableConn.GetImportPolicy(); len(importPolicy) == 0 || !containsValue(importPolicy, v4RoutePolicy) { + t.Errorf("Import policy: %v, want: %s", importPolicy, []string{v4RoutePolicy}) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().Prefix(advertisedIPv4.address).State(), 30*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_ExtendedIpv4Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv4.address + }).Await(t) + if ok { + t.Errorf("Prefix found, not want: %s", advertisedIPv4.address) + } +} + +func verifyMatchingPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + rPolicy := gnmi.Get[*oc.RoutingPolicy](t, dut, gnmi.OC().RoutingPolicy().State()) + pfxSet := rPolicy.GetDefinedSets().GetPrefixSet(v4PrefixSet) + if pName := pfxSet.GetName(); pName != v4PrefixSet { + t.Errorf("Prefix set name: %s, want: %s", pName, v4PrefixSet) + } + if prefix := pfxSet.GetPrefix(advertisedIPv4.cidr(t), maskLenExact); prefix == nil { + t.Errorf("Prefix is nil, want: %s", advertisedIPv4.cidr(t)) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().Prefix(advertisedIPv4.address).State(), 30*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_ExtendedIpv4Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv4.address + }).Await(t) + if !ok { + t.Errorf("Prefix not found, want: %s", advertisedIPv4.address) + } +} + +func verifyNonMatchingCommunityTelemetry(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + commSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().BgpDefinedSets().CommunitySet(v4CommunitySet).State()) + if commSet == nil { + t.Errorf("Community set is nil, want non-nil") + } + + cm, _ := strconv.ParseInt(fmt.Sprintf("%04x%04x", dummyAS, 200), 16, 0) + + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !(containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(nonMatchingCommunityVal))) || containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionUint32(cm)))) { + t.Errorf("Community set member: %v, want: %s or %d", commSetMember, nonMatchingCommunityVal, cm) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().Prefix(advertisedIPv4.address).State(), 30*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_ExtendedIpv4Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv4.address + }).Await(t) + if ok { + t.Errorf("Prefix found, not want: %s", advertisedIPv4.address) + } +} + +func verifyMatchingCommunityTelemetry(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + commSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().BgpDefinedSets().CommunitySet(v4CommunitySet).State()) + if commSet == nil { + t.Errorf("Community set is nil, want non-nil") + } + + cm, _ := strconv.ParseInt(fmt.Sprintf("%04x%04x", ateAS, 100), 16, 0) + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !(containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(matchingCommunityVal))) || containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionUint32(cm)))) { + t.Errorf("Community set member: %v, want: %s or %d", commSetMember, matchingCommunityVal, cm) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().Prefix(advertisedIPv4.address).State(), 30*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_ExtendedIpv4Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv4.address + }).Await(t) + if !ok { + t.Errorf("Prefix not found, want: %s", advertisedIPv4.address) + } +} + +func nonMatchingPrefixRoutePolicyV6(t *testing.T, dut *ondatra.DUTDevice) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(v6RoutePolicy) + stmt, err := pdef.AppendNewStatement(v6Statement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6Statement, err) + } + stmt.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + if !deviations.SkipIsisSetLevel(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetLevel(2) + } + if !deviations.SkipIsisSetMetricStyleType(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetMetricStyleType(oc.IsisPolicy_MetricStyle_WIDE_METRIC) + } + + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v6PrefixSet) + prefixSet.SetMode(oc.PrefixSet_Mode_IPV6) + prefixSet.GetOrCreatePrefix(nonAdvertisedIPv6.cidr(t), maskLenExact) + + if !deviations.SkipSetRpMatchSetOptions(dut) { + stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v6PrefixSet) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) +} + +func matchingPrefixRoutePolicyV6(t *testing.T, dut *ondatra.DUTDevice) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v6PrefixSet) + prefixSet.GetOrCreatePrefix(advertisedIPv6.cidr(t), maskLenExact) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(v6PrefixSet).Config(), prefixSet) +} + +func nonMatchingCommunityRoutePolicyV6(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.CommunityMatchWithRedistributionUnsupported(dut) { + configureBGPTablePolicyWithSetTag(t, v6PrefixSet, advertisedIPv6.cidr(t), v6CommunitySet, dummyAS, 200, false) + bgpISISRedistributionWithRouteTagPolicy(t, dut, oc.Types_ADDRESS_FAMILY_IPV6) + } else { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(v6RoutePolicy) + stmt, err := pdef.AppendNewStatement(v6Statement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6Statement, err) + } + stmt.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + if !deviations.SkipIsisSetLevel(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetLevel(2) + } + if !deviations.SkipIsisSetMetricStyleType(dut) { + stmt.GetOrCreateActions().GetOrCreateIsisActions().SetSetMetricStyleType(oc.IsisPolicy_MetricStyle_WIDE_METRIC) + } + + communitySet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(v6CommunitySet) + communitySet.SetCommunityMember([]oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString(fmt.Sprintf("%d:%d", dummyAS, 200))}) + communitySet.SetMatchSetOptions(oc.BgpPolicy_MatchSetOptionsType_ANY) + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(v6CommunitySet) + } else { + stmt.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(v6CommunitySet) + } + // Configure ALLOWAll policy + pdef = rp.GetOrCreatePolicyDefinition(allowAllPolicy) + stmt, err = pdef.AppendNewStatement("id-1") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "id-1", err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } +} + +func matchingCommunityRoutePolicyV6(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.CommunityMatchWithRedistributionUnsupported(dut) { + configureBGPTablePolicyWithSetTag(t, v6PrefixSet, advertisedIPv6.cidr(t), v6CommunitySet, ateAS, 100, false) + bgpISISRedistributionWithRouteTagPolicy(t, dut, oc.Types_ADDRESS_FAMILY_IPV6) + } else { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + communitySet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(v6CommunitySet) + communitySet.SetCommunityMember([]oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString(fmt.Sprintf("%d:%d", ateAS, 100))}) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().BgpDefinedSets().CommunitySet(v6CommunitySet).Config(), communitySet) + } +} + +func verifyNonMatchingPrefixTelemetryV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + rPolicy := gnmi.Get[*oc.RoutingPolicy](t, dut, gnmi.OC().RoutingPolicy().State()) + + rPolicyDef := rPolicy.GetPolicyDefinition(v6RoutePolicy) + if rpName := rPolicyDef.GetName(); rpName != v6RoutePolicy { + t.Errorf("Routing policy name: %s, want: %s", rpName, v6RoutePolicy) + } + if stmtName := rPolicyDef.GetStatement(v6Statement).GetName(); stmtName != v6Statement { + t.Errorf("Routing policy statement name: %s, want: %s", stmtName, v6Statement) + } + if polResult := rPolicyDef.GetStatement(v6Statement).GetActions().GetPolicyResult(); polResult != oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE { + t.Errorf("Routing policy statement result: %s, want: %s", polResult, oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + if !deviations.SkipIsisSetLevel(dut) { + if isisLevel := rPolicyDef.GetStatement(v6Statement).GetActions().GetIsisActions().GetSetLevel(); isisLevel != 2 { + t.Errorf("IS-IS level: %d, want: %d", isisLevel, 2) + } + } + + prefixSet := rPolicy.GetDefinedSets().GetPrefixSet(v6PrefixSet) + if pName := prefixSet.GetName(); pName != v6PrefixSet { + t.Errorf("Prefix set name: %s, want: %s", pName, v6PrefixSet) + } + if pMode := prefixSet.GetMode(); pMode != oc.PrefixSet_Mode_IPV6 { + t.Errorf("Prefix set mode: %s, want: %s", pMode, oc.PrefixSet_Mode_IPV6) + } + if prefix := prefixSet.GetPrefix(nonAdvertisedIPv6.cidr(t), maskLenExact); prefix == nil { + t.Errorf("Prefix is nil, want: %s", nonAdvertisedIPv6.cidr(t)) + } + + stmt := rPolicyDef.GetStatement(v6Statement) + if matchSetOpts := stmt.GetConditions().GetMatchPrefixSet().GetMatchSetOptions(); matchSetOpts != oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY { + t.Errorf("Match prefix set options: %s, want: %s", matchSetOpts, oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + if prefixSet := stmt.GetConditions().GetMatchPrefixSet().GetPrefixSet(); prefixSet != v6PrefixSet { + t.Errorf("Match prefix set prefix set: %s, want: %s", prefixSet, v6PrefixSet) + } + + tableConn := gnmi.Get[*oc.NetworkInstance_TableConnection](t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV6).State()) + if tableConn == nil { + t.Errorf("Table connection is nil, want non-nil") + } + if metricProp := tableConn.GetDisableMetricPropagation(); metricProp != false { + t.Errorf("Metric propagation: %t, want: %t", metricProp, false) + } + if defaultImportPolicy := tableConn.GetDefaultImportPolicy(); defaultImportPolicy != oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE { + t.Errorf("Default import policy: %s, want: %s", defaultImportPolicy, oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + if importPolicy := tableConn.GetImportPolicy(); len(importPolicy) == 0 || !containsValue(importPolicy, v6RoutePolicy) { + t.Errorf("Import policy: %v, want: %s", importPolicy, []string{v6RoutePolicy}) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().Ipv6Reachability().Prefix(advertisedIPv6.address).State(), 60*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_Ipv6Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv6.address + }).Await(t) + if ok { + t.Errorf("Prefix found, not want: %s", advertisedIPv6.address) + } +} + +func verifyMatchingPrefixTelemetryV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + rPolicy := gnmi.Get[*oc.RoutingPolicy](t, dut, gnmi.OC().RoutingPolicy().State()) + pfxSet := rPolicy.GetDefinedSets().GetPrefixSet(v6PrefixSet) + if pName := pfxSet.GetName(); pName != v6PrefixSet { + t.Errorf("Prefix set name: %s, want: %s", pName, v6PrefixSet) + } + if prefix := pfxSet.GetPrefix(advertisedIPv6.cidr(t), maskLenExact); prefix == nil { + t.Errorf("Prefix is nil, want: %s", advertisedIPv6.cidr(t)) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().Ipv6Reachability().Prefix(advertisedIPv6.address).State(), 60*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_Ipv6Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv6.address + }).Await(t) + if !ok { + t.Errorf("Prefix not found, want: %s", advertisedIPv6.address) + } +} + +func verifyNonMatchingCommunityTelemetryV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + commSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().BgpDefinedSets().CommunitySet(v6CommunitySet).State()) + if commSet == nil { + t.Errorf("Community set is nil, want non-nil") + } + cm, _ := strconv.ParseInt(fmt.Sprintf("%04x%04x", dummyAS, 200), 16, 0) + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !(containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(nonMatchingCommunityVal))) || containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionUint32(cm)))) { + t.Errorf("Community set member: %v, want: %s or %d", commSetMember, nonMatchingCommunityVal, cm) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().Ipv6Reachability().Prefix(advertisedIPv6.address).State(), 60*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_Ipv6Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv6.address + }).Await(t) + if ok { + t.Errorf("Prefix found, not want: %s", advertisedIPv6.address) + } +} + +func verifyMatchingCommunityTelemetryV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + commSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().BgpDefinedSets().CommunitySet(v6CommunitySet).State()) + if commSet == nil { + t.Errorf("Community set is nil, want non-nil") + } + + cm, _ := strconv.ParseInt(fmt.Sprintf("%04x%04x", ateAS, 100), 16, 0) + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !(containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(matchingCommunityVal))) || containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionUint32(cm)))) { + t.Errorf("Community set member: %v, want: %s or %d", commSetMember, matchingCommunityVal, cm) + } + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().Ipv6Reachability().Prefix(advertisedIPv6.address).State(), 60*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_Ipv6Reachability_Prefix]) bool { + prefix, present := v.Val() + return present && prefix.GetPrefix() == advertisedIPv6.address + }).Await(t) + if !ok { + t.Errorf("Prefix not found, want: %s", advertisedIPv6.address) + } +} + +func bgpISISRedistribution(t *testing.T, dut *ondatra.DUTDevice, operation string) { + dni := deviations.DefaultNetworkInstance(dut) + root := &oc.Root{} + tableConn := root.GetOrCreateNetworkInstance(dni).GetOrCreateTableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV4) + if operation == "set" { + if !deviations.SkipSettingDisableMetricPropagation(dut) { + tableConn.SetDisableMetricPropagation(false) + } + if !deviations.DefaultRoutePolicyUnsupported(dut) { + tableConn.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + tableConn.SetImportPolicy([]string{v4RoutePolicy}) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(dni).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV4).Config(), tableConn) + } else if operation == "delete" { + gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(dni).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV4).Config()) + } +} + +func bgpISISRedistributionV6(t *testing.T, dut *ondatra.DUTDevice, operation string) { + dni := deviations.DefaultNetworkInstance(dut) + root := &oc.Root{} + tableConn := root.GetOrCreateNetworkInstance(dni).GetOrCreateTableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV6) + if operation == "set" { + if !deviations.SkipSettingDisableMetricPropagation(dut) { + tableConn.SetDisableMetricPropagation(false) + } + if !deviations.DefaultRoutePolicyUnsupported(dut) { + tableConn.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + tableConn.SetImportPolicy([]string{v6RoutePolicy}) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(dni).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV6).Config(), tableConn) + } else if operation == "delete" { + gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(dni).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, oc.Types_ADDRESS_FAMILY_IPV6).Config()) + } +} +func bgpISISRedistributionWithRouteTagPolicy(t *testing.T, dut *ondatra.DUTDevice, afi oc.E_Types_ADDRESS_FAMILY) { + dni := deviations.DefaultNetworkInstance(dut) + root := &oc.Root{} + tableConn := root.GetOrCreateNetworkInstance(dni).GetOrCreateTableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, afi) + if !deviations.SkipSettingDisableMetricPropagation(dut) { + tableConn.SetDisableMetricPropagation(false) + } + tableConn.SetImportPolicy([]string{matchTagRedistributionPolicy}) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(dni).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, afi).Config(), tableConn) +} + +func configureBGPTablePolicyWithSetTag(t *testing.T, prefixSetName, prefixSetAddress, communitySetName string, commAS, commValue uint32, v4Nbr bool) { + dut := ondatra.DUT(t, "dut") + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + //BGP Table-policy to match community & prefix and set the route-Tag + pdef1 := rp.GetOrCreatePolicyDefinition(tablePolicyMatchCommunitySetTag) + stmt1, err := pdef1.AppendNewStatement("SetTag") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + } + // Create prefix-set + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(prefixSetName) + prefixSet.GetOrCreatePrefix(prefixSetAddress, maskLenExact) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetName).Config(), prefixSet) + // Create community-set + communitySet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySetName) + communitySet.SetCommunityMember([]oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString(fmt.Sprintf("%d:%d", commAS, commValue))}) + communitySet.SetMatchSetOptions(oc.BgpPolicy_MatchSetOptionsType_ANY) + + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(prefixSetName) + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(communitySetName) + stmt1.GetOrCreateActions().GetOrCreateSetTag().SetMode(oc.SetTag_Mode_INLINE) + stmt1.GetOrCreateActions().GetOrCreateSetTag().GetOrCreateInline().SetTag([]oc.RoutingPolicy_PolicyDefinition_Statement_Actions_SetTag_Inline_Tag_Union{oc.UnionUint32(routeTagVal)}) + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + // Create tag-set with above route tag value + tagSet := rp.GetOrCreateDefinedSets().GetOrCreateTagSet("RouteTagForRedistribution") + tagSet.SetName("RouteTagForRedistribution") + tagSet.SetTagValue([]oc.RoutingPolicy_DefinedSets_TagSet_TagValue_Union{oc.UnionUint32(routeTagVal)}) + + // Route-policy to match tag and accept + pdef2 := rp.GetOrCreatePolicyDefinition("MatchTagRedistributionPolicy") + stmt2, err := pdef2.AppendNewStatement("matchTag") + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(prefixSetName) + stmt2.GetOrCreateConditions().GetOrCreateMatchTagSet().SetTagSet("RouteTagForRedistribution") + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + } + + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + var bgpTablePolicyCLI string + if v4Nbr { + bgpTablePolicyCLI = fmt.Sprintf("router bgp %v instance BGP address-family ipv4 unicast \n table-policy %v", dutAS, tablePolicyMatchCommunitySetTag) + helpers.GnmiCLIConfig(t, dut, bgpTablePolicyCLI) + } else { + bgpTablePolicyCLI = fmt.Sprintf("router bgp %v instance BGP address-family ipv6 unicast \n table-policy %v", dutAS, tablePolicyMatchCommunitySetTag) + helpers.GnmiCLIConfig(t, dut, bgpTablePolicyCLI) + } +} + +func configureRoutePolicyAllow(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 createFlow(t *testing.T, ts *isissession.TestSession) { + ts.ATETop.Flows().Clear() + srcIpv4 := ts.ATEIntf1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + + t.Log("Configuring v4 traffic flow ") + v4Flow := ts.ATETop.Flows().Add().SetName(v4FlowName) + v4Flow.Metrics().SetEnable(true) + v4Flow.TxRx().Device(). + SetTxNames([]string{srcIpv4.Name()}). + SetRxNames([]string{"v4-bgpNet-dev1"}) + v4Flow.Size().SetFixed(512) + v4Flow.Rate().SetPps(100) + v4Flow.Duration().Continuous() + e1 := v4Flow.Packet().Add().Ethernet() + e1.Src().SetValue(isissession.ATEISISAttrs.MAC) + v4 := v4Flow.Packet().Add().Ipv4() + v4.Src().SetValue(isissession.ATEISISAttrs.IPv4) + v4.Dst().Increment().SetStart(v4TrafficStart).SetCount(1) + + ts.ATE.OTG().PushConfig(t, ts.ATETop) + ts.ATE.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv4") + cfgplugins.VerifyDUTBGPEstablished(t, ts.DUT) +} + +func createFlowV6(t *testing.T, ts *isissession.TestSession) { + ts.ATETop.Flows().Clear() + srcIpv6 := ts.ATEIntf1.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + t.Log("Configuring v6 traffic flow ") + v6Flow := ts.ATETop.Flows().Add().SetName(v6FlowName) + v6Flow.Metrics().SetEnable(true) + v6Flow.TxRx().Device(). + SetTxNames([]string{srcIpv6.Name()}). + SetRxNames([]string{"v6-bgpNet-dev1"}) + v6Flow.Size().SetFixed(512) + v6Flow.Rate().SetPps(100) + v6Flow.Duration().Continuous() + e1 := v6Flow.Packet().Add().Ethernet() + e1.Src().SetValue(isissession.ATEISISAttrs.MAC) + v6 := v6Flow.Packet().Add().Ipv6() + v6.Src().SetValue(isissession.ATEISISAttrs.IPv6) + v6.Dst().Increment().SetStart(v6TrafficStart).SetCount(1) + + ts.ATE.OTG().PushConfig(t, ts.ATETop) + ts.ATE.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv6") + cfgplugins.VerifyDUTBGPEstablished(t, ts.DUT) +} + +func checkTraffic(t *testing.T, ts *isissession.TestSession, flowName string) { + ts.ATE.OTG().StartTraffic(t) + time.Sleep(time.Second * 30) + ts.ATE.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, ts.ATE.OTG(), ts.ATETop) + otgutils.LogPortMetrics(t, ts.ATE.OTG(), ts.ATETop) + + t.Log("Checking flow telemetry...") + recvMetric := gnmi.Get(t, ts.ATE.OTG(), gnmi.OTG().Flow(flowName).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + + if lossPct > 1 { + t.Errorf("FAIL- Got %v%% packet loss for %s ; expected < 1%%", lossPct, flowName) + } +} + +func containsValue[T comparable](slice []T, val T) bool { + found := false + for _, v := range slice { + if v == val { + found = true + break + } + } + return found +} diff --git a/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/metadata.textproto b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/metadata.textproto new file mode 100644 index 00000000000..7c29f99bbfa --- /dev/null +++ b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/metadata.textproto @@ -0,0 +1,46 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "169cbf57-f750-4f14-8317-6f78e780607e" +plan_id: "RT-1.28" +description: "BGP to IS-IS redistribution" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + isis_instance_enabled_required: true + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + isis_interface_afi_unsupported: true + skip_isis_set_level: true + skip_isis_set_metric_style_type: true + skip_set_rp_match_set_options: true + skip_setting_disable_metric_propagation: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + skip_isis_set_level: true + skip_isis_set_metric_style_type: true + bgp_conditions_match_community_set_unsupported: true + bgp_community_member_is_a_string: true + community_match_with_redistribution_unsupported: true + default_route_policy_unsupported: true + } +} diff --git a/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/README.md b/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/README.md new file mode 100644 index 00000000000..bba3cecb4d8 --- /dev/null +++ b/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/README.md @@ -0,0 +1,46 @@ +# RT-1.55: BGP session mode (active/passive) + +## Summary + +* Validate the correct behavior of BGP session establishment in both active and passive modes. +* Verify the accurate reflection of BGP transport mode in telemetry output. +* Confirm the functionality of passive mode configuration at both the neighbor and peer group levels. + +## Topology + +DUT Port1 (AS 65501) ---eBGP --- ATE Port1 (AS 65502) + +## Procedure + +* Configure both DUT and ATE to operate in BGP passive mode under the neighbor section. +* Verify that the BGP adjacency will not be established. +* Verify the telemetry path output to confirm that the neighbor's BGP transport mode is displayed as "passive for the DUT. +* Configure BGP session on ATE to operate in BGP active mode when interacting with DUT. +* Verify that a BGP adjacency is established between the ATE and DUT +* Verify the telemetry path output to confirm that the neighbor's BGP transport mode is displayed as "passive for the DUT. +* Redo the same above steps but configure the passive mode under the peer group instead of the bgp neighbor configuration. + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +```yaml +paths: + ## Config paths + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/passive-mode: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/config/passive-mode: + + ## State paths + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/passive-mode: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/state/passive-mode: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* MFF - A modular form factor device containing LINECARDs, FABRIC and redundant CONTROLLER_CARD components +* FFF - fixed form factor diff --git a/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/bgp_session_mode_configuration_test.go b/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/bgp_session_mode_configuration_test.go new file mode 100644 index 00000000000..85bea8459af --- /dev/null +++ b/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/bgp_session_mode_configuration_test.go @@ -0,0 +1,343 @@ +// 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_session_mode_configuration_test + +import ( + "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/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" +) + +// The testbed consists of ate:port1 -> dut:port1. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// List of variables. +var ( + dutAttrs = attrs.Attributes{ + Desc: "To ATE", + IPv4: "192.0.2.1", + IPv4Len: 30, + } + ateAttrs = attrs.Attributes{ + Desc: "To DUT", + Name: "ateSrc", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: 30, + } +) + +// Constants. +const ( + dutAS = 65540 + ateAS = 65550 + peerGrpName = "eBGP-PEER-GROUP" + peerLvlPassive = "PeerGrpLevelPassive" + peerLvlActive = "PeerGrpLevelActive" + nbrLvlPassive = "nbrLevelPassive" + nbrLvlActive = "nbrLevelActive" +) + +// 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) + } +} + +// 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) + } + } +} + +// Struct is to pass bgp session parameters. +type bgpTestParams struct { + localAS, peerAS, nbrLocalAS uint32 + peerIP string + transportMode string +} + +// bgpCreateNbr creates a BGP object with neighbors pointing to ate and returns bgp object. +func bgpCreateNbr(bgpParams *bgpTestParams, dut *ondatra.DUTDevice) *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(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(dutAS) + 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) 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)) + + bgp := dev.Bgp().SetRouterId(ateAttrs.IPv4) + peerBGP := bgp.Ipv4Interfaces().Add().SetIpv4Name(ip.Name()).Peers().Add() + peerBGP.SetName(ateAttrs.Name + ".BGP4.peer") + peerBGP.SetPeerAddress(ip.Gateway()).SetAsNumber(uint32(ateParams.localAS)) + peerBGP.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + switch ateParams.transportMode { + case nbrLvlPassive: + peerBGP.Advanced().SetPassiveMode(true) + case peerLvlPassive: + peerBGP.Advanced().SetPassiveMode(true) + case peerLvlActive: + peerBGP.Advanced().SetPassiveMode(false) + case nbrLvlActive: + peerBGP.Advanced().SetPassiveMode(false) + } + + 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.") +} + +// TestBgpSessionModeConfiguration is to verify when transport mode is set +// active/passive at both neighbor level and peer group level. +func TestBgpSessionModeConfiguration(t *testing.T) { + 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 + }{ + { + name: "Test transport mode passive at neighbor level on both DUT and ATE ", + dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS, transportMode: nbrLvlPassive}, dut), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP, transportMode: nbrLvlPassive}), + wantBGPState: oc.Bgp_Neighbor_SessionState_ACTIVE, + dutTransportMode: nbrLvlPassive, + otgTransportMode: nbrLvlPassive, + }, + { + name: "Test transport mode active on ATE and passive on DUT at neighbor level", + dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS, nbrLocalAS: dutAS, transportMode: nbrLvlPassive}, dut), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP, transportMode: nbrLvlActive}), + wantBGPState: oc.Bgp_Neighbor_SessionState_ESTABLISHED, + dutTransportMode: nbrLvlPassive, + otgTransportMode: nbrLvlActive, + }, + { + name: "Test transport passive mode at Peer group level on both DUT and ATE.", + dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS, transportMode: peerLvlPassive}, dut), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP, transportMode: peerLvlPassive}), + wantBGPState: oc.Bgp_Neighbor_SessionState_ACTIVE, + dutTransportMode: peerLvlPassive, + otgTransportMode: peerLvlPassive, + }, + { + name: "Test transport mode active on ATE and passive on DUT at peer group level", + dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS, nbrLocalAS: dutAS, transportMode: peerLvlPassive}, dut), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP, transportMode: peerLvlActive}), + wantBGPState: oc.Bgp_Neighbor_SessionState_ESTABLISHED, + dutTransportMode: peerLvlPassive, + otgTransportMode: peerLvlActive, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Log("Clear BGP configuration") + bgpClearConfig(t, dut) + + t.Log("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.Log("Configure BGP on ATE") + 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.Log("Clear BGP Configs on ATE") + ate.OTG().StopProtocols(t) + }) + } +} diff --git a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto b/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/metadata.textproto similarity index 62% rename from feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto rename to feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/metadata.textproto index 6442cbf3b84..0795ff831b0 100644 --- a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto +++ b/feature/bgp/bgp_session_mode/otg_tests/bgp_session_mode_configuration_test/metadata.textproto @@ -1,9 +1,9 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "4f04fb5b-5bc0-4214-a74d-2a30c0433078" -plan_id: "TE-9.1" -description: "FIB FAILURE DUE TO HARDWARE RESOURCE EXHAUST" +uuid: "7e2082f6-4fbc-4e2b-a8a8-c83af2574ec4" +plan_id: "RT-1.55" +description: "BGP session mode (active/passive)" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { @@ -11,14 +11,8 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true - } -} -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - skip_fib_failed_traffic_forwarding_check: true + connect_retry: true + bgp_session_state_idle_in_passive_mode: true } } platform_exceptions: { @@ -26,9 +20,9 @@ platform_exceptions: { vendor: NOKIA } deviations: { - explicit_gribi_under_network_instance: true explicit_port_speed: true explicit_interface_in_default_vrf: true + missing_value_for_defaults: true interface_enabled: true } } @@ -37,10 +31,12 @@ platform_exceptions: { vendor: ARISTA } deviations: { + connect_retry: true omit_l2_mtu: true - interface_config_vrf_before_address: true + network_instance_table_deletion_required: true + bgp_md5_requires_reset: true + missing_value_for_defaults: true interface_enabled: true default_network_instance: "default" } } -tags: TAGS_TRANSIT diff --git a/feature/bgp/dynamicneighbors/feature.textproto b/feature/bgp/dynamicneighbors/feature.textproto index 9d374d8eaa6..10b2877f662 100644 --- a/feature/bgp/dynamicneighbors/feature.textproto +++ b/feature/bgp/dynamicneighbors/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/bgp/feature.textproto b/feature/bgp/feature.textproto index 943c9169ad1..fcc9e02b447 100644 --- a/feature/bgp/feature.textproto +++ b/feature/bgp/feature.textproto @@ -11,6 +11,9 @@ # 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" 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 780618105b2..d84c42a4972 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 @@ -2,53 +2,152 @@ ## Summary -BGP Graceful Restart +This is to test the BGP graceful restart capability for BGP. A router that supports BGP graceful restart can work either as a Restarting speaker mode or in helper mode. By advertising BGP graceful restart capability, a router announces to the peer its ability to, +1. [Restarting Speaker] Maintain forwarding state on all the routes in its FIB even when its BGP process is restarting. Therefore the peer functioning as a helper should continue to direct flows at the subject router undergoing BGP process restart. +2. [Helper Router] Support a peer whose BGP process is restarting by continuing to direct flows at the peer. +3. The test checks support for RFC4724 and RFC8538. + +While testing for the above, this test also confirms that the implementation respects stale-routes-time timer setting. + + +## Topology +Create the following connections: +```mermaid +graph LR; +A[ATE:Port1] -- EBGP --> B[Port1:DUT:Port2]; +B -- IBGP --> C[Port2:ATE]; +``` ## Procedure -* Establish eBGP sessions between: - * ATE port-1 and DUT port-1 - * ATE port-2 and DUT port-2 - * Configure allow route-policy under BGP peer-group address-family +**RT-1.4.1: Enable and validate BGP Graceful restart feature** +* 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. +* 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 - restart. -* For IPv4 and IPv6 routes: - * (Restarting speaker) Advertise prefixes between the ATE ports, through - the DUT. Trigger DUT session restart by disconnecting TCP session - between DUT and ATE (this may be achieved by using an ACL), determine - that packets are: - * Forwarded between ATE port-1 and DUT port-1 for the duration of the - specified stale routes time. - * Dropped after the stale routes timer has expired. - * Forwarded again between ATE port-1 and DUT port-1 after the session - is re-established. - * (Receiving speaker) Advertise prefixes between the ATE ports, through - the DUT. Trigger session restart by disconnecting the BGP session from - ATE port-2. - * Ensure that prefixes are propagated to ATE port-2 during the - restart. - * Ensure that traffic can be forwarded between ATE port-1 and ATE - port-2 during stale routes time. - * Ensure that prefixes are withdrawn, and traffic cannot be forwarded - between ATE port-1 and port-2 after the stale routes time expires. + restart. + + +**RT-1.4.2: Restarting DUT speaker whose BGP process was killed gracefully** +* Advertise prefixes between the ATE ports, through the DUT. +* Trigger DUT session restart by killing the BGP process in the DUT. Please use the `gNOI.killProcessRequest_Signal_Term` as per [gNOI_proto](https://github.com/openconfig/gnoi/blob/main/system/system.proto#L326). + * Please kill the right process to restart BGP. For Juniper it is the `RPD` process. For Arista and Cisco this is the `BGP` process. For Nokia this is `sr_bgp_mgr`. +* Once the BGP process on DUT is killed, configure ATE to delay the BGP reestablishment for a period longer than the `stale-routes-time` and start regular traffic from ATE and verify that the packets are, + * Forwarded between ATE port-1 and ATE port-2 for the duration of the specified `stale-routes-time`. Before the stale routes timer expires, stop traffic and ensure that there is zero packet loss. + * After the stale routes timer expires, restart traffic and confirm that there is 100% packet loss. +* Stop traffic, revert ATE configuration to start accepting packets for BGP reestablishement from DUT and wait for the BGP session w/ ATE to be reestablished. Once established, restart traffic to ensure that packets are forwarded again between ATE port-1 and ATE port2 and there is zero packet loss. +* Conduct the above steps once for the EBGP peering and once for the IBGP peering + + +**RT-1.4.3: Restarting DUT speaker whose BGP process was killed abruptly** +* Follow the same steps as in RT-1.4.2 above but use `gNOI.killProcessRequest_Signal_KILL` this time as per `gNOI proto` +* Pass/Fail criteria in this case too is the same as that for RT-1.4.2. Router that supports Graceful restart is expected to allow traffic flow w/o any packet drops until the `stale-routes-time` timer expires. + + +**RT-1.4.4: DUT Helper for a restarting EBGP speaker whose BGP process was gracefully killed** +* Advertise prefixes between the ATE ports through the DUT. Send Graceful restart trigger from ATE port-1. +* Start traffic between ATE port-1 and ATE port-2 and prior to the expiry of `stale-routes-time`, stop traffic and ensure that there is zero packet loss. +* Restart traffic post the stale routes timer expiry. Ensure that the subject prefixes are withdrawn, and there is 100% traffic loss between ATE:Port1 and ATE:Port2. +* Repeat the above for the IBGP peering on ATE port2 + +**RT-1.4.5: DUT Helper for a restarting EBGP speaker whose BGP process was killed abruptly** +* Advertise prefixes between the ATE ports through the DUT. Use `gNOI.killProcessRequest_Signal_KILL` as per `gNOI proto` to ATE:Port1. +* Once the BGP process on DUT is killed, configure ATE to delay the BGP reestablishment for a period longer than the `stale-routes-time` and start regular traffic from ATE and verify that the packets are, + * Forwarded between ATE port-1 and ATE port-2 for the duration of the specified `stale-routes-time`. Before the stale routes timer expires, stop traffic and ensure that there is zero packet loss. + * After the stale routes timer expires, restart traffic and confirm that there is 100% packet loss. +* Stop traffic, revert ATE configuration to start accepting/sending packets for BGP reestablishement from/to DUT and wait for the BGP session w/ ATE to be reestablished. Once established, restart traffic to ensure that packets are forwarded again between ATE port-1 and ATE port2 and there is zero packet loss. +* Conduct the above steps once for the IBGP peering between DUT:Port2 and ATE:Port2. + +**RT-1.4.6: Test support for RFC8538 compliance by letting Hold-time expire** + +RFC-8538 builds on RFC4724 by adding Graceful restart support for scenarios when the BGP holdtime expires. In order to simulate holdtime expiry, please install an ACL on DUT that drops BGP packets from the Peer (i.e. ATE). Also this time, configure the stale-routes-timer to be longer than the hold-timer. Start traffic and ensure that the packets are, +* Forwarded between ATE port-1 and ATE port-2 for the duration of the specified stale routes time. Stop traffic somtime after the holdtime expires but before the stale-routes-timer expires and confirm that there was zero packet loss. +* Once the stale-routes-timer expires, start traffic again and confirm that there is 100% packet loss. Stop traffic. +* Remove the ACL on DUT:Port1 and allow BGP to be reestablished. Start traffic again between ATE port1 and ATE port2. This time ensure that there is zero packet loss. Stop traffic again. +* Repeat the same process above for the IBGP peering between DUT:Port2 and the ATE:Port2 +**RT-1.4.7: (Send Soft Notification) Test support for RFC8538 compliance by sending a BGP Notification message to the peer** + +The origial RFC4724 had no coverage for Graceful restart process post send/receive of a Soft BGP notification message. Hence, even though the peers supported Graceful restart, they were expected to flush their FIB for the peering when a BGP Notification is received on the session. However with RFC8538, supporting peers should maintain their FIB even when they receive a Soft Notification. Folowing process to test, +* Advertise prefixes between the ATE ports, through the DUT. +* Trigger BGP soft Notification to/from DUT Port1 towards ATE port1. Please use the `gNOI.ClearBGPNeighborRequest_Soft` message as per [gNOI_proto](https://github.com/openconfig/gnoi/blob/main/bgp/bgp.proto#L41). Once the Notification is received and the TCP connection is reset, configure ATE Port1 to not send/accept any more TCP conenctions from the DUT:Port1 until the stale-routes-timer on the DUT expires. + * Start traffic from ATE Port1 towards ATE Port2 and stop the same right before the stale-routes-timer expires. Confirm there is zero packet loss. + * Once the stale-routes-timer expires, restart traffic. Expectations are that there is 100% packet loss. Stop traffic +* Revert ATE configurtion blocking TCP connection 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 + +**RT-1.4.8: (Receive Soft Notification) Test support for RFC8538 compliance by receiving a BGP Notification message from the peer** +* Advertise prefixes between the ATE ports, through the DUT. +* Trigger BGP soft Notification from ATE port1. Please use the `gNOI.ClearBGPNeighborRequest_Soft` message as per [gNOI_proto](https://github.com/openconfig/gnoi/blob/main/bgp/bgp.proto#L41). Once the Notification is sent and the TCP connection is reset, configure ATE Port1 to not start/accept any more TCP conenctions from the DUT:Port1 until the stale-routes-timer on the DUT expires. + * Start traffic from ATE Port1 towards ATE Port2 and stop the same right before the stale-routes-timer expires. Confirm there is zero packet loss. + * Once the stale-routes-timer expires, restart traffic. Expectations are that there is 100% 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 + + +**RT-1.4.9: (Send hard Notification) Test support for RFC8538 compliance by sending a BGP Hard Notification message to the peer** +* Advertise prefixes between the ATE ports, through the DUT. +* Trigger BGP hard Notification from DUT port1. Please use the `gNOI.ClearBGPNeighborRequest_hard` message as per [gNOI_proto](https://github.com/openconfig/gnoi/blob/main/bgp/bgp.proto#L43). Once the Notification is sent and the TCP connection is reset, configure ATE Port1 to not start/accept any more TCP conenctions to/from the DUT:Port1. + * 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 + + +**RT-1.4.10: (Receive hard Notification) Test support for RFC8538 compliance by receiving a BGP Hard Notification message from the peer** +* Advertise prefixes between the ATE ports, through the DUT. +* Trigger BGP hard Notification from ATE port1. Please use the `gNOI.ClearBGPNeighborRequest_hard` message as per [gNOI_proto](https://github.com/openconfig/gnoi/blob/main/bgp/bgp.proto#L43). Once the Notification is sent and the TCP connection is reset, configure ATE Port1 to not start/accept any more TCP conenctions to/from the DUT:Port1. + * 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 ## Config Parameter Coverage For prefixes: * /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor +* gNOI.killProcessRequest_Signal_Term [To gracefully kill BGP process] Parameters: -* graceful-restart/config/enabled -* graceful-restart/config/restart-time -* graceful-restart/config/stale-routes-time -* graceful-restart/config/helper-only +* /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/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 + +BGP conifguration: +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/peer-group/ + +* Policy-Definition + * /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/conditions/match-prefix-set/config/prefix-set + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result/ACCEPT_ROUTE + +* Apply Policy at Peer-Group level + * afi-safis/afi-safi/apply-policy/config/import-policy + * afi-safis/afi-safi/apply-policy/config/export-policy ## Telemetry Parameter Coverage -* afi-safis/afi-safi/afi-safi-name -* afi-safis/afi-safi/graceful-restart/state/advertised -* afi-safis/afi-safi/graceful-restart/state/peer-restart-time -* afi-safis/afi-safi/graceful-restart/state/received +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/afi-safi-name +* /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 + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + 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 545290eba11..8eacae295cf 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,15 +15,12 @@ package bgp_graceful_restart_test import ( - "context" - "encoding/json" "testing" "time" "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" @@ -466,215 +463,6 @@ func configACLInterface(t *testing.T, iFace *oc.Acl_Interface, ifName string) *a return aclConf } -// Helper function to replicate configACL() configs in native model -// Define the values for each ACL entry and marshal for json encoding. -// Then craft a gNMI set Request to update the changes. -func configACLNative(t testing.TB, d *ondatra.DUTDevice, name string) { - t.Helper() - switch d.Vendor() { - case ondatra.NOKIA: - var aclEntry10Val = []any{ - map[string]any{ - "action": map[string]any{ - "drop": map[string]any{}, - }, - "match": map[string]any{ - "destination-ip": map[string]any{ - "prefix": ateDstCIDR, - }, - "source-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - }, - }, - } - entry10Update, err := json.Marshal(aclEntry10Val) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - - var aclEntry20Val = []any{ - map[string]any{ - "action": map[string]any{ - "drop": map[string]any{}, - }, - "match": map[string]any{ - "source-ip": map[string]any{ - "prefix": ateDstCIDR, - }, - "destination-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - }, - }, - } - entry20Update, err := json.Marshal(aclEntry20Val) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - - var aclEntry30Val = []any{ - map[string]any{ - "action": map[string]any{ - "accept": map[string]any{}, - }, - "match": map[string]any{ - "source-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - "destination-ip": map[string]any{ - "prefix": aclNullPrefix, - }, - }, - }, - } - entry30Update, err := json.Marshal(aclEntry30Val) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - gpbSetRequest := &gpb.SetRequest{ - Prefix: &gpb.Path{ - Origin: "srl", - }, - Update: []*gpb.Update{ - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "10"}}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: entry10Update, - }, - }, - }, - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "20"}}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: entry20Update, - }, - }, - }, - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "30"}}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: entry30Update, - }, - }, - }, - }, - } - gnmiClient := d.RawAPIs().GNMI(t) - if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { - t.Fatalf("Unexpected error configuring SRL ACL: %v", err) - } - default: - t.Fatalf("Unsupported vendor %s for deviation 'UseVendorNativeACLConfiguration'", d.Vendor()) - } -} - -// Helper function to replicate AdmitAllACL() configs in native model, -// then craft a gNMI set Request to update the changes. -func configAdmitAllACLNative(t testing.TB, d *ondatra.DUTDevice, name string) { - t.Helper() - switch d.Vendor() { - case ondatra.NOKIA: - gpbDelRequest := &gpb.SetRequest{ - Prefix: &gpb.Path{ - Origin: "srl", - }, - Delete: []*gpb.Path{ - { - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "10"}}, - }, - }, - { - Elem: []*gpb.PathElem{ - {Name: "acl"}, - {Name: "ipv4-filter", Key: map[string]string{"name": name}}, - {Name: "entry", Key: map[string]string{"sequence-id": "20"}}, - }, - }, - }, - } - gnmiClient := d.RawAPIs().GNMI(t) - if _, err := gnmiClient.Set(context.Background(), gpbDelRequest); err != nil { - t.Fatalf("Unexpected error removing SRL ACL: %v", err) - } - default: - t.Fatalf("Unsupported vendor %s for deviation 'UseVendorNativeACLConfiguration'", d.Vendor()) - } -} - -// Helper function to replicate configACLInterface in native model. -// Set ACL at interface ingress, -// then craft a gNMI set Request to update the changes. -func configACLInterfaceNative(t *testing.T, d *ondatra.DUTDevice, ifName string) { - t.Helper() - switch d.Vendor() { - case ondatra.NOKIA: - var interfaceAclVal = []any{ - map[string]any{ - "ipv4-filter": []any{ - aclName, - }, - }, - } - interfaceAclUpdate, err := json.Marshal(interfaceAclVal) - if err != nil { - t.Fatalf("Error with json Marshal: %v", err) - } - gpbSetRequest := &gpb.SetRequest{ - Prefix: &gpb.Path{ - Origin: "srl", - }, - Update: []*gpb.Update{ - { - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - {Name: "interface", Key: map[string]string{"name": ifName}}, - {Name: "subinterface", Key: map[string]string{"index": "0"}}, - {Name: "acl"}, - {Name: "input"}, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: interfaceAclUpdate, - }, - }, - }, - }, - } - gnmiClient := d.RawAPIs().GNMI(t) - if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { - t.Fatalf("Unexpected error configuring interface ACL: %v", err) - } - default: - t.Fatalf("Unsupported vendor %s for deviation 'UseVendorNativeACLConfiguration'", d.Vendor()) - } -} - func TestTrafficWithGracefulRestartSpeaker(t *testing.T) { dut := ondatra.DUT(t, "dut") ate := ondatra.ATE(t, "ate") @@ -728,14 +516,9 @@ func TestTrafficWithGracefulRestartSpeaker(t *testing.T) { startTime := time.Now() t.Log("Trigger Graceful Restart on ATE") ate.Actions().NewBGPGracefulRestart().WithRestartTime(grRestartTime * time.Second).WithPeers(bgpPeer).Send(t) - if deviations.UseVendorNativeACLConfig(dut) { - configACLNative(t, dut, aclName) - configACLInterfaceNative(t, dut, ifName) - } else { - 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) - } + 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!") @@ -770,14 +553,9 @@ func TestTrafficWithGracefulRestartSpeaker(t *testing.T) { t.Run("RemoveAclInterface", func(t *testing.T) { t.Log("Removing Acl on the interface to restore BGP GR. Traffic should now pass!") - if deviations.UseVendorNativeACLConfig(dut) { - configAdmitAllACLNative(t, dut, aclName) - configACLInterfaceNative(t, dut, ifName) - } else { - 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) - } + 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) { diff --git a/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/metadata.textproto b/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/metadata.textproto index 4407d7edefe..6bb6b0f824b 100644 --- a/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/metadata.textproto +++ b/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/metadata.textproto @@ -18,7 +18,6 @@ platform_exceptions: { vendor: NOKIA } deviations: { - use_vendor_native_acl_config: true explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true diff --git a/feature/bgp/gracefulrestart/feature.textproto b/feature/bgp/gracefulrestart/feature.textproto index 8de12815c8c..4e28255f0f0 100644 --- a/feature/bgp/gracefulrestart/feature.textproto +++ b/feature/bgp/gracefulrestart/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/README.md b/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/README.md new file mode 100644 index 00000000000..46466da8b60 --- /dev/null +++ b/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/README.md @@ -0,0 +1,80 @@ +# RT-7.6: BGP Link Bandwidth Community - Cumulative + +## Summary + +This test verifies Link-bandwidth (LBW) extended community cumulative feature by DUT. + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Test environment setup + + ``` + | | + [ 64x eBGP ] --- [ ATE Port 1 ] ---- | DUT | ---- [ ATE Port 2 ] + | | + ``` + +#### Configuration + +* Configure DUT with 2 routed ports. +* Configure 64x eBGP peers on ATE Port 1 interface +* Configure 64x eBGP peers on DUT Port 1 interface in peering group UPSTREAM. +* Configure a single eBGP peer on ATE Port 2 interface. +* Configure a single eBGP peer on DUT Port 2 interface in peering group DOWNSTREAM. + + +### RT-7.6.1: Verify LBW cumulative to eBGP peer + +* Enable BGP LBW receive for peering group UPSTREAM. +* Enable BGP LBW send for peering group DOWNSTREAM. +* Enable Link Bandwidth Cumulative feature on DOWNSTREAM. + +**TODO:** [Cumulative Link Bandwidth feature](https://datatracker.ietf.org/doc/draft-ietf-bess-ebgp-dmz/) is not currently modeled in OC. Related PR: https://github.com/openconfig/public/pull/1131 + + +2. Advertise the same test prefix from ATE from all UPSTREAM peers with LBW community: + * 32 peers - 10Mbps + * 16 peers - 20Mbps + * 8 peers - 40Mbps + * 8 peers - 80Mbps + +3. Verify that DUT advertises the test route to DOWNSTREAM eBGP peer with cumulative bandwidth community of 1600Mbps. + +### RT-7.6.2: Verify LBW changes. +Using RT-7.6.1 set up conduct following changes: + +1) Disable 32 peers advertising 10Mpbs bandwidth community. +2) Verify that DUT advertises the test route to Upstream peer with cumulative bandwidth community of 1280Mbps. +3) Re-enable 32 peers advertising 10Mpbs bandwidth community. +4) Verify that DUT advertises the test route to Upstream peer with cumulative bandwidth community of 1600Mbps. + +### RT-7.6.3: Verify LBW cumulative to iBGP peer + +1. Reconfigure 64x peers in peering group UPSTREAM to iBGP +2. Reconfigure a single peer in peering group DOWNSTREAM to iBGP +3. Repeat test RT-7.6.1 for iBGP. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /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: + # TODO: Add Cumulative LBW path. + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF \ No newline at end of file diff --git a/feature/bgp/multihop/README.md b/feature/bgp/multihop/README.md new file mode 100644 index 00000000000..b83ddab4ce1 --- /dev/null +++ b/feature/bgp/multihop/README.md @@ -0,0 +1,93 @@ +# RT-1.63: BGP Multihop + +## Summary + +This test case validates the multihop eBGP feature, where two BGP speakers establish a peering session even when they are not directly connected. + +## 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 ----> C[Port2:ATE] --> eBGP peer; +``` + +2) Configure the eBGP peering session: Establish an eBGP peering relationship between ATE1 and Port 1 on the DUT. +3) Assign IP addresses: Configure IPv4 and IPv6 addresses on all relevant interfaces on the DUT, ATE1, and ATE2. +4) Create a loopback interface: Configure a loopback interface on the DUT and assign it an IP address. This will serve as the DUT's identifier for the BGP session. +5) Establish static routing on the DUT: Configure a static route on the DUT that directs traffic destined for the eBGP peer's IP address (connected to ATE2) through ATE2. This ensures the DUT can reach the peer. +6) Establish static routing on ATE2: Configure a static route on ATE2 that directs traffic destined for the DUT's loopback IP address towards the DUT. This allows the eBGP peer on ATE1 to reach and identify the DUT via its loopback address. + +### Tests + +### RT-1.63.1: Establish eBGP session over multihop + +1) Configure the eBGP peering session: Establish an eBGP peering relationship between the DUT's loopback interface and the eBGP peer connected to ATE2. This means the DUT will use its loopback address as its BGP identifier. +2) Specify the neighbor address: Configure the DUT to use the eBGP peer's IP address (connected to ATE2) as the neighbor address for this BGP session. +3) Enable multihop: Since the eBGP peer is not directly connected to the DUT, enable the multihop option for this BGP neighbor on the DUT. Set the Time-to-Live (TTL) value to 2 to allow the BGP packets to traverse the intermediate link between the DUT and ATE2. +4) Validate session establishment: Confirm that the eBGP session is successfully established between the DUT's loopback interface and the eBGP peer connected to ATE2. This can be verified by checking the BGP neighbor status, following this path + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state +5) Verify BGP updates: Ensure that the DUT and the eBGP peer are exchanging BGP updates correctly, indicating that the peering is functional and routing information is being shared. + * Check the counters in the followong paths are incrementing: + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/sent/UPDATE + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/received/UPDATE + +### RT-1.63.2: Advertise prefixes over multihop eBGP + +1) Advertise prefixes: Configure the eBGP peer to advertise the following IPv4 and IPv6 prefixes to the DUT: + * BGP-V4 = 203.0.200.0/24 + * BGP-V6 = 2001:db8:128:200::/64 +2) Verify prefix reception: Confirm that the DUT successfully receives the advertised prefixes from the eBGP peer. +3) Check routing table: Inspect the DUT's routing table to ensure that the received prefixes have been installed correctly. +4) Validate next hop: Verify that the next hop associated with the installed prefixes in the DUT's routing table is the IP address of ATE Port 2. This confirms that the DUT knows to forward traffic for these prefixes towards ATE2, following paths: + * /network-instances/network-instance/afts/next-hops/next-hop/state/ip-address + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + +### RT-1.63.3: Traffic forwarding over multihop eBGP + +1) Generate traffic: Initiate traffic from ATE port-1 towards the DUT. This traffic should be destined for the prefixes that were advertised by the eBGP peer connected to ATE2. + * Send 1000 packets on 10 flows per address family +2) Verify forwarding: Confirm that the DUT correctly forwards the traffic to the eBGP peer via ATE port-2. This demonstrates that the multihop eBGP session is functioning as expected and that the DUT is using the learned routes to direct traffic appropriately. +3) Monitor traffic counters: Examine the traffic counters on the relevant DUT and ATE interfaces to verify that traffic is flowing as expected and that there are no drops or errors. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + ## Config paths + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/ebgp-multihop/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/ebgp-multihop/config/multihop-ttl: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/ebgp-multihop/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/ebgp-multihop/config/multihop-ttl: + + ## state paths + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/ebgp-multihop/state/enabled: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/ebgp-multihop/state/multihop-ttl: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/ebgp-multihop/state/enabled: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/ebgp-multihop/state/multihop-ttl: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: + /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/state/ip-address: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Minimum DUT platform requirement + +* FFF - Fixed Form Factor \ No newline at end of file diff --git a/feature/bgp/multihop/feature.textproto b/feature/bgp/multihop/feature.textproto index 1a685ee3ca2..d5a4431e9e8 100644 --- a/feature/bgp/multihop/feature.textproto +++ b/feature/bgp/multihop/feature.textproto @@ -11,6 +11,9 @@ # 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_multihop" diff --git a/feature/bgp/multipath/feature.textproto b/feature/bgp/multipath/feature.textproto index 4ec0acef1a6..af19a786e95 100644 --- a/feature/bgp/multipath/feature.textproto +++ b/feature/bgp/multipath/feature.textproto @@ -11,6 +11,9 @@ # 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" @@ -55,14 +58,6 @@ 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" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/ebgp/state/allow-multiple-as" -} - # 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" diff --git a/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/README.md b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/README.md new file mode 100644 index 00000000000..56c9dd115bb --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/README.md @@ -0,0 +1,97 @@ +# RT-1.51: BGP multipath ECMP + +## Summary + +Validate BGP in multipath scenario + +## Testbed type + +[TESTBED_DUT_ATE_4LINKS](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Setup + +* Connect DUT port 1, 2, 3 and 4 to ATE port 1, 2, 3 and 4 respectively +* Configure IPv4/IPv6 addresses on the interfaces +* Establish eBGP sessions between: + * ATE port-1 and DUT port-1 + * ATE port-2 and DUT port-2 + * ATE port-3 and DUT port-3 + * ATE port-4 and DUT port-4 +* Enable an Accept-route all import-policy/export-policy for eBGP session + under the neighbor AFI/SAFI +* Create an IPv4 internal target network attached to ATE port 2, 3 and 4 + +### Tests + +* RT-1.51.1: Verify best path + + * Configure ATE devices(ports) on same AS + * Advertise equal cost paths from 3 interfaces of ATE of same AS + * Check entries in FIB for advertised prefix, it should only have 1 entry + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops + * Initiate traffic from ATE port-1 to the DUT and destined to internal + target network + * Check entire traffic should only be forwarded by one of DUT port2, port3 + or port4 + +* RT-1.51.2: Enforcing multipath hence ECMP scope to only one peer AS + + * Configure ATE devices(ports) on same AS + * Enable multipath and set maximum-paths limit to 2 + * /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/maximum-paths + * Advertise equal cost paths from 3 interfaces of ATE of same AS + * Check entries in FIB for advertised prefix, it should only have 2 + entries + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops + * Initiate traffic from ATE port-1 to the DUT and destined to internal + target network + * Check entire traffic should only be equally forwarded by any two among DUT + port2, port3 or port4 + +* RT-1.51.3: Verify use of allow-multiple-as for ECMP routing across different + peer AS + + * Configure ATE devices(ports) on different AS + * Enable multipath, set maximum-paths limit to 2 and enable allow multiple + AS + * /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 + * Advertise equal cost paths from 3 interfaces of ATE of different AS + * Check entries in FIB for advertised prefix, it should only have 2 + entries + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops + * Initiate traffic from ATE port-1 to the DUT and destined to internal + target network + * Check entire traffic should only be equally forwarded by any two among DUT + port2, port3 or port4 + +## Config Parameter Coverage + +* /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 + +## Telemetry Parameter Coverage + +* /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state +* /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group +* /network-instances/network-instance/afts/next-hop-groups/next-hop-group[id=]/state +* /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: +``` + +## Required DUT platform + +* FFF - Fixed Form Factor + diff --git a/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/bgp_multipath_ecmp_test.go b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/bgp_multipath_ecmp_test.go new file mode 100644 index 00000000000..a2eaf8195e6 --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/bgp_multipath_ecmp_test.go @@ -0,0 +1,278 @@ +// 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 bgp_multipath_ecmp_test + +import ( + "math/rand" + "sort" + "strconv" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "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 ( + prefixesStart = "198.51.100.0" + prefixP4Len = 32 + prefixesCount = 4 + pathID = 1 + maxPaths = 2 + trafficPps = 1000 + totalPackets = 120000 + lossTolerancePct = 0 + lbToleranceFms = 20 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureOTG(t *testing.T, bs *cfgplugins.BGPSession) { + devices := bs.ATETop.Devices().Items() + byName := func(i, j int) bool { return devices[i].Name() < devices[j].Name() } + sort.Slice(devices, byName) + for i, otgPort := range bs.ATEPorts { + if i < 2 { + continue + } + + ipv4 := devices[i].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := devices[i].Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + bgp4PeerRoute := bgp4Peer.V4Routes().Add() + bgp4PeerRoute.SetName(otgPort.Name + ".BGP4.peer.rr4") + bgp4PeerRoute.SetNextHopIpv4Address(ipv4.Address()) + bgp4PeerRoute.SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4) + bgp4PeerRoute.SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + routeAddress := bgp4PeerRoute.Addresses().Add().SetAddress(prefixesStart) + routeAddress.SetPrefix(prefixP4Len) + routeAddress.SetCount(prefixesCount) + bgp4PeerRoute.AddPath().SetPathId(pathID) + } + + configureFlow(t, bs) +} + +func randRange(t *testing.T, start, end uint32, count int) []uint32 { + if count > int(end-start) { + t.Fatal("randRange: count greater than end-start.") + } + rand.New(rand.NewSource(time.Now().UnixNano())) + var result []uint32 + for len(result) < count { + diff := end - start + randomValue := rand.Int31n(int32(diff)) + int32(start) + result = append(result, uint32(randomValue)) + } + return result +} + +func configureFlow(t *testing.T, bs *cfgplugins.BGPSession) { + bs.ATETop.Flows().Clear() + + var rxNames []string + for i := 2; i < len(bs.ATEPorts); i++ { + rxNames = append(rxNames, bs.ATEPorts[i].Name+".BGP4.peer.rr4") + } + flow := bs.ATETop.Flows().Add().SetName("flow") + flow.Metrics().SetEnable(true) + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv4"}). + SetRxNames(rxNames) + flow.Duration().FixedPackets().SetPackets(totalPackets) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(trafficPps) + + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(bs.ATEPorts[0].MAC) + v4 := flow.Packet().Add().Ipv4() + v4.Src().Increment().SetCount(1000).SetStep("0.0.0.1").SetStart(bs.ATEPorts[0].IPv4) + v4.Dst().Increment().SetCount(4).SetStep("0.0.0.1").SetStart(prefixesStart) + udp := flow.Packet().Add().Udp() + udp.SrcPort().SetValues(randRange(t, 34525, 65535, 500)) + udp.DstPort().SetValues(randRange(t, 49152, 65535, 500)) + v4.Src().SetValue(bs.ATEPorts[0].IPv4) + v4.Dst().SetValue(prefixesStart) +} + +func verifyECMPLoadBalance(t *testing.T, ate *ondatra.ATEDevice, pc int, expectedLinks int) { + dut := ondatra.DUT(t, "dut") + framesTx := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(ate.Port(t, "port1").ID()).Counters().OutFrames().State()) + expectedPerLinkFms := framesTx / uint64(expectedLinks) + t.Logf("Total packets %d flow through the %d links and expected per link packets %d", framesTx, expectedLinks, expectedPerLinkFms) + min := expectedPerLinkFms - (expectedPerLinkFms * lbToleranceFms / 100) + max := expectedPerLinkFms + (expectedPerLinkFms * lbToleranceFms / 100) + + got := 0 + for i := 3; i <= pc; i++ { + framesRx := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(ate.Port(t, "port"+strconv.Itoa(i)).ID()).Counters().InFrames().State()) + if framesRx <= lbToleranceFms { + t.Logf("Skip: Traffic through port%d interface is %d", i, framesRx) + continue + } + if int64(min) < int64(framesRx) && int64(framesRx) < int64(max) { + t.Logf("Traffic %d is in expected range: %d - %d, Load balance Test Passed", framesRx, min, max) + got++ + } else { + if !deviations.BgpMaxMultipathPathsUnsupported(dut) { + t.Errorf("Traffic is expected in range %d - %d but got %d. Load balance Test Failed", min, max, framesRx) + } + } + } + if !deviations.BgpMaxMultipathPathsUnsupported(dut) { + if got != expectedLinks { + t.Errorf("invalid number of load balancing interfaces, got: %d want %d", got, expectedLinks) + } + } +} + +func checkPacketLoss(t *testing.T, ate *ondatra.ATEDevice) { + countersPath := gnmi.OTG().Flow("flow").Counters() + rxPackets := gnmi.Get(t, ate.OTG(), countersPath.InPkts().State()) + txPackets := gnmi.Get(t, ate.OTG(), countersPath.OutPkts().State()) + lostPackets := txPackets - rxPackets + if txPackets < 1 { + t.Fatalf("Tx packets should be higher than 0") + } + + if got := lostPackets * 100 / txPackets; got != lossTolerancePct { + t.Errorf("Packet loss percentage for flow: got %v, want %v", got, lossTolerancePct) + } +} + +type testCase struct { + desc string + enableMultipath bool + enableMultiAS bool + expectedPaths int +} + +func TestBGPSetup(t *testing.T) { + testCases := []testCase{ + { + desc: "ebgp setup test", + enableMultipath: false, + enableMultiAS: false, + expectedPaths: 1, + }, + { + desc: "ebgp multipath same as", + enableMultipath: true, + enableMultiAS: false, + expectedPaths: 2, + }, + { + desc: "ebgp multipath different as", + enableMultipath: true, + enableMultiAS: true, + expectedPaths: 2, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount4, nil) + bs.WithEBGP(t, []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST}, []string{"port3", "port4"}, true, !tc.enableMultiAS) + dni := deviations.DefaultNetworkInstance(bs.DUT) + bgp := bs.DUTConf.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").GetOrCreateBgp() + gEBGP := bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + if tc.enableMultipath { + t.Logf("Enable Multipath") + switch bs.DUT.Vendor() { + case ondatra.NOKIA: + //BGP multipath enable/disable at the peer-group level not required b/376799583 + t.Logf("BGP Multipath enable/disable not required under Peer-group by %s hence skipping", bs.DUT.Vendor()) + default: + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().Enabled = ygot.Bool(true) + } + t.Logf("Enable Maximum Paths") + if deviations.EnableMultipathUnderAfiSafi(bs.DUT) { + gEBGP.MaximumPaths = ygot.Uint32(maxPaths) + } else { + bgp.GetOrCreateGlobal().GetOrCreateUseMultiplePaths().GetOrCreateEbgp().MaximumPaths = ygot.Uint32(maxPaths) + } + } + if tc.enableMultiAS && !deviations.SkipSettingAllowMultipleAS(bs.DUT) && deviations.SkipAfiSafiPathForBgpMultipleAs(bs.DUT) { + t.Logf("Enable MultiAS ") + gEBGP := bgp.GetOrCreateGlobal().GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + gEBGP.AllowMultipleAs = ygot.Bool(true) + } + if tc.enableMultiAS && !deviations.SkipSettingAllowMultipleAS(bs.DUT) && !deviations.SkipAfiSafiPathForBgpMultipleAs(bs.DUT) { + t.Logf("Enable MultiAS ") + gEBGP.AllowMultipleAs = ygot.Bool(true) + } + + configureOTG(t, bs) + bs.PushAndStart(t) + + t.Logf("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + + t.Logf("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) + + aftsPath := gnmi.OC().NetworkInstance(dni).Afts() + prefix := prefixesStart + "/" + strconv.Itoa(prefixP4Len) + + if deviations.BgpMaxMultipathPathsUnsupported(bs.DUT) { + tc.expectedPaths = 3 + } else { + val, ok := gnmi.Watch(t, bs.DUT, aftsPath.Ipv4Entry(prefix).State(), time.Minute, + func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + ipv4Entry, present := val.Val() + if !present { + return false + } + + hopGroup := gnmi.Get[*oc.NetworkInstance_Afts_NextHopGroup](t, bs.DUT, aftsPath.NextHopGroup(ipv4Entry.GetNextHopGroup()).State()) + got := len(hopGroup.NextHop) + want := tc.expectedPaths + return got == want + }).Await(t) + + if !ok { + ipv4Entry, present := val.Val() + if !present { + t.Errorf("prefix: %s, found no aft entry", ipv4Entry.GetPrefix()) + } else { + hopGroup := gnmi.Get[*oc.NetworkInstance_Afts_NextHopGroup](t, bs.DUT, aftsPath.NextHopGroup(ipv4Entry.GetNextHopGroup()).State()) + got := len(hopGroup.NextHop) + want := tc.expectedPaths + if got != want { + t.Errorf("prefix: %s, found %d hops, want %d", ipv4Entry.GetPrefix(), got, want) + } + } + } + } + sleepTime := time.Duration(totalPackets/trafficPps) + 5 + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + checkPacketLoss(t, bs.ATE) + verifyECMPLoadBalance(t, bs.ATE, int(cfgplugins.PortCount4), tc.expectedPaths) + }) + } +} 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 new file mode 100644 index 00000000000..b696e86ea0b --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto @@ -0,0 +1,50 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "41faf6fd-dcc6-4ebf-8ace-b71fa22d8bf7" +plan_id: "RT-1.51" +description: "BGP multipath ECMP" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + skip_afi_safi_path_for_bgp_multiple_as: true + enable_multipath_under_afi_safi: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_max_multipath_paths_unsupported: true + multipath_unsupported_neighbor_or_afisafi: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + enable_multipath_under_afi_safi: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + missing_value_for_defaults: true + skip_setting_allow_multiple_as: true + } +} +tags: TAGS_DATACENTER_EDGE diff --git a/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/README.md b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/README.md new file mode 100644 index 00000000000..caf12697c34 --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/README.md @@ -0,0 +1,130 @@ +# RT-1.52: BGP multipath UCMP support with Link Bandwidth Community + +## Summary + +Validate BGP in multipath UCMP support with link bandwidth community + +## Testbed type + +[TESTBED_DUT_ATE_4LINKS](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Setup + +* Connect DUT port 1, 2 and 3 to ATE port 1, 2 and 3 respectively +* Configure IPv4/IPv6 addresses on the interfaces +* Establish eBGP sessions between: + * ATE port-1 and DUT port-1 + * ATE port-2 and DUT port-2 + * ATE port-3 and DUT port-3 +* Enable an Accept-route all import-policy/export-policy for eBGP session + under the neighbor AFI/SAFI - IPv6 unicast and IPv4 unicast. +* Create an single IPv4 internal target network attached to ATE port 2 and 3 +* Create an single IPv6 internal target network attached to ATE port 2 and 3 + + +### Tests + +* RT-1.52.1: Verify use of unequal community type + + * Test Configuration + * Configure ATE port 1, 2 and 3 on different AS, with boths AFI/SAFI + * Advertise IPv4 and IPv6 internal target, both, form both ATE port-1 and ATE port-2 in eBGP. + * For ATE port 2 attach `link-bandwidth:23456:10K` extended-community + * For ATE port 3 attach `link-bandwidth:23456:5K` extended-community + * Enable multipath, set maximum-paths limit to 2, enable allow multiple + AS, and send community type to [STANDARD, EXTENDED, LARGE] + * /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/protocols/protocol/bgp/peer-groups/peer-group/config/send-community-type + * /network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ebgp/link-bandwidth-ext-community/config/enabled + * Advertise equal cost paths from port2 and port3 of ATE + * Initiate traffic from ATE port-1 to the DUT and destined to internal + target network. + * Use UDP traffic with src and dst port randomly selected from 1-65535 range for each packet. Or equivalent pattern guaranteeng high entropy of traffic. + * Behaviour Validation + * Check entries in AFT for advertised prefix, it should have 2 entries.\ + The `weight` leafs of next-hops shall be in 2:1 ratio. + * Find next-hop-group IDs for both internal target networks: + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry[prefix=IPv4 internal target network]/state/**next-hop-group** + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry[prefix=IPv6 internal target network]/state/**next-hop-group** + * using next-hop-group as key find number and weight of next-hops of both internal target network + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group[id=next-hop-group ID]/next-hops/next-hop/state/index + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group[id=next-hop-group ID]/next-hops/next-hop/state/**weight** + * Check entire traffic should be unequally forwarded between DUT + port2 and port3 only + * 66% via port2 + * 33% via port3 + * with +/-5% tolerance + +* RT-1.52.2: Verify use of equal community type + + * Test Configuration + Use test configuration as in RT-1.52.1 above with following modifications: + * Advertise IPv4 and IPv6 internal target, both, form both ATE port-1 and ATE port-2 in eBGP. + * For ATE port 2 attach `link-bandwidth:23456:10K` extended-community + * For ATE port 3 attach `link-bandwidth:23456:10K` extended-community + * Behaviour Validation + * Check entries in AFT for advertised prefix, it should have 2 entries.\ + The `weight` leafs of next-hops shall be in 1:1 ratio. + * Find next-hop-group IDs for both internal target networks: + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry[prefix=IPv4 internal target network]/state/**next-hop-group** + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry[prefix=IPv6 internal target network]/state/**next-hop-group** + * using next-hop-group as key find number and weight of next-hops of both internal target network + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group[id=next-hop-group ID]/next-hops/next-hop/state/index + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group[id=next-hop-group ID]/next-hops/next-hop/state/**weight** + * Check entire traffic should be unequally forwarded between DUT + port2 and port3 only + * 50% via port2 + * 50% via port3 + * with +/-5% tolerance + +* RT-1.52.3: Verify BGP multipath when some path missing link-bandwidth extended-community + + * Test Configuration + Use test configuration as in RT-1.52.1 above with following modifications: + * Configure ATE port 1, 2 and 3 on different AS, with boths AFI/SAFI + * Advertise IPv4 and IPv6 internal target, both, form both ATE port-1 and ATE port-2 in eBGP. + * For ATE port 2 attach `link-bandwidth:23456:10K` extended-community + * For ATE port 3 **DO NOT** attach any link-bandwidth extended-community + * Behaviour Validation + * Check entries in AFT for advertised prefix, it should have 2 entries.\ + The `weight` leafs of next-hops shall be in 1:1 ratio. + * Find next-hop-group IDs for both internal target networks: + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry[prefix=IPv4 internal target network]/state/**next-hop-group** + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry[prefix=IPv6 internal target network]/state/**next-hop-group** + * using next-hop-group as key find number and weight of next-hops of both internal target network + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group[id=next-hop-group ID]/next-hops/next-hop/state/index + * /network-instances/network-instance/afts/next-hop-groups/next-hop-group[id=next-hop-group ID]/next-hops/next-hop/state/**weight** + * Check entire traffic should be unequally forwarded between DUT + port2 and port3 only + * 50% via port2 + * 50% via port3 + * with +/-5% tolerance + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: +paths: + ## Config Parameter Coverage + /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/protocols/protocol/bgp/peer-groups/peer-group/config/send-community-type: + /network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ebgp/link-bandwidth-ext-community/config/enabled: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight: + +``` +## Required DUT platform + +* FFF - Fixed Form Factor + 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 new file mode 100644 index 00000000000..f6db0a2adab --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/bgp_multipath_wecmp_test.go @@ -0,0 +1,290 @@ +// 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 bgp_multipath_wecmp_test + +import ( + "fmt" + "sort" + "strconv" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "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/ygot/ygot" +) + +const ( + prefixesStart = "198.51.100.0" + prefixP4Len = 32 + prefixesCount = 3 + pathID = 1 + maxPaths = 2 + trafficPps = 1000 + totalPackets = 120000 + lossTolerancePct = 0 + lbToleranceFms = 10 +) + +var linkBw = []int{10} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureOTG(t *testing.T, bs *cfgplugins.BGPSession) { + devices := bs.ATETop.Devices().Items() + byName := func(i, j int) bool { return devices[i].Name() < devices[j].Name() } + sort.Slice(devices, byName) + for i, otgPort := range bs.ATEPorts { + if i < 2 { + continue + } + + ipv4 := devices[i].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := devices[i].Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + bgp4PeerRoute := bgp4Peer.V4Routes().Add() + bgp4PeerRoute.SetName(otgPort.Name + ".BGP4.peer.rr4") + bgp4PeerRoute.SetNextHopIpv4Address(ipv4.Address()) + bgp4PeerRoute.SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4) + bgp4PeerRoute.SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + routeAddress := bgp4PeerRoute.Addresses().Add().SetAddress(prefixesStart) + routeAddress.SetPrefix(prefixP4Len) + routeAddress.SetCount(prefixesCount) + bgp4PeerRoute.AddPath().SetPathId(pathID) + } + + configureFlow(bs) +} + +func attachLBWithInternalNetwork(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) { + devices := top.Devices().Items() + byName := func(i, j int) bool { return devices[i].Name() < devices[j].Name() } + sort.Slice(devices, byName) + + for _, i := range []int{2, 3} { + bgp4Peer := devices[i].Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + bgp4PeerRoute := bgp4Peer.V4Routes().Items()[0] + bgp4PeerRoute.ExtendedCommunities().Clear() + if i-2 < len(linkBw) { + bgpExtCom := bgp4PeerRoute.ExtendedCommunities().Add() + bgpExtCom.NonTransitive2OctetAsType().LinkBandwidthSubtype().SetBandwidth(float32(linkBw[i-2] * 1000)) + bgpExtCom.NonTransitive2OctetAsType().LinkBandwidthSubtype().SetGlobal2ByteAs(23456) + } + } + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") +} + +func configureFlow(bs *cfgplugins.BGPSession) { + bs.ATETop.Flows().Clear() + + var rxNames []string + for i := 2; i < len(bs.ATEPorts); i++ { + rxNames = append(rxNames, bs.ATEPorts[i].Name+".BGP4.peer.rr4") + } + flow := bs.ATETop.Flows().Add().SetName("flow") + flow.Metrics().SetEnable(true) + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv4"}). + SetRxNames(rxNames) + flow.Duration().FixedPackets().SetPackets(totalPackets) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(trafficPps) + + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(bs.ATEPorts[0].MAC) + v4 := flow.Packet().Add().Ipv4() + v4.Src().Increment().SetCount(1000).SetStep("0.0.0.1").SetStart(bs.ATEPorts[0].IPv4) + v4.Dst().Increment().SetCount(3).SetStep("0.0.0.1").SetStart(prefixesStart) +} + +func checkPacketLoss(t *testing.T, ate *ondatra.ATEDevice) { + countersPath := gnmi.OTG().Flow("flow").Counters() + rxPackets := gnmi.Get(t, ate.OTG(), countersPath.InPkts().State()) + txPackets := gnmi.Get(t, ate.OTG(), countersPath.OutPkts().State()) + lostPackets := txPackets - rxPackets + + if txPackets < 1 { + t.Fatalf("Tx packets should be higher than 0") + } + + if got := lostPackets * 100 / txPackets; got != lossTolerancePct { + t.Errorf("Packet loss percentage for flow: got %v, want %v", got, lossTolerancePct) + } +} + +func verifyECMPLoadBalance(t *testing.T, ate *ondatra.ATEDevice, pc int, expectedLinks int) { + framesTx := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(ate.Port(t, "port1").ID()).Counters().OutFrames().State()) + var lb1, lb2 float32 + if len(linkBw) == 1 { + lb1, lb2 = float32(linkBw[0]), float32(linkBw[0]) + } else { + lb1, lb2 = float32(linkBw[0]), float32(linkBw[1]) + } + + expectedPerLinkFmsP3 := int(lb1 / (lb1 + lb2) * float32(framesTx)) + expectedPerLinkFmsP4 := int(lb2 / (lb1 + lb2) * float32(framesTx)) + t.Logf("Total packets %d flow through the %d links and expected per link packets: %d, %d", framesTx, expectedLinks, expectedPerLinkFmsP3, expectedPerLinkFmsP4) + + p3Min := expectedPerLinkFmsP3 - (expectedPerLinkFmsP3 * lbToleranceFms / 100) + p3Max := expectedPerLinkFmsP3 + (expectedPerLinkFmsP3 * lbToleranceFms / 100) + framesRxP3 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(ate.Port(t, "port"+strconv.Itoa(3)).ID()).Counters().InFrames().State()) + + if int64(p3Min) < int64(framesRxP3) && int64(framesRxP3) < int64(p3Max) { + t.Logf("Traffic of %d on port-3 is within expected range: %d - %d", framesRxP3, p3Min, p3Max) + } else { + t.Errorf("Traffic on port-3 is expected to be in the range %d - %d but got %d. Load balance Test Failed", p3Min, p3Max, framesRxP3) + } + + p4Min := expectedPerLinkFmsP4 - (expectedPerLinkFmsP4 * lbToleranceFms / 100) + p4Max := expectedPerLinkFmsP4 + (expectedPerLinkFmsP4 * lbToleranceFms / 100) + framesRxP4 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(ate.Port(t, "port"+strconv.Itoa(4)).ID()).Counters().InFrames().State()) + + if int64(p4Min) < int64(framesRxP4) && int64(framesRxP4) < int64(p4Max) { + t.Logf("Traffic of %d on port-4 is within expected range: %d - %d", framesRxP4, p4Min, p4Max) + } else { + t.Errorf("Traffic on port-4 is expected to be in the range %d - %d but got %d. Load balance Test Failed", p4Min, p4Max, framesRxP4) + } +} + +func TestBGPSetup(t *testing.T) { + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount4, nil) + bs.WithEBGP(t, []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST}, []string{"port3", "port4"}, true, false) + + dni := deviations.DefaultNetworkInstance(bs.DUT) + bgp := bs.DUTConf.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").GetOrCreateBgp() + switch bs.DUT.Vendor() { + case ondatra.NOKIA: + //BGP multipath enable/disable at the peer-group level not required b/376799583 + t.Logf("BGP Multipath enable/disable is not required under Peer-group by %s hence skipping", bs.DUT.Vendor()) + default: + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().Enabled = ygot.Bool(true) + } + + if !deviations.SkipBgpSendCommunityType(bs.DUT) { + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD, oc.Bgp_CommunityType_EXTENDED, oc.Bgp_CommunityType_LARGE}) + } + + if deviations.MultipathUnsupportedNeighborOrAfisafi(bs.DUT) { + t.Logf("MultipathUnsupportedNeighborOrAfisafi is supported") + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateUseMultiplePaths().Enabled = ygot.Bool(true) + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateUseMultiplePaths().GetOrCreateEbgp().AllowMultipleAs = ygot.Bool(true) + } + + if deviations.SkipAfiSafiPathForBgpMultipleAs(bs.DUT) { + var communitySetCLIConfig string + t.Log("AfiSafi Path For BgpMultipleAs is not supported") + gEBGP := bgp.GetOrCreateGlobal().GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + gEBGPMP := bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + gEBGPMP.MaximumPaths = ygot.Uint32(maxPaths) + if deviations.SkipSettingAllowMultipleAS(bs.DUT) { + gEBGP.AllowMultipleAs = ygot.Bool(false) + switch bs.DUT.Vendor() { + case ondatra.CISCO: + communitySetCLIConfig = fmt.Sprintf("router bgp %v instance BGP neighbor-group %v \n ebgp-recv-extcommunity-dmz \n ebgp-send-extcommunity-dmz\n", cfgplugins.DutAS, cfgplugins.BGPPeerGroup1) + default: + t.Fatalf("Unsupported vendor %s for deviation 'CommunityMemberRegexUnsupported'", bs.DUT.Vendor()) + } + 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.AllowMultipleAs = ygot.Bool(true) + gEBGP.MaximumPaths = ygot.Uint32(maxPaths) + gEBGP.GetOrCreateLinkBandwidthExtCommunity().Enabled = ygot.Bool(true) + } + } + + configureOTG(t, bs) + bs.PushAndStart(t) + + t.Logf("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + + t.Logf("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) + + testCases := []struct { + name string + desc string + linkBw []int + }{ + { + name: "RT-1.52.1", + desc: "Verify BGP multipath when some path missing link-bandwidth extended-community", + linkBw: []int{10}, + }, + { + name: "RT-1.52.2", + desc: "Verify use of equal community type", + linkBw: []int{10, 10}, + }, + { + name: "RT-1.52.1", + desc: "Verify use of unequal community type", + linkBw: []int{10, 5}, + }, + } + + aftsPath := gnmi.OC().NetworkInstance(dni).Afts() + prefix := prefixesStart + "/" + strconv.Itoa(prefixP4Len) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + linkBw = tc.linkBw + attachLBWithInternalNetwork(t, bs.ATE, bs.ATETop) + time.Sleep(30 * time.Second) + + ipv4Entry := gnmi.Get[*oc.NetworkInstance_Afts_Ipv4Entry](t, bs.DUT, aftsPath.Ipv4Entry(prefix).State()) + hopGroup := gnmi.Get[*oc.NetworkInstance_Afts_NextHopGroup](t, bs.DUT, aftsPath.NextHopGroup(ipv4Entry.GetNextHopGroup()).State()) + if got, want := len(hopGroup.NextHop), 2; got != want { + t.Errorf("prefix: %s, found %d hops, want %d", ipv4Entry.GetPrefix(), got, want) + } else { + for i, nh := range hopGroup.NextHop { + t.Logf("Prefix %s, NextHop(%d) weight: %d", ipv4Entry.GetPrefix(), i, nh.GetWeight()) + } + } + + sleepTime := time.Duration(totalPackets/trafficPps) + 5 + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + checkPacketLoss(t, bs.ATE) + verifyECMPLoadBalance(t, bs.ATE, int(cfgplugins.PortCount4), 2) + }) + } +} diff --git a/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/metadata.textproto b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/metadata.textproto new file mode 100644 index 00000000000..1f758c5b4f5 --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/metadata.textproto @@ -0,0 +1,51 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "f76a89e9-a2f1-4160-b6e6-e762dc10219a" +plan_id: "RT-1.52" +description: "BGP multipath UCMP support with Link Bandwidth Community" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + skip_setting_allow_multiple_as: true + skip_afi_safi_path_for_bgp_multiple_as: true + skip_bgp_send_community_type: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + multipath_unsupported_neighbor_or_afisafi: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + skip_bgp_send_community_type: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + missing_value_for_defaults: true + skip_setting_allow_multiple_as: true + } +} +tags: TAGS_DATACENTER_EDGE + diff --git a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/README.md b/feature/bgp/otg_tests/base_bgp_session_parameters/README.md similarity index 61% rename from feature/experimental/bgp/ate_tests/base_bgp_session_parameters/README.md rename to feature/bgp/otg_tests/base_bgp_session_parameters/README.md index dc3416dbd47..8bdeb896e1a 100644 --- a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/README.md +++ b/feature/bgp/otg_tests/base_bgp_session_parameters/README.md @@ -48,48 +48,21 @@ Test the normal session establishment and termination: * Explicit holdtime interval and keepalive interval. * Explicit connect retry interval. -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/bgp/global - -* For Parameters: - - * config/as - * config/router-id - * config/peer-as - * config/local-as - * config/description - * timers/config/hold-time - * timers/config/keepalive-interval - * timers/config/minimum-route-advertisement-interval - -* For prefixes: - - * /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group - * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/bgp/ - -* For Parameters: - - * state/last-established - * state/messages/received/NOTIFICATION - * state/negotiated-hold-time - * state/supported-capabilities - -## Protocol/RPC Parameter coverage - -* BGP - - * OPEN - - * Version - * My Autonomous System - * BGP Identifier - * Hold Time +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /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: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/last-established: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/received/NOTIFICATION: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/negotiated-hold-time: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/supported-capabilities: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` diff --git a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go b/feature/bgp/otg_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go similarity index 77% rename from feature/experimental/bgp/ate_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go rename to feature/bgp/otg_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go index 14b8bbdf57d..576f1267096 100644 --- a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go +++ b/feature/bgp/otg_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "github.com/open-traffic-generator/snappi/gosnappi" "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/confirm" "github.com/openconfig/featureprofiles/internal/deviations" @@ -42,8 +43,9 @@ var ( IPv4Len: 30, } ateAttrs = attrs.Attributes{ - //Desc: "To DUT", + Desc: "To DUT", Name: "ateSrc", + MAC: "02:00:01:01:01:01", IPv4: "192.0.2.2", IPv4Len: 30, } @@ -70,6 +72,13 @@ const ( connExternal connType = "EXTERNAL" ) +type authType string + +const ( + noAuth authType = "NONE" + md5Auth authType = "MD5" +) + // configureDUT is used to configure interfaces on the DUT. func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { dc := gnmi.OC() @@ -236,26 +245,47 @@ func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { } // Function to configure ATE configs based on args and returns ate topology handle. -func configureATE(t *testing.T, ateParams *bgpTestParams, connectionType connType) *ondatra.ATETopology { +func configureATE(t *testing.T, ateParams *bgpTestParams, connectionType connType, auth authType) gosnappi.Config { + t.Helper() ate := ondatra.ATE(t, "ate") port1 := ate.Port(t, "port1") - topo := ate.Topology().New() - - iDut1 := topo.AddInterface(ateAttrs.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateAttrs.IPv4CIDR()).WithDefaultGateway(ateParams.peerIP) - bgpDut1 := iDut1.BGP() - + 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)) + + bgp := dev.Bgp().SetRouterId(ateAttrs.IPv4) + peerBGP := bgp.Ipv4Interfaces().Add().SetIpv4Name(ip.Name()).Peers().Add() + peerBGP.SetName(ateAttrs.Name + ".BGP4.peer") + peerBGP.Advanced().SetHoldTimeInterval(ateHoldTime) + if auth == md5Auth { + peerBGP.Advanced().SetMd5Key(authPassword) + } + peerBGP.SetPeerAddress(ip.Gateway()).SetAsNumber(uint32(ateParams.localAS)) if connectionType == connInternal { - bgpDut1.AddPeer().WithPeerAddress(ateParams.peerIP).WithLocalASN(ateParams.localAS).WithTypeInternal(). - WithHoldTime(ateHoldTime) + peerBGP.SetAsType(gosnappi.BgpV4PeerAsType.IBGP) } else { - bgpDut1.AddPeer().WithPeerAddress(ateParams.peerIP).WithLocalASN(ateParams.localAS).WithTypeExternal(). - WithHoldTime(ateHoldTime) + peerBGP.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) } + return topo } +// createCeaseAction creates the BGP cease notification action in gosnappi +func createCeaseAction(t *testing.T) gosnappi.ControlAction { + t.Helper() + ceaseAction := gosnappi.NewControlAction() + ceaseAction.Protocol().Bgp().Notification().SetNames([]string{ateAttrs.Name + ".BGP4.peer"}).Custom().SetCode(6).SetSubcode(6) + return ceaseAction +} + // TestEstablishAndDisconnect Establishes BGP session between DUT and ATE and Verifies // abnormal termination of session using notification message: func TestEstablishAndDisconnect(t *testing.T) { @@ -279,7 +309,7 @@ func TestEstablishAndDisconnect(t *testing.T) { bgpClearConfig(t, dut) dutConf := bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS}, dut) gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - // Configure Md5 auth password. + t.Log("Configure matching Md5 auth password on DUT") gnmi.Replace(t, dut, dutConfPath.Bgp().Neighbor(ateAttrs.IPv4).AuthPassword().Config(), authPassword) fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) @@ -287,17 +317,12 @@ func TestEstablishAndDisconnect(t *testing.T) { // ATE Configuration. t.Log("Configure port and BGP configs on ATE") ate := ondatra.ATE(t, "ate") - port1 := ate.Port(t, "port1") - topo := ate.Topology().New() - iDut1 := topo.AddInterface(ateAttrs.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateAttrs.IPv4CIDR()).WithDefaultGateway(dutAttrs.IPv4) - bgpDut1 := iDut1.BGP() - bgpPeer := bgpDut1.AddPeer().WithPeerAddress(dutAttrs.IPv4).WithLocalASN(ateAS).WithTypeExternal(). - WithMD5Key(authPassword).WithHoldTime(ateHoldTime) + topo := configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutAttrs.IPv4}, connExternal, md5Auth) t.Log("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) + otg := ate.OTG() + otg.PushConfig(t, topo) + otg.StartProtocols(t) // Verify Port Status t.Log("Verifying port status") @@ -312,8 +337,8 @@ func TestEstablishAndDisconnect(t *testing.T) { verifyBGPCapabilities(t, dut) // Send Cease Notification from ATE to DUT - t.Log("Send Cease Notification from ATE to DUT") - ate.Actions().NewBGPPeerNotification().WithCode(6).WithSubCode(6).WithPeers(bgpPeer).Send(t) + t.Log("Send Cease Notification from OTG to DUT -- ") + otg.SetControlAction(t, createCeaseAction(t)) t.Log("Verify BGP session state : NOT in ESTABLISHED State") _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Second*60, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { currBgpState, present := val.Val() @@ -335,7 +360,7 @@ func TestEstablishAndDisconnect(t *testing.T) { } // Clear config on DUT and ATE - topo.StopProtocols(t) + otg.StopProtocols(t) bgpClearConfig(t, dut) } @@ -371,52 +396,64 @@ func TestPassword(t *testing.T) { // ATE Configuration. t.Log("Configure port and BGP configs on ATE") ate := ondatra.ATE(t, "ate") - port1 := ate.Port(t, "port1") - topo := ate.Topology().New() - iDut1 := topo.AddInterface(ateAttrs.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateAttrs.IPv4CIDR()).WithDefaultGateway(dutAttrs.IPv4) - bgpDut1 := iDut1.BGP() - bgpPeer := bgpDut1.AddPeer().WithPeerAddress(dutAttrs.IPv4).WithLocalASN(ateAS).WithTypeExternal(). - WithMD5Key(authPassword).WithHoldTime(ateHoldTime) + topo := configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutAttrs.IPv4}, connExternal, md5Auth) t.Log("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) + ate.OTG().PushConfig(t, topo) + ate.OTG().StartProtocols(t) // Verify BGP status t.Log("Check BGP parameters") verifyBgpTelemetry(t, dut) - if !deviations.SkipBGPTestPasswordMismatch(dut) { - t.Log("Configure mismatching md5 auth password on DUT") - gnmi.Replace(t, dut, dutConfPath.Bgp().Neighbor(ateAttrs.IPv4).AuthPassword().Config(), "PASSWORDNEGSCENARIO") - - // If the DUT will not fail a BGP session when the BGP MD5 key configuration changes, - // change the key from the ATE side to time out the session. - if deviations.BGPMD5RequiresReset(dut) { - bgpPeer.WithMD5Key("PASSWORDNEGSCENARIO-ATE") - topo.UpdateBGPPeerStates(t) - } - t.Log("Wait till hold time expires: BGP should not be in ESTABLISHED state when passwords do not match.") - _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), (dutHoldTime+10)*time.Second, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { - state, ok := val.Val() - return ok && state != oc.Bgp_Neighbor_SessionState_ESTABLISHED - }).Await(t) - if !ok { - fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) - t.Error("BGP Adjacency is ESTABLISHED when passwords are not matching") - } - t.Log("Revert md5 auth password on DUT to match with ATE.") - gnmi.Replace(t, dut, dutConfPath.Bgp().Neighbor(ateAttrs.IPv4).AuthPassword().Config(), authPassword) - if deviations.BGPMD5RequiresReset(dut) { - bgpPeer.WithMD5Key(authPassword) - topo.UpdateBGPPeerStates(t) - } - t.Log("Verify BGP session state : Should be ESTABLISHED") - gnmi.Await(t, dut, nbrPath.SessionState().State(), time.Second*50, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + t.Log("Configure mismatching md5 auth password on DUT") + gnmi.Replace(t, dut, dutConfPath.Bgp().Neighbor(ateAttrs.IPv4).AuthPassword().Config(), "PASSWORDNEGSCENARIO") + + // If the DUT will not fail a BGP session when the BGP MD5 key configuration changes, + // change the key from the ATE side to time out the session. + if deviations.BGPMD5RequiresReset(dut) { + 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)) + + bgp := dev.Bgp().SetRouterId(ateAttrs.IPv4) + peerBGP := bgp.Ipv4Interfaces().Add().SetIpv4Name(ip.Name()).Peers().Add() + peerBGP.SetName(ateAttrs.Name + ".BGP4.peer").Advanced().SetMd5Key("PASSWORDNEGSCENARIO-ATE").SetHoldTimeInterval(ateHoldTime) + peerBGP.SetPeerAddress(ip.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + ate.OTG().PushConfig(t, topo) + ate.OTG().StartProtocols(t) + } + t.Log("Wait till hold time expires: BGP should not be in ESTABLISHED state when passwords do not match.") + _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), (dutHoldTime+10)*time.Second, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state != oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Error("BGP Adjacency is ESTABLISHED when passwords are not matching") + } + + t.Log("Revert md5 auth password on DUT to match with ATE.") + gnmi.Replace(t, dut, dutConfPath.Bgp().Neighbor(ateAttrs.IPv4).AuthPassword().Config(), authPassword) + if deviations.BGPMD5RequiresReset(dut) { + topo := configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutAttrs.IPv4}, connExternal, md5Auth) + t.Log("Pushing config to ATE and starting protocols...") + ate.OTG().PushConfig(t, topo) + ate.OTG().StartProtocols(t) + } + t.Log("Verify BGP session state : Should be ESTABLISHED") + gnmi.Await(t, dut, nbrPath.SessionState().State(), (dutHoldTime+10)*time.Second, oc.Bgp_Neighbor_SessionState_ESTABLISHED) // Clear config on DUT and ATE - topo.StopProtocols(t) + ate.OTG().StopProtocols(t) bgpClearConfig(t, dut) } @@ -427,6 +464,7 @@ func TestParameters(t *testing.T) { ateIP := ateAttrs.IPv4 dutIP := dutAttrs.IPv4 dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") // Configure Network instance type on DUT t.Log("Configure Network Instance") @@ -440,27 +478,27 @@ func TestParameters(t *testing.T) { cases := []struct { name string dutConf *oc.NetworkInstance_Protocol - ateConf *ondatra.ATETopology + ateConf gosnappi.Config }{ { name: "Test the eBGP session establishment: Global AS", dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS}, dut), - ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP}, connExternal), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP}, connExternal, noAuth), }, { name: "Test the eBGP session establishment: Neighbor AS", dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS2, peerAS: ateAS, nbrLocalAS: dutAS}, dut), - ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP}, connExternal), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP}, connExternal, noAuth), }, { - name: "Test the iBGP session establishment: Gloabl AS", + name: "Test the iBGP session establishment: Global AS", dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS2, peerAS: ateAS2}, dut), - ateConf: configureATE(t, &bgpTestParams{localAS: ateAS2, peerIP: dutIP}, connInternal), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS2, peerIP: dutIP}, connInternal, noAuth), }, { name: "Test the iBGP session establishment: Neighbor AS", dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS2, nbrLocalAS: dutAS2}, dut), - ateConf: configureATE(t, &bgpTestParams{localAS: ateAS2, peerIP: dutIP}, connInternal), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS2, peerIP: dutIP}, connInternal, noAuth), }, } for _, tc := range cases { @@ -471,8 +509,8 @@ func TestParameters(t *testing.T) { gnmi.Replace(t, dut, dutConfPath.Config(), tc.dutConf) fptest.LogQuery(t, "DUT BGP Config ", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) t.Log("Configure BGP on ATE") - tc.ateConf.Push(t) - tc.ateConf.StartProtocols(t) + ate.OTG().PushConfig(t, tc.ateConf) + ate.OTG().StartProtocols(t) t.Log("Verify BGP session state : ESTABLISHED") gnmi.Await(t, dut, nbrPath.SessionState().State(), time.Second*100, oc.Bgp_Neighbor_SessionState_ESTABLISHED) stateDut := gnmi.Get(t, dut, statePath.State()) @@ -484,7 +522,7 @@ func TestParameters(t *testing.T) { } confirm.State(t, wantState, stateDut) t.Log("Clear BGP Configs on ATE") - tc.ateConf.StopProtocols(t) + ate.OTG().StopProtocols(t) }) } } diff --git a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/metadata.textproto b/feature/bgp/otg_tests/base_bgp_session_parameters/metadata.textproto similarity index 95% rename from feature/experimental/bgp/ate_tests/base_bgp_session_parameters/metadata.textproto rename to feature/bgp/otg_tests/base_bgp_session_parameters/metadata.textproto index db169e1e760..b2db3691fa7 100644 --- a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/metadata.textproto +++ b/feature/bgp/otg_tests/base_bgp_session_parameters/metadata.textproto @@ -11,7 +11,6 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true - skip_bgp_test_password_mismatch: true connect_retry: true } } diff --git a/feature/bgp/otg_tests/bgp_2byte_4byte_asn/README.md b/feature/bgp/otg_tests/bgp_2byte_4byte_asn/README.md new file mode 100644 index 00000000000..b372590de53 --- /dev/null +++ b/feature/bgp/otg_tests/bgp_2byte_4byte_asn/README.md @@ -0,0 +1,38 @@ +# RT-1.19: BGP 2-Byte and 4-Byte ASN support + +## Summary + +BGP 2-Byte and 4-Byte ASN support + +## Procedure + +* Establish BGP sessions as follows and verify all the sessions are established + * ATE (2-byte) - DUT (4-byte) - eBGP IPv4 with ASN < 65535 on DUT side + * ATE (2-byte) - DUT (4-byte) - eBGP IPv6 with ASN < 65535 on DUT side + * ATE (4-byte) - DUT (4-byte) - eBGP IPv4 + * ATE (4-byte) - DUT (4-byte) - eBGP IPv6 + * ATE (2-byte) - DUT (4-byte) - iBGP IPv4 with ASN < 65535 on DUT side + * ATE (4-byte) - DUT (4-byte) - iBGP IPv6 with ASN < 65535 on DUT side + * ATE (4-byte) - DUT (4-byte) - iBGP IPv4 + * ATE (4-byte) - DUT (4-byte) - iBGP IPv6 + +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/global/config/as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/local-as: + + ## Telemetry Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/global/state/as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/local-as: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` diff --git a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go b/feature/bgp/otg_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go similarity index 79% rename from feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go rename to feature/bgp/otg_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go index 0ce921676f8..dcc80fbed56 100644 --- a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go +++ b/feature/bgp/otg_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "github.com/open-traffic-generator/snappi/gosnappi" "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/ondatra/gnmi/oc" @@ -43,6 +44,7 @@ var ( } ateSrc = attrs.Attributes{ Name: "ateSrc", + MAC: "02:11:01:00:01:01", IPv4: "192.0.2.2", IPv6: "2001:db8::192:0:2:2", IPv4Len: 30, @@ -61,11 +63,19 @@ func TestMain(m *testing.M) { } func TestBgpSession(t *testing.T) { + t.Log("Clear ATE configuration") + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + ate.OTG().PushConfig(t, top) + t.Log("Configure DUT interface") dut := ondatra.DUT(t, "dut") dc := gnmi.OC() i1 := dutSrc.NewOCInterface(dut.Port(t, "port1").Name(), dut) gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, i1.GetName(), deviations.DefaultNetworkInstance(dut), 0) + } t.Log("Configure Network Instance") dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) @@ -78,53 +88,54 @@ func TestBgpSession(t *testing.T) { name string nbr *bgpNbr dutConf *oc.NetworkInstance_Protocol - ateConf *ondatra.ATETopology + ateConf gosnappi.Config }{ { name: "Establish eBGP connection between ATE (2-byte) - DUT (4-byte < 65535) for ipv4 peers", nbr: &bgpNbr{globalAS: 300, localAS: 100, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 100, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv4, peerAS: 100, isV4: true}, connExternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv4, peerAS: 100, isV4: true}, connExternal, 2), }, { name: "Establish eBGP connection between ATE (2-byte) - DUT (4-byte < 65535) for ipv6 peers", nbr: &bgpNbr{globalAS: 300, localAS: 100, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 100, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv6, peerAS: 100, isV4: false}, connExternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv6, peerAS: 100, isV4: false}, connExternal, 2), }, { name: "Establish eBGP connection between ATE (4-byte) - DUT (4-byte) for ipv4 peers", nbr: &bgpNbr{globalAS: 300, localAS: 70000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 70000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 70000, isV4: true}, connExternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 70000, isV4: true}, connExternal, 4), }, { name: "Establish eBGP connection between ATE (4-byte) - DUT (4-byte) for ipv6 peers", nbr: &bgpNbr{globalAS: 300, localAS: 70000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 70000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 70000, isV4: false}, connExternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 70000, isV4: false}, connExternal, 4), }, { name: "Establish iBGP connection between ATE (2-byte) - DUT (4-byte < 65535) for ipv4 peers", nbr: &bgpNbr{globalAS: 300, localAS: 200, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 200, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv4, peerAS: 200, isV4: true}, connInternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv4, peerAS: 200, isV4: true}, connInternal, 2), }, { name: "Establish iBGP connection between ATE (4-byte) - DUT (4-byte < 65535) for ipv6 peers", nbr: &bgpNbr{globalAS: 300, localAS: 200, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 200, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv6, peerAS: 200, isV4: false}, connInternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 200, peerIP: dutSrc.IPv6, peerAS: 200, isV4: false}, connInternal, 4), }, { name: "Establish iBGP connection between ATE (4-byte) - DUT (4-byte) for ipv4 peers", nbr: &bgpNbr{globalAS: 300, localAS: 80000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 80000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 80000, isV4: true}, connInternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 80000, isV4: true}, connInternal, 4), }, { name: "Establish iBGP connection between ATE (4-byte) - DUT (4-byte) for ipv6 peers", nbr: &bgpNbr{globalAS: 300, localAS: 80000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: false}, dutConf: createBgpNeighbor(&bgpNbr{globalAS: 300, localAS: 80000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 80000, isV4: false}, connInternal), + ateConf: configureATE(t, &bgpNbr{globalAS: 300, localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 80000, isV4: false}, connInternal, 4), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { + ate := ondatra.ATE(t, "ate") t.Log("Clear BGP Configs on DUT") bgpClearConfig(t, dut) @@ -133,18 +144,18 @@ func TestBgpSession(t *testing.T) { fptest.LogQuery(t, "DUT BGP Config ", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) t.Log("Configure BGP on ATE") - tc.ateConf.Push(t) - tc.ateConf.StartProtocols(t) + ate.OTG().PushConfig(t, tc.ateConf) + ate.OTG().StartProtocols(t) t.Log("Verify BGP session state : ESTABLISHED") nbrPath := statePath.Neighbor(tc.nbr.peerIP) - gnmi.Await(t, dut, nbrPath.SessionState().State(), time.Second*60, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + gnmi.Await(t, dut, nbrPath.SessionState().State(), time.Second*120, oc.Bgp_Neighbor_SessionState_ESTABLISHED) t.Log("Verify BGP AS numbers") verifyPeer(t, tc.nbr, dut) t.Log("Clear BGP Configs on ATE") - tc.ateConf.StopProtocols(t) + ate.OTG().StopProtocols(t) }) } } @@ -193,25 +204,47 @@ func verifyPeer(t *testing.T, nbr *bgpNbr, dut *ondatra.DUTDevice) { } } -func configureATE(t *testing.T, ateParams *bgpNbr, connectionType string) *ondatra.ATETopology { +func configureATE(t *testing.T, ateParams *bgpNbr, connectionType string, asWidth int) gosnappi.Config { t.Helper() t.Log("Configure ATE interface") ate := ondatra.ATE(t, "ate") port1 := ate.Port(t, "port1") - topo := ate.Topology().New() - - iDut1 := topo.AddInterface(ateSrc.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateSrc.IPv4CIDR()).WithDefaultGateway(dutSrc.IPv4) - iDut1.IPv6().WithAddress(ateSrc.IPv6CIDR()).WithDefaultGateway(dutSrc.IPv6) - - bgpDut1 := iDut1.BGP() - - peer := bgpDut1.AddPeer().WithPeerAddress(ateParams.peerIP).WithLocalASN(ateParams.localAS) - if connectionType == connInternal { - peer.WithTypeInternal() + topo := gosnappi.NewConfig() + + topo.Ports().Add().SetName(port1.ID()) + srcDev := topo.Devices().Add().SetName(ateSrc.Name) + srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) + srcEth.Connection().SetPortName(port1.ID()) + srcIpv4 := srcEth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4") + srcIpv4.SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) + srcIpv6 := srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6") + srcIpv6.SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) + + srcBgp := srcDev.Bgp().SetRouterId(srcIpv4.Address()) + if ateParams.isV4 { + srcBgpPeer := srcBgp.Ipv4Interfaces().Add().SetIpv4Name(srcIpv4.Name()).Peers().Add().SetName(ateSrc.Name + ".BGP4.peer") + srcBgpPeer.SetPeerAddress(ateParams.peerIP).SetAsNumber(ateParams.localAS) + if connectionType == connInternal { + srcBgpPeer.SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + } else { + srcBgpPeer.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + } + if asWidth == 2 { + srcBgpPeer.SetAsNumberWidth(gosnappi.BgpV4PeerAsNumberWidth.TWO) + } } else { - peer.WithTypeExternal() + srcBgpPeer := srcBgp.Ipv6Interfaces().Add().SetIpv6Name(srcIpv6.Name()).Peers().Add().SetName(ateSrc.Name + ".BGP6.peer") + srcBgpPeer.SetPeerAddress(ateParams.peerIP).SetAsNumber(ateParams.localAS) + if connectionType == connInternal { + srcBgpPeer.SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + } else { + srcBgpPeer.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + } + if asWidth == 2 { + srcBgpPeer.SetAsNumberWidth(gosnappi.BgpV6PeerAsNumberWidth.TWO) + } } + return topo } @@ -224,6 +257,7 @@ func createBgpNeighbor(nbr *bgpNbr, dut *ondatra.DUTDevice) *oc.NetworkInstance_ global := bgp.GetOrCreateGlobal() global.As = ygot.Uint32(nbr.globalAS) global.RouterId = ygot.String(dutSrc.IPv4) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) pg := bgp.GetOrCreatePeerGroup("ATE") pg.PeerAs = ygot.Uint32(nbr.peerAS) diff --git a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/metadata.textproto b/feature/bgp/otg_tests/bgp_2byte_4byte_asn/metadata.textproto similarity index 77% rename from feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/metadata.textproto rename to feature/bgp/otg_tests/bgp_2byte_4byte_asn/metadata.textproto index 4113b28d18c..18a90886ebd 100644 --- a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_2byte_4byte_asn/metadata.textproto @@ -12,9 +12,18 @@ platform_exceptions: { deviations: { route_policy_under_afi_unsupported: true omit_l2_mtu: true + network_instance_table_deletion_required: true interface_enabled: true default_network_instance: "default" - network_instance_table_deletion_required: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + explicit_interface_in_default_vrf: true } } tags: TAGS_AGGREGATION diff --git a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/README.md b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/README.md similarity index 91% rename from feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/README.md rename to feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/README.md index 51d125b5eb9..2864d7d9409 100644 --- a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/README.md +++ b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/README.md @@ -32,3 +32,13 @@ BGP 2-Byte and 4-Byte ASN support with policy * /global/config/as * /neighbors/neighbor/config/peer-as * /neighbors/neighbor/config/local-as + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + gNMI.Subscribe: +``` \ No newline at end of file diff --git a/feature/experimental/bgp/ate_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 similarity index 75% rename from feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go rename to feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go index edfaa80babb..a90d728ebc8 100644 --- a/feature/experimental/bgp/ate_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 @@ -17,9 +17,12 @@ package bgp_2byte_4byte_asn_with_policy_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/deviations" "github.com/openconfig/featureprofiles/internal/fptest" @@ -60,6 +63,7 @@ var ( } ateSrc = attrs.Attributes{ Name: "ateSrc", + MAC: "02:11:01:00:01:01", IPv4: "192.0.2.2", IPv6: "2001:db8::192:0:2:2", IPv4Len: 30, @@ -77,6 +81,11 @@ func TestMain(m *testing.M) { fptest.RunTests(m) } func TestBgpSession(t *testing.T) { + t.Log("Clear ATE configuration") + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + ate.OTG().PushConfig(t, top) + t.Log("Configure DUT interface") dut := ondatra.DUT(t, "dut") dc := gnmi.OC() @@ -94,53 +103,54 @@ func TestBgpSession(t *testing.T) { name string nbr *bgpNbr dutConf *oc.NetworkInstance_Protocol - ateConf *ondatra.ATETopology + ateConf gosnappi.Config }{ { name: "Establish eBGP connection between ATE (2-byte) - DUT (4-byte < 65535) for ipv4 peers", nbr: &bgpNbr{localAS: 100, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 100, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv4, peerAS: 100, isV4: true}, connExternal, prefixV4), + ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv4, peerAS: 100, isV4: true}, connExternal, 2), }, { name: "Establish eBGP connection between ATE (2-byte) - DUT (4-byte < 65535) for ipv6 peers", nbr: &bgpNbr{localAS: 100, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 100, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv6, peerAS: 100, isV4: false}, connExternal, prefixV6), + ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv6, peerAS: 100, isV4: false}, connExternal, 2), }, { name: "Establish eBGP connection between ATE (4-byte) - DUT (4-byte) for ipv4 peers", nbr: &bgpNbr{localAS: 70000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 70000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 70000, isV4: true}, connExternal, prefixV4), + ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 70000, isV4: true}, connExternal, 4), }, { name: "Establish eBGP connection between ATE (4-byte) - DUT (4-byte) for ipv6 peers", nbr: &bgpNbr{localAS: 70000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: false}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 70000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 70000, isV4: false}, connExternal, prefixV6), + ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 70000, isV4: false}, connExternal, 4), }, { name: "Establish iBGP connection between ATE (2-byte) - DUT (4-byte < 65535) for ipv4 peers", nbr: &bgpNbr{localAS: 200, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 200, peerIP: ateSrc.IPv4, peerAS: 200, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv4, peerAS: 200, isV4: true}, connInternal, prefixV4), + ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv4, peerAS: 200, isV4: true}, connInternal, 2), }, { name: "Establish iBGP connection between ATE (4-byte) - DUT (4-byte < 65535) for ipv6 peers", nbr: &bgpNbr{localAS: 200, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 200, peerIP: ateSrc.IPv6, peerAS: 200, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv6, peerAS: 200, isV4: false}, connInternal, prefixV6), + ateConf: configureATE(t, &bgpNbr{localAS: 200, peerIP: dutSrc.IPv6, peerAS: 200, isV4: false}, connInternal, 4), }, { name: "Establish iBGP connection between ATE (4-byte) - DUT (4-byte) for ipv4 peers", nbr: &bgpNbr{localAS: 80000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 80000, peerIP: ateSrc.IPv4, peerAS: 80000, isV4: true}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 80000, isV4: true}, connInternal, prefixV4), + ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv4, peerAS: 80000, isV4: true}, connInternal, 4), }, { name: "Establish iBGP connection between ATE (4-byte) - DUT (4-byte) for ipv6 peers", nbr: &bgpNbr{localAS: 80000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: false}, dutConf: createBgpNeighbor(&bgpNbr{localAS: 80000, peerIP: ateSrc.IPv6, peerAS: 80000, isV4: false}, dut), - ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 80000, isV4: false}, connInternal, prefixV6), + ateConf: configureATE(t, &bgpNbr{localAS: 80000, peerIP: dutSrc.IPv6, peerAS: 80000, isV4: false}, connInternal, 4), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { + otg := ondatra.ATE(t, "ate").OTG() t.Log("Clear BGP Configs on DUT") bgpClearConfig(t, dut) @@ -155,8 +165,8 @@ func TestBgpSession(t *testing.T) { fptest.LogQuery(t, "DUT BGP Config ", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) t.Log("Configure BGP on ATE") - tc.ateConf.Push(t) - tc.ateConf.StartProtocols(t) + otg.PushConfig(t, tc.ateConf) + otg.StartProtocols(t) t.Log("Verify BGP session state : ESTABLISHED") nbrPath := statePath.Neighbor(tc.nbr.peerIP) @@ -185,7 +195,7 @@ func TestBgpSession(t *testing.T) { verifyPrefixesTelemetry(t, dut, 2, tc.nbr.isV4) t.Log("Clear BGP Configs on ATE") - tc.ateConf.StopProtocols(t) + otg.StopProtocols(t) }) } } @@ -217,6 +227,9 @@ func juniperCLI() string { from as-path match-as-path; then reject; } + term term2 { + then accept; + } } as-path match-as-path ".* 4400 3300"; }`, rejectAspath) @@ -374,52 +387,101 @@ func verifyPeer(t *testing.T, nbr *bgpNbr, dut *ondatra.DUTDevice) { verifyPrefixesTelemetry(t, dut, 3, nbr.isV4) } -func configureATE(t *testing.T, ateParams *bgpNbr, connectionType string, prefixes []string) *ondatra.ATETopology { +func configureATE(t *testing.T, ateParams *bgpNbr, connectionType string, asWidth int) gosnappi.Config { t.Helper() - t.Log("Configure ATE interface") + t.Log("Create otg configuration") ate := ondatra.ATE(t, "ate") port1 := ate.Port(t, "port1") - topo := ate.Topology().New() - - iDut1 := topo.AddInterface(ateSrc.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateSrc.IPv4CIDR()).WithDefaultGateway(dutSrc.IPv4) - iDut1.IPv6().WithAddress(ateSrc.IPv6CIDR()).WithDefaultGateway(dutSrc.IPv6) - - bgpDut1 := iDut1.BGP() - peer := bgpDut1.AddPeer().WithPeerAddress(ateParams.peerIP).WithLocalASN(ateParams.localAS) - - if connectionType == connInternal { - peer.WithTypeInternal() - } else { - peer.WithTypeExternal() - } - - network1 := iDut1.AddNetwork("bgpNeti1") - network2 := iDut1.AddNetwork("bgpNeti2") - network3 := iDut1.AddNetwork("bgpNeti3") - + config := gosnappi.NewConfig() + + config.Ports().Add().SetName(port1.ID()) + srcDev := config.Devices().Add().SetName(ateSrc.Name) + srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) + srcEth.Connection().SetPortName(port1.ID()) + srcIpv4 := srcEth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4") + srcIpv4.SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) + srcIpv6 := srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6") + srcIpv6.SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) + + srcBgp := srcDev.Bgp().SetRouterId(srcIpv4.Address()) if ateParams.isV4 { - network1.IPv4().WithAddress(prefixes[0]).WithCount(1) - network1.BGP().WithNextHopAddress(ateSrc.IPv4).AddASPathSegment(55000, 4400, 3300) - - network2.IPv4().WithAddress(prefixes[1]).WithCount(1) - network2.BGP().WithNextHopAddress(ateSrc.IPv4).AddASPathSegment(55000, 7700) - network2.BGP().Communities().WithPrivateCommunities("200:1") + srcBgpPeer := srcBgp.Ipv4Interfaces().Add().SetIpv4Name(srcIpv4.Name()).Peers().Add().SetName(ateSrc.Name + ".BGP4.peer") + srcBgpPeer.SetPeerAddress(ateParams.peerIP).SetAsNumber(ateParams.localAS) + if connectionType == connInternal { + srcBgpPeer.SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + } else { + srcBgpPeer.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + } + if asWidth == 2 { + srcBgpPeer.SetAsNumberWidth(gosnappi.BgpV4PeerAsNumberWidth.TWO) + } + subnetAddr1, subnetLen1 := prefixAndLen(prefixV4[0]) + subnetAddr2, subnetLen2 := prefixAndLen(prefixV4[1]) + subnetAddr3, subnetLen3 := prefixAndLen(prefixV4[2]) + + network1 := srcBgpPeer.V4Routes().Add().SetName("bgpNeti1") + network1.SetNextHopIpv4Address(ateSrc.IPv4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + network1.Addresses().Add().SetAddress(subnetAddr1).SetPrefix(subnetLen1).SetCount(1) + network1.AsPath().Segments().Add().SetAsNumbers([]uint32{55000, 4400, 3300}) + + network2 := srcBgpPeer.V4Routes().Add().SetName("bgpNeti2") + network2.SetNextHopIpv4Address(ateSrc.IPv4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + network2.Addresses().Add().SetAddress(subnetAddr2).SetPrefix(subnetLen2).SetCount(1) + network2.AsPath().Segments().Add().SetAsNumbers([]uint32{55000, 7700}) + network2.Communities().Add().SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER).SetAsNumber(200).SetAsCustom(1) + + network3 := srcBgpPeer.V4Routes().Add().SetName("bgpNeti3") + network3.SetNextHopIpv4Address(ateSrc.IPv4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + network3.Addresses().Add().SetAddress(subnetAddr3).SetPrefix(subnetLen3).SetCount(1) - network3.IPv4().WithAddress(prefixes[2]).WithCount(1) - network3.BGP().WithNextHopAddress(ateSrc.IPv4) } else { - network1.IPv6().WithAddress(prefixes[0]).WithCount(1) - network1.BGP().WithNextHopAddress(ateSrc.IPv6).AddASPathSegment(55000, 4400, 3300) - - network2.IPv6().WithAddress(prefixes[1]).WithCount(1) - network2.BGP().WithNextHopAddress(ateSrc.IPv6).AddASPathSegment(55000, 7700) - network2.BGP().Communities().WithPrivateCommunities("200:1") + srcBgpPeer := srcBgp.Ipv6Interfaces().Add().SetIpv6Name(srcIpv6.Name()).Peers().Add().SetName(ateSrc.Name + ".BGP6.peer") + srcBgpPeer.SetPeerAddress(ateParams.peerIP).SetAsNumber(ateParams.localAS) + if connectionType == connInternal { + srcBgpPeer.SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + } else { + srcBgpPeer.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + } + if asWidth == 2 { + srcBgpPeer.SetAsNumberWidth(gosnappi.BgpV6PeerAsNumberWidth.TWO) + } + prefixArr1 := strings.Split(prefixV6[0], "/") + mask1, _ := strconv.Atoi(prefixArr1[1]) + prefixArr2 := strings.Split(prefixV6[1], "/") + mask2, _ := strconv.Atoi(prefixArr2[1]) + prefixArr3 := strings.Split(prefixV6[2], "/") + mask3, _ := strconv.Atoi(prefixArr3[1]) + + network1 := srcBgpPeer.V6Routes().Add().SetName("bgpNeti1") + network1.SetNextHopIpv6Address(ateSrc.IPv6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + network1.Addresses().Add().SetAddress(prefixArr1[0]).SetPrefix(uint32(mask1)).SetCount(1) + network1.AsPath().Segments().Add().SetAsNumbers([]uint32{55000, 4400, 3300}) + + network2 := srcBgpPeer.V6Routes().Add().SetName("bgpNeti2") + network2.SetNextHopIpv6Address(ateSrc.IPv6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + network2.Addresses().Add().SetAddress(prefixArr2[0]).SetPrefix(uint32(mask2)).SetCount(1) + network2.AsPath().Segments().Add().SetAsNumbers([]uint32{55000, 7700}) + network2.Communities().Add().SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER).SetAsNumber(200).SetAsCustom(1) + + network3 := srcBgpPeer.V6Routes().Add().SetName("bgpNeti3") + network3.SetNextHopIpv6Address(ateSrc.IPv6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + network3.Addresses().Add().SetAddress(prefixArr3[0]).SetPrefix(uint32(mask3)).SetCount(1) - network3.IPv6().WithAddress(prefixes[2]).WithCount(1) - network3.BGP().WithNextHopAddress(ateSrc.IPv6) } - return topo + + return config } func applyBgpPolicy(policyName string, dut *ondatra.DUTDevice, isV4 bool) *oc.NetworkInstance_Protocol { @@ -494,3 +556,10 @@ func createBgpNeighbor(nbr *bgpNbr, dut *ondatra.DUTDevice) *oc.NetworkInstance_ } return niProto } + +func prefixAndLen(prefix string) (string, uint32) { + subnetAddr := strings.Split(prefix, "/")[0] + len, _ := strconv.Atoi(strings.Split(prefix, "/")[1]) + subnetLen := uint32(len) + return subnetAddr, subnetLen +} diff --git a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto similarity index 95% rename from feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto rename to feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto index bc335a7ac99..eaae52b496a 100644 --- a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto @@ -18,7 +18,6 @@ platform_exceptions: { vendor: NOKIA } deviations: { - use_vendor_native_acl_config: true explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true diff --git a/feature/bgp/otg_tests/bgp_afi_safi_defaults/README.md b/feature/bgp/otg_tests/bgp_afi_safi_defaults/README.md new file mode 100644 index 00000000000..10037d7171c --- /dev/null +++ b/feature/bgp/otg_tests/bgp_afi_safi_defaults/README.md @@ -0,0 +1,122 @@ +# RT-1.23: BGP AFI SAFI OC DEFAULTS + +## Summary + +BGP AFI SAFI OC DEFAULTS TEST + +## Procedure + +* 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" +* For BGP neighbor level extended-next-hop encoding to be configured and validated too +* 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) + +* [Test case-1.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. + * Ensure that there is extended-next-hop encoding feature is configured via OC path and the + default value of this should be set to false + + * 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-1.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-1.3] 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. + +* [Test case-2] IPv4-unicast and IPv6-Unicast AFI-SAFIs set to FALSE at neighbor level: + + * AFI-SAFI for IPv4-UNICAST is set to false for BGPv4 peers and AFI-SAFI for IPv6-UNICAST is set to + false for BGPv6 peers. + + * Verification: + * For IPv4 neighbor, ensure that the IPv4 neighborship is not ESTABLISHED and + IPv4-unicast capabilities are set to FALSE. + * For IPv6 neighbor ensure that the IPv6 neighborship is not ESTABLISHED and + IPv6-unicast capabilities are set to FALSE. + + +## 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 Parameter coverage + + /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/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/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding: + + ## Telemetry Parameter coverage + + /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/neighbors/neighbor/state/peer-type: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-as: + /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/global/afi-safis/afi-safi/ipv4-unicast/state/extended-next-hop-encoding: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + gNMI.Subscribe: +``` +## Minimum DUT Required + +vRX - Virtual Router Device diff --git a/feature/bgp/otg_tests/bgp_afi_safi_defaults/bgp_afi_safi_defaults_test.go b/feature/bgp/otg_tests/bgp_afi_safi_defaults/bgp_afi_safi_defaults_test.go new file mode 100644 index 00000000000..0898f0285e2 --- /dev/null +++ b/feature/bgp/otg_tests/bgp_afi_safi_defaults/bgp_afi_safi_defaults_test.go @@ -0,0 +1,599 @@ +// 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 bgp_afi_safi_defaults_test + +import ( + "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/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/gnmi/oc/netinstbgp" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + otg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + trafficDuration = 1 * time.Minute + ipv4SrcTraffic = "192.0.2.2" + advertisedRoutesv4CIDR = "203.0.113.1/32" + advertisedRoutesv4Net = "203.0.113.1" + advertisedRoutesv4Prefix = 32 + ipv4DstTrafficStart = "203.0.113.1" + ipv4DstTrafficEnd = "203.0.113.254" + peerGrpName1 = "BGP-PEER-GROUP1" + peerGrpName2 = "BGP-PEER-GROUP2" + tolerancePct = 2 + tolerance = 50 + routeCount = 254 + dutAS = 65501 + ateAS = 65502 + plenIPv4 = 30 + plenIPv6 = 126 + nbrLevel = "NEIGHBOR" + peerGrpLevel = "PEER-GROUP" + globalLevel = "GLOBAL" + afiSafiSetToFalse = "AFISAFI-SET-TO-FALSE" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE Port1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + MAC: "02:00:01:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE Port2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + IPv6: "2001:db8::192:0:2:6", + MAC: "02:00:02:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + nbr1 = &bgpNeighbor{as: ateAS, neighborip: atePort1.IPv4, isV4: true, peerGrp: peerGrpName1} + nbr2 = &bgpNeighbor{as: ateAS, neighborip: atePort1.IPv6, isV4: false, peerGrp: peerGrpName2} + nbr3 = &bgpNeighbor{as: dutAS, neighborip: atePort2.IPv4, isV4: true, peerGrp: peerGrpName1} + nbr4 = &bgpNeighbor{as: dutAS, neighborip: atePort2.IPv6, isV4: false, peerGrp: peerGrpName2} + + otgPort1V4Peer = "atePort1.BGP4.peer" + otgPort1V6Peer = "atePort1.BGP6.peer" + otgPort2V4Peer = "atePort2.BGP4.peer" + otgPort2V6Peer = "atePort2.BGP6.peer" +) + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) +} + +// 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) + } + } +} + +// bgpCreateNbr creates a BGP object with neighbors pointing to ateSrc and ateDst. +func bgpCreateNbr(t *testing.T, localAs, peerAs uint32, dut *ondatra.DUTDevice, afiSafiLevel string, nbrs []*bgpNeighbor, isV4Only bool) *oc.NetworkInstance_Protocol { + t.Helper() + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutPort2.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) + + // 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. + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) // V4 peer group + pg1.PeerAs = ygot.Uint32(ateAS) + pg1.PeerGroupName = ygot.String(peerGrpName1) + + pg2 := bgp.GetOrCreatePeerGroup(peerGrpName2) // V6 peer group + pg2.PeerAs = ygot.Uint32(dutAS) + pg2.PeerGroupName = ygot.String(peerGrpName2) + + for _, nbr := range nbrs { + nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.PeerGroup = ygot.String(nbr.peerGrp) + nv4.PeerAs = ygot.Uint32(nbr.as) + nv4.Enabled = ygot.Bool(true) + + switch afiSafiLevel { + case globalLevel: + pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + if isV4Only { + 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(false) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + } else { + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + } + if deviations.BGPGlobalExtendedNextHopEncodingUnsupported(dut) { + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast = nil + } + case nbrLevel: + if isV4Only { + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + extNh := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateIpv4Unicast() + if !deviations.BgpExtendedNextHopEncodingLeafUnsupported(dut) { + extNh.ExtendedNextHopEncoding = ygot.Bool(true) + } + } else { + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + } + if deviations.BGPGlobalExtendedNextHopEncodingUnsupported(dut) { + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast = nil + } + case peerGrpLevel: + if isV4Only { + pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + } else { + pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + } + case afiSafiSetToFalse: + t.Log("AFI-SAFI is set to false") + if isV4Only { + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + } else { + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + } + } + } + return niProto +} + +func verifyOtgBgpTelemetry(t *testing.T, otg *otg.OTG, c gosnappi.Config, otgPeerList []string, state string) { + t.Helper() + for _, configPeer := range otgPeerList { + nbrPath := gnmi.OTG().BgpPeer(configPeer) + _, ok := gnmi.Watch(t, otg, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + currState, ok := val.Val() + return ok && currState.String() == state + }).Await(t) + if !ok { + t.Errorf("No BGP neighbor formed for peer %s", configPeer) + } + } +} + +// verifyBgpTelemetry checks that the dut has an established BGP session with reasonable settings. +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbrsList []*bgpNeighbor) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + for _, nbr := range nbrsList { + nbrPath := bgpPath.Neighbor(nbr.neighborip) + t.Logf("Waiting for BGP neighbor to establish...") + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", nbr.neighborip, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", nbr.neighborip, state, want) + } + } +} + +// verifyBgpSession checks BGP session state. +func verifyBgpSession(t *testing.T, dut *ondatra.DUTDevice, nbrsList []*bgpNeighbor) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + for _, nbr := range nbrsList { + nbrPath := bgpPath.Neighbor(nbr.neighborip) + state := gnmi.Get(t, dut, nbrPath.SessionState().State()) + t.Logf("BGP adjacency for %s: %v", nbr.neighborip, state) + if state == oc.Bgp_Neighbor_SessionState_ESTABLISHED { + t.Errorf("BGP peer %s status got %d, want other than ESTABLISHED", nbr.neighborip, state) + } else { + t.Logf("BGP peer %s status got %d, want other than ESTABLISHED", nbr.neighborip, state) + } + } +} + +// configureOTG configures the interfaces and BGP protocols on an ATE, including +// advertising some(faked) networks over BGP. +func configureOTG(t *testing.T, otg *otg.OTG, otgPeerList []string) gosnappi.Config { + t.Helper() + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + iDut1Bgp := iDut1Dev.Bgp().SetRouterId(iDut1Ipv4.Address()) + iDut2Bgp := iDut2Dev.Bgp().SetRouterId(iDut2Ipv4.Address()) + + // BGP seesion + for _, peer := range otgPeerList { + switch peer { + case otgPort1V4Peer: + iDut1Bgp4Peer := iDut1Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut1Ipv4.Name()).Peers().Add().SetName(otgPort1V4Peer) + iDut1Bgp4Peer.SetPeerAddress(iDut1Ipv4.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + case otgPort1V6Peer: + iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(otgPort1V6Peer) + iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut1Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true).SetExtendedNextHopEncoding(true) + iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + case otgPort2V4Peer: + iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2Ipv4.Name()).Peers().Add().SetName(otgPort2V4Peer) + iDut2Bgp4Peer.SetPeerAddress(iDut2Ipv4.Gateway()).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDut2Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + case otgPort2V6Peer: + iDut2Bgp6Peer := iDut2Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut2Ipv6.Name()).Peers().Add().SetName(otgPort2V6Peer) + iDut2Bgp6Peer.SetPeerAddress(iDut2Ipv6.Gateway()).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + iDut2Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true).SetExtendedNextHopEncoding(true) + iDut2Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + } + } + + t.Logf("Pushing config to OTG and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + + return config +} + +// verifyBGPCapabilities is used to Verify BGP capabilities like route refresh as32 and mpbgp. +func verifyBgpCapabilities(t *testing.T, dut *ondatra.DUTDevice, afiSafiLevel string, nbrs []*bgpNeighbor, isV4Only bool) { + t.Helper() + t.Log("Verifying BGP AFI-SAFI capabilities.") + + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + var nbrPath *netinstbgp.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafiPathAny + + for _, nbr := range nbrs { + nbrPath = statePath.Neighbor(nbr.neighborip).AfiSafiAny() + + capabilities := map[oc.E_BgpTypes_AFI_SAFI_TYPE]bool{ + oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST: false, + oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST: false, + } + if deviations.BgpAfiSafiWildcardNotSupported(dut) { + t.Logf("AFI-SAFI wildcard/getall not supported") + afiSafiType := []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST} + afiSafiList := []*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi{} + for _, afiSafi := range afiSafiType { + if nbr.isV4 && afiSafi == oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST { + continue + } + if !nbr.isV4 && afiSafi == oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST { + continue + } + t.Logf("AFI-SAFI state: %v", gnmi.Get(t, dut, statePath.Neighbor(nbr.neighborip).AfiSafi(afiSafi).State())) + afiSafiList = append(afiSafiList, gnmi.Get(t, dut, statePath.Neighbor(nbr.neighborip).AfiSafi(afiSafi).State())) + t.Logf("AFI-SAFI list: %v", afiSafiList) + } + for _, cap := range afiSafiList { + capabilities[cap.GetAfiSafiName()] = cap.GetActive() + } + t.Logf("Capabilities for peer %v are %v", nbr.neighborip, capabilities) + } else { + gnmi.GetAll(t, dut, nbrPath.State()) + for _, cap := range gnmi.GetAll(t, dut, nbrPath.State()) { + capabilities[cap.GetAfiSafiName()] = cap.GetActive() + } + t.Logf("Capabilities for peer %v are %v", nbr.neighborip, capabilities) + } + switch afiSafiLevel { + case nbrLevel: + if nbr.isV4 && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST] { + t.Errorf("AFI_SAFI_TYPE_IPV6_UNICAST should not be enabled for v4 Peer: %v, %v", capabilities, nbr.neighborip) + } + if !nbr.isV4 && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST] { + t.Errorf("AFI_SAFI_TYPE_IPV4_UNICAST should not be for v6 Peer: %v, %v", capabilities, nbr.neighborip) + } + t.Logf("Capabilities for peer %v are %v", nbr.neighborip, capabilities) + case peerGrpLevel: + if isV4Only && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST] == true { + t.Logf("Both V4 and V6 AFI-SAFI are inherited from peer-group level for peer: %v, %v", nbr.neighborip, capabilities) + } else if !isV4Only && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST] == true { + t.Logf("Both V4 and V6 AFI-SAFI are inherited from peer-group level for peer: %v, %v", nbr.neighborip, capabilities) + } else { + t.Errorf("Both V4 and V6 AFI-SAFI are not inherited from peer-group level for peer: %v, %v", nbr.neighborip, capabilities) + } + t.Logf("Capabilities for peer %v are %v", nbr.neighborip, capabilities) + case globalLevel: + if isV4Only && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST] == true { + t.Logf("Both V4 and V6 AFI-SAFI are inherited from global level for peer: %v, %v", nbr.neighborip, capabilities) + } else if !isV4Only && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST] == true { + t.Logf("Both V4 and V6 AFI-SAFI are inherited from global level for peer: %v, %v", nbr.neighborip, capabilities) + } else { + t.Errorf("Both V4 and V6 AFI-SAFI are not inherited from global level for peer: %v, %v", nbr.neighborip, capabilities) + } + t.Logf("Capabilities for peer %v are %v", nbr.neighborip, capabilities) + case afiSafiSetToFalse: + t.Logf("afiSafiSetToFalse capabilities: %v, v4 -> %v, v6 ->%v", isV4Only, capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST], capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST]) + if nbr.isV4 && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST] == true { + t.Errorf("AFI-SAFI are Active after disabling: %v, %v", capabilities, nbr.neighborip) + } + if !nbr.isV4 && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST] == true { + t.Errorf("AFI-SAFI are Active after disabling: %v, %v", capabilities, nbr.neighborip) + } + } + } +} + +// 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) +} + +type bgpNeighbor struct { + as uint32 + neighborip string + isV4 bool + peerGrp string +} + +// TestAfiSafiOcDefaults validates AFI-SAFI configuration enabled at neighbor, +// peer group and global levels. +func TestAfiSafiOcDefaults(t *testing.T) { + t.Logf("Start DUT config load.") + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + t.Run("Configure DUT interfaces", func(t *testing.T) { + configureDUT(t, dut) + }) + + t.Run("Configure DEFAULT network instance", func(t *testing.T) { + dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + name := deviations.DefaultNetworkInstance(dut) + c := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + gnmi.Update(t, dut, c.Config(), &oc.NetworkInstance{ + Name: ygot.String(name), + }) + gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + }) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + cases := []struct { + desc string + afiSafiLevel string + nbrs []*bgpNeighbor + isV4Only bool + otgPeerList []string + }{{ + desc: "Validate AFI-SAFI OC defaults at neighbor level for BGPv4 peers", + afiSafiLevel: nbrLevel, + nbrs: []*bgpNeighbor{nbr1, nbr3}, + isV4Only: true, + otgPeerList: []string{otgPort1V4Peer, otgPort2V4Peer}, + }, { + desc: "Validate AFI-SAFI OC defaults at peer group level for BGPv4 peers", + afiSafiLevel: peerGrpLevel, + nbrs: []*bgpNeighbor{nbr1, nbr3}, + isV4Only: true, + otgPeerList: []string{otgPort1V4Peer, otgPort2V4Peer}, + }, { + desc: "Validate AFI-SAFI OC defaults at global level for V4 peers", + afiSafiLevel: globalLevel, + nbrs: []*bgpNeighbor{nbr1, nbr3}, + isV4Only: true, + otgPeerList: []string{otgPort1V4Peer, otgPort2V4Peer}, + }, { + desc: "Validate AFI-SAFI OC defaults at neighbor level for BGPv6 peers", + afiSafiLevel: nbrLevel, + nbrs: []*bgpNeighbor{nbr2, nbr4}, + isV4Only: false, + otgPeerList: []string{otgPort1V6Peer, otgPort2V6Peer}, + }, { + desc: "Validate AFI-SAFI OC defaults at peer group level for BGPv6 peers", + afiSafiLevel: peerGrpLevel, + nbrs: []*bgpNeighbor{nbr2, nbr4}, + isV4Only: false, + otgPeerList: []string{otgPort1V6Peer, otgPort2V6Peer}, + }, { + desc: "Validate AFI-SAFI OC defaults at global level for BGPv6 peers", + afiSafiLevel: globalLevel, + nbrs: []*bgpNeighbor{nbr2, nbr4}, + isV4Only: false, + otgPeerList: []string{otgPort1V6Peer, otgPort2V6Peer}, + }} + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + + t.Run("Configure BGP Neighbors", func(t *testing.T) { + bgpClearConfig(t, dut) + dutConf := bgpCreateNbr(t, dutAS, ateAS, dut, tc.afiSafiLevel, tc.nbrs, tc.isV4Only) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + }) + + otg := ate.OTG() + var otgConfig gosnappi.Config + t.Run("Configure OTG", func(t *testing.T) { + otgConfig = configureOTG(t, otg, tc.otgPeerList) + }) + + t.Run("Verify port status on DUT", func(t *testing.T) { + verifyPortsUp(t, dut.Device) + }) + + t.Run("Verify BGP telemetry", func(t *testing.T) { + verifyBgpTelemetry(t, dut, tc.nbrs) + verifyOtgBgpTelemetry(t, otg, otgConfig, tc.otgPeerList, "ESTABLISHED") + verifyBgpCapabilities(t, dut, tc.afiSafiLevel, tc.nbrs, tc.isV4Only) + }) + }) + } +} + +// TestAfiSafiSetToFalse validates AFI-SAFI configuration is set to false. +func TestAfiSafiSetToFalse(t *testing.T) { + t.Logf("Start DUT config load.") + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + if deviations.SkipBgpSessionCheckWithoutAfisafi(dut) { + t.Skip("Skip test BGP when AFI-SAFI is disabled...") + } + + t.Run("Configure DUT interfaces", func(t *testing.T) { + configureDUT(t, dut) + }) + + t.Run("Configure DEFAULT network instance", func(t *testing.T) { + dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + }) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + cases := []struct { + desc string + afiSafiLevel string + nbrs []*bgpNeighbor + isV4Only bool + otgPeerList []string + }{{ + desc: "Validate AFI-SAFI Not enabled at any level for BGPv4 peers", + afiSafiLevel: afiSafiSetToFalse, + nbrs: []*bgpNeighbor{nbr1, nbr3}, + isV4Only: true, + otgPeerList: []string{otgPort1V4Peer, otgPort2V4Peer}, + }, { + desc: "Validate AFI-SAFI Not enabled at any level for BGPv6 peers", + afiSafiLevel: afiSafiSetToFalse, + nbrs: []*bgpNeighbor{nbr2, nbr4}, + isV4Only: false, + otgPeerList: []string{otgPort1V6Peer, otgPort2V6Peer}, + }} + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + + t.Run("Configure BGP Neighbors", func(t *testing.T) { + bgpClearConfig(t, dut) + dutConf := bgpCreateNbr(t, dutAS, ateAS, dut, tc.afiSafiLevel, tc.nbrs, tc.isV4Only) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + }) + + otg := ate.OTG() + t.Run("Configure OTG", func(t *testing.T) { + configureOTG(t, otg, tc.otgPeerList) + }) + t.Logf("Verify BGP session on DUT") + time.Sleep(60 * time.Second) + + t.Run("Verify BGP session", func(t *testing.T) { + verifyBgpSession(t, dut, tc.nbrs) + }) + t.Run("Verify BGP capabilities", func(t *testing.T) { + verifyBgpCapabilities(t, dut, tc.afiSafiLevel, tc.nbrs, tc.isV4Only) + }) + }) + } +} diff --git a/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/metadata.textproto b/feature/bgp/otg_tests/bgp_afi_safi_defaults/metadata.textproto similarity index 77% rename from feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/metadata.textproto rename to feature/bgp/otg_tests/bgp_afi_safi_defaults/metadata.textproto index fd58d64d3ee..070b871264d 100644 --- a/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_afi_safi_defaults/metadata.textproto @@ -11,6 +11,16 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + bgp_extended_next_hop_encoding_leaf_unsupported: true + bgp_afi_safi_wildcard_not_supported: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + skip_bgp_session_check_without_afisafi: true } } platform_exceptions: { @@ -28,9 +38,9 @@ platform_exceptions: { deviations: { route_policy_under_afi_unsupported: true omit_l2_mtu: true + network_instance_table_deletion_required: true interface_enabled: true default_network_instance: "default" - network_instance_table_deletion_required: true bgp_global_extended_next_hop_encoding_unsupported: true } } diff --git a/feature/experimental/bgp/otg_tests/bgp_always_compare_med/README.md b/feature/bgp/otg_tests/bgp_always_compare_med/README.md similarity index 57% rename from feature/experimental/bgp/otg_tests/bgp_always_compare_med/README.md rename to feature/bgp/otg_tests/bgp_always_compare_med/README.md index 91fdcdad95e..d239bc302e9 100644 --- a/feature/experimental/bgp/otg_tests/bgp_always_compare_med/README.md +++ b/feature/bgp/otg_tests/bgp_always_compare_med/README.md @@ -18,20 +18,22 @@ BGP always compare MED * Validate the change of traffic flow because of the change (OTG Port2). * Validate session state and capabilities received on DUT using telemetry. -## Config Parameter coverage - -* /route-selection-options/config/always-compare-med -* /global/afi-safis/afi-safi/route-selection-options/config/always-compare-med -* /global/route-selection-options/config/always-compare-med - -## Telemetry Parameter coverage - -* /global/afi-safis/afi-safi/route-selection-options/state/always-compare-med -* /global/route-selection-options/state/always-compare-med - -## Protocol/RPC Parameter coverage - -N/A +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/config/always-compare-med: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/route-selection-options/config/always-compare-med: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/state/always-compare-med: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/route-selection-options/state/always-compare-med: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/bgp/otg_tests/bgp_always_compare_med/bgp_always_compare_med_test.go b/feature/bgp/otg_tests/bgp_always_compare_med/bgp_always_compare_med_test.go similarity index 98% rename from feature/experimental/bgp/otg_tests/bgp_always_compare_med/bgp_always_compare_med_test.go rename to feature/bgp/otg_tests/bgp_always_compare_med/bgp_always_compare_med_test.go index da00ed71614..a7ced348df7 100644 --- a/feature/experimental/bgp/otg_tests/bgp_always_compare_med/bgp_always_compare_med_test.go +++ b/feature/bgp/otg_tests/bgp_always_compare_med/bgp_always_compare_med_test.go @@ -288,19 +288,19 @@ func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { iDut1Dev := config.Devices().Add().SetName(ateSrc.Name) iDut1Eth := iDut1Dev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - iDut1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port1.Name()) + iDut1Eth.Connection().SetPortName(port1.Name()) iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4") iDut1Ipv4.SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) iDut2Dev := config.Devices().Add().SetName(ateDst1.Name) iDut2Eth := iDut2Dev.Ethernets().Add().SetName(ateDst1.Name + ".Eth").SetMac(ateDst1.MAC) - iDut2Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port2.Name()) + iDut2Eth.Connection().SetPortName(port2.Name()) iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(ateDst1.Name + ".IPv4") iDut2Ipv4.SetAddress(ateDst1.IPv4).SetGateway(dutDst1.IPv4).SetPrefix(uint32(ateDst1.IPv4Len)) iDut3Dev := config.Devices().Add().SetName(ateDst2.Name) iDut3Eth := iDut3Dev.Ethernets().Add().SetName(ateDst2.Name + ".Eth").SetMac(ateDst2.MAC) - iDut3Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port3.Name()) + iDut3Eth.Connection().SetPortName(port3.Name()) iDut3Ipv4 := iDut3Eth.Ipv4Addresses().Add().SetName(ateDst2.Name + ".IPv4") iDut3Ipv4.SetAddress(ateDst2.IPv4).SetGateway(dutDst2.IPv4).SetPrefix(uint32(ateDst2.IPv4Len)) @@ -345,7 +345,7 @@ func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { SetRxNames([]string{bgpNeti1Bgp4PeerRoutes.Name()}) flow1ipv4.Size().SetFixed(512) flow1ipv4.Rate().SetPps(100) - flow1ipv4.Duration().SetChoice("continuous") + flow1ipv4.Duration().Continuous() e1 := flow1ipv4.Packet().Add().Ethernet() e1.Src().SetValue(iDut1Eth.Mac()) v4 := flow1ipv4.Packet().Add().Ipv4() @@ -360,7 +360,7 @@ func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { SetRxNames([]string{bgpNeti2Bgp4PeerRoutes.Name()}) flow2ipv4.Size().SetFixed(512) flow2ipv4.Rate().SetPps(100) - flow2ipv4.Duration().SetChoice("continuous") + flow2ipv4.Duration().Continuous() e2 := flow2ipv4.Packet().Add().Ethernet() e2.Src().SetValue(iDut1Eth.Mac()) v4Flow2 := flow2ipv4.Packet().Add().Ipv4() diff --git a/feature/experimental/bgp/otg_tests/bgp_always_compare_med/metadata.textproto b/feature/bgp/otg_tests/bgp_always_compare_med/metadata.textproto similarity index 100% rename from feature/experimental/bgp/otg_tests/bgp_always_compare_med/metadata.textproto rename to feature/bgp/otg_tests/bgp_always_compare_med/metadata.textproto index a2fb22a415a..ecf4c782d29 100644 --- a/feature/experimental/bgp/otg_tests/bgp_always_compare_med/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_always_compare_med/metadata.textproto @@ -10,8 +10,8 @@ platform_exceptions: { vendor: NOKIA } deviations: { - interface_enabled: true explicit_interface_in_default_vrf: true + interface_enabled: true } } platform_exceptions: { @@ -19,8 +19,8 @@ platform_exceptions: { vendor: ARISTA } deviations: { - omit_l2_mtu: true route_policy_under_afi_unsupported: true + omit_l2_mtu: true interface_enabled: true default_network_instance: "default" bgp_set_med_requires_equal_ospf_set_metric: true diff --git a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md new file mode 100644 index 00000000000..b5a15e90e4e --- /dev/null +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md @@ -0,0 +1,71 @@ +# RT-1.54: BGP Override AS-path split-horizon + +## Summary + +BGP Override AS-path split-horizon + +## Topology + +ATE Port1 (AS 65502) --- eBGP --------- DUT Port1 (DUT Local AS 65501) +ATE Port2 (AS 65503) --- eBGP --------- DUT Port2 (DUT Local AS 64513) + +## Procedure + +* Establish BGP Session: Configure and establish an eBGP session between the DUT (Port1) and the ATE (Port1). +* +### RT-1.54.1 Test no allow-own-in +* Baseline Test (No "allow-own-in"): + * Advertise a prefix from the ATE (e.g., 192.168.1.0/24) with an AS-path that includes AS 65501 (DUT's AS) in the middle (e.g., AS-path: 65502 65500 65501 65499). + * Verify that the ATE Port2 doesn't receive the route. due to the presence of its own AS in the path. + * Validate session state and capabilities received on DUT using telemetry. + +### RT-1.54.2 Test "allow-own-as 1" +* Test "allow-own-as 1": + * Enable "allow-own-as 1" on the DUT. + * Re-advertise the prefix from the ATE with the same AS-path. + * Verify that the DUT accepts the route. + * Verify that the ATE Port2 receives the route. + * Validate session state and capabilities received on DUT using telemetry. +### RT-1.54.3 Test "allow-own-as 3" +* Test "allow-own-as 3": + * Change the DUT's configuration to "allow-own-as 3". + * Test with the following AS-path occurrences: + * 1 Occurrence: 65502 65500 65501 65499 + * 3 Occurrences: 65502 65501 65501 65501 65499 + * 4 Occurrences: 65502 65501 65501 65501 65501 65499 (Should be rejected) + * Verify that the ATE Port2 receives the route with 1 and 3 occurrences of AS 65501 but rejects it with 4 occurrences. + * Validate session state and capabilities received on DUT using telemetry. +### RT-1.54.4 Test "allow-own-as 4" +* Test "allow-own-as 4: + * Change the DUT's configuration to "allow-own-as 4". + * Test with the following AS-path occurrences: + * 1 Occurrence: 65502 65500 65501 65499 + * 3 Occurrences: 65502 65501 65501 65501 65499 + * 4 Occurrences: 65502 65501 65501 65501 65501 65499 + * Verify that the ATE Port2 receives the route with 1, 3 and 4 occurrences of AS 65501. + * Validate session state and capabilities received on DUT using telemetry. + +## OpenConfig Path and RPC Coverage + +The below example yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config paths + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/config/allow-own-as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/config/allow-own-as: + + ## State paths + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/state/allow-own-as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/state/allow-own-as: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* MFF - A modular form factor device containing LINECARDs, FABRIC and redundant CONTROLLER_CARD components +* FFF - fixed form factor 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 new file mode 100644 index 00000000000..030f0d4fa88 --- /dev/null +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go @@ -0,0 +1,505 @@ +// 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_override_as_path_split_horizon_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" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + otg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + advertisedRoutesv4CIDR = "203.0.113.1/32" + advertisedRoutesv4Net = "203.0.113.1" + advertisedRoutesv4Prefix = 32 + peerGrpName1 = "BGP-PEER-GROUP1" + peerGrpName2 = "BGP-PEER-GROUP2" + dutGlobalAS = 64512 + dutLocalAS1 = 65501 + dutLocalAS2 = 64513 + ateAS1 = 65502 + ateAS2 = 65503 + plenIPv4 = 30 + plenIPv6 = 126 + policyName = "ALLOW" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE Port1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + MAC: "02:00:01:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE Port2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + IPv6: "2001:db8::192:0:2:6", + MAC: "02:00:02:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + nbr1 = &cfgplugins.BgpNeighbor{LocalAS: dutLocalAS1, PeerAS: ateAS1, Neighborip: atePort1.IPv4, IsV4: true, PeerGrp: peerGrpName1} + nbr2 = &cfgplugins.BgpNeighbor{LocalAS: dutLocalAS2, PeerAS: ateAS2, Neighborip: atePort2.IPv4, IsV4: true, PeerGrp: peerGrpName2} + + otgPort1V4Peer = "atePort1.BGP4.peer" + otgPort2V4Peer = "atePort2.BGP4.peer" +) + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) +} + +// bgpCreateNbr creates a BGP object with neighbors pointing to atePort1 and atePort2 +func bgpCreateNbr(t *testing.T, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + t.Helper() + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutPort2.IPv4) + global.As = ygot.Uint32(dutGlobalAS) + 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) + + // 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. + + for _, nbr := range []*cfgplugins.BgpNeighbor{nbr1, nbr2} { + + pg := bgp.GetOrCreatePeerGroup(nbr.PeerGrp) + pg.PeerAs = ygot.Uint32(nbr.PeerAS) + pg.LocalAs = ygot.Uint32(nbr.LocalAS) + pg.PeerGroupName = ygot.String(nbr.PeerGrp) + pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + + nv4 := bgp.GetOrCreateNeighbor(nbr.Neighborip) + nv4.PeerGroup = ygot.String(nbr.PeerGrp) + nv4.PeerAs = ygot.Uint32(nbr.PeerAS) + nv4.Enabled = ygot.Bool(true) + af4 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + + 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) + rpl := pgaf.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{policyName} + rpl.ExportPolicy = []string{policyName} + } + } + return niProto +} + +// configureOTG configures the interfaces and BGP protocols on an ATE. +func configureOTG(t *testing.T, otg *otg.OTG) (gosnappi.BgpV4Peer, gosnappi.DeviceIpv4, gosnappi.Config) { + t.Helper() + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + iDut1Bgp := iDut1Dev.Bgp().SetRouterId(iDut1Ipv4.Address()) + iDut2Bgp := iDut2Dev.Bgp().SetRouterId(iDut2Ipv4.Address()) + + iDut1Bgp4Peer := iDut1Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut1Ipv4.Name()).Peers().Add().SetName(otgPort1V4Peer) + iDut1Bgp4Peer.SetPeerAddress(iDut1Ipv4.Gateway()).SetAsNumber(ateAS1).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2Ipv4.Name()).Peers().Add().SetName(otgPort2V4Peer) + iDut2Bgp4Peer.SetPeerAddress(iDut2Ipv4.Gateway()).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut2Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + t.Logf("Pushing config to OTG and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + + return iDut1Bgp4Peer, iDut1Ipv4, config +} + +// advBGPRouteFromOTG is to advertise prefix with specific AS sequence set. +func advBGPRouteFromOTG(t *testing.T, args *otgTestArgs, asSeg []uint32) { + + args.otgBgpPeer.V4Routes().Clear() + + bgpNeti1Bgp4PeerRoutes := args.otgBgpPeer.V4Routes().Add().SetName(atePort1.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(args.otgIPv4Device.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp4PeerRoutes.Addresses().Add(). + SetAddress(advertisedRoutesv4Net). + SetPrefix(uint32(advertisedRoutesv4Prefix)). + SetCount(1) + + bgpNeti1AsPath := bgpNeti1Bgp4PeerRoutes.AsPath().SetAsSetMode(gosnappi.BgpAsPathAsSetMode.INCLUDE_AS_SET) + bgpNeti1AsPath.Segments().Add().SetAsNumbers(asSeg).SetType(gosnappi.BgpAsPathSegmentType.AS_SEQ) + + t.Logf("Pushing config to OTG and starting protocols...") + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) +} + +// verifyPrefixesTelemetry confirms that the dut shows the correct numbers of installed, +// sent and received IPv4 prefixes. +func verifyPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr string, wantInstalled, wantSent uint32) { + t.Helper() + time.Sleep(15 * time.Second) + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + prefixesv4 := statePath.Neighbor(nbr).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) + } + if gotSent := gnmi.Get(t, dut, prefixesv4.Sent().State()); gotSent != wantSent { + t.Errorf("Sent prefixes mismatch: got %v, want %v", gotSent, wantSent) + } +} + +// 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() + pdef := rp.GetOrCreatePolicyDefinition(name) + stmt, err := pdef.AppendNewStatement(name) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", name, err) + } + stmt.GetOrCreateActions().PolicyResult = pr + stmt.GetOrCreateConditions().InstallProtocolEq = oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + +} + +// verifyOTGPrefixTelemetry is to Validate prefix received on OTG por2. +func verifyOTGPrefixTelemetry(t *testing.T, otg *otg.OTG, wantPrefix bool) { + t.Helper() + _, ok := gnmi.WatchAll(t, otg, gnmi.OTG().BgpPeer(atePort2.Name+".BGP4.peer").UnicastIpv4PrefixAny().State(), + time.Minute, func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + return v.IsPresent() + }).Await(t) + + if ok { + bgpPrefixes := gnmi.GetAll(t, otg, gnmi.OTG().BgpPeer(atePort2.Name+".BGP4.peer").UnicastIpv4PrefixAny().State()) + for _, prefix := range bgpPrefixes { + if prefix.GetAddress() == advertisedRoutesv4Net { + if wantPrefix { + gotASPath := prefix.AsPath[len(prefix.AsPath)-1].GetAsNumbers() + t.Logf("Received prefix %v on otg as expected with AS-PATH %v", prefix.GetAddress(), gotASPath) + } else { + t.Errorf("Prefix %v is not received on otg", prefix.GetAddress()) + } + } + } + } +} + +// ### RT-1.54.1 Test no allow-own-in +func testSplitHorizonNoAllowOwnIn(t *testing.T, args *otgTestArgs) { + t.Log("Baseline Test No allow-own-in") + + t.Log("Advertise a prefix from the ATE with an AS-path that includes AS dutLocalAS1 (DUT's AS) in the middle (e.g., AS-path: 65500 dutLocalAS1 65499") + advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the ATE Port2 doesn't receive the route. due to the presence of its own AS in the path.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 0, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 0) + verifyOTGPrefixTelemetry(t, args.otg, false) +} + +// ### RT-1.54.2 Test "allow-own-as 1" +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) + } + + t.Log("Re-advertise the prefix from the ATE with the same AS-path.") + advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + +} + +// ### RT-1.54.3 Test "allow-own-as 3" +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) + } + + 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}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 3 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 4 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499 (Should be rejected)", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 0, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 0) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, false) + }) +} + +// ### RT-1.54.4 Test "allow-own-as 4" +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) + } + + 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}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 3 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 4 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499 (Should be accepted)", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.Neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.Neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) +} + +type otgTestArgs struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgBgpPeer gosnappi.BgpV4Peer + otgIPv4Device gosnappi.DeviceIpv4 + otgConfig gosnappi.Config + otg *otg.OTG +} + +// TestBGPOverrideASPathSplitHorizon validates BGP Override AS-path split-horizon. +func TestBGPOverrideASPathSplitHorizon(t *testing.T) { + t.Logf("Start DUT config load.") + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + t.Run("Configure DUT interfaces", func(t *testing.T) { + configureDUT(t, dut) + }) + + t.Run("Configure DEFAULT network instance", func(t *testing.T) { + fptest.ConfigureDefaultNetworkInstance(t, dut) + }) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + t.Run("Configure BGP Neighbors", func(t *testing.T) { + configureRoutePolicy(t, dut, policyName, oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + cfgplugins.BGPClearConfig(t, dut) + dutConf := bgpCreateNbr(t, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + }) + + otg := ate.OTG() + var otgConfig gosnappi.Config + var otgBgpPeer gosnappi.BgpV4Peer + var otgIPv4Device gosnappi.DeviceIpv4 + otgBgpPeer, otgIPv4Device, otgConfig = configureOTG(t, otg) + + args := &otgTestArgs{ + dut: dut, + ate: ate, + otgBgpPeer: otgBgpPeer, + otgIPv4Device: otgIPv4Device, + otgConfig: otgConfig, + otg: otg, + } + + t.Run("Verify port status on DUT", func(t *testing.T) { + cfgplugins.VerifyPortsUp(t, args.dut.Device) + }) + + t.Run("Verify BGP telemetry", func(t *testing.T) { + cfgplugins.VerifyDUTBGPEstablished(t, args.dut) + cfgplugins.VerifyBGPCapabilities(t, args.dut, []*cfgplugins.BgpNeighbor{nbr1, nbr2}) + }) + + cases := []struct { + desc string + funcName func() + skipMsg string + }{{ + desc: " Baseline Test No allow-own-in", + funcName: func() { testSplitHorizonNoAllowOwnIn(t, args) }, + }, { + desc: " Test allow-own-as 1", + funcName: func() { testSplitHorizonAllowOwnAs1(t, args) }, + }, { + desc: " Test allow-own-as 3", + funcName: func() { testSplitHorizonAllowOwnAs3(t, args) }, + }, { + desc: " Test allow-own-as 4", + funcName: func() { testSplitHorizonAllowOwnAs4(t, args) }, + }} + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + tc.funcName() + }) + } +} diff --git a/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/metadata.textproto b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto similarity index 68% rename from feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/metadata.textproto rename to feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto index 8e53ae5c154..b4b418d51e2 100644 --- a/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto @@ -1,27 +1,17 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "15d601c4-43af-4d94-b6a8-da39cf42b14f" -plan_id: "RT-1.5" -description: "BGP Prefix Limit" +uuid: "97f0e45a-2970-4227-9409-3003e7c7cdd7" +plan_id: "RT-1.54" +description: "BGP Override AS-path split-horizon" testbed: TESTBED_DUT_ATE_2LINKS -platform_exceptions: { - platform: { - vendor: CISCO - } - deviations: { - ipv4_missing_enabled: true - } -} platform_exceptions: { platform: { vendor: NOKIA } deviations: { - explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true - bgp_explicit_prefix_limit_received: true } } platform_exceptions: { @@ -31,8 +21,17 @@ platform_exceptions: { deviations: { route_policy_under_afi_unsupported: true omit_l2_mtu: true - bgp_tolerance_value: 2 interface_enabled: true default_network_instance: "default" + 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/experimental/bgp/otg_tests/bgp_remove_private_as/README.md b/feature/bgp/otg_tests/bgp_remove_private_as/README.md similarity index 60% rename from feature/experimental/bgp/otg_tests/bgp_remove_private_as/README.md rename to feature/bgp/otg_tests/bgp_remove_private_as/README.md index 6a5ce3e8a52..7da3a2fc346 100644 --- a/feature/experimental/bgp/otg_tests/bgp_remove_private_as/README.md +++ b/feature/bgp/otg_tests/bgp_remove_private_as/README.md @@ -18,17 +18,20 @@ BGP remove private AS * PRIV_AS1 AS1 * AS1 PRIV_AS1 AS2 -## Config Parameter coverage - -* /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/remove-private-as - -## Telemetry Parameter coverage - -* /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as4-path/as4-segment/state - -## Protocol/RPC Parameter coverage - -N/A +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/remove-private-as: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as4-path/as4-segment/state/index: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/bgp/otg_tests/bgp_remove_private_as/bgp_remove_private_as_test.go b/feature/bgp/otg_tests/bgp_remove_private_as/bgp_remove_private_as_test.go similarity index 98% rename from feature/experimental/bgp/otg_tests/bgp_remove_private_as/bgp_remove_private_as_test.go rename to feature/bgp/otg_tests/bgp_remove_private_as/bgp_remove_private_as_test.go index ba297277319..3b865545acd 100644 --- a/feature/experimental/bgp/otg_tests/bgp_remove_private_as/bgp_remove_private_as_test.go +++ b/feature/bgp/otg_tests/bgp_remove_private_as/bgp_remove_private_as_test.go @@ -244,7 +244,7 @@ func configureOTG(t *testing.T, otg *otg.OTG, asSeg []uint32, asSEQMode bool) go iDut1Dev := config.Devices().Add().SetName(ateSrc.Name) iDut1Eth := iDut1Dev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - iDut1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port1.Name()) + 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") @@ -252,7 +252,7 @@ func configureOTG(t *testing.T, otg *otg.OTG, asSeg []uint32, asSEQMode bool) go iDut2Dev := config.Devices().Add().SetName(ateDst.Name) iDut2Eth := iDut2Dev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) - iDut2Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port2.Name()) + 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") diff --git a/feature/experimental/bgp/otg_tests/bgp_remove_private_as/metadata.textproto b/feature/bgp/otg_tests/bgp_remove_private_as/metadata.textproto similarity index 99% rename from feature/experimental/bgp/otg_tests/bgp_remove_private_as/metadata.textproto rename to feature/bgp/otg_tests/bgp_remove_private_as/metadata.textproto index 7d1b701fdd3..6090afb28bb 100644 --- a/feature/experimental/bgp/otg_tests/bgp_remove_private_as/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_remove_private_as/metadata.textproto @@ -23,4 +23,3 @@ platform_exceptions: { } } tags: TAGS_AGGREGATION - diff --git a/feature/experimental/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md similarity index 67% rename from feature/experimental/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md rename to feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md index 45457c0b801..8c21d568b0e 100644 --- a/feature/experimental/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md +++ b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md @@ -25,19 +25,22 @@ * Re-establish the IBGP sessions by tcp reset. * Validate that the min MSS value has been adjusted to be below 1500 bytes on the tcp session. -## Config Parameter coverage - -* /neighbors/neighbor/transport/config/tcp-mss -* /neighbors/neighbor/transport/config/mtu-discovery - -## Telemetry Parameter coverage - -* /neighbors/neighbor/transport/state/tcp-mss -* /neighbors/neighbor/transport/state/mtu-discovery - -## Protocol/RPC Parameter coverage - -N/A +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/tcp-mss: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/mtu-discovery: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/tcp-mss: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/mtu-discovery: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/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 similarity index 98% rename from feature/experimental/bgp/otg_tests/bgp_tcp_mss_path_mtu/bgp_tcp_mss_path_mtu_test.go rename to feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/bgp_tcp_mss_path_mtu_test.go index 6953d93da2d..468f7480fdb 100644 --- a/feature/experimental/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 @@ -213,7 +213,7 @@ func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { iDut1Dev := config.Devices().Add().SetName(atePort1.Name) iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - iDut1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port1.Name()) + iDut1Eth.Connection().SetPortName(port1.Name()) iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dut1Port1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") @@ -247,7 +247,7 @@ func configOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { iDut1Dev := config.Devices().Add().SetName(atePort1.Name) iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC).SetMtu(uint32(mtu5040B)) - iDut1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port1.Name()) + iDut1Eth.Connection().SetPortName(port1.Name()) iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dut1Port1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") diff --git a/feature/experimental/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto similarity index 66% rename from feature/experimental/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto rename to feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto index ae06ba6b756..ff417e46546 100644 --- a/feature/experimental/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto @@ -1,16 +1,16 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "5a1c71f3-9c37-4ea3-8101-8f3058ed8c33" -plan_id: "RT-1.21" -description: "BGP TCP MSS and PMTUD" -testbed: TESTBED_DUT_DUT_ATE_2LINKS -platform_exceptions: { - platform: { - vendor: JUNIPER +uuid: "5a1c71f3-9c37-4ea3-8101-8f3058ed8c33" +plan_id: "RT-1.21" +description: "BGP TCP MSS and PMTUD" +testbed: TESTBED_DUT_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: JUNIPER } - deviations: { - skip_tcp_negotiated_mss_check: true + deviations: { + skip_tcp_negotiated_mss_check: true isis_level_enabled: true } } @@ -19,10 +19,10 @@ platform_exceptions: { vendor: ARISTA } deviations: { + isis_instance_enabled_required: true + omit_l2_mtu: true interface_enabled: true default_network_instance: "default" - omit_l2_mtu: true - isis_instance_enabled_required: true isis_interface_afi_unsupported: true } } diff --git a/feature/bgp/policybase/feature.textproto b/feature/bgp/policybase/feature.textproto index d8863bca301..645d0930e23 100644 --- a/feature/bgp/policybase/feature.textproto +++ b/feature/bgp/policybase/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md b/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md new file mode 100644 index 00000000000..82d6fabacce --- /dev/null +++ b/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md @@ -0,0 +1,474 @@ +# RT-1.31: BGP 3 levels of nested import/export policy with match-set-options + +## Summary + +- AS-path prepending by more the 10 repetitions +- Recursive policy subroutines (multi-level nesting). At least 3 levels +- match-set-options of ANY, INVERT for match-prefix-set conditions +- Applicable to both IPv4 and IPv6 BGP neighbors + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed + +## Procedure + +### Applying configuration + +For each section of configuration below, prepare a gnmi.SetBatch with all the configuration items appended to one SetBatch. Then apply the configuration to the DUT in one gnmi.Set using the `replace` option + +#### Initial Setup: + +* Connect DUT port-1, 2 to ATE port-1, 2 +* Configure IPv4/IPv6 addresses on the ports +* Create an IPv4 networks i.e. ```ipv4-network-1 = 192.168.10.0/24``` attached to ATE port-1 +* Create an IPv6 networks i.e. ```ipv6-network-1 = 2024:db8:128:128::/64``` attached to ATE port-1 +* Create an IPv4 networks i.e. ```ipv4-network-2 = 192.168.20.0/24``` attached to ATE port-2 +* Create an IPv6 networks i.e. ```ipv6-network-2 = 2024:db8:64:64::/64``` attached to ATE port-2 +* Configure IPv4 and IPv6 eBGP between DUT Port-1 and ATE Port-1 + * Note: Nested policies will be applied to this eBGP session later in the test to validate the results + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:128:128::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-1 + * Configure DUT to advertise standard communities to ATE + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/send-community-type = ```STANDARD``` +* Configure IPv4 and IPv6 eBGP between DUT Port-2 and ATE Port-2 + * Note: This eBGP session is only used to advertise prefixes to DUT and receive prefixes from DUT + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-2 = 192.168.20.0/24``` and ```ipv6-network-2 = 2024:db8:64:64::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-2 + * Set default import and export policy to ```ACCEPT_ROUTE``` for this eBGP session only + * /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/default-export-policy + +##### Parent Route Policy: Configure a route-policy to inverse match any prefix in the given prefix-set (INVERT) +* Note: This parent policy will be applied to both import and export route policy on the neighbor. +* This policy will call unique nested policies for both import and export scenarios defined in the sub-tests +* Configure an IPv4 route-policy definition with the name ```invert-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```invert-policy-v4``` configure a statement with the name ```invert-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```invert-policy-v4``` statement ```invert-statement-v4``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```10.0.0.0/8``` and masklength to ```exact``` + * Our intention is to allow the prefix that does not match 10.0.0.0/8 (inverse the match result) + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```invert-policy-v4``` statement ```invert-statement-v4``` set match options to ```INVERT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```invert-policy-v4``` statement ```invert-statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + + +### RT-1.31.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2612] +#### IPv4 BGP 3 levels of nested import policy with match-prefix-set conditions +--- + +##### 2nd Route Policy: Configure a route-policy to match the a prefix in the given prefix-set (ANY) +* Configure an IPv4 route-policy definition with the name ```match-import-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-import-policy-v4``` configure a statement with the name ```match-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-import-policy-v4``` statement ```match-statement-v4``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```ipv4-network-1``` i.e. ```192.168.10.0/24``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-import-policy-v4``` statement ```match-statement-v4``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-import-policy-v4``` statement ```match-statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + + +##### 3rd Route Policy: Configure a route-policy to set the bgp local preference +* Configure an IPv4 route-policy definition with the name ```lp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```lp-policy-v4``` configure a statement with the name ```lp-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```lp-policy-v4``` statement ```lp-statement-v4``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set local-pref +* For routing-policy ```lp-policy-v4``` statement ```lp-statement-v4``` set local-preference to ```200``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref + + +##### 4th Route Policy: Configure a route-policy to set the bgp community +* Configure an IPv4 route-policy definition with the name ```community-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```community-policy-v4``` configure a statement with the name ```community-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```community-policy-v4``` statement ```community-statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a community-set +* Configure a community set with name ```community-set-v4``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name +* For community set ```community-set-v4``` configure a community member value to ```64512:100``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Attach the community-set to route-policy +* For routing-policy ```community-policy-v4``` statement ```community-statement-v4``` reference the community set ```community-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref + + +##### Configure policy nesting +* For Parent routing-policy ```invert-policy-v4``` and statement ```invert-statement-v4``` call the policy ```match-import-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```match-import-policy-v4``` and statement ```match-statement-v4``` call the policy ```lp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```lp-policy-v4``` and statement ```lp-statement-v4``` call the policy ```community-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + + +##### Configure the parent bgp import policy for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Apply the parent policy ```invert-policy-v4``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + + +##### Verification +* Verify that the parent ```invert-policy-v4``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy +* Verify that the parent ```invert-policy-v4``` policy has a child policy ```match-import-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the sub-parent ```match-import-policy-v4``` policy has a child policy ```lp-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the sub-parent ```lp-policy-v4``` policy has a child policy ```community-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + + +##### Validate test results +* Validate that the DUT receives the prefix ```ipv4-network-1``` i.e. ```192.168.10.0/24``` from BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv4-network-1``` i.e. ```192.168.10.0/24``` from BGP neighbor on ATE Port-1 has local preference of ```200``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Validate that the prefix ```ipv4-network-1``` i.e. ```192.168.10.0/24``` from BGP neighbor on ATE Port-1 has community of ```64512:100``` + * /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 +* Initiate traffic from ATE Port-2 towards the DUT destined to ```ipv4-network-1``` i.e. ```192.168.10.0/24``` + * Validate that the traffic is received on ATE Port-1 + + +### RT-1.31.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2612] +#### IPv4 BGP 3 levels of nested export policy with match-prefix-set conditions +--- + +##### 2nd Route Policy: Configure a route-policy to match the a prefix in the given prefix-set (ANY) +* Configure an IPv4 route-policy definition with the name ```match-export-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-export-policy-v4``` configure a statement with the name ```match-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-export-policy-v4``` statement ```match-statement-v4``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```ipv4-network-2``` i.e. ```192.168.20.0/24``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-export-policy-v4``` statement ```match-statement-v4``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-export-policy-v4``` statement ```match-statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + + +##### 3rd Route Policy: Configure a route-policy to prepend AS-PATH +* Configure an IPv4 route-policy definition with the name ```asp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```asp-policy-v4``` configure a statement with the name ```asp-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```asp-policy-v4``` statement ```asp-statement-v4``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to prepend AS by more than 10 times +* For routing-policy ```asp-policy-v4``` statement ```asp-statement-v4``` set AS-PATH prepend to the ASN of the DUT + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn +* For routing-policy ```asp-policy-v4``` statement ```asp-statement-v4``` set the prepended ASN to repeat ```15``` times + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n + + +##### 4th Route Policy: Configure a route-policy to set the MED +* Configure an IPv4 route-policy definition with the name ```med-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy-v4``` configure a statement with the name ```med-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy-v4``` statement ```med-statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set MED +* For routing-policy ```med-policy-v4``` statement ```med-statement-v4``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med + + +##### Configure policy nesting +* For Parent routing-policy ```invert-policy-v4``` and statement ```invert-statement-v4``` call the policy ```match-export-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```match-export-policy-v4``` and statement ```match-statement-v4``` call the policy ```asp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```asp-policy-v4``` and statement ```asp-statement-v4``` call the policy ```med-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + + +##### Configure the parent bgp export policy for the DUT BGP neighbor on ATE Port-1 +* Set default export policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy +* Apply the parent policy ```invert-export-policy-v4``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy + + +##### Verification +* Verify that the parent ```invert-policy-v4``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy +* Verify that the parent ```invert-policy-v4``` policy has a child policy ```match-export-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the parent ```match-export-policy-v4``` policy has a child policy ```asp-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the parent ```asp-policy-v4``` policy has a child policy ```med-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + + +##### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` from BGP neighbor on DUT Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` on ATE from BGP neighbor on DUT Port-1 has AS-PATH with the ASN of DUT occuring more than 10 times + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member +* Validate that the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` from BGP neighbor on DUT Port-1 has MED set to ```1000``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-1 towards the DUT destined ```ipv4-network-2``` i.e. ```192.168.20.0/24``` + * Validate that the traffic is received on ATE Port-2 + + +### RT-1.31.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2612] +#### IPv6 BGP 3 levels of nested import policy with match-prefix-set conditions +--- + +##### 2nd Route Policy: Configure a route-policy to match the a prefix in the given prefix-set (ANY) +* Configure an IPv6 route-policy definition with the name ```match-import-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-import-policy-v6``` configure a statement with the name ```match-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-import-policy-v6``` statement ```match-statement-v6``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v6``` set the ip-prefix to ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-import-policy-v6``` statement ```match-statement-v6``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-import-policy-v6``` statement ```match-statement-v6``` set prefix set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + + +##### 3rd Route Policy: Configure a route-policy to set the bgp local preference +* Configure an IPv6 route-policy definition with the name ```lp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```lp-policy-v6``` configure a statement with the name ```lp-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```lp-policy-v6``` statement ```lp-statement-v6``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set local-pref +* For routing-policy ```lp-policy-v6``` statement ```lp-statement-v6``` set local-preference to ```200``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref + +##### 4th Route Policy: Configure a route-policy to set the bgp community +* Configure an IPv6 route-policy definition with the name ```community-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```community-policy-v6``` configure a statement with the name ```community-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```community-policy-v6``` statement ```community-statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a community-set +* Configure a community set with name ```community-set-v6``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name +* For community set ```community-set-v6``` configure a community member value to ```64512:100``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Attach the community-set to route-policy +* For routing-policy ```community-policy-v6``` statement ```community-statement-v6``` reference the community set ```community-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref + + +##### Configure policy nesting +* For Parent routing-policy ```invert-policy-v6``` and statement ```invert-statement-v6``` call the policy ```match-import-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```match-import-policy-v6``` and statement ```match-statement-v6``` call the policy ```lp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```lp-policy-v6``` and statement ```lp-statement-v6``` call the policy ```community-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + + +##### Configure the parent bgp import policy for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Apply the parent policy ```invert-policy-v6``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + + +##### Verification +* Verify that the parent ```invert-policy-v6``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy +* Verify that the parent ```invert-policy-v6``` policy has a child policy ```match-import-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the sub-parent ```match-import-policy-v6``` policy has a child policy ```lp-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the sub-parent ```lp-policy-v6``` policy has a child policy ```community-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + + +##### Validate test results +* Validate that the DUT receives the prefix ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` from BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` from BGP neighbor on ATE Port-1 has local preference of ```200``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Validate that the prefix ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` from BGP neighbor on ATE Port-1 has community of ```64512:100``` + * /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/ipv6-unicast/loc-rib/routes/route/state/community-index +* Initiate traffic from ATE Port-2 towards the DUT destined to ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` + * Validate that the traffic is received on ATE Port-1 + + +### RT-1.31.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2612] +#### IPv6 BGP 3 levels of nested export policy with match-prefix-set conditions +--- + +##### 2nd Route Policy: Configure a route-policy to match the a prefix in the given prefix-set (ANY) +* Configure an IPv6 route-policy definition with the name ```match-export-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-export-policy-v6``` configure a statement with the name ```match-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-export-policy-v6``` statement ```match-statement-v6``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v6``` set the ip-prefix to ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-export-policy-v6``` statement ```match-statement-v6``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-export-policy-v6``` statement ```match-statement-v6``` set prefix set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + + +##### 3rd Route Policy: Configure a route-policy to prepend AS-PATH +* Configure an IPv6 route-policy definition with the name ```asp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```asp-policy-v6``` configure a statement with the name ```asp-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```asp-policy-v6``` statement ```asp-statement-v6``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to prepend AS by more than 10 times +* For routing-policy ```asp-policy-v6``` statement ```asp-statement-v6``` set AS-PATH prepend to the ASN of the DUT + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn +* For routing-policy ```asp-policy-v6``` statement ```asp-statement-v6``` set the prepended ASN to repeat ```15``` times + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n + + +##### 4th Route Policy: Configure a route-policy to set the MED +* Configure an IPv6 route-policy definition with the name ```med-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy-v6``` configure a statement with the name ```med-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy-v6``` statement ```med-statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set MED +* For routing-policy ```med-policy-v6``` statement ```med-statement-v6``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med + + +##### Configure policy nesting +* For Parent routing-policy ```invert-policy-v6``` and statement ```invert-statement-v6``` call the policy ```match-export-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```match-export-policy-v6``` and statement ```match-statement-v6``` call the policy ```asp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy +* For routing-policy ```asp-policy-v6``` and statement ```asp-statement-v6``` call the policy ```med-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + + +##### Configure the parent bgp export policy for the DUT BGP neighbor on ATE Port-1 +* Set default export policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy +* Apply the parent policy ```invert-export-policy-v6``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy + + +##### Verification +* Verify that the parent ```invert-policy-v6``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy +* Verify that the parent ```invert-policy-v6``` policy has a child policy ```match-export-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the parent ```match-export-policy-v6``` policy has a child policy ```asp-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy +* Verify that the parent ```asp-policy-v6``` policy has a child policy ```med-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + + +##### Validate test results +* Validate that the ATE receives the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` from BGP neighbor on DUT Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` on ATE from BGP neighbor on DUT Port-1 has AS-PATH with the ASN of DUT occuring more than 10 times + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member +* Validate that the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` from BGP neighbor on DUT Port-1 has MED set to ```1000``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* 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 + +## Required DUT platform + +* vRX diff --git a/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/README.md b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/README.md new file mode 100644 index 00000000000..4424f17aa04 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/README.md @@ -0,0 +1,344 @@ +# RT-1.32: BGP policy actions - MED, LocPref, prepend, flow-control + +## Summary + +- Verify abilty to set MED to fixed value in export and import policy +- Verify abilty to increment MED by fixed value in export and import policy +- Verify abilty to set Local Preference to fixed value in export and import policy +- Verify abilty to prepend AS path with 10 additional repetitions of local ASN in export and import policy +- Verify abilty to prepend AS path with 10 additional repetitions of configured ASN in export and import policy +- verify ```NEXT-STATEMENT``` flow-control action +- Applicable to both IPv4 and IPv6 BGP neighbors + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed + +## Procedure +### Applying configuration + +For each section of configuration below, prepare a gnmi.SetBatch with all the configuration items appended to one SetBatch. Then apply the configuration to the DUT in one gnmi.Set using the `replace` option. +> WARNING: Replace operations should be performed at an appropriate level in the config tree to ensure that preexisting configuration objects necessary for DUT management access and base operation are not removed. + +#### Initial Setup: + +* Connect DUT port-1, 2 to ATE port-1, 2 +* Configure IPv4/IPv6 addresses on the ports +* Create an IPv4 networks i.e. ```ipv4-network-1 = 192.168.10.0/24``` attached to ATE port-1 +* Create an IPv6 networks i.e. ```ipv6-network-1 = 2024:db8:128:128::/64``` attached to ATE port-1 +* Create an IPv4 networks i.e. ```ipv4-network-2 = 192.168.20.0/24``` attached to ATE port-2 +* Create an IPv6 networks i.e. ```ipv6-network-2 = 2024:db8:64:64::/64``` attached to ATE port-2 +* Configure IPv4 and IPv6 iBGP between DUT Port-1 and ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:128:128::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-1 with: + * MED = 50 + * Local Preference = 50 +* Configure IPv4 and IPv6 eBGP between DUT Port-2 and ATE Port-2 + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-2 = 192.168.20.0/24``` and ```ipv6-network-2 = 2024:db8:64:64::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-2. The ATE should advertise both prefixes with: + * MED = 50 + * Local Preference = 50 + +### RT-1.32.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2615] +#### IPv4, IPv6 eBGP set MED +--- +##### Configure a route-policy to set MED +* Configure an route-policy definition with the name ```med-policy``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy``` configure a statement with the name ```match-statement-1``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy``` statement ```match-statement-1``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```med-policy``` statement ```match-statement-1``` set MED as ```100``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med +##### Configure bgp import and export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-2 +* Set default import and export policy to ```REJECT_ROUTE``` + * /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/default-export-policy +* Add `policy-definition["med-policy"]` to import-policy and export-policy leaf-lists. + * /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/export-policy +##### Configure default policies and remove any import, export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-1 +* Set default import and export policy to ```ACCEPT_ROUTE``` + * /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/default-export-policy +* Remove policy as import and export as a chain/list ```[med-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/export-policy +##### Verification +* Verify that policies are successfully applied to the DUT BGP neighbor on ATE Port-2 and default policies are set to ```REJECT_ROUTE``` + * /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/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/default-export-policy +* Verify that there is no policies applied to the DUT BGP neighbor on ATE Port-1 and default policies are set to ```ACCEPT_ROUTE``` + * /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/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/default-export-policy +#### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-1``` from DUT neighbor on ATE Port-2 and it has MED == 100 +* Validate that the ATE receives the prefix ```ipv6-network-1``` from DUT neighbor on ATE Port-2 and it has MED == 100 +* Validate that the ATE receives the prefix ```ipv4-network-2``` from DUT neighbor on ATE Port-1 and it has MED == 100 +* Validate that the ATE receives the prefix ```ipv6-network-2``` from DUT neighbor on ATE Port-1 and it has MED == 100 + +### RT-1.32.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2615] +#### IPv4, IPv6 eBGP increase MED +--- +##### Configure a route-policy to increase MED +* Configure an route-policy definition with the name ```med-policy``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy``` configure a statement with the name ```match-statement-1``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy``` statement ```match-statement-1``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```med-policy``` statement ```match-statement-1``` set MED as ```+100``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med +##### Configure bgp import and export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-2 +* Set default import and export policy to ```REJECT_ROUTE``` + * /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/default-export-policy +* Add `policy-definition["med-policy"]` to import-policy and export-policy leaf-lists. + * /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/export-policy +##### Configure default policies and remove any import, export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-1 +* Set default import and export policy to ```ACCEPT_ROUTE``` + * /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/default-export-policy +* Remove policy as import and export as a chain/list ```[med-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/export-policy +##### Verification +* Verify that policies are successfully applied to the DUT BGP neighbor on ATE Port-2 and default policies are set to ```REJECT_ROUTE``` + * /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/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/default-export-policy +* Verify that there is no policies applied to the DUT BGP neighbor on ATE Port-1 and default policies are set to ```ACCEPT_ROUTE``` + * /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/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/default-export-policy +#### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-1``` from DUT neighbor on ATE Port-2 and it has MED == 150 +* Validate that the ATE receives the prefix ```ipv6-network-1``` from DUT neighbor on ATE Port-2 and it has MED == 150 +* Validate that the ATE receives the prefix ```ipv4-network-2``` from DUT neighbor on ATE Port-1 and it has MED == 150 +* Validate that the ATE receives the prefix ```ipv6-network-2``` from DUT neighbor on ATE Port-1 and it has MED == 150 + +### RT-1.32.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2615] +#### IPv4, IPv6 iBGP set Local Preference +--- +##### Configure a route-policy to set Local Preference +* Configure an route-policy definition with the name ```lp-policy``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```lp-policy``` configure a statement with the name ```match-statement-1``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```lp-policy``` statement ```match-statement-1``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```lp-policy``` statement ```match-statement-1``` set Local Preference as ```100``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref +##### Configure bgp import and export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-1 +* Set default import and export policy to ```REJECT_ROUTE``` + * /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/default-export-policy +* Add `policy-definition["lp-policy"]` to import-policy and export-policy leaf-lists. + * /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/export-policy +##### Configure default policies and remove any import, export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-2 +* Set default import and export policy to ```ACCEPT_ROUTE``` + * /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/default-export-policy +* Remove all import and export policies + * /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/export-policy +##### Verification +* Verify that policies are successfully applied to the DUT BGP neighbor on ATE Port-1 and default policies are set to ```REJECT_ROUTE``` + * /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/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/default-export-policy +* Verify that there is no policies applied to the DUT BGP neighbor on ATE Port-2 and default policies are set to ```ACCEPT_ROUTE``` + * /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/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/default-export-policy +#### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-1``` from DUT neighbor on ATE Port-2 and it has LocPref == 100 +* Validate that the ATE receives the prefix ```ipv6-network-1``` from DUT neighbor on ATE Port-2 and it has LocPref == 100 +* Validate that the ATE receives the prefix ```ipv4-network-2``` from DUT neighbor on ATE Port-1 and it has LocPref == 100 +* Validate that the ATE receives the prefix ```ipv6-network-2``` from DUT neighbor on ATE Port-1 and it has LocPref == 100 + +### RT-1.32.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2615] +#### IPv4, IPv6 eBGP NEXT-STATEMENT +--- +##### Configure a route-policy set MED, LocalPreferemce is separate statements +* Configure an route-policy definition with the name ```flow-control-policy``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```flow-control-policy``` configure a statement with the name ```match-statement-1``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```flow-control-policy``` statement ```match-statement-1``` set policy-result as ```NEXT-STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```flow-control-policy``` statement ```match-statement-1``` set MED to 70 + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med +* For routing-policy ```flow-control-policy``` configure a statement with the name ```match-statement-2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```flow-control-policy``` statement ```match-statement-2``` set policy-result as ```ACCEPT-ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```flow-control-policy``` statement ```match-statement-2``` prepend as-path with local ASN ```10``` times + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n +##### Configure bgp import and export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-2 +* Set default import and export policy to ```REJECT_ROUTE``` + * /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/default-export-policy +* Apply as import and export only policy - ```[flow-control-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/export-policy +##### Configure default policies and remove any import, export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-1 +* Set default import and export policy to ```ACCEPT_ROUTE``` + * /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/default-export-policy +* Remove all import and export policies + * /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/export-policy +##### Verification +* Verify that policies are successfully applied to the DUT BGP neighbor on ATE Port-2 and default policies are set to ```REJECT_ROUTE``` + * /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/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/default-export-policy +* Verify that there is no policies applied to the DUT BGP neighbor on ATE Port-1 and default policies are set to ```ACCEPT_ROUTE``` + * /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/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/default-export-policy +#### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-1``` from DUT neighbor on ATE Port-2 and it has MED == 70 and it has 11 ASN on as-path. All equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv6-network-1``` from DUT neighbor on ATE Port-2 and it has MED == 70 and it has 11 ASN on as-path. All equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv4-network-2``` from DUT neighbor on ATE Port-1 and it has MED == 70 and it has 11 ASN on as-path. All equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv6-network-2``` from DUT neighbor on ATE Port-1 and it has MED == 70 and it has 11 ASN on as-path. All equal to DUT's ASN. + +### RT-1.32.5 [TODO: https://github.com/openconfig/featureprofiles/issues/2615] +#### IPv4, IPv6 eBGP prepend 10 x local ASN +--- +##### Configure a route-policy to prepend 10 +* Configure an route-policy definition with the name ```prepend-policy``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```prepend-policy``` configure a statement with the name ```match-statement-1``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```prepend-policy``` statement ```match-statement-1``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```prepend-policy``` statement ```match-statement-1``` prepend as-path with local ASN ```10``` times + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n +##### Configure bgp import and export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-2 +* Set default import and export policy to ```REJECT_ROUTE``` + * /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/default-export-policy +* Apply as import and export only policy - ```[prepend-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/export-policy +##### Configure default policies and remove any import, export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-1 +* Set default import and export policy to ```ACCEPT_ROUTE``` + * /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/default-export-policy +* Remove all import and export policies + * /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/export-policy +##### Verification +* Verify that policies are successfully applied to the DUT BGP neighbor on ATE Port-2 and default policies are set to ```REJECT_ROUTE``` + * /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/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/default-export-policy +* Verify that there is no policies applied to the DUT BGP neighbor on ATE Port-1 and default policies are set to ```ACCEPT_ROUTE``` + * /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/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/default-export-policy +#### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-1``` from DUT neighbor on ATE Port-2 and it has 11 ASN on as-path. All equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv6-network-1``` from DUT neighbor on ATE Port-2 and it has 11 ASN on as-path. All equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv4-network-2``` from DUT neighbor on ATE Port-1 and it has 11 ASN on as-path. First equial to ATE port-2 ASN and other equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv6-network-2``` from DUT neighbor on ATE Port-1 and it has 11 ASN on as-path. First equial to ATE port-2 ASN and other equal to DUT's ASN. + +### RT-1.32.6 [TODO: https://github.com/openconfig/featureprofiles/issues/2615] +#### IPv4, IPv6 eBGP prepend 10 x ASN +--- +##### Configure a route-policy to prepend 10 +* Configure an route-policy definition with the name ```prepend-policy``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```prepend-policy``` configure a statement with the name ```match-statement-1``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```prepend-policy``` statement ```match-statement-1``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```prepend-policy``` statement ```match-statement-1``` prepend as-path with ```23456``` ASN ```10``` times + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repead-n + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn +##### Configure bgp import and export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-2 +* Set default import and export policy to ```REJECT_ROUTE``` + * /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/default-export-policy +* Apply as import and export only policy - ```[prepend-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/export-policy +##### Configure default policies and remove any import, export policy for the DUT IPv4 and IPv6 BGP neighbors on ATE Port-1 +* Set default import and export policy to ```ACCEPT_ROUTE``` + * /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/default-export-policy +* Remove all import and export policies + * /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/export-policy +##### Verification +* Verify that policies are successfully applied to the DUT BGP neighbor on ATE Port-2 and default policies are set to ```REJECT_ROUTE``` + * /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/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/default-export-policy +* Verify that there is no policies applied to the DUT BGP neighbor on ATE Port-1 and default policies are set to ```ACCEPT_ROUTE``` + * /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/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/default-export-policy +#### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-1``` from DUT neighbor on ATE Port-2 and it has 11 ASN on as-path. First 10 equal to ```23456``` and last equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv6-network-1``` from DUT neighbor on ATE Port-2 and it has 11 ASN on as-path. First 10 equal to ```23456``` and last equal to DUT's ASN. +* Validate that the ATE receives the prefix ```ipv4-network-2``` from DUT neighbor on ATE Port-1 and it has 11 ASN on as-path. First equal to ATE port-2 ASN and other 10 equal to ```23456``` ASN. +* Validate that the ATE receives the prefix ```ipv6-network-2``` from DUT neighbor on ATE Port-1 and it has 11 ASN on as-path. First equal to ATE port-2 ASN and other 10 equal to ```23456``` ASN. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config parameter coverage + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/enabled: + /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/default-import-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy: + /routing-policy/policy-definitions/policy-definition/config/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref: + /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: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result: + /routing-policy/policy-definitions/policy-definition/statements/statement/config/name: + + ## Telemetry parameter coverage + /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/default-import-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/default-export-policy: + + ## Protocol/RPC Parameter Coverage +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` + +## Required DUT platform + +* FFF 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 new file mode 100644 index 00000000000..c4df5796bf2 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/actions_MED_LocPref_prepend_flow_control_test.go @@ -0,0 +1,882 @@ +// 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 actions_med_localpref_prepend_flow_control_test + +import ( + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "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/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/gnmi/oc/netinstbgp" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + otg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + advertisedRoutesv4Net1 = "192.168.10.1" + advertisedRoutesv6Net1 = "2024:db8:128:128::1" + advertisedRoutesv4Net2 = "192.168.20.1" + advertisedRoutesv6Net2 = "2024:db8:64:64::1" + advertisedRoutesv4PrefixLen = 32 + advertisedRoutesv6PrefixLen = 128 + dutAS = 64500 + ateAS = 64501 + plenIPv4 = 30 + plenIPv6 = 126 + setLocalPrefPolicy = "lp-policy" + initialLocalPrefValue = 50 + initialMEDValue = 50 + setMEDPolicy = "med-policy" + matchStatement1 = "match-statement-1" + setPrependPolicy = "prepend-policy" + testASN = 23456 + defRejectRoute = oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE + defAcceptRoute = oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE + peerGrpName1v4 = "iBGP-PEER-GROUP1-V4" + peerGrpName1v6 = "iBGP-PEER-GROUP1-V6" + peerGrpName2v4 = "eBGP-PEER-GROUP2-V4" + peerGrpName2v6 = "eBGP-PEER-GROUP2-V6" + nxtMED = 70 + nxtLocalPref = 70 + setNxtPolicy = "flow-control-policy" + matchStatement2 = "match-statement-2" + asnRepeatN = 10 +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE source", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "ateSrc", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE destination", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + atePort2 = attrs.Attributes{ + Name: "atedst", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::192:0:2:6", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } +) + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dc := gnmi.OC() + i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, "port1")) + fptest.SetPortSpeed(t, dut.Port(t, "port2")) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, i1.GetName(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, i2.GetName(), deviations.DefaultNetworkInstance(dut), 0) + } +} + +// 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) + } + } +} + +type bgpNghbrs struct { + localAs, peerAs uint32 + localIP string + peerIP, peerGrpName []string +} + +// createNewBgpSession configures BGP on DUT with neighbors pointing to ateSrc and ateDst and +// a peer group policy. +func createNewBgpSession(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + nb1 := &bgpNghbrs{localAs: dutAS, peerAs: dutAS, localIP: dutPort1.IPv4, peerIP: []string{atePort1.IPv4, atePort1.IPv6}, peerGrpName: []string{peerGrpName1v4, peerGrpName1v6}} + nb2 := &bgpNghbrs{localAs: dutAS, peerAs: ateAS, localIP: dutPort2.IPv4, peerIP: []string{atePort2.IPv4, atePort2.IPv6}, peerGrpName: []string{peerGrpName2v4, peerGrpName2v6}} + nbs := []*bgpNghbrs{nb1, nb2} + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + global := bgp.GetOrCreateGlobal() + for _, nb := range nbs { + routerID := nb.localIP + peerV4 := nb.peerIP[0] + peerV6 := nb.peerIP[1] + peerGrpNameV4 := nb.peerGrpName[0] + peerGrpNameV6 := nb.peerGrpName[1] + global.RouterId = ygot.String(routerID) + global.As = ygot.Uint32(nb.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) + // 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(peerGrpNameV4) + pg.PeerAs = ygot.Uint32(nb.peerAs) + pg.PeerGroupName = ygot.String(peerGrpNameV4) + + bgpNbr := bgp.GetOrCreateNeighbor(peerV4) + bgpNbr.PeerGroup = ygot.String(peerGrpNameV4) + bgpNbr.PeerAs = ygot.Uint32(nb.peerAs) + bgpNbr.Enabled = ygot.Bool(true) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + + pg1 := bgp.GetOrCreatePeerGroup(peerGrpNameV6) + pg1.PeerAs = ygot.Uint32(nb.peerAs) + pg1.PeerGroupName = ygot.String(peerGrpNameV6) + + bgpNbr1 := bgp.GetOrCreateNeighbor(peerV6) + bgpNbr1.PeerGroup = ygot.String(peerGrpNameV6) + bgpNbr1.PeerAs = ygot.Uint32(nb.peerAs) + bgpNbr1.Enabled = ygot.Bool(true) + af6 := bgpNbr1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + } + return niProto +} + +// VerifyBgpState verifies that BGP is established +func VerifyBgpState(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + var nbrIP = []string{atePort1.IPv4, atePort1.IPv6, atePort2.IPv4, atePort2.IPv6} + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + watch := gnmi.Watch(t, dut, bgpPath.State(), 2*time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Protocol_Bgp]) bool { + path, _ := val.Val() + for _, nbr := range nbrIP { + if path.GetNeighbor(nbr).GetSessionState() != oc.Bgp_Neighbor_SessionState_ESTABLISHED { + return false + } + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established: got %v", val) + } + t.Log("BGP sessions Established") +} + +// juniperBgpPolicyMEDAdd is used to configure set metric add via native cli as an alternative for below xpath. +// routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med +func juniperBgpPolicyMEDAdd(polName string, metric int) string { + return fmt.Sprintf(` + policy-options { + policy-statement %s { + term 1 { + then { + metric add %d; + } + } + term 2 { + then accept; + } + } + }`, polName, metric) +} + +// configureASLocalPrefMEDPolicy configures MED, Local Pref, AS prepend etc +func configureASLocalPrefMEDPolicy(t *testing.T, dut *ondatra.DUTDevice, policyType, policyValue, statement string, ASN uint32) { + t.Helper() + dutOcRoot := &oc.Root{} + batchConfig := &gnmi.SetBatch{} + rp := dutOcRoot.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(policyType) + stmt, err := pdef.AppendNewStatement(statement) + if err != nil { + t.Fatal(err) + } + actions := stmt.GetOrCreateActions() + switch policyType { + case setLocalPrefPolicy: + metric, _ := strconv.Atoi(policyValue) + actions.GetOrCreateBgpActions().SetLocalPref = ygot.Uint32(uint32(metric)) + actions.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + case setMEDPolicy: + if strings.Contains(policyValue, "+") { + if deviations.BgpSetMedV7Unsupported(dut) { + t.Logf("Push the CLI config:%s", dut.Vendor()) + metric, _ := strconv.Atoi(policyValue) + switch dut.Vendor() { + case ondatra.JUNIPER: + config := juniperBgpPolicyMEDAdd(setMEDPolicy, metric) + helpers.GnmiCLIConfig(t, dut, config) + default: + t.Fatalf("BgpSetMedV7Unsupported deviation needs cli configuration for vendor %s which is not defined", dut.Vendor()) + } + } else { + actions.GetOrCreateBgpActions().SetMed = oc.UnionString(policyValue) + actions.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + } + } else { + metric, _ := strconv.Atoi(policyValue) + actions.GetOrCreateBgpActions().SetMed = oc.UnionUint32(uint32(metric)) + actions.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + } + case setPrependPolicy: + metric, _ := strconv.Atoi(policyValue) + asPrepend := actions.GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend() + asPrepend.Asn = ygot.Uint32(ASN) + asPrepend.RepeatN = ygot.Uint8(uint8(metric)) + actions.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + case setNxtPolicy: + if !deviations.SkipSettingStatementForPolicy(dut) { + actions.PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT + } + metric, _ := strconv.Atoi(policyValue) + actions.GetOrCreateBgpActions().SetMed = oc.UnionUint32(uint32(metric)) + + stmt2, err := pdef.AppendNewStatement(matchStatement2) + if err != nil { + t.Fatal(err) + } + actions2 := stmt2.GetOrCreateActions() + asPrepend := actions2.GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend() + asPrepend.Asn = ygot.Uint32(ASN) + asPrepend.RepeatN = ygot.Uint8(asnRepeatN) + actions2.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + default: + rp = nil + } + gnmi.BatchReplace(batchConfig, gnmi.OC().RoutingPolicy().Config(), rp) + batchConfig.Set(t, dut) +} + +// configureBGPDefaultImportExportPolicy configures default import/export policies +func configureBGPDefaultImportExportPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4, ipv6 string, polType oc.E_RoutingPolicy_DefaultPolicyType) { + t.Helper() + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + batchConfig := &gnmi.SetBatch{} + nbrPolPathv4 := bgpPath.Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + nbrPolPathv6 := bgpPath.Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + gnmi.BatchReplace(batchConfig, nbrPolPathv4.DefaultImportPolicy().Config(), polType) + gnmi.BatchReplace(batchConfig, nbrPolPathv4.DefaultExportPolicy().Config(), polType) + gnmi.BatchReplace(batchConfig, nbrPolPathv6.DefaultImportPolicy().Config(), polType) + gnmi.BatchReplace(batchConfig, nbrPolPathv6.DefaultExportPolicy().Config(), polType) + batchConfig.Set(t, dut) +} + +// configureBGPImportExportPolicy configures import/export policies +func configureBGPImportExportPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4, ipv6, policyDef string) { + t.Helper() + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + batchConfig := &gnmi.SetBatch{} + nbrPolPathv4 := bgpPath.Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + nbrPolPathv6 := bgpPath.Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + gnmi.BatchReplace(batchConfig, nbrPolPathv4.ImportPolicy().Config(), []string{policyDef}) + gnmi.BatchReplace(batchConfig, nbrPolPathv4.ExportPolicy().Config(), []string{policyDef}) + gnmi.BatchReplace(batchConfig, nbrPolPathv6.ImportPolicy().Config(), []string{policyDef}) + gnmi.BatchReplace(batchConfig, nbrPolPathv6.ExportPolicy().Config(), []string{policyDef}) + batchConfig.Set(t, dut) +} + +// deleteBGPImportExportPolicy configures import/export policies +func deleteBGPImportExportPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4, ipv6 string, ipv4_2 string, ipv6_2 string) { + t.Helper() + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + batchConfig := &gnmi.SetBatch{} + nbrPolPathv4 := bgpPath.Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + nbrPolPathv6 := bgpPath.Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + nbrPolPathv4_2 := bgpPath.Neighbor(ipv4_2).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + nbrPolPathv6_2 := bgpPath.Neighbor(ipv6_2).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + gnmi.BatchDelete(batchConfig, nbrPolPathv4_2.ImportPolicy().Config()) + gnmi.BatchDelete(batchConfig, nbrPolPathv4_2.ExportPolicy().Config()) + gnmi.BatchDelete(batchConfig, nbrPolPathv6_2.ImportPolicy().Config()) + gnmi.BatchDelete(batchConfig, nbrPolPathv6_2.ExportPolicy().Config()) + + gnmi.BatchDelete(batchConfig, nbrPolPathv4.ImportPolicy().Config()) + gnmi.BatchDelete(batchConfig, nbrPolPathv4.ExportPolicy().Config()) + gnmi.BatchDelete(batchConfig, nbrPolPathv6.ImportPolicy().Config()) + gnmi.BatchDelete(batchConfig, nbrPolPathv6.ExportPolicy().Config()) + batchConfig.Set(t, dut) +} + +// verifyBgpPolicyTelemetry verifies that the BGP policy telemetry matches +func verifyBgpPolicyTelemetry(t *testing.T, dut *ondatra.DUTDevice, ipAddr string, defPol oc.E_RoutingPolicy_DefaultPolicyType, appliedPol string, isV4 bool) { + t.Helper() + + t.Logf("BGP Policy telemetry verification for the neighbor %v", ipAddr) + + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + var afiSafiPath *netinstbgp.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafiPath + if isV4 { + afiSafiPath = statePath.Neighbor(ipAddr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + } else { + afiSafiPath = statePath.Neighbor(ipAddr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + } + + peerTel := gnmi.Get(t, dut, afiSafiPath.State()) + + if !deviations.DefaultRoutePolicyUnsupported(dut) { + if gotDefExPolicy := peerTel.GetApplyPolicy().GetDefaultExportPolicy(); gotDefExPolicy != defPol { + t.Errorf("Default export policy type mismatch: got %v, want %v", gotDefExPolicy, defPol) + } + + if gotDefImPolicy := peerTel.GetApplyPolicy().GetDefaultImportPolicy(); gotDefImPolicy != defPol { + t.Errorf("Default import policy type mismatch: got %v, want %v", gotDefImPolicy, defPol) + } + } + + if appliedPol != "" { + if gotExportPol := peerTel.GetApplyPolicy().GetExportPolicy(); cmp.Diff(gotExportPol, []string{appliedPol}) != "" { + t.Errorf("Export policy type mismatch: got %v, want %v", gotExportPol, []string{appliedPol}) + } + } else { + if gotExportPol := peerTel.GetApplyPolicy().GetExportPolicy(); gotExportPol != nil { + t.Errorf("Export policy type mismatch: got %v, want %v", gotExportPol, "nil") + } + } + + if appliedPol != "" { + if gotImportPol := peerTel.GetApplyPolicy().GetImportPolicy(); cmp.Diff(gotImportPol, []string{appliedPol}) != "" { + t.Errorf("Import policy type mismatch: got %v, want %v", gotImportPol, []string{appliedPol}) + } + } else { + if gotImportPol := peerTel.GetApplyPolicy().GetImportPolicy(); gotImportPol != nil { + t.Errorf("Import policy type mismatch: got %v, want %v", gotImportPol, "nil") + } + } +} + +// configureOTG configures the interfaces and BGP protocols on an OTG, including advertising some +// networks over BGP. +func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + // iBGP v4 and v6 sessions 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(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") + iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + // eBGP v4 and v6 sessions on port2 + iDut2Bgp := iDut2Dev.Bgp().SetRouterId(iDut2Ipv4.Address()) + iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2Ipv4.Name()).Peers().Add().SetName(atePort2.Name + ".BGP4.peer") + iDut2Bgp4Peer.SetPeerAddress(iDut2Ipv4.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + iDut2Bgp6Peer := iDut2Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut2Ipv6.Name()).Peers().Add().SetName(atePort2.Name + ".BGP6.peer") + iDut2Bgp6Peer.SetPeerAddress(iDut2Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut2Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + // iBGP V4 routes from Port1 and set MED, Local Preference. + bgpNeti1Bgp4PeerRoutes := iDut1Bgp4Peer.V4Routes().Add().SetName(atePort1.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(iDut1Ipv4.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp4PeerRoutes.Addresses().Add(). + SetAddress(advertisedRoutesv4Net1).SetPrefix(advertisedRoutesv4PrefixLen) + bgpNeti1Bgp4PeerRoutes.Advanced().SetIncludeMultiExitDiscriminator(true).SetMultiExitDiscriminator(50) + bgpNeti1Bgp4PeerRoutes.Advanced().SetIncludeLocalPreference(true).SetLocalPreference(50) + + // iBGP V6 routes from Port1 and set MED, Local Preference. + bgpNeti1Bgp6PeerRoutes := iDut1Bgp6Peer.V6Routes().Add().SetName(atePort1.Name + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(iDut1Ipv6.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp6PeerRoutes.Addresses().Add(). + SetAddress(advertisedRoutesv6Net1).SetPrefix(advertisedRoutesv6PrefixLen) + bgpNeti1Bgp6PeerRoutes.Advanced().SetIncludeMultiExitDiscriminator(true).SetMultiExitDiscriminator(50) + bgpNeti1Bgp6PeerRoutes.Advanced().SetIncludeLocalPreference(true).SetLocalPreference(50) + + // eBGP V4 routes from Port2 and set MED, Local Preference. + bgpNeti2Bgp4PeerRoutes := iDut2Bgp4Peer.V4Routes().Add().SetName(atePort2.Name + ".BGP4.Route") + bgpNeti2Bgp4PeerRoutes.SetNextHopIpv4Address(iDut2Ipv4.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti2Bgp4PeerRoutes.Addresses().Add(). + SetAddress(advertisedRoutesv4Net2).SetPrefix(advertisedRoutesv4PrefixLen) + bgpNeti2Bgp4PeerRoutes.Advanced().SetIncludeMultiExitDiscriminator(true).SetMultiExitDiscriminator(50) + bgpNeti2Bgp4PeerRoutes.Advanced().SetIncludeLocalPreference(true).SetLocalPreference(50) + + // eBGP V6 routes from Port2 and set MED, Local Preference. + bgpNeti2Bgp6PeerRoutes := iDut2Bgp6Peer.V6Routes().Add().SetName(atePort2.Name + ".BGP6.Route") + bgpNeti2Bgp6PeerRoutes.SetNextHopIpv6Address(iDut2Ipv6.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + bgpNeti2Bgp6PeerRoutes.Addresses().Add(). + SetAddress(advertisedRoutesv6Net2).SetPrefix(advertisedRoutesv6PrefixLen) + bgpNeti2Bgp6PeerRoutes.Advanced().SetIncludeMultiExitDiscriminator(true).SetMultiExitDiscriminator(50) + bgpNeti2Bgp6PeerRoutes.Advanced().SetIncludeLocalPreference(true).SetLocalPreference(50) + + t.Logf("Pushing config to ATE and starting protocols...") + otg.PushConfig(t, config) + otg.StartProtocols(t) + + return config +} + +// validateOTGBgpPrefixV4AndASLocalPrefMED verifies that the IPv4 prefix is received on OTG. +func validateOTGBgpPrefixV4AndASLocalPrefMED(t *testing.T, otg *otg.OTG, dut *ondatra.DUTDevice, config gosnappi.Config, peerName, ipAddr string, prefixLen uint32, pathAttr string, metric uint32) { + // t.Helper() + _, ok := gnmi.WatchAll(t, + otg, + gnmi.OTG().BgpPeer(peerName).UnicastIpv4PrefixAny().State(), + 60*time.Second, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + _, present := v.Val() + return present + }).Await(t) + var foundPrefix = false + if ok { + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv4Prefix](t, otg, gnmi.OTG().BgpPeer(peerName).UnicastIpv4PrefixAny().State()) + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == ipAddr && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == prefixLen { + foundPrefix = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix.GetAddress(), ipAddr) + switch pathAttr { + case setMEDPolicy: + if bgpPrefix.GetMultiExitDiscriminator() != metric { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } else { + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } + case setLocalPrefPolicy: + if !deviations.BGPRibOcPathUnsupported(dut) { + validateImportRoutingPolicy(t, dut, ipAddr, metric) + } + case setPrependPolicy: + if len(bgpPrefix.AsPath[0].GetAsNumbers()) != int(metric) { + t.Errorf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), int(metric)) + } else { + t.Logf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), int(metric)) + } + case setNxtPolicy: + if bgpPrefix.GetMultiExitDiscriminator() != metric { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } else { + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } + if len(bgpPrefix.AsPath[0].GetAsNumbers()) != asnRepeatN+1 { + t.Errorf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), asnRepeatN+1) + } else { + t.Logf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), asnRepeatN+1) + } + default: + t.Errorf("Incorrect BGP Path Attribute. Expected MED, Local Pref or AS Path Prepend!!!!") + } + break + } + } + } + if !foundPrefix { + t.Errorf("Prefix %v not received on OTG", ipAddr) + } +} + +// validateOTGBgpPrefixV6AndASLocalPrefMED verifies that the IPv6 prefix is received on OTG. +func validateOTGBgpPrefixV6AndASLocalPrefMED(t *testing.T, otg *otg.OTG, dut *ondatra.DUTDevice, config gosnappi.Config, peerName, ipAddr string, prefixLen uint32, pathAttr string, metric uint32) { + // t.Helper() + _, ok := gnmi.WatchAll(t, + otg, + gnmi.OTG().BgpPeer(peerName).UnicastIpv6PrefixAny().State(), + 60*time.Second, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv6Prefix]) bool { + _, present := v.Val() + return present + }).Await(t) + var foundPrefix = false + if ok { + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv6Prefix](t, otg, gnmi.OTG().BgpPeer(peerName).UnicastIpv6PrefixAny().State()) + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == ipAddr && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == prefixLen { + foundPrefix = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix.GetAddress(), ipAddr) + switch pathAttr { + case setMEDPolicy: + if bgpPrefix.GetMultiExitDiscriminator() != metric { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } else { + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } + case setLocalPrefPolicy: + if !deviations.BGPRibOcPathUnsupported(dut) { + validateImportRoutingPolicyV6(t, dut, ipAddr, metric) + } + case setPrependPolicy: + if len(bgpPrefix.AsPath[0].GetAsNumbers()) != int(metric) { + t.Errorf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), int(metric)) + } else { + t.Logf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), int(metric)) + } + case setNxtPolicy: + if bgpPrefix.GetMultiExitDiscriminator() != metric { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } else { + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } + if len(bgpPrefix.AsPath[0].GetAsNumbers()) != asnRepeatN+1 { + t.Errorf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), asnRepeatN+1) + } else { + t.Logf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), asnRepeatN+1) + } + default: + t.Errorf("Incorrect Routing Policy. Expected MED, Local Pref or AS Path Prepend!!!!") + } + break + } + } + } + if !foundPrefix { + t.Errorf("Prefix %v not received on OTG", ipAddr) + } +} + +func TestBGPPolicy(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + otg := ate.OTG() + var otgConfig gosnappi.Config + t.Run("Configure OTG", func(t *testing.T) { + otgConfig = configureOTG(t, otg) + }) + + // DUT configurations. + t.Run("Configure DUT interfaces", func(t *testing.T) { + configureDUT(t, dut) + }) + + t.Run("Configure DEFAULT network instance", func(t *testing.T) { + fptest.ConfigureDefaultNetworkInstance(t, dut) + }) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + t.Run("Configure BGP v4 and v6 Neighbors", func(t *testing.T) { + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := createNewBgpSession(dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + }) + + t.Run("Verify port status on DUT", func(t *testing.T) { + verifyPortsUp(t, dut.Device) + }) + + t.Run("Verify BGP session", func(t *testing.T) { + VerifyBgpState(t, dut) + }) + + cases := []struct { + desc string + rpPolicy, policyTypePort1, policyTypePort2, policyStatement string + defPolicyPort1, defPolicyPort2 oc.E_RoutingPolicy_DefaultPolicyType + policyValue string + port1v4Prefix, port1v6Prefix, port2v4Prefix, port2v6Prefix string + isDeletePolicy bool + metricValue, asn uint32 + deleteNbrv4, deleteNbrv6 string + polNbrv4, polNbrv6 string + }{{ + desc: "Configure eBGP set MED Import Export Policy", + rpPolicy: setMEDPolicy, + policyTypePort1: "", + policyValue: "100", + policyStatement: matchStatement1, + defPolicyPort1: defAcceptRoute, + defPolicyPort2: defRejectRoute, + policyTypePort2: setMEDPolicy, + port1v4Prefix: advertisedRoutesv4Net2, + port1v6Prefix: advertisedRoutesv6Net2, + port2v4Prefix: advertisedRoutesv4Net1, + port2v6Prefix: advertisedRoutesv6Net1, + metricValue: 100, + polNbrv4: atePort2.IPv4, + polNbrv6: atePort2.IPv6, + isDeletePolicy: true, + deleteNbrv4: atePort1.IPv4, + deleteNbrv6: atePort1.IPv6, + asn: dutAS, + }, { + desc: "Configure eBGP increase MED Import Export Policy", + rpPolicy: setMEDPolicy, + policyTypePort1: "", + policyValue: "+100", + policyStatement: matchStatement1, + defPolicyPort1: defAcceptRoute, + defPolicyPort2: defRejectRoute, + policyTypePort2: setMEDPolicy, + port1v4Prefix: advertisedRoutesv4Net2, + port1v6Prefix: advertisedRoutesv6Net2, + port2v4Prefix: advertisedRoutesv4Net1, + port2v6Prefix: advertisedRoutesv6Net1, + metricValue: 150, + polNbrv4: atePort2.IPv4, + polNbrv6: atePort2.IPv6, + isDeletePolicy: true, + deleteNbrv4: atePort1.IPv4, + deleteNbrv6: atePort1.IPv6, + asn: dutAS, + }, { + desc: "Configure iBGP set Local Preference Import Export Policy", + rpPolicy: setLocalPrefPolicy, + policyTypePort1: setLocalPrefPolicy, + policyValue: "100", + policyStatement: matchStatement1, + defPolicyPort1: defRejectRoute, + defPolicyPort2: defAcceptRoute, + policyTypePort2: "", + port1v4Prefix: advertisedRoutesv4Net2, + port1v6Prefix: advertisedRoutesv6Net2, + port2v4Prefix: advertisedRoutesv4Net1, + port2v6Prefix: advertisedRoutesv6Net1, + metricValue: 100, + polNbrv4: atePort1.IPv4, + polNbrv6: atePort1.IPv6, + isDeletePolicy: true, + deleteNbrv4: atePort2.IPv4, + deleteNbrv6: atePort2.IPv6, + asn: dutAS, + }, { + desc: "Configure eBGP set NEXT-STATEMENT Import Export Policy", + rpPolicy: setNxtPolicy, + policyTypePort1: "", + policyValue: "70", + policyStatement: matchStatement1, + defPolicyPort1: defAcceptRoute, + defPolicyPort2: defRejectRoute, + policyTypePort2: setNxtPolicy, + port1v4Prefix: advertisedRoutesv4Net2, + port1v6Prefix: advertisedRoutesv6Net2, + port2v4Prefix: advertisedRoutesv4Net1, + port2v6Prefix: advertisedRoutesv6Net1, + metricValue: 70, + polNbrv4: atePort2.IPv4, + polNbrv6: atePort2.IPv6, + isDeletePolicy: true, + deleteNbrv4: atePort1.IPv4, + deleteNbrv6: atePort1.IPv6, + asn: dutAS, + }, { + desc: "Configure eBGP prepend 10 x local ASN Import Export Policy", + rpPolicy: setPrependPolicy, + policyTypePort1: "", + policyValue: "10", + policyStatement: matchStatement1, + defPolicyPort1: defAcceptRoute, + defPolicyPort2: defRejectRoute, + policyTypePort2: setPrependPolicy, + port1v4Prefix: advertisedRoutesv4Net2, + port1v6Prefix: advertisedRoutesv6Net2, + port2v4Prefix: advertisedRoutesv4Net1, + port2v6Prefix: advertisedRoutesv6Net1, + metricValue: asnRepeatN + 1, + polNbrv4: atePort2.IPv4, + polNbrv6: atePort2.IPv6, + isDeletePolicy: true, + deleteNbrv4: atePort1.IPv4, + deleteNbrv6: atePort1.IPv6, + asn: dutAS, + }, { + desc: "Configure eBGP prepend 10 x ASN Import Export Policy", + rpPolicy: setPrependPolicy, + policyTypePort1: "", + policyValue: "10", + policyStatement: matchStatement1, + defPolicyPort1: defAcceptRoute, + defPolicyPort2: defRejectRoute, + policyTypePort2: setPrependPolicy, + port1v4Prefix: advertisedRoutesv4Net2, + port1v6Prefix: advertisedRoutesv6Net2, + port2v4Prefix: advertisedRoutesv4Net1, + port2v6Prefix: advertisedRoutesv6Net1, + metricValue: asnRepeatN + 1, + polNbrv4: atePort2.IPv4, + polNbrv6: atePort2.IPv6, + isDeletePolicy: true, + deleteNbrv4: atePort1.IPv4, + deleteNbrv6: atePort1.IPv6, + asn: 23456, + }} + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + // Delete BGP import export policy + if tc.isDeletePolicy { + deleteBGPImportExportPolicy(t, dut, tc.deleteNbrv4, tc.deleteNbrv6, atePort2.IPv4, atePort2.IPv6) + } + + // Configure Routing Policy on the DUT. + configureASLocalPrefMEDPolicy(t, dut, tc.rpPolicy, tc.policyValue, tc.policyStatement, tc.asn) + if !deviations.DefaultRoutePolicyUnsupported(dut) { + // Configure BGP default import export policy on Port1 + configureBGPDefaultImportExportPolicy(t, dut, atePort1.IPv4, atePort1.IPv6, tc.defPolicyPort1) + // Configure BGP default import export policy on Port2 + configureBGPDefaultImportExportPolicy(t, dut, atePort2.IPv4, atePort2.IPv6, tc.defPolicyPort2) + } else { + if tc.rpPolicy == setLocalPrefPolicy { + tc.policyTypePort2 = setLocalPrefPolicy + // when default policy is not configured on port2 ebgp configuration is needed for setLocalPrefPolicy + t.Logf("Configuring BGP import export policy on Port2 when default policy is not configured for %v", tc.rpPolicy) + configureBGPImportExportPolicy(t, dut, atePort2.IPv4, atePort2.IPv6, tc.rpPolicy) + } + } + // Configure BGP import export policy + configureBGPImportExportPolicy(t, dut, tc.polNbrv4, tc.polNbrv6, tc.rpPolicy) + + // Verify BGP policy + verifyBgpPolicyTelemetry(t, dut, atePort1.IPv4, tc.defPolicyPort1, tc.policyTypePort1, true) + verifyBgpPolicyTelemetry(t, dut, atePort1.IPv6, tc.defPolicyPort1, tc.policyTypePort1, false) + verifyBgpPolicyTelemetry(t, dut, atePort2.IPv4, tc.defPolicyPort2, tc.policyTypePort2, true) + verifyBgpPolicyTelemetry(t, dut, atePort2.IPv6, tc.defPolicyPort2, tc.policyTypePort2, false) + + // Validate Prefixes + validateOTGBgpPrefixV4AndASLocalPrefMED(t, otg, dut, otgConfig, atePort1.Name+".BGP4.peer", tc.port1v4Prefix, advertisedRoutesv4PrefixLen, tc.rpPolicy, tc.metricValue) + validateOTGBgpPrefixV6AndASLocalPrefMED(t, otg, dut, otgConfig, atePort1.Name+".BGP6.peer", tc.port1v6Prefix, advertisedRoutesv6PrefixLen, tc.rpPolicy, tc.metricValue) + validateOTGBgpPrefixV4AndASLocalPrefMED(t, otg, dut, otgConfig, atePort2.Name+".BGP4.peer", tc.port2v4Prefix, advertisedRoutesv4PrefixLen, tc.rpPolicy, tc.metricValue) + validateOTGBgpPrefixV6AndASLocalPrefMED(t, otg, dut, otgConfig, atePort2.Name+".BGP6.peer", tc.port2v6Prefix, advertisedRoutesv6PrefixLen, tc.rpPolicy, tc.metricValue) + }) + } +} + +func validateImportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, prefix string, metricValue uint32) { + dni := deviations.DefaultNetworkInstance(dut) + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv4Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + if prefixAddr[0] == prefix { + found = true + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + if !deviations.SkipCheckingAttributeIndex(dut) { + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != metricValue { + t.Errorf("No local pref found for prefix %s", prefix) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == metricValue { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), prefix) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", prefix) + } + } + } + } + + if !found { + t.Errorf("No Route found for prefix %s", prefix) + } +} + +func validateImportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice, prefix string, metricValue uint32) { + dni := deviations.DefaultNetworkInstance(dut) + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv6Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Ipv6Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + if prefixAddr[0] == prefix { + found = true + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + if !deviations.SkipCheckingAttributeIndex(dut) { + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != metricValue { + t.Errorf("No local pref found for prefix %s", prefix) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == metricValue { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), prefix) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", prefix) + } + } + } + } + + if !found { + t.Errorf("No Route found for prefix %s", prefix) + } +} 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 new file mode 100644 index 00000000000..c11bd3995b2 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto @@ -0,0 +1,51 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "b4b2249f-9226-4555-9946-dc22cf6adae2" +plan_id: "RT-1.32" +description: "BGP policy actions - MED, LocPref, prepend, flow-control" +testbed: TESTBED_DUT_ATE_2LINKS +tags: [TAGS_DATACENTER_EDGE] +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + prepolicy_received_routes: true + default_route_policy_unsupported: true + skip_checking_attribute_index: true + skip_setting_statement_for_policy: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_rib_oc_path_unsupported: true + bgp_set_med_v7_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + } +} + diff --git a/feature/bgp/policybase/otg_tests/aspath_and_community_test/README.md b/feature/bgp/policybase/otg_tests/aspath_and_community_test/README.md new file mode 100644 index 00000000000..8f434b0bcb4 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/aspath_and_community_test/README.md @@ -0,0 +1,178 @@ +# RT-7.4: BGP Policy AS Path Set and Community Set + +## Summary + +BGP policy configuration for AS Paths and Community Sets + +## Procedure + +* RT-7.4.1 - Test setup + * Generate config for 2 DUT ports, with DUT port 1 eBGP session to ATE port 1. + + * Generate config for ATE 2 ports, with ATE port 1 eBGP session to DUT port 1. + + * Configure ATE port 1 to advertise ipv4 and ipv6 prefixes using the following aspaths and communities: + * prefix-set-1 with as path `[100, 200, 300]` and communities `[100:1, 200:2, 300:3]` + * prefix-set-2 with as path `[100, 400, 300]` and communities `[101:1]` + * prefix-set-3 with as path `[109]` and communities `[109:1]` + * prefix-set-4 with as path `[200]` and communities `[200:1]` + * prefix-set-5 with as path `[300]` and communities `[100:1]` + + * Establish eBGP sessions between ATE port-1 and DUT port-1 + * Generate traffic from ATE port-2 to all prefixes + * Validate that traffic is received on ATE port-1 for all prefixes + +* RT-7.4.2 - Validate single routing-policy containing as-path-set and ext-community-set + + * Create a as-path-set named `any_my_regex_aspath` with members + * `{ as-path-set-member = [ "(10[0-9]]|200)" ] }` + * Create a community-set named `any_my_regex_comms` with members and match options as follows: + * `{ community-member = [ "10[0-9]:1" ] }` + + * Create a `policy-definition` named 'path_and_community' with the following `statements` + * statement[name='match_community']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'any_my_regex_comms' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + * statement[name='match_as']/ + * conditions/bgp-conditions/match-as-path-set/config/as-path-set = 'any_my_regex_aspath' + * conditions/bgp-conditions/match-as-path-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + + * Send traffic + * Verify traffic is forwarded for routes with matching policy + * Verify traffic is not forwarded for routes without matching policy + +### Expected prefix matches + +| prefix-set | path_and_community | +| ------------ | ------------------ | +| prefix-set-1 | accept | +| prefix-set-2 | accept | +| prefix-set-3 | accept | +| prefix-set-4 | reject | +| prefix-set-5 | reject | + +## Config Parameter Coverage + +### Policy definition + +* /routing-policy/policy-definitions/policy-definition/config/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/config/name + +### Policy for community-set match + +* /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/config/community-set +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/ +import-policy + +### Policy for ext-community-set match + +* /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config/ext-community-set-name +* /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config/ext-community-member +* /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/config/ext-community-set +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/ +import-policy + +### Policy for as-path match + +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-name +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-member +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/config/as-path-set +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/config/match-set-options +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/ +import-policy + +## Telemetry Parameter Coverage + +### Policy definition state + +* /routing-policy/policy-definitions/policy-definition/state/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/state/name + +### Policy for community-set match state + +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/community-set + +### Policy for ext-community-set match state + +* /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/state/ext-community-set-name +* /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/state/ext-community-member +* /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/state/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/ext-community-set + +### Policy for as-path match state + +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/state/as-path-set-name +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/state/as-path-set-member +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/state/as-path-set +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/state/match-set-options + +### Paths to verify policy state + +* /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy + +### Paths to verify prefixes sent and received + +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy +* /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/installed + +### 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 + ### Policy definition + /routing-policy/policy-definitions/policy-definition/config/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/config/name: + ### Policy for community-set match + /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: + + ## State paths + ### Policy definition state + + /routing-policy/policy-definitions/policy-definition/state/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/state/name: + + ### Policy for community-set match state + + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/community-set: + + ### Paths to verify policy state + + /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy: + + ### Paths to verify prefixes sent and received + + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy: + /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/installed: +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + gNMI.Subscribe: +``` + diff --git a/feature/bgp/policybase/otg_tests/aspath_and_community_test/aspath_and_community_test.go b/feature/bgp/policybase/otg_tests/aspath_and_community_test/aspath_and_community_test.go new file mode 100644 index 00000000000..de109463b72 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/aspath_and_community_test/aspath_and_community_test.go @@ -0,0 +1,318 @@ +// 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 aspath_and_community_test + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "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" +) + +const ( + prefixV4Len = 30 + prefixV6Len = 126 + trafficPps = 100 + totalPackets = 1200 + bgpName = "BGP" + ImpPolicy = "routing-policy-ASPATH-COMMUNITY" + RPLPermitAll = "PERMIT-ALL" +) + +var prefixesV4 = [][]string{ + {"198.51.100.0", "198.51.100.4"}, + {"198.51.100.8", "198.51.100.12"}, + {"198.51.100.16", "198.51.100.20"}, + {"198.51.100.24", "198.51.100.28"}, + {"198.51.100.32", "198.51.100.40"}, +} + +var prefixesV6 = [][]string{ + {"2048:db1:64:64::0", "2048:db1:64:64::4"}, + {"2048:db1:64:64::8", "2048:db1:64:64::c"}, + {"2048:db1:64:64::10", "2048:db1:64:64::14"}, + {"2048:db1:64:64::18", "2048:db1:64:64::1c"}, + {"2048:db1:64:64::20", "2048:db1:64:64::24"}, +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureImportBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4 string, ipv6 string, aspathMatch []string, communityMatch string, aspathSetName string, communitySetName string, commMatchSetOptions oc.E_BgpPolicy_MatchSetOptionsType, aspMatchSetOptions oc.E_RoutingPolicy_MatchSetOptionsType) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition(ImpPolicy) + stmt1, err := pdef1.AppendNewStatement("match_as_and_community") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "any_my_aspath", err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + + aspathSet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateAsPathSet(aspathSetName) + aspathSet.SetAsPathSetMember(aspathMatch) + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetAsPathSet(aspathSetName) + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetMatchSetOptions(aspMatchSetOptions) + pdAllow := rp.GetOrCreatePolicyDefinition(RPLPermitAll) + st, err := pdAllow.AppendNewStatement("id-1") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + } + st.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + + // stmt2, err := pdef1.AppendNewStatement("match_comms") + // if err != nil { + // t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + // } + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + + if !(deviations.CommunityMemberRegexUnsupported(dut) && communitySetName == "any_my_3_comms") { + communitySet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySetName) + cs := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + if communityMatch != "" { + cs = append(cs, oc.UnionString(communityMatch)) + } + communitySet.SetCommunityMember(cs) + communitySet.SetMatchSetOptions(commMatchSetOptions) + } + + var communitySetCLIConfig string + if deviations.CommunityMemberRegexUnsupported(dut) && communitySetName == "any_my_3_comms" { + switch dut.Vendor() { + case ondatra.CISCO: + communitySetCLIConfig = fmt.Sprintf("community-set %v\n ios-regex '(10[0-9]:1)'\n end-set", communitySetName) + default: + t.Fatalf("Unsupported vendor %s for deviation 'CommunityMemberRegexUnsupported'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) + } + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(communitySetName) + } else { + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(communitySetName) + } + + if deviations.CommunityMemberRegexUnsupported(dut) && communitySetName == "any_my_3_comms" { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } else { + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } + + dni := deviations.DefaultNetworkInstance(dut) + pathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV6.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + policyV6.SetImportPolicy([]string{ImpPolicy}) + gnmi.Replace(t, dut, pathV6.Config(), policyV6) + + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policyV4 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV4.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + policyV4.SetImportPolicy([]string{ImpPolicy}) + gnmi.Replace(t, dut, pathV4.Config(), policyV4) +} + +func configureOTG(t *testing.T, bs *cfgplugins.BGPSession, prefixesV4 [][]string, prefixesV6 [][]string) { + var communityMembers = [][][]int{ + { + {100, 1}, {200, 2}, {300, 3}, + }, + { + {100, 1}, + }, + { + {109, 1}, + }, + { + {200, 1}, + }, + { + {100, 1}, + }, + } + + var aspathMembers = [][]uint32{ + {100, 200, 300}, + {100, 400, 300}, + {109}, + {200}, + {300}, + } + + devices := bs.ATETop.Devices().Items() + + ipv4 := devices[1].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := devices[1].Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + + ipv6 := devices[1].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := devices[1].Bgp().Ipv6Interfaces().Items()[0].Peers().Items()[0] + + for index, prefixes := range prefixesV4 { + bgp4PeerRoute := bgp4Peer.V4Routes().Add() + bgp4PeerRoute.SetName(bs.ATEPorts[1].Name + ".BGP4.peer.dut." + strconv.Itoa(index)) + bgp4PeerRoute.SetNextHopIpv4Address(ipv4.Address()) + + route4Address1 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[0]) + route4Address1.SetPrefix(prefixV4Len) + route4Address2 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[1]) + route4Address2.SetPrefix(prefixV4Len) + + asp4 := bgp4PeerRoute.AsPath().Segments().Add() + asp4.SetAsNumbers(aspathMembers[index]) + + bgp6PeerRoute := bgp6Peer.V6Routes().Add() + bgp6PeerRoute.SetName(bs.ATEPorts[1].Name + ".BGP6.peer.dut." + strconv.Itoa(index)) + bgp6PeerRoute.SetNextHopIpv6Address(ipv6.Address()) + + route6Address1 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][0]) + route6Address1.SetPrefix(prefixV6Len) + route6Address2 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][1]) + route6Address2.SetPrefix(prefixV6Len) + + asp6 := bgp6PeerRoute.AsPath().Segments().Add() + asp6.SetAsNumbers(aspathMembers[index]) + + for _, commu := range communityMembers[index] { + if commu[0] != 0 { + commv4 := bgp4PeerRoute.Communities().Add() + commv4.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv4.SetAsNumber(uint32(commu[0])) + commv4.SetAsCustom(uint32(commu[1])) + + commv6 := bgp6PeerRoute.Communities().Add() + commv6.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv6.SetAsNumber(uint32(commu[0])) + commv6.SetAsCustom(uint32(commu[1])) + } + } + } +} + +func configureFlow(t *testing.T, bs *cfgplugins.BGPSession, prefixPair []string, prefixType string, index int) { + + flow := bs.ATETop.Flows().Add().SetName("flow" + prefixType + strconv.Itoa(index)) + flow.Metrics().SetEnable(true) + + if prefixType == "ipv4" { + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv4"}). + SetRxNames([]string{bs.ATEPorts[1].Name + ".BGP4.peer.dut." + strconv.Itoa(index)}) + } else { + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv6"}). + SetRxNames([]string{bs.ATEPorts[1].Name + ".BGP6.peer.dut." + strconv.Itoa(index)}) + } + + flow.Duration().FixedPackets().SetPackets(totalPackets) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(trafficPps) + + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(bs.ATEPorts[1].MAC) + + if prefixType == "ipv4" { + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(bs.ATEPorts[0].IPv4) + v4.Dst().SetValues(prefixPair) + } else { + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(bs.ATEPorts[0].IPv6) + v6.Dst().SetValues(prefixPair) + } +} + +func verifyTraffic(t *testing.T, ate *ondatra.ATEDevice, prefixType string, testResults bool, index int) { + recvMetric := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow("flow"+prefixType+strconv.Itoa(index)).State()) + framesTx := recvMetric.GetCounters().GetOutPkts() + framesRx := recvMetric.GetCounters().GetInPkts() + + if framesTx == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if (testResults && framesRx == framesTx) || (!testResults && framesRx == 0) { + t.Logf("Traffic validation successful for criteria [%t] FramesTx: %d FramesRx: %d", testResults, framesTx, framesRx) + } else { + t.Errorf("Traffic validation failed for criteria [%t] FramesTx: %d FramesRx: %d", testResults, framesTx, framesRx) + } +} + +func TestCommunitySet(t *testing.T) { + testResults := [5]bool{true, true, true, false, false} + + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount2, nil) + bs.WithEBGP(t, []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST}, []string{"port2"}, true, true) + + configureOTG(t, bs, prefixesV4, prefixesV6) + bs.PushAndStart(t) + + t.Log("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + t.Log("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) + + ipv4 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv4Addresses().Items()[0].Address() + ipv6 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv6Addresses().Items()[0].Address() + aspathMatch := []string{"(10[0-9]|200)"} + communityMatch := "(10[0-9]:1)" + communitySetName := "any_my_3_comms" + aspathSetName := "any_my_aspath" + commMatchSetOptions := oc.BgpPolicy_MatchSetOptionsType_ANY + aspMatchSetOptions := oc.RoutingPolicy_MatchSetOptionsType_ANY + configureImportBGPPolicy(t, bs.DUT, ipv4, ipv6, aspathMatch, communityMatch, aspathSetName, communitySetName, commMatchSetOptions, aspMatchSetOptions) + sleepTime := time.Duration(totalPackets/trafficPps) + 5 + + for index, prefixPairV4 := range prefixesV4 { + bs.ATETop.Flows().Clear() + t.Logf("Running traffic test for IPv4 prefixes: [%s, %s]. Expected Result: [%t]", prefixPairV4[0], prefixPairV4[1], testResults[index]) + configureFlow(t, bs, prefixPairV4, "ipv4", index) + bs.PushAndStartATE(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + verifyTraffic(t, bs.ATE, "ipv4", testResults[index], index) + + bs.ATETop.Flows().Clear() + t.Logf("Running traffic test for IPv6 prefixes: [%s, %s]. Expected Result: [%t]", prefixesV6[index][0], prefixesV6[index][1], testResults[index]) + configureFlow(t, bs, prefixesV6[index], "ipv6", index) + bs.PushAndStartATE(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + verifyTraffic(t, bs.ATE, "ipv6", testResults[index], index) + } +} diff --git a/feature/bgp/policybase/otg_tests/aspath_and_community_test/metadata.textproto b/feature/bgp/policybase/otg_tests/aspath_and_community_test/metadata.textproto new file mode 100644 index 00000000000..27c44c4f08d --- /dev/null +++ b/feature/bgp/policybase/otg_tests/aspath_and_community_test/metadata.textproto @@ -0,0 +1,37 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + + +uuid: "61f4c5b2-e188-4cc8-9b8b-c744660db2f0" +plan_id: "RT-7.4" +description: "BGP Policy AS Path Set and Community Set" +testbed: TESTBED_DUT_ATE_2LINKS +tags: [TAGS_TRANSIT, TAGS_DATACENTER_EDGE] +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + skip_set_rp_match_set_options: true + skip_setting_disable_metric_propagation: true + } +} + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + bgp_conditions_match_community_set_unsupported: true + skip_setting_statement_for_policy: true + default_import_export_policy_unsupported: true + community_member_regex_unsupported: true + default_route_policy_unsupported: true + } +} + diff --git a/feature/bgp/policybase/otg_tests/aspath_test/README.md b/feature/bgp/policybase/otg_tests/aspath_test/README.md new file mode 100644 index 00000000000..24771c17653 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/aspath_test/README.md @@ -0,0 +1,139 @@ +# RT-7.3: BGP Policy AS Path Set + +## Summary + +BGP policy configuration for AS Paths and Community Sets + +## Procedure + +* RT-7.3.1 - Test setup + * Generate config for 2 DUT ports, with DUT port 1 eBGP session to ATE port 1 + + * Generate config for ATE 2 ports, with ATE port 1 eBGP session to DUT port 1 + + * Configure ATE port 1 to advertise ipv4 and ipv6 prefixes using the following as paths + * prefix-set-1 with as path `[100, 200, 300]` + * prefix-set-2 with as path `[100, 400, 300]` + * prefix-set-3 with as path `[110]` + * prefix-set-4 with as path `[400]` + * prefix-set-5 with as path `[100, 300, 200]` + * prefix-set-6 with as path `[1, 100, 200, 300, 400]` + + * Establish eBGP sessions between ATE port-1 and DUT port-1 + * Generate traffic from ATE port-2 to all prefixes + * Validate that traffic is received on ATE port-1 for all installed prefixes + +* RT-7.3.2 - Configure as-path-sets + * Configure DUT with the following routing policies + * Create the following /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/ + * Create as-path-set-name = "my_3_aspaths" with members + * `{ as-path-set-member = [ "100", "200", "300" ] }` + * Create an as-path-set-name 'my_regex_aspath-1' with members + * `{ as-path-set-member = [ "^100", "20[0-9]", "200$" ] }` + * Create an as-path-set-name "my_regex_aspath-2" as follows + * `{ as-path-set-member = [ "(^100)(.*)+(300$)" ] }` + + * Create /routing-policy/policy-definitions/policy-definition named 'match_any_aspaths' with the following statements + * statement[name='accept_any_my_3_aspaths']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'my_3_aspaths' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + + * Create /routing-policy/policy-definitions/policy-definition named 'match_not_my_3_aspaths' with the following statements + * statement[name='accept_not_my_3_aspaths']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'my_3_aspaths' + * conditions/bgp-conditions/match-community-set/config/match-set-options = INVERT + * actions/config/policy-result = ACCEPT_ROUTE + + * Create /routing-policy/policy-definitions/policy-definition named 'match_my_regex_aspath-1' with the following statements + * statement[name='accept_my_regex_aspath-1']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'my_regex_aspath-1' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + + * Create /routing-policy/policy-definitions/policy-definition named 'match_my_regex_aspath-2' with the following statements + * statement[name='accept_my_regex_aspath-2']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'my_regex_aspath-2' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + +* RT-7.3.3 - Replace /routing-policy DUT config + * For each DUT policy-definition + * Replace the configuration for BGP neighbor policy (`.../apply-policy/config/import-policy`) to the currently tested policy + * Verify prefixes sent, received and installed are as expected + * Send traffic + * Verify traffic is forwarded for prefixes with matching policy + * Verify traffic is not forwarded for prefixes without matching policy + +### Expected as-path matches + +| prefix-set | match_any_aspaths | match_not_my_3_aspaths | match_my_regex_aspath-1 | my_regex_aspath-2 | +| ------------ | ----------------- | ---------------------- | ----------------------- | ----------------- | +| prefix-set-1 | accept | reject | accept | accept | +| prefix-set-2 | accept | reject | accept | accept | +| prefix-set-3 | reject | accept | reject | reject | +| prefix-set-4 | reject | accept | reject | reject | +| prefix-set-5 | accept | reject | accept | reject | +| prefix-set-6 | accept | reject | accept | reject | + +## Config Parameter Coverage + +### Policy definition + +* /routing-policy/policy-definitions/policy-definition/config/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/config/name + +### Policy for as-path match + +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-name +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-member +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/config/as-path-set +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/config/match-set-options +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/ +import-policy + +## Telemetry Parameter Coverage + +### Policy definition state + +* /routing-policy/policy-definitions/policy-definition/state/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/state/name + +### Policy for as-path match state + +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/state/as-path-set-name +* /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/state/as-path-set-member +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/state/as-path-set +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/state/match-set-options + +### Paths to verify policy state + +* /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy + +### Paths to verify prefixes sent and received + +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy +* /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/installed + +### 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 ## + /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/conditions/match-prefix-set/config/prefix-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy: +## State Paths ## + /interfaces/interface/ethernet/state/mac-address: + +rpcs: + gnmi: + gNMI.Get: +``` diff --git a/feature/bgp/policybase/otg_tests/aspath_test/aspath_test.go b/feature/bgp/policybase/otg_tests/aspath_test/aspath_test.go new file mode 100644 index 00000000000..21acc4176fd --- /dev/null +++ b/feature/bgp/policybase/otg_tests/aspath_test/aspath_test.go @@ -0,0 +1,315 @@ +// 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 aspath_test + +import ( + "math/big" + "net" + "strconv" + "testing" + "time" + + "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" +) + +const ( + prefixV4Len = 30 + prefixV6Len = 126 + trafficPps = 100 + totalPackets = 1200 + bgpName = "BGP" + RPLPermitAll = "PERMIT-ALL" +) + +var prefixesV4 = [][]string{ + {"198.51.100.0", "198.51.100.4"}, + {"198.51.100.8", "198.51.100.12"}, + {"198.51.100.16", "198.51.100.20"}, + {"198.51.100.24", "198.51.100.28"}, + {"198.51.100.32", "198.51.100.36"}, + {"198.51.100.40", "198.51.100.44"}, +} + +var prefixesV6 = [][]string{ + {"2048:db1:64:64::0", "2048:db1:64:64::4"}, + {"2048:db1:64:64::8", "2048:db1:64:64::12"}, + {"2048:db1:64:64::16", "2048:db1:64:64::20"}, + {"2048:db1:64:64::24", "2048:db1:64:64::28"}, + {"2048:db1:64:64::32", "2048:db1:64:64::36"}, + {"2048:db1:64:64::40", "2048:db1:64:64::44"}, +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureImportBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4 string, ipv6 string, aspathSetName string, aspathMatch []string, matchSetOptions oc.E_RoutingPolicy_MatchSetOptionsType) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition("routePolicy") + stmt1, err := pdef1.AppendNewStatement("routePolicyStatement") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + aspathSet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateAsPathSet(aspathSetName) + aspathSet.SetAsPathSetMember(aspathMatch) + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetAsPathSet(aspathSetName) + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetMatchSetOptions(matchSetOptions) + pdAllow := rp.GetOrCreatePolicyDefinition(RPLPermitAll) + st, err := pdAllow.AppendNewStatement("id-1") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + } + st.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + + dni := deviations.DefaultNetworkInstance(dut) + pathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV6.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + policyV6.SetImportPolicy([]string{"routePolicy"}) + gnmi.Replace(t, dut, pathV6.Config(), policyV6) + + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policyV4 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV4.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } + policyV4.SetImportPolicy([]string{"routePolicy"}) + gnmi.Replace(t, dut, pathV4.Config(), policyV4) +} + +func configureOTG(t *testing.T, bs *cfgplugins.BGPSession, prefixesV4 [][]string, prefixesV6 [][]string, aspathMembers [][]uint32) { + devices := bs.ATETop.Devices().Items() + + //Configure ATE port 1 to advertise ipv4 and ipv6 prefixes using the following as paths + ipv4 := devices[0].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := devices[0].Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + + ipv6 := devices[0].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := devices[0].Bgp().Ipv6Interfaces().Items()[0].Peers().Items()[0] + + bgp6PeerRoute := bgp6Peer.V6Routes().Add() + bgp6PeerRoute.SetName(bs.ATEPorts[0].Name + ".BGP6.peer.dut") + bgp6PeerRoute.SetNextHopIpv6Address(ipv6.Address()) + + for index, prefixes := range prefixesV4 { + bgp4PeerRoute := bgp4Peer.V4Routes().Add() + bgp4PeerRoute.SetName("prefix-set-" + strconv.Itoa(index) + "-" + bs.ATEPorts[0].Name + ".BGP4.peer.dut") + bgp4PeerRoute.SetNextHopIpv4Address(ipv4.Address()) + route4Address1 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[0]) + route4Address1.SetPrefix(prefixV4Len) + route4Address2 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[1]) + route4Address2.SetPrefix(prefixV4Len) + asp4 := bgp4PeerRoute.AsPath().Segments().Add() + asp4.SetAsNumbers(aspathMembers[index]) + + route6Address1 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][0]) + route6Address1.SetPrefix(prefixV6Len) + route6Address2 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][1]) + route6Address2.SetPrefix(prefixV6Len) + + asp6 := bgp6PeerRoute.AsPath().Segments().Add() + asp6.SetAsNumbers(aspathMembers[index]) + } + +} + +// Generate traffic from ATE port-2 to all prefixes +func configureFlow(bs *cfgplugins.BGPSession, prefixPair []string, prefixType string, DstMac string, index int) { + bs.ATETop.Flows().Clear() + var rxNames []string + // port 1 will be the one receiving the traffic + rxNames = append(rxNames, "prefix-set-"+strconv.Itoa(index)+"-"+bs.ATEPorts[0].Name+".BGP4.peer.dut") + flow := bs.ATETop.Flows().Add().SetName("flow") + flow.Metrics().SetEnable(true) + + if prefixType == "ipv4" { + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[1].Name + ".IPv4"}). + SetRxNames(rxNames) + } else { + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[1].Name + ".IPv6"}). + SetRxNames(rxNames) + } + + flow.Duration().FixedPackets().SetPackets(totalPackets) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(trafficPps) + + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(bs.ATEPorts[1].MAC) + e.Dst().SetValue(DstMac) + + if prefixType == "ipv4" { + // write up one ip address for each prefixPair + incrementedSlice := incrementIPSlice(prefixPair) + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(bs.ATEPorts[1].IPv4) + v4.Dst().SetValues(incrementedSlice) + } else { + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(bs.ATEPorts[1].IPv6) + v6.Dst().SetValues(prefixPair) + } +} +func incrementIPSlice(ipSlice []string) []string { + incrementedSlice := make([]string, len(ipSlice)) + + for i, ipStr := range ipSlice { + ip := net.ParseIP(ipStr) + ipInt := big.NewInt(0) + ipInt.SetBytes(ip.To4()) + + ipInt.Add(ipInt, big.NewInt(1)) + + byteIP := ipInt.Bytes() + newIP := net.IP(byteIP) + + incrementedSlice[i] = newIP.String() + } + + return incrementedSlice +} + +func verifyTraffic(t *testing.T, ate *ondatra.ATEDevice, ports int, testResults bool) { + // compare the flows transmitted and received instead of the ports counters + framesTx := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow("flow").State()).GetCounters().GetOutPkts() + framesRx := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow("flow").State()).GetCounters().GetInPkts() + if framesTx == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if (testResults && framesRx == framesTx) || (!testResults && framesRx == 0) { + t.Logf("Traffic validation successful for criteria [%t] FramesTx: %d FramesRx: %d", testResults, framesTx, framesRx) + } else { + t.Errorf("Traffic validation failed for criteria [%t] FramesTx: %d FramesRx: %d", testResults, framesTx, framesRx) + } +} + +type testCase struct { + desc string + aspathSetName string + aspathMatch []string + matchSetOptions oc.E_RoutingPolicy_MatchSetOptionsType + testResults [6]bool +} + +func TestAsPathSet(t *testing.T) { + //Generate config for 2 DUT ports, with DUT port 1 eBGP session to ATE port 1 + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount2, nil) + //Generate config for ATE 2 ports, with ATE port 1 eBGP session to DUT port 1 + bs.WithEBGP(t, []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST}, []string{"port1"}, true, true) + + //Configure ATE port 1 to advertise ipv4 and ipv6 prefixes using the following as paths + var aspathMembers = [][]uint32{ + {100, 200, 300}, + {100, 400, 300}, + {110}, + {400}, + {100, 300, 200}, + {1, 100, 200, 300, 400}, + } + + configureOTG(t, bs, prefixesV4, prefixesV6, aspathMembers) + bs.PushAndStart(t) + + t.Log("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + t.Log("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) + + //Get the ipv4 and ipv6 addresses of the ATE port 1 + ipv4 := bs.ATETop.Devices().Items()[0].Ethernets().Items()[0].Ipv4Addresses().Items()[0].Address() + ipv6 := bs.ATETop.Devices().Items()[0].Ethernets().Items()[0].Ipv6Addresses().Items()[0].Address() + dutDstInterface := bs.DUT.Port(t, "port2").Name() + dstMac := gnmi.Get(t, bs.DUT, gnmi.OC().Interface(dutDstInterface).Ethernet().MacAddress().State()) + testCases := []testCase{ + { + desc: "Testing with match_any_aspaths", + aspathSetName: "match_any_aspaths", + aspathMatch: []string{"100", "200", "300"}, + matchSetOptions: oc.RoutingPolicy_MatchSetOptionsType_ANY, + testResults: [6]bool{true, true, false, false, true, true}, + }, + { + desc: "Testing with match_not_my_3_aspaths", + aspathSetName: "match_not_my_3_aspaths", + aspathMatch: []string{"100", "200", "300"}, + matchSetOptions: oc.RoutingPolicy_MatchSetOptionsType_INVERT, + testResults: [6]bool{false, false, true, true, false, false}, + }, + { + desc: "Testing with match_my_regex_aspath-1", + aspathSetName: "match_my_regex_aspath-1", + aspathMatch: []string{"^100", "20[0-9]", "200$"}, + matchSetOptions: oc.RoutingPolicy_MatchSetOptionsType_ANY, + testResults: [6]bool{true, true, false, false, true, true}, + }, + { + desc: "Testing with my_regex_aspath-2", + aspathSetName: "my_regex_aspath-2", + aspathMatch: []string{"(^100)(.*)+(300$)"}, + matchSetOptions: oc.RoutingPolicy_MatchSetOptionsType_ANY, + testResults: [6]bool{true, true, false, false, false, false}, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + if deviations.CommunityMemberRegexUnsupported(bs.DUT) { + for i, entry := range tc.aspathMatch { + switch entry { + case "^100": + tc.aspathMatch[i] = "65511 100" + case "(^100)(.*)+(300$)": + tc.aspathMatch[i] = "^65511_100_.*_300$" + } + } + } + configureImportBGPPolicy(t, bs.DUT, ipv4, ipv6, tc.aspathSetName, tc.aspathMatch, tc.matchSetOptions) + sleepTime := time.Duration(totalPackets/trafficPps) + 5 + + for index, prefixPairV4 := range prefixesV4 { + t.Logf("Running traffic test for IPv4 prefixes: [%s, %s]. Expected Result: [%t]", prefixPairV4[0], prefixPairV4[1], tc.testResults[index]) + configureFlow(bs, prefixPairV4, "ipv4", dstMac, index) + bs.ATE.OTG().PushConfig(t, bs.ATETop) + bs.ATE.OTG().StartProtocols(t) + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + verifyTraffic(t, bs.ATE, int(cfgplugins.PortCount2), tc.testResults[index]) + + t.Logf("Running traffic test for IPv6 prefixes: [%s, %s]. Expected Result: [%t]", prefixesV6[index][0], prefixesV6[index][1], tc.testResults[index]) + configureFlow(bs, prefixesV6[index], "ipv6", dstMac, index) + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + verifyTraffic(t, bs.ATE, int(cfgplugins.PortCount2), tc.testResults[index]) + } + }) + } +} diff --git a/feature/bgp/policybase/otg_tests/aspath_test/metadata.textproto b/feature/bgp/policybase/otg_tests/aspath_test/metadata.textproto new file mode 100644 index 00000000000..c59c7f78251 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/aspath_test/metadata.textproto @@ -0,0 +1,31 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "f4387453-37e8-4c2b-99c3-24b13cc51fd1" +plan_id: "RT-7.3" +description: "BGP Policy AS Path Set" +testbed: TESTBED_DUT_ATE_2LINKS +tags: [TAGS_TRANSIT, TAGS_DATACENTER_EDGE] +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + skip_set_rp_match_set_options: true + skip_setting_disable_metric_propagation: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + community_member_regex_unsupported: true + default_route_policy_unsupported: true + } +} diff --git a/feature/bgp/policybase/otg_tests/chained_policies_test/README.md b/feature/bgp/policybase/otg_tests/chained_policies_test/README.md new file mode 100644 index 00000000000..4134cc9d86c --- /dev/null +++ b/feature/bgp/policybase/otg_tests/chained_policies_test/README.md @@ -0,0 +1,271 @@ +# RT-1.29: BGP chained import/export policy attachment + +## Summary + +- A list of policies to be attached to a neighbor's import-policy +- A list of policies to be attached to a neighbor's export-policy +- Applicable to both IPv4 and IPv6 BGP neighbors + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed + +## Procedure +### Applying configuration + +For each section of configuration below, prepare a gnmi.SetBatch with all the configuration items appended to one SetBatch. Then apply the configuration to the DUT in one gnmi.Set using the `replace` option. + +#### Initial Setup: + +* Connect DUT port-1, 2 to ATE port-1, 2 +* Configure IPv4/IPv6 addresses on the ports +* Create an IPv4 networks i.e. ```ipv4-network-1 = 192.168.10.0/24``` attached to ATE port-1 +* Create an IPv6 networks i.e. ```ipv6-network-1 = 2024:db8:128:128::/64``` attached to ATE port-1 +* Create an IPv4 networks i.e. ```ipv4-network-2 = 192.168.20.0/24``` attached to ATE port-2 +* Create an IPv6 networks i.e. ```ipv6-network-2 = 2024:db8:64:64::/64``` attached to ATE port-2 +* Configure IPv4 and IPv6 eBGP between DUT Port-1 and ATE Port-1 + * Note: Chained policies will be applied to this eBGP session later in the test to validate the results + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:128:128::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-1 +* Configure IPv4 and IPv6 eBGP between DUT Port-2 and ATE Port-2 + * Note: This eBGP session is only used to advertise prefixes to DUT and receive prefixes from DUT + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-2 = 192.168.20.0/24``` and ```ipv6-network-2 = 2024:db8:64:64::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-2 + * Set default import and export policy to ```ACCEPT_ROUTE``` for this eBGP session only + * /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/default-export-policy + +### RT-1.29.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2594] +#### IPv4 BGP chained import policy test +--- +##### Configure a route-policy to match the prefix +* Configure an IPv4 route-policy definition with the name ```match-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-policy-v4``` configure a statement with the name ```match-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-policy-v4``` statement ```match-statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```ipv4-network-1``` i.e. ```192.168.10.0/24``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-policy-v4``` statement ```match-statement-v4``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-policy-v4``` statement ```match-statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + +##### Configure another route-policy to set the local preference +* Configure an IPv4 route-policy definition with the name ```lp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```lp-policy-v4``` configure a statement with the name ```lp-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```lp-policy-v4``` statement ```lp-statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set local-pref +* For routing-policy ```lp-policy-v4``` statement ```lp-statement-v4``` set local-preference to ```200``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref + +##### Configure chained bgp import policies for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Add both policies in the specified order to the leaf-list `import-policy`: ```[match-policy-v4, lp-policy-v4]``` + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + +##### Verification +* Use gNMI `replace` to send the configuration to the DUT. +* Use gNMI `subscribe` with mode `once` to retrieve the configuration `state` from the DUT. This is to confirm the chained import policies are successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy + +##### Validate test results +* Validate that the DUT receives the prefix ```ipv4-network-1``` i.e. ```192.168.10.0/24``` from BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv4-network-1``` i.e. ```192.168.10.0/24``` on DUT from BGP neighbor on ATE Port-1 has local preference set to 200 + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/local-pref +* Initiate traffic from ATE Port-2 towards the DUT destined to ```ipv4-network-1``` i.e. ```192.168.10.0/24``` + * Validate that the traffic is received on ATE Port-1 + +### RT-1.29.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2594] +#### IPv4 BGP chained export policy test +--- +##### Configure a route-policy to prepend AS-PATH +* Configure an IPv4 route-policy definition with the name ```asp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```asp-policy-v4``` configure a statement with the name ```asp-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +##### Configure BGP actions to prepend AS +* For routing-policy ```asp-policy-v4``` statement ```asp-statement-v4``` set AS-PATH prepend to the ASN of the DUT + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn + +##### Configure another route-policy to set the MED +* Configure an IPv4 route-policy definition with the name ```med-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy-v4``` configure a statement with the name ```med-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy-v4``` statement ```med-statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set MED +* For routing-policy ```med-policy-v4``` statement ```med-statement-v4``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med + +##### Configure chained bgp export policies for the DUT BGP neighbor on ATE Port-1 +* Set default export policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy +* Add both policies in order to the `export-policy` leaf-list, ie: ```[asp-policy-v4, med-policy-v4]``` + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy + +##### Verification +* Verify that chained export policies are successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy + +##### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` from BGP neighbor on DUT Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` on ATE from BGP neighbor on DUT Port-1 has AS-PATH with the ASN of DUT occuring twice + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member +* Validate that the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` on ATE from BGP neighbor on DUT Port-1 has MED set to ```1000``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-1 towards the DUT destined ```ipv4-network-2``` i.e. ```192.168.20.0/24``` + * Validate that the traffic is received on ATE Port-2 + +### RT-1.29.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2594] +#### IPv6 BGP chained import policy test +--- +##### Configure a route-policy to match the prefix +* Configure an IPv6 route-policy definition with the name ```match-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-policy-v6``` configure a statement with the name ```match-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-policy-v6``` statement ```match-statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v6``` set the ip-prefix to ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-policy-v6``` statement ```match-statement-v6``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-policy-v6``` statement ```match-statement-v6``` set prefix set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + +##### Configure another route-policy to set the local preference +* Configure an IPv6 route-policy definition with the name ```lp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```lp-policy-v6``` configure a statement with the name ```lp-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```lp-policy-v6``` statement ```lp-statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set local-pref +* For routing-policy ```lp-policy-v6``` statement ```lp-statement-v6``` set local-preference to ```200``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref + +##### Configure chained bgp import policies for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Add both policies in order to the leaf-list `import-policy`: ```[match-policy-v6, lp-policy-v6]``` + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + +##### Verification +* Verify that chained import policies are successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy + +##### Validate test results +* Validate that the DUT receives the prefix ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` from BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` from BGP neighbor on ATE Port-1 has local preference set to 200 + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-2 towards the DUT destined to ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` + * Validate that the traffic is received on ATE Port-1 + +### RT-1.29.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2594] +#### IPv6 BGP chained export policy test +--- +##### Configure a route-policy to prepend AS-PATH +* Configure an IPv6 route-policy definition with the name ```asp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```asp-policy-v6``` configure a statement with the name ```asp-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +##### Configure BGP actions to prepend AS +* For routing-policy ```asp-policy-v6``` statement ```asp-statement-v6``` set AS-PATH prepend to the ASN of the DUT + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn + +##### Configure another route-policy to set the MED +* Configure an IPv6 route-policy definition with the name ```med-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy-v6``` configure a statement with the name ```med-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy-v6``` statement ```med-statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set MED +* For routing-policy ```med-policy-v6``` statement ```med-statement-v6``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med + +##### Configure chained bgp export policies for the DUT BGP neighbor on ATE Port-1 +* Set default export policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy + +* Add both policies in order to the leaf-list `export-policy`: ```[asp-policy-v6, med-policy-v6]``` + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy + +##### Verification +* Verify that chained export policies are successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy + +##### Validate test results +* Validate that the ATE receives the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` from BGP neighbor on DUT Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` on ATE from BGP neighbor on DUT Port-1 has AS-PATH with the ASN of DUT occuring twice + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member +* Validate that the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` from BGP neighbor on DUT Port-1 has MED set to ```1000``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-1 towards the DUT destined to ```ipv6-network-1``` 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/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 +* /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 + +* /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 + + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: + +``` + +## Required DUT platform + +* vRX 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 new file mode 100644 index 00000000000..01cf548ea50 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/chained_policies_test/chained_policies_test.go @@ -0,0 +1,914 @@ +// 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 chained_policies_test + +import ( + "fmt" + "net" + "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" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + v41Route = "203.0.113.0" + v41TrafficStart = "203.0.113.1" + v42Route = "198.51.100.0" + v42TrafficStart = "198.51.100.1" + v4RoutePrefix = uint32(24) + v61Route = "2001:db8:128:128::" + v61TrafficStart = "2001:db8:128:128::1" + v62Route = "2001:db8:128:129::" + v62TrafficStart = "2001:db8:128:129::1" + v6RoutePrefix = uint32(64) + dutAS = uint32(65656) + ateAS1 = uint32(65657) + ateAS2 = uint32(65658) + bgpName = "BGP" + maskLenExact = "exact" + localPref = 200 + med = 1000 + v4Flow = "flow-v4" + v4PrefixPolicy = "prefix-policy-v4" + v4PrefixStatement = "prefix-statement-v4" + v4PrefixSet = "prefix-set-v4" + v4LPPolicy = "lp-policy-v4" + v4LPStatement = "lp-statement-v4" + v4ASPPolicy = "asp-policy-v4" + v4ASPStatement = "asp-statement-v4" + v4MedPolicy = "med-policy-v4" + v4MedStatement = "med-statement-v4" + v6Flow = "flow-v6" + v6PrefixPolicy = "prefix-policy-v6" + v6PrefixStatement = "prefix-statement-v6" + v6PrefixSet = "prefix-set-v6" + v6LPPolicy = "lp-policy-v6" + v6LPStatement = "lp-statement-v6" + v6ASPPolicy = "asp-policy-v6" + v6ASPStatement = "asp-statement-v6" + v6MedPolicy = "med-policy-v6" + v6MedStatement = "med-statement-v6" + peerGrpNamev4 = "BGP-PEER-GROUP-V4" + peerGrpNamev6 = "BGP-PEER-GROUP-V6" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:1", + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:2", + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:5", + IPv6Len: ipv6PrefixLen, + } + + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:01:01:01:02", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + advertisedIPv41 = ipAddr{address: v41Route, prefix: v4RoutePrefix} + advertisedIPv42 = ipAddr{address: v42Route, prefix: v4RoutePrefix} + advertisedIPv61 = ipAddr{address: v61Route, prefix: v6RoutePrefix} + advertisedIPv62 = ipAddr{address: v62Route, prefix: v6RoutePrefix} +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type ipAddr struct { + address string + prefix uint32 +} + +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() +} + +type testData struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + otgP1 gosnappi.Device + otgP2 gosnappi.Device +} + +type testCase struct { + name string + desc string + applyPolicy func(t *testing.T, dut *ondatra.DUTDevice, operation string) + validate func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) + ipv4 bool + flowConfig flowConfig +} + +type flowConfig struct { + src attrs.Attributes + dstNw string + dstIP string +} + +func TestBGPChainedPolicies(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + devs := configureOTG(t, ate, top) + td := testData{ + dut: dut, + ate: ate, + top: top, + otgP1: devs[0], + otgP2: devs[1], + } + td.advertiseRoutesWithEBGP(t) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + defer ate.OTG().StopProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + td.verifyDUTBGPEstablished(t) + td.verifyOTGBGPEstablished(t) + + testCases := []testCase{ + { + name: "IPv4BGPChainedImportPolicy", + desc: "IPv4 BGP chained import policy test", + applyPolicy: configureImportRoutingPolicy, + validate: validateImportRoutingPolicy, + ipv4: true, + flowConfig: flowConfig{src: atePort2, dstNw: "v4-bgpNet-dev1", dstIP: v41TrafficStart}, + }, + { + name: "IPv4BGPChainedExportPolicy", + desc: "IPv4 BGP chained export policy test", + applyPolicy: configureExportRoutingPolicy, + validate: validateExportRoutingPolicy, + ipv4: true, + flowConfig: flowConfig{src: atePort1, dstNw: "v4-bgpNet-dev2", dstIP: v42TrafficStart}, + }, + { + name: "IPv6BGPChainedImportPolicy", + desc: "IPv6 BGP chained import policy test", + applyPolicy: configureImportRoutingPolicyV6, + validate: validateImportRoutingPolicyV6, + ipv4: false, + flowConfig: flowConfig{src: atePort2, dstNw: "v6-bgpNet-dev1", dstIP: v61TrafficStart}, + }, + { + name: "IPv6BGPChainedExportPolicy", + desc: "IPv6 BGP chained export policy test", + applyPolicy: configureExportRoutingPolicyV6, + validate: validateExportRoutingPolicyV6, + ipv4: false, + flowConfig: flowConfig{src: atePort1, dstNw: "v6-bgpNet-dev2", dstIP: v62TrafficStart}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Logf("Description: %s", tc.desc) + tc.applyPolicy(t, dut, "set") + defer tc.applyPolicy(t, dut, "delete") + tc.validate(t, dut, ate) + + if tc.ipv4 { + createFlow(t, td, tc.flowConfig) + checkTraffic(t, td, v4Flow) + } else { + createFlowV6(t, td, tc.flowConfig) + checkTraffic(t, td, v6Flow) + } + }) + } +} + +func configureImportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, operation string) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition(v4PrefixPolicy) + stmt1, err := pdef1.AppendNewStatement(v4PrefixStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4PrefixStatement, err) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v4PrefixSet) + prefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + prefixSet.GetOrCreatePrefix(advertisedIPv41.cidr(t), maskLenExact) + + if !deviations.SkipSetRpMatchSetOptions(dut) { + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v4PrefixSet) + + pdef2 := rp.GetOrCreatePolicyDefinition(v4LPPolicy) + stmt2, err := pdef2.AppendNewStatement(v4LPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4LPStatement, err) + } + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetLocalPref(localPref) + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + } else if operation == "delete" { + gnmi.BatchDelete(batch, gnmi.OC().RoutingPolicy().Config()) + } + } + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultImportExportPolicyUnsupported(dut) { + policy.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + policy.SetImportPolicy([]string{v4PrefixPolicy, v4LPPolicy}) + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, path.Config(), policy) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, path.Config(), policy) + } else if operation == "delete" { + gnmi.BatchDelete(batch, path.Config()) + } + batch.Set(t, dut) + } +} + +func validateImportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policy := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy](t, dut, path.State()) + importPolicies := policy.GetImportPolicy() + if !deviations.FlattenPolicyWithMultipleStatements(dut) { + if len(importPolicies) != 2 { + t.Errorf("ImportPolicy = %v, want %v", importPolicies, []string{v4PrefixPolicy, v4LPPolicy}) + } + } + if !deviations.BGPRibOcPathUnsupported(dut) { + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv4Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + t.Logf("Route: %v, lr.GetPrefix() -> %v, advertisedIPv41.address: %s, prefixAddr[0]: %s", k, lr.GetPrefix(), advertisedIPv41.address, prefixAddr[0]) + if prefixAddr[0] == advertisedIPv41.address { + found = true + if !deviations.SkipCheckingAttributeIndex(dut) { + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != localPref { + t.Errorf("No local pref found for prefix %s", advertisedIPv41.address) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == localPref { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), advertisedIPv41.address) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", advertisedIPv41.address) + } + } + } + } + + if !found { + t.Errorf("No Route found for prefix %s", advertisedIPv41.address) + } + } +} + +func configureExportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, operation string) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition(v4ASPPolicy) + stmt1, err := pdef1.AppendNewStatement(v4ASPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4ASPStatement, err) + } + stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend().SetAsn(dutAS) + + if deviations.FlattenPolicyWithMultipleStatements(dut) { + stmt2, err := pdef1.AppendNewStatement(v4MedStatement) + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(med)) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4MedStatement, err) + } + } else { + pdef2 := rp.GetOrCreatePolicyDefinition(v4MedPolicy) + stmt2, err := pdef2.AppendNewStatement(v4MedStatement) + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(med)) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4MedStatement, err) + } + } + + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + } else if operation == "delete" { + gnmi.BatchDelete(batch, gnmi.OC().RoutingPolicy().Config()) + } + } + + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultImportExportPolicyUnsupported(dut) { + policy.SetDefaultExportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + + // As Per RFC 8212, Routes contained in an Adj-RIB-In associated with an EBGP peer + // SHALL NOT be considered eligible in the Decision Process if no + // explicit Import Policy has been applied. + importPolPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort2.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + eBGPPeerPolicy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort2.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultImportExportPolicyUnsupported(dut) { + eBGPPeerPolicy.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } + + if deviations.FlattenPolicyWithMultipleStatements(dut) { + policy.SetExportPolicy([]string{v4ASPPolicy}) + } else { + policy.SetExportPolicy([]string{v4ASPPolicy, v4MedPolicy}) + } + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, path.Config(), policy) + gnmi.Update(t, dut, importPolPath.Config(), eBGPPeerPolicy) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, path.Config(), policy) + gnmi.BatchReplace(batch, importPolPath.Config(), eBGPPeerPolicy) + } else if operation == "delete" { + gnmi.BatchDelete(batch, path.Config()) + gnmi.BatchDelete(batch, importPolPath.Config()) + } + batch.Set(t, dut) + } + time.Sleep(time.Second * 60) +} + +func validateExportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policy := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy](t, dut, path.State()) + exportPolicies := policy.GetExportPolicy() + if !deviations.FlattenPolicyWithMultipleStatements(dut) { + if len(exportPolicies) != 2 { + t.Errorf("ExportPolicy = %v, want %v", exportPolicies, []string{v4ASPPolicy, v4MedPolicy}) + } + } + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv4Prefix](t, ate.OTG(), gnmi.OTG().BgpPeer("atePort1.BGP4.peer").UnicastIpv4PrefixAny().State()) + found := false + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == v42Route && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == v4RoutePrefix { + found = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix.GetAddress(), v42Route) + if bgpPrefix.GetMultiExitDiscriminator() != med { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), med) + } + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), med) + asPaths := bgpPrefix.AsPath + for _, ap := range asPaths { + count := 0 + for _, an := range ap.AsNumbers { + if an == dutAS { + count++ + } + } + if count == 2 { + t.Logf("ASP for prefix %v is correct, got ASP %v", bgpPrefix.GetAddress(), ap.AsNumbers) + } + } + break + } + } + if !found { + t.Errorf("No Route found for prefix %s", v42Route) + } +} + +func configureImportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice, operation string) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition(v6PrefixPolicy) + stmt1, err := pdef1.AppendNewStatement(v6PrefixStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6PrefixStatement, err) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v6PrefixSet) + prefixSet.SetMode(oc.PrefixSet_Mode_IPV6) + prefixSet.GetOrCreatePrefix(advertisedIPv61.cidr(t), maskLenExact) + + if !deviations.SkipSetRpMatchSetOptions(dut) { + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v6PrefixSet) + + pdef2 := rp.GetOrCreatePolicyDefinition(v6LPPolicy) + stmt2, err := pdef2.AppendNewStatement(v6LPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6LPStatement, err) + } + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetLocalPref(localPref) + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + } else if operation == "delete" { + gnmi.BatchDelete(batch, gnmi.OC().RoutingPolicy().Config()) + } + } + + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultImportExportPolicyUnsupported(dut) { + policy.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + + policy.SetImportPolicy([]string{v6PrefixPolicy, v6LPPolicy}) + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, path.Config(), policy) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, path.Config(), policy) + } else if operation == "delete" { + gnmi.BatchDelete(batch, path.Config()) + } + batch.Set(t, dut) + } + time.Sleep(time.Second * 60) +} + +func validateImportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policy := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy](t, dut, path.State()) + importPolicies := policy.GetImportPolicy() + if !deviations.FlattenPolicyWithMultipleStatements(dut) { + if len(importPolicies) != 2 { + t.Errorf("ImportPolicy = %v, want %v", importPolicies, []string{v6PrefixPolicy, v6LPPolicy}) + } + } + if !deviations.BGPRibOcPathUnsupported(dut) { + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv6Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Ipv6Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + if prefixAddr[0] == advertisedIPv61.address { + found = true + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + if !deviations.SkipCheckingAttributeIndex(dut) { + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != localPref { + t.Errorf("No local pref found for prefix %s", advertisedIPv61.address) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == localPref { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), advertisedIPv61.address) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", advertisedIPv61.address) + } + } + } + } + if !found { + t.Errorf("No Route found for prefix %s", advertisedIPv61.address) + } + } +} + +func configureExportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice, operation string) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition(v6ASPPolicy) + stmt1, err := pdef1.AppendNewStatement(v6ASPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6ASPStatement, err) + } + stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend().SetAsn(dutAS) + + if deviations.FlattenPolicyWithMultipleStatements(dut) { + stmt2, err := pdef1.AppendNewStatement(v6MedStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6MedStatement, err) + } + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(med)) + } else { + pdef2 := rp.GetOrCreatePolicyDefinition(v6MedPolicy) + stmt2, err := pdef2.AppendNewStatement(v6MedStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6MedStatement, err) + } + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(med)) + } + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + } else if operation == "delete" { + gnmi.BatchDelete(batch, gnmi.OC().RoutingPolicy().Config()) + } + } + + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultImportExportPolicyUnsupported(dut) { + policy.SetDefaultExportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + + // As Per RFC 8212, Routes contained in an Adj-RIB-In associated with an EBGP peer + // SHALL NOT be considered eligible in the Decision Process if no + // explicit Import Policy has been applied. + importPolPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort2.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + eBGPPeerPolicy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort2.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + if !deviations.DefaultImportExportPolicyUnsupported(dut) { + eBGPPeerPolicy.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } + + if deviations.FlattenPolicyWithMultipleStatements(dut) { + policy.SetExportPolicy([]string{v6ASPPolicy}) + } else { + policy.SetExportPolicy([]string{v6ASPPolicy, v6MedPolicy}) + } + if deviations.SkipSettingStatementForPolicy(dut) { + gnmi.Update(t, dut, path.Config(), policy) + gnmi.Update(t, dut, importPolPath.Config(), eBGPPeerPolicy) + } else { + if operation == "set" { + gnmi.BatchReplace(batch, path.Config(), policy) + gnmi.BatchReplace(batch, importPolPath.Config(), eBGPPeerPolicy) + } else if operation == "delete" { + gnmi.BatchDelete(batch, path.Config()) + gnmi.BatchDelete(batch, importPolPath.Config()) + } + batch.Set(t, dut) + } + time.Sleep(time.Second * 60) +} + +func validateExportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policy := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy](t, dut, path.State()) + exportPolicies := policy.GetExportPolicy() + if !deviations.FlattenPolicyWithMultipleStatements(dut) { + if len(exportPolicies) != 2 { + t.Errorf("ExportPolicy = %v, want %v", exportPolicies, []string{v6ASPPolicy, v6MedPolicy}) + } + } + + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv6Prefix](t, ate.OTG(), gnmi.OTG().BgpPeer("atePort1.BGP6.peer").UnicastIpv6PrefixAny().State()) + found := false + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == v62Route && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == v6RoutePrefix { + found = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix, v62Route) + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), med) + if bgpPrefix.GetMultiExitDiscriminator() != med { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), med) + } + asPaths := bgpPrefix.AsPath + for _, ap := range asPaths { + count := 0 + for _, an := range ap.AsNumbers { + if an == dutAS { + count++ + } + } + if count == 2 { + t.Logf("ASP for prefix %v is correct, got ASP %v", bgpPrefix.GetAddress(), ap.AsNumbers) + } + } + break + } + } + if !found { + t.Errorf("No Route found for prefix %s", v62Route) + } +} + +func createFlow(t *testing.T, td testData, fc flowConfig) { + td.top.Flows().Clear() + + t.Log("Configuring v4 traffic flow") + v4Flow := td.top.Flows().Add().SetName(v4Flow) + v4Flow.Metrics().SetEnable(true) + v4Flow.TxRx().Device(). + SetTxNames([]string{fc.src.Name + ".IPv4"}). + SetRxNames([]string{fc.dstNw}) + v4Flow.Size().SetFixed(512) + v4Flow.Rate().SetPps(100) + v4Flow.Duration().Continuous() + e1 := v4Flow.Packet().Add().Ethernet() + e1.Src().SetValue(fc.src.MAC) + v4 := v4Flow.Packet().Add().Ipv4() + v4.Src().SetValue(fc.src.IPv4) + v4.Dst().Increment().SetStart(fc.dstIP).SetCount(1) + + td.ate.OTG().PushConfig(t, td.top) + td.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv4") +} + +func createFlowV6(t *testing.T, td testData, fc flowConfig) { + td.top.Flows().Clear() + + t.Log("Configuring v6 traffic flow") + v6Flow := td.top.Flows().Add().SetName(v6Flow) + v6Flow.Metrics().SetEnable(true) + v6Flow.TxRx().Device(). + SetTxNames([]string{fc.src.Name + ".IPv6"}). + SetRxNames([]string{fc.dstNw}) + v6Flow.Size().SetFixed(512) + v6Flow.Rate().SetPps(100) + v6Flow.Duration().Continuous() + e1 := v6Flow.Packet().Add().Ethernet() + e1.Src().SetValue(fc.src.MAC) + v6 := v6Flow.Packet().Add().Ipv6() + v6.Src().SetValue(fc.src.IPv6) + v6.Dst().Increment().SetStart(fc.dstIP).SetCount(1) + + td.ate.OTG().PushConfig(t, td.top) + td.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv6") +} + +func checkTraffic(t *testing.T, td testData, flowName string) { + td.ate.OTG().StartTraffic(t) + time.Sleep(time.Second * 30) + td.ate.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + otgutils.LogPortMetrics(t, td.ate.OTG(), td.top) + + t.Log("Checking flow telemetry...") + recvMetric := gnmi.Get(t, td.ate.OTG(), gnmi.OTG().Flow(flowName).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + + if lossPct > 1 { + t.Errorf("FAIL- Got %v%% packet loss for %s ; expected < 1%%", lossPct, flowName) + } +} + +func (td *testData) advertiseRoutesWithEBGP(t *testing.T) { + t.Helper() + + root := &oc.Root{} + ni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(td.dut)) + bgpP := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName) + bgpP.SetEnabled(true) + bgp := bgpP.GetOrCreateBgp() + + g := bgp.GetOrCreateGlobal() + g.SetAs(dutAS) + g.SetRouterId(dutPort1.IPv4) + 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) + + if deviations.DefaultImportExportPolicyUnsupported(td.dut) { + t.Logf("Configuring default route-policy for BGP on DUT") + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition("PERMIT-ALL") + stmt, err := pdef.AppendNewStatement("20") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "20", err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Update(t, td.dut, gnmi.OC().RoutingPolicy().Config(), rp) + } + pgv4 := bgp.GetOrCreatePeerGroup(peerGrpNamev4) + pgv4.PeerGroupName = ygot.String(peerGrpNamev4) + pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNamev6) + pgv6.PeerGroupName = ygot.String(peerGrpNamev6) + pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + + nV41 := bgp.GetOrCreateNeighbor(atePort1.IPv4) + nV41.SetPeerAs(ateAS1) + nV41.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV41.PeerGroup = ygot.String(peerGrpNamev4) + if deviations.DefaultImportExportPolicyUnsupported(td.dut) { + afisafiv41 := nV41.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afisafiv41.GetOrCreateApplyPolicy().SetImportPolicy([]string{"PERMIT-ALL"}) + afisafiv41.GetOrCreateApplyPolicy().SetExportPolicy([]string{"PERMIT-ALL"}) + } + nV42 := bgp.GetOrCreateNeighbor(atePort2.IPv4) + nV42.SetPeerAs(ateAS2) + nV42.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV42.PeerGroup = ygot.String(peerGrpNamev4) + if deviations.DefaultImportExportPolicyUnsupported(td.dut) { + afisafiv42 := nV42.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afisafiv42.GetOrCreateApplyPolicy().SetImportPolicy([]string{"PERMIT-ALL"}) + afisafiv42.GetOrCreateApplyPolicy().SetExportPolicy([]string{"PERMIT-ALL"}) + } + nV61 := bgp.GetOrCreateNeighbor(atePort1.IPv6) + nV61.SetPeerAs(ateAS1) + nV61.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nV61.PeerGroup = ygot.String(peerGrpNamev6) + if deviations.DefaultImportExportPolicyUnsupported(td.dut) { + afisafiv61 := nV61.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + afisafiv61.GetOrCreateApplyPolicy().SetImportPolicy([]string{"PERMIT-ALL"}) + afisafiv61.GetOrCreateApplyPolicy().SetExportPolicy([]string{"PERMIT-ALL"}) + } + nV62 := bgp.GetOrCreateNeighbor(atePort2.IPv6) + nV62.SetPeerAs(ateAS2) + nV62.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nV62.PeerGroup = ygot.String(peerGrpNamev6) + if deviations.DefaultImportExportPolicyUnsupported(td.dut) { + afisafiv62 := nV62.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + afisafiv62.GetOrCreateApplyPolicy().SetImportPolicy([]string{"PERMIT-ALL"}) + afisafiv62.GetOrCreateApplyPolicy().SetExportPolicy([]string{"PERMIT-ALL"}) + } + gnmi.Update(t, td.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Config(), ni) + + // configure eBGP on OTG port1 + ipv41 := td.otgP1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dev1BGP := td.otgP1.Bgp().SetRouterId(atePort1.IPv4) + bgp4Peer1 := dev1BGP.Ipv4Interfaces().Add().SetIpv4Name(ipv41.Name()).Peers().Add().SetName(td.otgP1.Name() + ".BGP4.peer") + bgp4Peer1.SetPeerAddress(dutPort1.IPv4).SetAsNumber(ateAS1).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + bgp4Peer1.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + + ipv61 := td.otgP1.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer1 := dev1BGP.Ipv6Interfaces().Add().SetIpv6Name(ipv61.Name()).Peers().Add().SetName(td.otgP1.Name() + ".BGP6.peer") + bgp6Peer1.SetPeerAddress(dutPort1.IPv6).SetAsNumber(ateAS1).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + bgp6Peer1.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // configure emulated network on ATE port1 + netv41 := bgp4Peer1.V4Routes().Add().SetName("v4-bgpNet-dev1") + netv41.Addresses().Add().SetAddress(advertisedIPv41.address).SetPrefix(advertisedIPv41.prefix) + netv61 := bgp6Peer1.V6Routes().Add().SetName("v6-bgpNet-dev1") + netv61.Addresses().Add().SetAddress(advertisedIPv61.address).SetPrefix(advertisedIPv61.prefix) + + // configure eBGP on OTG port2 + ipv42 := td.otgP2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dev2BGP := td.otgP2.Bgp().SetRouterId(atePort2.IPv4) + bgp4Peer2 := dev2BGP.Ipv4Interfaces().Add().SetIpv4Name(ipv42.Name()).Peers().Add().SetName(td.otgP2.Name() + ".BGP4.peer") + bgp4Peer2.SetPeerAddress(dutPort2.IPv4).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + bgp4Peer2.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + + ipv62 := td.otgP2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer2 := dev2BGP.Ipv6Interfaces().Add().SetIpv6Name(ipv62.Name()).Peers().Add().SetName(td.otgP2.Name() + ".BGP6.peer") + bgp6Peer2.SetPeerAddress(dutPort2.IPv6).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + bgp6Peer2.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // configure emulated network on ATE port2 + netv42 := bgp4Peer2.V4Routes().Add().SetName("v4-bgpNet-dev2") + netv42.Addresses().Add().SetAddress(advertisedIPv42.address).SetPrefix(advertisedIPv42.prefix) + netv62 := bgp6Peer2.V6Routes().Add().SetName("v6-bgpNet-dev2") + netv62.Addresses().Add().SetAddress(advertisedIPv62.address).SetPrefix(advertisedIPv62.prefix) +} + +func (td *testData) verifyDUTBGPEstablished(t *testing.T) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().NeighborAny().SessionState().State() + watch := gnmi.WatchAll(t, td.dut, sp, 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if !ok || state != oc.Bgp_Neighbor_SessionState_ESTABLISHED { + return false + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established: got %v", val) + } + t.Log("DUT BGP sessions established") +} + +// VerifyOTGBGPEstablished verifies on OTG BGP peer establishment +func (td *testData) verifyOTGBGPEstablished(t *testing.T) { + sp := gnmi.OTG().BgpPeerAny().SessionState().State() + watch := gnmi.WatchAll(t, td.ate.OTG(), sp, 2*time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + state, ok := val.Val() + if !ok || state != otgtelemetry.BgpPeer_SessionState_ESTABLISHED { + return false + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established: got %v", val) + } + t.Log("OTG BGP sessions established") +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + b := &gnmi.SetBatch{} + gnmi.BatchReplace(b, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.BatchReplace(b, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + b.Set(t, dut) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p1) + fptest.SetPortSpeed(t, p2) + } + + 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) + } +} + +func configureOTG(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) []gosnappi.Device { + t.Helper() + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + + d1 := atePort1.AddToOTG(top, p1, &dutPort1) + d2 := atePort2.AddToOTG(top, p2, &dutPort2) + return []gosnappi.Device{d1, d2} +} diff --git a/feature/bgp/policybase/otg_tests/chained_policies_test/metadata.textproto b/feature/bgp/policybase/otg_tests/chained_policies_test/metadata.textproto new file mode 100644 index 00000000000..6dc207736c3 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/chained_policies_test/metadata.textproto @@ -0,0 +1,42 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "bf6a9bb0-caa7-40e9-bb2a-5a47b4589262" +plan_id: "RT-1.29" +description: "BGP chained import/export policy attachment" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + missing_value_for_defaults: true + skip_set_rp_match_set_options: true + default_import_export_policy_unsupported: false + skip_setting_statement_for_policy: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_rib_oc_path_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + bgp_rib_oc_path_unsupported: true + default_import_export_policy_unsupported: true + skip_setting_statement_for_policy: true + skip_checking_attribute_index: true + flatten_policy_with_multiple_statements: true + } +} + 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 new file mode 100644 index 00000000000..0f3f756d0d6 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/comm_match_action_test/README.md @@ -0,0 +1,134 @@ +# RT-7.8: BGP Policy Match Standard Community and Add Community Import/Export Policy + +## Summary + +Configure bgp policy to add communities to routes by matching on the following +criteria. + +* RT-7.8.1 Validate test environment +* RT-7.8.2 Validate policy to set standard community for various policies using OC release 3.x + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed + +## Procedure + +* Testbed configuration - Setup eBGP sessions and prefixes. + * Generate config for 2 DUT and ATE ports where: + * DUT port 1 to ATE port 1. + * DUT port 2 to ATE port 2. + * Configure ATE port 1 with an external type BGP session to DUT port 1. + * Advertise ipv4 and ipv6 prefixes to DUT port 1 using the following communities: + * prefix-set-1 with 2 ipv6 and 2 ipv4 routes without communities. + * prefix-set-2 with 2 ipv6 and 2 ipv4 routes with communities `[5:5, 6:6 ]`. + +* RT-7.8.1 - Validate prefixes are propagated by DUT + * For IPv4 and IPv6 prefixes: + * Observe received prefixes at ATE port-2. + * Send traffic from ATE port-2 to all prefix-sets-1,2. + * Verify traffic is received on ATE port 1 for all prefixes. + * Stop traffic + +* RT-7.8.2 - Create policy to set standard community for all routes using OC release 3.x + * Configure the following community sets on the DUT. + (prefix: `/routing-policy/defined-sets/bgp-defined-sets/`) + * Create a `community-sets/community-set` named 'match_std_comms' with members as follows: + * community-member = [ "5:5" ] + * Create a `community-sets/community-set` named 'add_std_comms' with members as follows: + * community-member = [ "10:10", "20:20", "30:30" ] + + * Create `/routing-policy/policy-definitions/policy-definition/policy-definition[name='add_std_comms']/` + with the following `statements/` + * statement[name='add_std_comms']/ + * actions/bgp-actions/set-community/reference/config/community-set-refs = + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set[name='add_std_comms'] + * actions/bgp-actions/set-community/config/options = ADD + * actions/bgp-actions/set-community/config/method = REFERENCE + * actions/config/policy-result = NEXT_STATEMENT + * statement[name='accept_all_routes']/ + * actions/config/policy-result = ACCEPT_ROUTE + + * Create a `/routing-policy/policy-definitions/policy-definition/policy-definition[name='match_and_add_comms'/` + with the following `statements/` + * statement[name='match_and_add_std_comms']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'match_std_comms' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/bgp-actions/set-community/reference/config/community-set-refs = 'add_std_comms' + * actions/bgp-actions/set-community/config/options = ADD + * actions/bgp-actions/set-community/config/method = REFERENCE + * actions/config/policy-result = NEXT_STATEMENT + * statement[name='accept_all_routes']/ + * actions/config/policy-result = ACCEPT_ROUTE + + * For each policy-definition created, run a subtest (RT-7.8.2.x-neighbor-) to + * Use gnmi Set REPLACE option for: + * `/routing-policy/policy-definitions` to configure the policy + * Use `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy` + to apply the policy on the DUT bgp neighbor to the ATE port 1. + * Verify routes are received on ATE port 1 for all prefixes (since all routes are accepted by policies). + * Verify expected communities are present in ATE. + * Verify expected communities are present in DUT state. + * Do not fail test if this path is not supported, only log results + * `/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/community-index` + * `/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/community-index` + + * For each policy-definition created, run a sub-test (RT-7.8.2.x-peer-group-) to + * Use gnmi Set REPLACE option for: + * `/routing-policy/policy-definitions` to configure the policy + * Use `/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy` + to apply the policy on the DUT bgp neighbor to the ATE port 1. + * Verify expected communities are present in ATE. + * Verify expected communities are present in DUT state. + * Do not fail test if this path is not supported, only log results + * `/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/community-index` + * `/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/community-index` + + * Expected result - communities + | | add_std_comms | match_and_add_std_comms | + | ------------ | --------------------------------------------- | --------------------------------- | + | prefix-set-1 | [ 10:10, 20:20, 30:30 ] | none | + | prefix-set-2 | [ 10:10, 20:20, 30:30, 5:5, 6:6 ] | [ 10:10, 20:20, 30:30, 5:5, 6:6 ] | + +## Config Parameter Coverage + +### Policy for community-set configuration + +* /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 + +### Policy action configuration + +* /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/bgp-actions/set-community/reference/config/community-set-refs + +### Policy for community-set match configuration + +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/config/community-set +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set/config/match-set-options + +### Policy attachment point configuration + +* /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/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 + +## Telemetry Parameter Coverage + +* /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/community-index +* /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/community-index + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT Required + +vRX - Virtual Router Device 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 new file mode 100644 index 00000000000..c6f66a3165f --- /dev/null +++ b/feature/bgp/policybase/otg_tests/comm_match_action_test/bgp_comm_match_action_test.go @@ -0,0 +1,783 @@ +// 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 bgp_comm_match_action_test + +import ( + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "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" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + otg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + trafficDuration = 1 * time.Minute + tolerancePct = 2 + peerGrpName = "BGP-PEER-GROUP" + dutAS = 65501 + ateAS = 65502 + plenIPv4 = 30 + plenIPv6 = 126 + acceptPolicy = "PERMIT-ALL" + matchStdCommunitySet = "match_std_comms" + addStdCommunitySet = "add_std_comms" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE Port1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + MAC: "02:00:01:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE Port2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + IPv6: "2001:db8::192:0:2:6", + MAC: "02:00:02:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + ebgp1NbrV4 = &bgpNeighbor{ + nbrAddr: atePort1.IPv4, + isV4: true, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, + as: ateAS} + ebgp1NbrV6 = &bgpNeighbor{ + nbrAddr: atePort1.IPv6, + isV4: false, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST, + as: ateAS} + ebgp2NbrV4 = &bgpNeighbor{ + nbrAddr: atePort2.IPv4, + isV4: true, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, + as: ateAS} + ebgp2NbrV6 = &bgpNeighbor{ + nbrAddr: atePort2.IPv6, + isV4: false, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST, + as: ateAS} + ebgpNbrs = []*bgpNeighbor{ebgp1NbrV4, ebgp2NbrV4, ebgp1NbrV6, ebgp2NbrV6} + + routes = map[string]*route{ + "prefix-set-1": { + prefixesV4: []string{"198.51.100.0", "198.51.100.4"}, + prefixesV6: []string{"2048:db1:64:64::", "2048:db1:64:64::4"}, + communityMembers: nil, + }, + "prefix-set-2": { + prefixesV4: []string{"198.51.100.8", "198.51.100.12"}, + prefixesV6: []string{"2048:db1:64:64::8", "2048:db1:64:64::c"}, + communityMembers: [][]int{{5, 5}, {6, 6}}, + }, + } + + communitySets = []communitySet{ + { + name: matchStdCommunitySet, + members: []string{"5:5"}, + }, + { + name: addStdCommunitySet, + members: []string{"10:10", "20:20", "30:30"}, + }, + } +) + +type route struct { + prefixesV4 []string + prefixesV6 []string + communityMembers [][]int +} + +type communitySet struct { + name string + members []string +} + +type bgpNeighbor struct { + as uint32 + nbrAddr string + isV4 bool + afiSafi oc.E_BgpTypes_AFI_SAFI_TYPE +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, "port1")) + fptest.SetPortSpeed(t, dut.Port(t, "port2")) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, dut.Port(t, "port1").Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, dut.Port(t, "port2").Name(), deviations.DefaultNetworkInstance(dut), 0) + } + + // Configure PERMIT_ALL Policy + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(acceptPolicy) + stmt, _ := pdef.AppendNewStatement("10") + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + + // Configure Community Sets on DUT + for _, communitySet := range communitySets { + configureCommunitySet(t, dut, communitySet) + } +} + +func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + + // Configure BGP on DUT + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(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) + + pg := bgp.GetOrCreatePeerGroup(peerGrpName) + pg.PeerAs = ygot.Uint32(ateAS) + pg.PeerGroupName = ygot.String(peerGrpName) + if !deviations.SkipBgpSendCommunityType(dut) { + 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) + + for _, nbr := range ebgpNbrs { + bgpNbr := bgp.GetOrCreateNeighbor(nbr.nbrAddr) + bgpNbr.PeerGroup = ygot.String(peerGrpName) + bgpNbr.PeerAs = ygot.Uint32(nbr.as) + bgpNbr.Enabled = ygot.Bool(true) + + if nbr.isV4 == true { + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + if nbr.nbrAddr == atePort2.IPv4 { + af4.GetOrCreateApplyPolicy().ExportPolicy = []string{acceptPolicy} + } + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(false) + } else { + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(false) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + if nbr.nbrAddr == atePort2.IPv6 { + af6.GetOrCreateApplyPolicy().ExportPolicy = []string{acceptPolicy} + } + } + } + return niProto +} + +func verifyBgpState(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + t.Logf("Waiting for BGP neighbor to establish...") + for _, nbr := range ebgpNbrs { + nbrPath := bgpPath.Neighbor(nbr.nbrAddr) + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", nbr.nbrAddr, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", nbr.nbrAddr, state, want) + } + } +} + +func configureCommunitySet(t *testing.T, dut *ondatra.DUTDevice, communitySet communitySet) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + commSet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySet.name) + var commMemberUnion []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union + for _, commMember := range communitySet.members { + commMemberUnion = append(commMemberUnion, oc.UnionString(commMember)) + } + commSet.SetCommunityMember(commMemberUnion) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) +} + +func configureRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, policyName string, nbr *bgpNeighbor, pgName string) { + addStdCommunitySetRefs := []string{addStdCommunitySet} + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + batchConfig := &gnmi.SetBatch{} + var pdef *oc.RoutingPolicy_PolicyDefinition + + switch policyName { + case "add_std_comms": + pdef = rp.GetOrCreatePolicyDefinition(policyName) + stmt1, _ := pdef.AppendNewStatement("add_std_comms") + sc := stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity() + if deviations.BgpCommunitySetRefsUnsupported(dut) { + sc.GetOrCreateReference().SetCommunitySetRef(addStdCommunitySet) + } else { + sc.GetOrCreateReference().SetCommunitySetRefs(addStdCommunitySetRefs) + } + sc.SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + if !deviations.BgpActionsSetCommunityMethodUnsupported(dut) { + sc.SetMethod(oc.SetCommunity_Method_REFERENCE) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT) + stmt2, _ := pdef.AppendNewStatement("accept_all_routes") + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + case "match_and_add_comms": + pdef = rp.GetOrCreatePolicyDefinition(policyName) + stmt1, _ := pdef.AppendNewStatement("match_and_add_std_comms") + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(matchStdCommunitySet) + ds := rp.GetOrCreateDefinedSets() + cs := ds.GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(matchStdCommunitySet) + cs.SetMatchSetOptions(oc.BgpPolicy_MatchSetOptionsType_ANY) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().DefinedSets().Config(), ds) + } else { + cs := stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet() + cs.SetCommunitySet(matchStdCommunitySet) + cs.SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) + } + sc := stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity() + if deviations.BgpCommunitySetRefsUnsupported(dut) { + sc.GetOrCreateReference().SetCommunitySetRef(addStdCommunitySet) + } else { + sc.GetOrCreateReference().SetCommunitySetRefs(addStdCommunitySetRefs) + } + sc.SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + if !deviations.BgpActionsSetCommunityMethodUnsupported(dut) { + sc.SetMethod(oc.SetCommunity_Method_REFERENCE) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT) + stmt2, _ := pdef.AppendNewStatement("accept_all_routes") + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + + if pdef != nil { + gnmi.BatchReplace(batchConfig, gnmi.OC().RoutingPolicy().PolicyDefinition(policyName).Config(), pdef) + } + + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + if nbr != nil { + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(nbr.nbrAddr).AfiSafi(nbr.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{policyName}) + } + if pgName != "" { + gnmi.BatchReplace(batchConfig, bgpPath.PeerGroup(pgName).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ImportPolicy().Config(), []string{policyName}) + gnmi.BatchReplace(batchConfig, bgpPath.PeerGroup(pgName).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ImportPolicy().Config(), []string{policyName}) + gnmi.BatchDelete(batchConfig, bgpPath.Neighbor(ebgp1NbrV4.nbrAddr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ImportPolicy().Config()) + gnmi.BatchDelete(batchConfig, bgpPath.Neighbor(ebgp1NbrV6.nbrAddr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ImportPolicy().Config()) + } + + batchConfig.Set(t, dut) +} + +func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { + t.Helper() + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + // Port1 Configuration. + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + // Port2 Configuration. + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + // 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(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + // eBGP v6 session on Port1. + iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") + iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // eBGP v4 session on Port2. + iDut2Bgp := iDut2Dev.Bgp().SetRouterId(iDut2Ipv4.Address()) + iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2Ipv4.Name()).Peers().Add().SetName(atePort2.Name + ".BGP4.peer") + iDut2Bgp4Peer.SetPeerAddress(iDut2Ipv4.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + // eBGP v6 session on Port2. + iDut2Bgp6Peer := iDut2Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut2Ipv6.Name()).Peers().Add().SetName(atePort2.Name + ".BGP6.peer") + iDut2Bgp6Peer.SetPeerAddress(iDut2Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut2Bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + for key, sendRoute := range routes { + // eBGP V4 routes from Port1. + bgpNeti1Bgp4PeerRoutes := iDut1Bgp4Peer.V4Routes().Add().SetName(atePort1.Name + ".BGP4.Route." + key) + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(iDut1Ipv4.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + + for _, prefixV4 := range sendRoute.prefixesV4 { + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(prefixV4).SetPrefix(plenIPv4) + } + + // eBGP V6 routes from Port1. + bgpNeti1Bgp6PeerRoutes := iDut1Bgp6Peer.V6Routes().Add().SetName(atePort1.Name + ".BGP6.Route." + key) + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(iDut1Ipv6.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + + for _, prefixV6 := range sendRoute.prefixesV6 { + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(prefixV6).SetPrefix(plenIPv6) + } + + if sendRoute.communityMembers != nil { + for _, community := range sendRoute.communityMembers { + commV4 := bgpNeti1Bgp4PeerRoutes.Communities().Add() + commV4.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commV4.SetAsNumber(uint32(community[0])) + commV4.SetAsCustom(uint32(community[1])) + + commV6 := bgpNeti1Bgp6PeerRoutes.Communities().Add() + commV6.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commV6.SetAsNumber(uint32(community[0])) + commV6.SetAsCustom(uint32(community[1])) + } + } + } + + // ATE Traffic Configuration. + t.Logf("TestBGP:start ate Traffic config") + + var dstBgp4PeerRoutes, dst4Prefixes []string + for _, routeV4 := range iDut1Bgp4Peer.V4Routes().Items() { + dstBgp4PeerRoutes = append(dstBgp4PeerRoutes, routeV4.Name()) + for _, prefix := range routeV4.Addresses().Items() { + dst4Prefixes = append(dst4Prefixes, prefix.Address()) + } + } + flowipv4 := config.Flows().Add().SetName("bgpv4RoutesFlow") + flowipv4.Metrics().SetEnable(true) + flowipv4.TxRx().Device(). + SetTxNames([]string{iDut2Ipv4.Name()}). + SetRxNames(dstBgp4PeerRoutes) + flowipv4.Size().SetFixed(512) + flowipv4.Duration().FixedPackets().SetPackets(1000) + e1 := flowipv4.Packet().Add().Ethernet() + e1.Src().SetValue(iDut2Eth.Mac()) + v4 := flowipv4.Packet().Add().Ipv4() + v4.Src().SetValue(iDut2Ipv4.Address()) + v4.Dst().SetValues(dst4Prefixes) + + var dstBgp6PeerRoutes, dst6Prefixes []string + for _, routeV6 := range iDut1Bgp6Peer.V6Routes().Items() { + dstBgp6PeerRoutes = append(dstBgp6PeerRoutes, routeV6.Name()) + for _, prefix := range routeV6.Addresses().Items() { + dst6Prefixes = append(dst6Prefixes, prefix.Address()) + } + } + flowipv6 := config.Flows().Add().SetName("bgpv6RoutesFlow") + flowipv6.Metrics().SetEnable(true) + flowipv6.TxRx().Device(). + SetTxNames([]string{iDut2Ipv6.Name()}). + SetRxNames(dstBgp6PeerRoutes) + flowipv6.Size().SetFixed(512) + flowipv6.Duration().FixedPackets().SetPackets(1000) + e2 := flowipv6.Packet().Add().Ethernet() + e2.Src().SetValue(iDut2Eth.Mac()) + v6 := flowipv6.Packet().Add().Ipv6() + v6.Src().SetValue(iDut2Ipv6.Address()) + v6.Dst().SetValues(dst6Prefixes) + + otg.PushConfig(t, config) + otg.StartProtocols(t) + return config +} + +func sendTraffic(t *testing.T, otg *otg.OTG) { + t.Logf("Starting traffic") + otg.StartTraffic(t) + time.Sleep(trafficDuration) + t.Logf("Stop traffic") + otg.StopTraffic(t) +} + +func verifyTraffic(t *testing.T, ate *ondatra.ATEDevice, conf gosnappi.Config) { + otg := ate.OTG() + otgutils.LogFlowMetrics(t, otg, conf) + for _, flow := range conf.Flows().Items() { + recvMetric := gnmi.Get(t, otg, gnmi.OTG().Flow(flow.Name()).State()) + txPackets := float32(recvMetric.GetCounters().GetOutPkts()) + rxPackets := float32(recvMetric.GetCounters().GetInPkts()) + if txPackets == 0 { + t.Fatalf("TxPkts = 0, want > 0") + } + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + if lossPct > tolerancePct { + t.Errorf("Traffic Loss Pct for Flow %s: got %v, want max %v pct failure", flow.Name(), lossPct, tolerancePct) + } else { + t.Logf("Traffic Test Passed! for flow %s", flow.Name()) + } + } +} + +func validateATEIPv4PrefixCommunitySet(t *testing.T, ate *ondatra.ATEDevice, bgpPeerName, subnet string, wantCommunitySet []string) { + otg := ate.OTG() + var gotCommunitySet []string + peerPath := gnmi.OTG().BgpPeer(bgpPeerName) + + _, ok := gnmi.Watch(t, + otg, + peerPath.UnicastIpv4Prefix(subnet, plenIPv4, otgtelemetry.UnicastIpv4Prefix_Origin_IGP, 0).State(), + time.Minute, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + prefix, ok := v.Val() + if ok { + gotCommunitySet = nil + for _, community := range prefix.Community { + gotCommunityNumber := community.GetCustomAsNumber() + gotCommunityValue := community.GetCustomAsValue() + gotCommunitySet = append(gotCommunitySet, fmt.Sprint(gotCommunityNumber)+":"+fmt.Sprint(gotCommunityValue)) + } + if cmp.Equal(gotCommunitySet, wantCommunitySet, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { + t.Logf("ATE: Prefix %v learned with community %v", prefix.GetAddress(), gotCommunitySet) + return true + } + prefix.Community = nil + } + return false + }).Await(t) + + if !ok { + fptest.LogQuery(t, "ATE BGP Peer reported state", peerPath.State(), gnmi.Get(t, otg, peerPath.State())) + t.Errorf("ATE: Prefix %v got communities %v, want communities %v", subnet, gotCommunitySet, wantCommunitySet) + } +} + +func validateDutIPv4PrefixCommunitySet(t *testing.T, dut *ondatra.DUTDevice, bgpNbr *bgpNeighbor, subnet string, wantCommunitySet []string) { + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + statePath := bgpPath.Rib().AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast() + state := gnmi.Get(t, dut, statePath.State()) + + if communityIndex := state.GetNeighbor(bgpNbr.nbrAddr).GetAdjRibInPost().GetRoute(subnet, 0).GetCommunityIndex(); communityIndex != 0 { + t.Logf("DUT: Prefix %v learned with CommunityIndex: %v", subnet, communityIndex) + } else { + fptest.LogQuery(t, "Node BGP", statePath.State(), state) + t.Logf("DUT: Could not find AdjRibInPost Community for Prefix %v", subnet) + } + // TODO Validate Community for ipv4 prefixes on DUT +} + +func validateATEIPv6PrefixCommunitySet(t *testing.T, ate *ondatra.ATEDevice, bgpPeerName, subnet string, wantCommunitySet []string) { + otg := ate.OTG() + var gotCommunitySet []string + peerPath := gnmi.OTG().BgpPeer(bgpPeerName) + + _, ok := gnmi.Watch(t, + otg, + peerPath.UnicastIpv6Prefix(subnet, plenIPv6, otgtelemetry.UnicastIpv6Prefix_Origin_IGP, 0).State(), + time.Minute, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv6Prefix]) bool { + prefix, ok := v.Val() + if ok { + for _, community := range prefix.Community { + gotCommunityNumber := community.GetCustomAsNumber() + gotCommunityValue := community.GetCustomAsValue() + gotCommunitySet = append(gotCommunitySet, fmt.Sprint(gotCommunityNumber)+":"+fmt.Sprint(gotCommunityValue)) + } + if cmp.Equal(gotCommunitySet, wantCommunitySet, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { + t.Logf("ATE: Prefix %v learned with community %v", prefix.GetAddress(), gotCommunitySet) + return true + } + prefix.Community = nil + gotCommunitySet = nil + } + return false + }).Await(t) + + if !ok { + fptest.LogQuery(t, "ATE BGP Peer reported state", peerPath.State(), gnmi.Get(t, otg, peerPath.State())) + t.Errorf("ATE: Prefix %v got communities %v, want communities %v", subnet, gotCommunitySet, wantCommunitySet) + } +} + +func validateDutIPv6PrefixCommunitySet(t *testing.T, dut *ondatra.DUTDevice, bgpNbr *bgpNeighbor, subnet string, wantCommunitySet []string) { + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + statePath := bgpPath.Rib().AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Ipv6Unicast() + state := gnmi.Get(t, dut, statePath.State()) + + if communityIndex := state.GetNeighbor(bgpNbr.nbrAddr).GetAdjRibInPost().GetRoute(subnet, 0).GetCommunityIndex(); communityIndex != 0 { + t.Logf("DUT: Prefix %v learned with CommunityIndex: %v", subnet, communityIndex) + } else { + fptest.LogQuery(t, "Node BGP", statePath.State(), state) + t.Logf("DUT: Could not find AdjRibInPost Community for Prefix %v", subnet) + } + // TODO Validate Community for ipv6 prefixes on DUT +} + +type TestResults struct { + prefixSetName string + wantCommunitySet []string +} + +type testCase struct { + desc string + nbr *bgpNeighbor + peerGrp string + policyName string + testResults []TestResults +} + +// TestBGPCommMatchAction is to test community match actions at BGP neighbor & peer group levels. +func TestBGPCommMatchAction(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + + configureDUT(t, dut) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, ateAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + + otgConfig := configureOTG(t, otg) + verifyBgpState(t, dut) + + t.Run("RT-7.8.1", func(t *testing.T) { + testCases := []testCase{ + { + desc: "Validate Initial Config", + peerGrp: "", + policyName: acceptPolicy, + testResults: []TestResults{ + { + prefixSetName: "prefix-set-1", + wantCommunitySet: nil, + }, + { + prefixSetName: "prefix-set-2", + wantCommunitySet: []string{"5:5", "6:6"}, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + configureRoutingPolicy(t, dut, tc.policyName, ebgp1NbrV4, tc.peerGrp) + configureRoutingPolicy(t, dut, tc.policyName, ebgp1NbrV6, tc.peerGrp) + for _, testResult := range tc.testResults { + for _, prefix := range routes[testResult.prefixSetName].prefixesV4 { + validateATEIPv4PrefixCommunitySet(t, ate, atePort2.Name+".BGP4.peer", prefix, testResult.wantCommunitySet) + validateDutIPv4PrefixCommunitySet(t, dut, ebgp1NbrV4, prefix, nil) + } + for _, prefix := range routes[testResult.prefixSetName].prefixesV6 { + validateATEIPv6PrefixCommunitySet(t, ate, atePort2.Name+".BGP6.peer", prefix, testResult.wantCommunitySet) + validateDutIPv6PrefixCommunitySet(t, dut, ebgp1NbrV6, prefix, nil) + } + } + // Starting ATE Traffic and verify Traffic Flows + sendTraffic(t, otg) + verifyTraffic(t, ate, otgConfig) + }) + } + }) + + t.Run("RT-7.8.2", func(t *testing.T) { + testCases := []testCase{ + { + desc: "neighborV4-match_and_add_comms", + nbr: ebgp1NbrV4, + peerGrp: "", + policyName: "match_and_add_comms", + testResults: []TestResults{ + { + prefixSetName: "prefix-set-1", + wantCommunitySet: nil, + }, + { + prefixSetName: "prefix-set-2", + wantCommunitySet: []string{"5:5", "6:6", "10:10", "20:20", "30:30"}, + }, + }, + }, + { + desc: "neighborV6-match_and_add_comms", + nbr: ebgp1NbrV6, + peerGrp: "", + policyName: "match_and_add_comms", + testResults: []TestResults{ + { + prefixSetName: "prefix-set-1", + wantCommunitySet: nil, + }, + { + prefixSetName: "prefix-set-2", + wantCommunitySet: []string{"5:5", "6:6", "10:10", "20:20", "30:30"}, + }, + }, + }, + { + desc: "PeerGrp-match_and_add_comms", + nbr: nil, + peerGrp: peerGrpName, + policyName: "match_and_add_comms", + testResults: []TestResults{ + { + prefixSetName: "prefix-set-1", + wantCommunitySet: nil, + }, + { + prefixSetName: "prefix-set-2", + wantCommunitySet: []string{"5:5", "6:6", "10:10", "20:20", "30:30"}, + }, + }, + }, + { + desc: "neighborV4-add_std_comms", + nbr: ebgp1NbrV4, + peerGrp: "", + policyName: "add_std_comms", + testResults: []TestResults{ + { + prefixSetName: "prefix-set-1", + wantCommunitySet: []string{"10:10", "20:20", "30:30"}, + }, + { + prefixSetName: "prefix-set-2", + wantCommunitySet: []string{"5:5", "6:6", "10:10", "20:20", "30:30"}, + }, + }, + }, + { + desc: "neighborV6-add_std_comms", + nbr: ebgp1NbrV6, + peerGrp: "", + policyName: "add_std_comms", + testResults: []TestResults{ + { + prefixSetName: "prefix-set-1", + wantCommunitySet: []string{"10:10", "20:20", "30:30"}, + }, + { + prefixSetName: "prefix-set-2", + wantCommunitySet: []string{"5:5", "6:6", "10:10", "20:20", "30:30"}, + }, + }, + }, + { + desc: "PeerGrp-add_std_comms", + nbr: nil, + peerGrp: peerGrpName, + policyName: "add_std_comms", + testResults: []TestResults{ + { + prefixSetName: "prefix-set-1", + wantCommunitySet: []string{"10:10", "20:20", "30:30"}, + }, + { + prefixSetName: "prefix-set-2", + wantCommunitySet: []string{"5:5", "6:6", "10:10", "20:20", "30:30"}, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + configureRoutingPolicy(t, dut, tc.policyName, tc.nbr, tc.peerGrp) + for _, testResult := range tc.testResults { + for _, prefix := range routes[testResult.prefixSetName].prefixesV4 { + if (tc.nbr == nil) || (tc.nbr != nil && tc.nbr.isV4 == true) { + validateATEIPv4PrefixCommunitySet(t, ate, atePort2.Name+".BGP4.peer", prefix, testResult.wantCommunitySet) + validateDutIPv4PrefixCommunitySet(t, dut, ebgp1NbrV4, prefix, nil) + } + } + for _, prefix := range routes[testResult.prefixSetName].prefixesV6 { + if (tc.nbr == nil) || (tc.nbr != nil && tc.nbr.isV4 != true) { + validateATEIPv6PrefixCommunitySet(t, ate, atePort2.Name+".BGP6.peer", prefix, testResult.wantCommunitySet) + validateDutIPv6PrefixCommunitySet(t, dut, ebgp1NbrV6, prefix, nil) + } + } + } + }) + } + }) +} 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 new file mode 100644 index 00000000000..e1a4c911ab4 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/comm_match_action_test/metadata.textproto @@ -0,0 +1,31 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "384964cf-2e53-4ee5-b8a5-eb99f4345cc1" +plan_id: "RT-7.8" +description: "BGP Policy Match Standard Community and Add Community Import/Export Policy" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + bgp_actions_set_community_method_unsupported: true + skip_bgp_send_community_type: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + } +} +tags: TAGS_AGGREGATION +tags: TAGS_TRANSIT +tags: TAGS_DATACENTER_EDGE diff --git a/feature/bgp/policybase/otg_tests/community_test/README.md b/feature/bgp/policybase/otg_tests/community_test/README.md new file mode 100644 index 00000000000..f1911b3c359 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/community_test/README.md @@ -0,0 +1,179 @@ +# RT-7.2: BGP Policy Community Set + +## Summary + +BGP policy configuration for Community Sets + +## Subtests + +* RT-7.2.1 - Setup BGP sessions + * Generate config for 2 DUT ports, with DUT port 1 eBGP session to ATE port 1. + * Generate config for ATE 2 ports, with ATE port 1 eBGP session to DUT port 1. + * Configure ATE port 1 to advertise ipv4 and ipv6 prefixes to DUT port 1 using the following communities: + * prefix-set-1 with 2 routes with communities `[100:1, 200:2, 300:3]` + * prefix-set-2 with 2 routes with communities `[100:1, 101:1, 200:1]` + * prefix-set-3 with 2 routes with communities `[109:1]` + * prefix-set-4 with 2 routes with communities `[400:1]` + + * For IPv4 and IPv6 prefixes: + * Observe received prefixes at ATE port-2. + * Generate traffic from ATE port-2 to ATE port-1. + * Validate that traffic can be received on ATE port-1 for all installed + routes. + +* RT-7.2.2 - Validate community-set + * Configure the following community sets on the DUT. + * Create a community-set named `any_my_3_comms` with members as follows: + * `{ community-member = [ "100:1", "200:2", "300:3" ] }` + * Create a community-set named `all_3_comms` with members and match options as follows: + * `{ community-member = [ "100:1", "200:2", "300:3" ] }` + * Create a community-set named `no_3_comms` with members and match options as follows: + * `{ community-member = [ "100:1", "200:2", "300:3" ]}` + * Create a community-set named `any_my_regex_comms` with members and match options as follows: + * `{ community-member = [ "10[0-9]:1" ] }` + + * Create a `policy-definition` named 'community-match' with the following `statements` + * statement[name='accept_any_3_comms']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'any_my_3_comms' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + * statement[name='accept_all_3_comms']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'all_3_comms' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ALL + * actions/config/policy-result = ACCEPT_ROUTE + * statement[name='accept_no_3_comms']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'no_3_comms' + * conditions/bgp-conditions/match-community-set/config/match-set-options = INVERT + * actions/config/policy-result = ACCEPT_ROUTE + * statement[name='accept_any_my_regex_comms']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'all_3_comms' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + + * Send traffic from ATE port-2 to all prefix-sets. + * Verify traffic is received on ATE port 1 for accepted prefixes. + * Verify traffic is not received on ATE port 1 for rejected prefixes. + +* RT-7.2.3 - Update community set and validate + 1. Configure a community-set named `update_comm_set` with "100:1" as member. + + 2. Create a `policy-definition` named 'community-match' with the following `statements` + * statement[name='accept_update_comm_set']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'update_comm_set' + * conditions/bgp-conditions/match-community-set/config/match-set-options = INVERT + * actions/config/policy-result = ACCEPT_ROUTE + + 3. Send traffic from ATE port-2 to all prefix-sets. + * Verify traffic is received on ATE port 1 for accepted prefixes for all community set except "100:1". + * Verify traffic is not received on ATE port 1 for rejected prefixes for community set "100:1". + + 4. Update the community-set named `update_comm_set` with "200:2" as member. + + 5. Send traffic from ATE port-2 to all prefix-sets. + * Verify traffic is received on ATE port 1 for accepted prefixes for all community set except "200:1". + * Verify traffic is not received on ATE port 1 for rejected prefixes for community set "200:1". + + + +### Expected community matches + +| prefix-set | any_my_3_comms | all_3_comms | no_3_comms | any_my_regex_comms | +| ------------ | -------------- | ----------- | ---------- | ------------------ | +| prefix-set-1 | accept | accept | reject | accept | +| prefix-set-2 | accept | reject | reject | accept | +| prefix-set-3 | reject | reject | accept | accept | +| prefix-set-4 | reject | reject | accept | reject | + +## Config Parameter Coverage + +### Policy definition + +* /routing-policy/policy-definitions/policy-definition/config/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/config/name + +### Policy for community-set match + +* /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 Parameter Coverage + +### Policy definition state + +* /routing-policy/policy-definitions/policy-definition/state/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/state/name + +### Policy for community-set match state + +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/community-set + +### Paths to verify policy state + +* /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy + +### Paths to verify prefixes sent and received + +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent +* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy +* /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/installed + +## 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 + ### Policy definition + /routing-policy/policy-definitions/policy-definition/config/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/config/name: + ### Policy for community-set match + /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: + + ## State paths + ### Policy definition state + + /routing-policy/policy-definitions/policy-definition/state/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/state/name: + + ### Policy for community-set match state + + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/community-set: + + ### Paths to verify policy state + + /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy: + + ### Paths to verify prefixes sent and received + + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy: + /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/installed: +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/bgp/policybase/otg_tests/community_test/community_test.go b/feature/bgp/policybase/otg_tests/community_test/community_test.go new file mode 100644 index 00000000000..c5164592725 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/community_test/community_test.go @@ -0,0 +1,372 @@ +/// 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 community_test + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "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" +) + +const ( + prefixV4Len = 30 + prefixV6Len = 126 + trafficPps = 100 + totalPackets = 1200 + bgpName = "BGP" + RPLPermitAll = "PERMIT-ALL" + comunitySetNameRegex = "any_my_regex_comms" +) + +var prefixesV4 = [][]string{ + {"198.51.100.0", "198.51.100.4"}, + {"198.51.100.8", "198.51.100.12"}, + {"198.51.100.16", "198.51.100.20"}, + {"198.51.100.24", "198.51.100.28"}, +} + +var prefixesV6 = [][]string{ + {"2048:db1:64:64::0", "2048:db1:64:64::4"}, + {"2048:db1:64:64::8", "2048:db1:64:64::c"}, + {"2048:db1:64:64::10", "2048:db1:64:64::14"}, + {"2048:db1:64:64::18", "2048:db1:64:64::1c"}, +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureImportBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4 string, ipv6 string, communitySetName string, communityMatch [3]string, matchSetOptions oc.E_BgpPolicy_MatchSetOptionsType) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition("routePolicy") + stmt1, err := pdef1.AppendNewStatement("routePolicyStatement") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + if !(deviations.CommunityMemberRegexUnsupported(dut) && communitySetName == comunitySetNameRegex) { + communitySet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySetName) + cs := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + for _, commMatch := range communityMatch { + if commMatch != "" { + cs = append(cs, oc.UnionString(commMatch)) + } + } + communitySet.SetCommunityMember(cs) + communitySet.SetMatchSetOptions(matchSetOptions) + } + var communitySetCLIConfig string + if deviations.CommunityMemberRegexUnsupported(dut) && communitySetName == comunitySetNameRegex { + switch dut.Vendor() { + case ondatra.CISCO: + communitySetCLIConfig = fmt.Sprintf("community-set %v\n ios-regex '10[0-9]:1'\n end-set", communitySetName) + default: + t.Fatalf("Unsupported vendor %s for deviation 'CommunityMemberRegexUnsupported'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) + } + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(communitySetName) + } else { + communitySet := stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet() + communitySet.SetCommunitySet(communitySetName) + communitySet.SetMatchSetOptions(oc.E_RoutingPolicy_MatchSetOptionsType(matchSetOptions)) + } + + if deviations.CommunityMemberRegexUnsupported(dut) && communitySetName == comunitySetNameRegex { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } else { + pdAllow := rp.GetOrCreatePolicyDefinition(RPLPermitAll) + st, err := pdAllow.AppendNewStatement("id-1") + if err != nil { + t.Fatal(err) + } + st.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } + + dni := deviations.DefaultNetworkInstance(dut) + pathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + // policyV6.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + policyV6.SetImportPolicy([]string{"routePolicy"}) + gnmi.Replace(t, dut, pathV6.Config(), policyV6) + + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policyV4 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + // policyV4.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + policyV4.SetImportPolicy([]string{"routePolicy"}) + gnmi.Replace(t, dut, pathV4.Config(), policyV4) +} + +func configureOTG(t *testing.T, bs *cfgplugins.BGPSession, prefixesV4 [][]string, prefixesV6 [][]string, communityMembers [][][]int) { + devices := bs.ATETop.Devices().Items() + + ipv4 := devices[1].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := devices[1].Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + + ipv6 := devices[1].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := devices[1].Bgp().Ipv6Interfaces().Items()[0].Peers().Items()[0] + + for index, prefixes := range prefixesV4 { + bgp4PeerRoute := bgp4Peer.V4Routes().Add() + bgp4PeerRoute.SetName(bs.ATEPorts[1].Name + ".BGP4.peer.dut." + strconv.Itoa(index)) + bgp4PeerRoute.SetNextHopIpv4Address(ipv4.Address()) + + route4Address1 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[0]) + route4Address1.SetPrefix(prefixV4Len) + route4Address2 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[1]) + route4Address2.SetPrefix(prefixV4Len) + + bgp6PeerRoute := bgp6Peer.V6Routes().Add() + bgp6PeerRoute.SetName(bs.ATEPorts[1].Name + ".BGP6.peer.dut." + strconv.Itoa(index)) + bgp6PeerRoute.SetNextHopIpv6Address(ipv6.Address()) + + route6Address1 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][0]) + route6Address1.SetPrefix(prefixV6Len) + route6Address2 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][1]) + route6Address2.SetPrefix(prefixV6Len) + + for _, commu := range communityMembers[index] { + if commu[0] != 0 { + commv4 := bgp4PeerRoute.Communities().Add() + commv4.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv4.SetAsNumber(uint32(commu[0])) + commv4.SetAsCustom(uint32(commu[1])) + + commv6 := bgp6PeerRoute.Communities().Add() + commv6.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv6.SetAsNumber(uint32(commu[0])) + commv6.SetAsCustom(uint32(commu[1])) + } + } + } +} + +func configureFlow(t *testing.T, bs *cfgplugins.BGPSession, prefixPair []string, prefixType string, index int) { + + flow := bs.ATETop.Flows().Add().SetName("flow" + prefixType + strconv.Itoa(index)) + flow.Metrics().SetEnable(true) + + if prefixType == "ipv4" { + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv4"}). + SetRxNames([]string{bs.ATEPorts[1].Name + ".BGP4.peer.dut." + strconv.Itoa(index)}) + } else { + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv6"}). + SetRxNames([]string{bs.ATEPorts[1].Name + ".BGP6.peer.dut." + strconv.Itoa(index)}) + } + + flow.Duration().FixedPackets().SetPackets(totalPackets) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(trafficPps) + + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(bs.ATEPorts[1].MAC) + + if prefixType == "ipv4" { + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(bs.ATEPorts[0].IPv4) + v4.Dst().SetValues(prefixPair) + } else { + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(bs.ATEPorts[0].IPv6) + v6.Dst().SetValues(prefixPair) + } +} + +func verifyTraffic(t *testing.T, ate *ondatra.ATEDevice, prefixType string, testResults bool, index int) { + recvMetric := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow("flow"+prefixType+strconv.Itoa(index)).State()) + framesTx := recvMetric.GetCounters().GetOutPkts() + framesRx := recvMetric.GetCounters().GetInPkts() + + if framesTx == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if (testResults && framesRx == framesTx) || (!testResults && framesRx == 0) { + t.Logf("Traffic validation successful for criteria [%t] FramesTx: %d FramesRx: %d", testResults, framesTx, framesRx) + } else { + t.Errorf("Traffic validation failed for criteria [%t] FramesTx: %d FramesRx: %d", testResults, framesTx, framesRx) + } +} + +type testCase struct { + desc string + communitySetName string + communityMatch [3]string + matchSetOptions oc.E_BgpPolicy_MatchSetOptionsType + testResults [4]bool +} + +func TestCommunitySet(t *testing.T) { + bs := testSetup(t) + ipv4 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv4Addresses().Items()[0].Address() + ipv6 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv6Addresses().Items()[0].Address() + + testCases := []testCase{ + { + desc: "Testing with any_my_3_comms", + communitySetName: "any_my_3_comms", + communityMatch: [3]string{"100:1", "200:2", "300:3"}, + matchSetOptions: oc.BgpPolicy_MatchSetOptionsType_ANY, + testResults: [4]bool{true, true, false, false}, + }, + { + desc: "Testing with all_3_comms", + communitySetName: "all_3_comms", + communityMatch: [3]string{"100:1", "200:2", "300:3"}, + matchSetOptions: oc.BgpPolicy_MatchSetOptionsType_ALL, + testResults: [4]bool{true, false, false, false}, + }, + { + desc: "Testing with no_3_comms", + communitySetName: "no_3_comms", + communityMatch: [3]string{"100:1", "200:2", "300:3"}, + matchSetOptions: oc.BgpPolicy_MatchSetOptionsType_INVERT, + testResults: [4]bool{false, false, true, true}, + }, + { + desc: "Testing with any_my_regex_comms", + communitySetName: comunitySetNameRegex, + communityMatch: [3]string{"10[0-9]:1"}, + matchSetOptions: oc.BgpPolicy_MatchSetOptionsType_ANY, + testResults: [4]bool{true, true, true, false}, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + + if tc.desc == "Testing with no_3_comms" && deviations.CommunityInvertAnyUnsupported(bs.DUT) { + t.Skip("Skipping community match invert testcase") + } + + configureImportBGPPolicy(t, bs.DUT, ipv4, ipv6, tc.communitySetName, tc.communityMatch, tc.matchSetOptions) + sleepTime := time.Duration(totalPackets/trafficPps) + 2 + + bs.ATETop.Flows().Clear() + for index, prefixPairV4 := range prefixesV4 { + configureFlow(t, bs, prefixPairV4, "ipv4", index) + configureFlow(t, bs, prefixesV6[index], "ipv6", index) + } + bs.PushAndStartATE(t) + + // Verify BGP session after its reset with OTG push config & start + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + + t.Logf("Starting traffic for IPv4 and v6") + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + + for index, prefixPairV4 := range prefixesV4 { + t.Logf("Validating traffic test for IPv4 prefixes: [%s, %s]. Expected Result: [%t]", prefixPairV4[0], prefixPairV4[1], tc.testResults[index]) + verifyTraffic(t, bs.ATE, "ipv4", tc.testResults[index], index) + t.Logf("Validating traffic test for IPv6 prefixes: [%s, %s]. Expected Result: [%t]", prefixesV6[index][0], prefixesV6[index][1], tc.testResults[index]) + verifyTraffic(t, bs.ATE, "ipv6", tc.testResults[index], index) + } + }) + } +} + +func testSetup(t *testing.T) *cfgplugins.BGPSession { + t.Helper() + + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount2, nil) + bs.WithEBGP(t, []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST}, []string{"port2"}, true, true) + + var communityMembers = [][][]int{ + { + {100, 1}, {200, 2}, {300, 3}, + }, + { + {100, 1}, {101, 1}, {200, 2}, + }, + { + {107, 1}, {108, 1}, {109, 1}, + }, + { + {400, 1}, {500, 1}, {600, 1}, + }, + } + + configureOTG(t, bs, prefixesV4, prefixesV6, communityMembers) + bs.PushAndStart(t) + + t.Log("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + t.Log("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) + + return bs +} + +func TestCommunitySetUpdate(t *testing.T) { + bs := testSetup(t) + ipv4 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv4Addresses().Items()[0].Address() + ipv6 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv6Addresses().Items()[0].Address() + + commMatch := [3]string{"100:1"} + configureImportBGPPolicy(t, bs.DUT, ipv4, ipv6, "update_comm_set", commMatch, oc.BgpPolicy_MatchSetOptionsType_INVERT) + validateCommunitySetUpdateTraffic(t, bs) + + // change community match set + commMatch = [3]string{"200:2"} + configureImportBGPPolicy(t, bs.DUT, ipv4, ipv6, "update_comm_set", commMatch, oc.BgpPolicy_MatchSetOptionsType_INVERT) + validateCommunitySetUpdateTraffic(t, bs) +} + +func validateCommunitySetUpdateTraffic(t *testing.T, bs *cfgplugins.BGPSession) { + t.Helper() + + sleepTime := time.Duration(totalPackets/trafficPps) + 2 + bs.ATETop.Flows().Clear() + for index, prefixPairV4 := range prefixesV4 { + configureFlow(t, bs, prefixPairV4, "ipv4", index) + configureFlow(t, bs, prefixesV6[index], "ipv6", index) + } + bs.PushAndStartATE(t) + + // Verify BGP session after its reset with OTG push config & start + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + + t.Logf("Starting traffic for IPv4 and v6") + bs.ATE.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + + testResults := [4]bool{false, false, true, true} + for index, prefixPairV4 := range prefixesV4 { + t.Logf("Validating traffic test for IPv4 prefixes: [%s, %s]. Expected Result: [%t]", prefixPairV4[0], prefixPairV4[1], testResults[index]) + verifyTraffic(t, bs.ATE, "ipv4", testResults[index], index) + t.Logf("Validating traffic test for IPv6 prefixes: [%s, %s]. Expected Result: [%t]", prefixesV6[index][0], prefixesV6[index][1], testResults[index]) + verifyTraffic(t, bs.ATE, "ipv6", testResults[index], index) + } +} diff --git a/feature/bgp/policybase/otg_tests/community_test/metadata.textproto b/feature/bgp/policybase/otg_tests/community_test/metadata.textproto new file mode 100644 index 00000000000..68da71ad8f8 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/community_test/metadata.textproto @@ -0,0 +1,50 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "4330effe-a20c-42fb-8372-80fb0901a325" +plan_id: "RT-7.2" +description: "BGP Policy Community Set" +testbed: TESTBED_DUT_ATE_2LINKS +tags: [TAGS_TRANSIT, TAGS_DATACENTER_EDGE] +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + skip_set_rp_match_set_options: true + skip_setting_disable_metric_propagation: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + bgp_conditions_match_community_set_unsupported: true + community_member_regex_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + bgp_conditions_match_community_set_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + community_invert_any_unsupported: true + } +} + diff --git a/feature/bgp/policybase/otg_tests/default_policies_test/README.md b/feature/bgp/policybase/otg_tests/default_policies_test/README.md index 53f14ceab4e..f339dfefee3 100644 --- a/feature/bgp/policybase/otg_tests/default_policies_test/README.md +++ b/feature/bgp/policybase/otg_tests/default_policies_test/README.md @@ -1,4 +1,4 @@ -# RT-7: BGP default policies +# RT-7.1: BGP default policies ## Summary @@ -23,7 +23,7 @@ B <-- IBGP+IS-IS --> C[Port2:OTG]; * DUT:Port2 has IBGP peering with ATE:Port2 using its loopback interface. The loopback interface is reachable only via IS-IS. Ensure ATE:Port2 advertises IPv4-prefix4, IPv4-prefix5, IPv4-prefix6, IPv6-prefix4, IPv6-prefix5 and IPv6-prefix6 over IBGP. Please also configure IPv4-prefix8 and IPv6-prefix8 on ATE:Port2 but these shouldnt be advertised over IBGP to the DUT * Conduct following test procedures by applying policies at the Peer-group and Neighbor AFI-SAFI levels. -### RT-7.1 : Policy definition in policy chain is not satisfied and Default Policy has REJECT_ROUTE action +### RT-7.1.1 : Policy definition in policy chain is not satisfied and Default Policy has REJECT_ROUTE action * Create a default-policy REJECT-ALL with action as REJECT_ROUTE and apply the same to both IPV4-unicast and IPV6-unicast AFI-SAFI * Create policy EBGP-IMPORT-IPV4 that only accepts IPv4-prefix1 and IPv4-prefix2 and then terminates * Create policy EBGP-IMPORT-IPV6 that only accepts IPv6-prefix1 and IPv6-prefix2 and then terminates @@ -44,8 +44,8 @@ B <-- IBGP+IS-IS --> C[Port2:OTG]; * DUT:Port2 should reject export of IPv4-prefix2 and IPv6-prefix2 * IS-IS and static routes shouldn't be advertised to the EBGP and IBGP peers. -### RT-7.2 : Policy definition in policy chain is not satisfied and Default Policy has ACCEPT_ROUTE action - * Continue with the same configuration as RT-7.1 +### RT-7.1.2 : Policy definition in policy chain is not satisfied and Default Policy has ACCEPT_ROUTE action + * Continue with the same configuration as RT-7.1.1 * Replace the default-policy REJECT-ALL with default-policy ACCEPT-ALL which has action ACCEPT_ROUTE. * Ensure ACCEPT-ALL default-policy is applied to both IPv4-unicast and IPv6-unicast AFI-SAFI of both IBGP and EBGP peers * Following test expectations. If expectations not met, the test should fail. @@ -55,8 +55,8 @@ B <-- IBGP+IS-IS --> C[Port2:OTG]; * DUT:Port2 should allow export of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 * IS-IS and static routes shouldn't be advertised to the EBGP and IBGP peers. -### RT-7.3 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has ACCEPT_ROUTE action - * Continue with the same configuration as RT-7.2. However, do not attach any non-default import/export policies to the peers at either the peer-group or neighbor levels. +### RT-7.1.3 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has ACCEPT_ROUTE action + * Continue with the same configuration as RT-7.1.2. However, do not attach any non-default import/export policies to the peers at either the peer-group or neighbor levels. * Ensure that the ACCEPT-ALL default-policy with default action of ACCEPT_ROUTE is appled to both IPv4-unicast and IPv6-unicast AFI-SAFI of both IBGP and EBGP peers * Following test expectations. If expectations not met, the test should fail. * DUT:Port1 should accept import of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 @@ -65,8 +65,8 @@ B <-- IBGP+IS-IS --> C[Port2:OTG]; * DUT:Port2 should allow export of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 * IS-IS and static routes shouldn't be advertised to the EBGP and IBGP peers. -### RT-7.4 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has REJECT_ROUTE action - * Continue with the same configuration as RT-7.3. Ensure no non-default import/export policies are applied to the peers at either the peer-group or neighbor levels. +### RT-7.1.4 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has REJECT_ROUTE action + * Continue with the same configuration as RT-7.1.3. Ensure no non-default import/export policies are applied to the peers at either the peer-group or neighbor levels. * Ensure that only the REJECT-ALL default-policy with default action of REJECT_ROUTE is appled to both IPv4-unicast and IPv6-unicast AFI-SAFI of both IBGP and EBGP peers * Following test expectations. If expectations not met, the test should fail. * DUT:Port1 should reject import of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 @@ -75,9 +75,9 @@ B <-- IBGP+IS-IS --> C[Port2:OTG]; * DUT:Port2 should reject export of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 * IS-IS and static routes shouldn't be advertised to the EBGP and IBGP peers. -### RT-7.5 : No policy, including the default-policy is attached either at the Peer-group or at the neighbor level for only IBGP peer +### RT-7.1.5 : No policy, including the default-policy is attached either at the Peer-group or at the neighbor level for only IBGP peer #### TODO: RT-7.5 should be automated only after the expected behavior is confirmed in https://github.com/openconfig/public/issues/981 - * Continue with the same configuration as RT-7.4. However, do not attach any non-default OR default import/export policies to the IBGP peer at the peer-group or neighbor levels. This is true for both IPv4-unicast and IPv6-unicast AFI-SAFI. + * Continue with the same configuration as RT-7.1.4. However, do not attach any non-default OR default import/export policies to the IBGP peer at the peer-group or neighbor levels. This is true for both IPv4-unicast and IPv6-unicast AFI-SAFI. * Ensure that only the ACCEPT-ALL IMPORT/EXPORT default-policy with default action of ACCEPT_ROUTE is appled to the EBGP peer on both IPv4-unicast and IPv6-unicast AFI-SAFI * Following test expectations. If expectations not met, the test should fail. * DUT:Port1 should accept import of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 @@ -86,9 +86,9 @@ B <-- IBGP+IS-IS --> C[Port2:OTG]; * DUT:Port2 should allow export of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 * IS-IS and static routes shouldn't be advertised to the EBGP and IBGP peers. -### RT-7.6 : No policy, including the default-policy is attached either at the Peer-group or at the neighbor level for both EBGP and IBGP peers -#### TODO: RT-7.6 should be automated only after the expected behavior is confirmed in https://github.com/openconfig/public/issues/981 - * Continue with the same configuration as RT-7.5. However, do not attach any non-default OR default import/export policies to the IBGP and EBGP peers at the peer-group or neighbor levels. This is true for both IPv4-unicast and IPv6-unicast AFI-SAFI. +### RT-7.1.6 : No policy, including the default-policy is attached either at the Peer-group or at the neighbor level for both EBGP and IBGP peers +#### TODO: RT-7.1.6 should be automated only after the expected behavior is confirmed in https://github.com/openconfig/public/issues/981 + * Continue with the same configuration as RT-7.1.5. However, do not attach any non-default OR default import/export policies to the IBGP and EBGP peers at the peer-group or neighbor levels. This is true for both IPv4-unicast and IPv6-unicast AFI-SAFI. * Following test expectations. If expectations not met, the test should fail. * DUT:Port1 should reject import of IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 * DUT:Port1 should reject export of IPv4-prefix4, IPv4-prefix5, IPv4-prefix6, IPv6-prefix4, IPv6-prefix5 and IPv6-prefix6 @@ -96,44 +96,34 @@ B <-- IBGP+IS-IS --> C[Port2:OTG]; * DUT:Port2 wouldn't export routes to IPv4-prefix1, IPv4-prefix2, IPv4-prefix3, IPv6-prefix1, IPv6-prefix2 and IPv6-prefix3 since they are missing from the DUT's forwarding table. * IS-IS and static routes shouldn't be advertised to the EBGP and IBGP peers. -### Config Parameter Coverage - * Defined Sets - * /routing-policy/defined-sets/prefix-sets/prefix-set/ - * /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/exact - - * Policy-Definition - * /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/conditions/match-prefix-set/config/prefix-set - * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options - * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result/ACCEPT_ROUTE - * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result/REJECT_ROUTE - - * Path to Neighbor or Peer-Group level - * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/ - * /network-instances/network-instance/protocols/protocol/bgp/neighbors/peer-group/ - - * Apply Policy at Neighbor or Peer-Group level - * afi-safis/afi-safi/apply-policy/config/import-policy - * afi-safis/afi-safi/apply-policy/config/export-policy - * afi-safis/afi-safi/apply-policy/config/default-import-policy/ACCEPT-ALL - * afi-safis/afi-safi/apply-policy/config/default-export-policy/ACCEPT-ALL - * afi-safis/afi-safi/apply-policy/config/default-import-policy/REJECT-ALL - * afi-safis/afi-safi/apply-policy/config/default-export-policy/REJECT-ALL - - -### Telemetry Parameter Coverage - - * Path to Neighbor or Peer-Group level: - * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor - * /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group - - * Paths under Neighbor and Peer-Group level: - * afi-safis/afi-safi/apply-policy/state/export-policy - * afi-safis/afi-safi/apply-policy/state/import-policy - * afi-safis/afi-safi/state/prefixes/installed - * afi-safis/afi-safi/state/prefixes/received - * afi-safis/afi-safi/state/prefixes/received-pre-policy - * afi-safis/afi-safi/state/prefixes/sent +## OpenConfig Path and RPC Coverage +```yaml +paths: + # Defined Sets + /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: + # Policy-Definition + /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/conditions/match-prefix-set/config/prefix-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result: + # Apply Policy at Neighbor or Peer-Group level + /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/export-policy: + /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/default-export-policy: + # Paths under Neighbor and Peer-Group level + /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/installed: + /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/received-pre-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` 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 new file mode 100644 index 00000000000..60178b91633 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/default_policies_test/bgp_default_policies_test.go @@ -0,0 +1,1019 @@ +// 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 bgp_default_policies_test + +import ( + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "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/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/gnmi/oc/netinstbgp" + "github.com/openconfig/ondatra/netutil" + otg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + peerGrpName1 = "BGP-PEER-GROUP1" + peerGrpName2 = "BGP-PEER-GROUP2" + peerGrpName3 = "BGP-PEER-GROUP3" + peerGrpName4 = "BGP-PEER-GROUP4" + dutAS = 65501 + ateAS = 65502 + plenIPv4 = 30 + plenIPv6 = 126 + dutAreaAddress = "49.0001" + dutSysID = "1920.0000.2001" + otgSysID2 = "640000000001" + isisInstance = "DEFAULT" + otgIsisPort2LoopV4 = "203.0.113.10" + otgIsisPort2LoopV6 = "2001:db8::203:0:113:10" + v4Prefixes = true + rejectAll = "REJECT-ALL" + rejectRoute = oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE + acceptRoute = oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE + ebgpImportIPv4 = "EBGP-IMPORT-IPV4" + ebgpImportIPv6 = "EBGP-IMPORT-IPV6" + ebgpExportIPv4 = "EBGP-EXPORT-IPV4" + ebgpExportIPv6 = "EBGP-EXPORT-IPV6" + ibgpImportIPv4 = "IBGP-IMPORT-IPV4" + ibgpImportIPv6 = "IBGP-IMPORT-IPV6" + ibgpExportIPv4 = "IBGP-EXPORT-IPV4" + ibgpExportIPv6 = "IBGP-EXPORT-IPV6" + maskLengthRange32 = "32..32" + maskLengthRange128 = "128..128" + maskLen32 = "32" + maskLen128 = "128" + ipv4Prefix1 = "198.51.100.1" + ipv4Prefix2 = "198.51.100.2" + ipv4Prefix3 = "198.51.100.3" + ipv4Prefix4 = "198.51.100.4" + ipv4Prefix5 = "198.51.100.5" + ipv4Prefix6 = "198.51.100.6" + ipv4Prefix7 = "198.51.100.7" + ipv4Prefix8 = "198.51.100.8" + ipv6Prefix1 = "2001:DB8:2::1" + ipv6Prefix2 = "2001:DB8:2::2" + ipv6Prefix3 = "2001:DB8:2::3" + ipv6Prefix4 = "2001:DB8:2::4" + ipv6Prefix5 = "2001:DB8:2::5" + ipv6Prefix6 = "2001:DB8:2::6" + ipv6Prefix7 = "2001:DB8:2::7" + ipv6Prefix8 = "2001:DB8:2::8" + maskLenExact = "exact" + defaultStatementOnly = true +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE Port1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + MAC: "02:00:01:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE Port2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + IPv6: "2001:db8::192:0:2:6", + MAC: "02:00:02:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.1", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + ebgpNbrV4 = &bgpNbrList{nbrAddr: atePort1.IPv4, isV4: true, afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST} + ebgpNbrV6 = &bgpNbrList{nbrAddr: atePort1.IPv6, isV4: false, afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST} + ibgpNbrV4 = &bgpNbrList{nbrAddr: otgIsisPort2LoopV4, isV4: true, afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST} + ibgpNbrV6 = &bgpNbrList{nbrAddr: otgIsisPort2LoopV6, isV4: false, afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST} + loopbackIntfName string +) + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) + + loopbackIntfName = netutil.LoopbackInterface(t, dut, 0) + lo0 := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + loop1 := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, dc.Interface(loopbackIntfName).Config(), loop1) + } else { + v4, ok := ipv4Addrs[0].Val() + if ok { + dutlo0Attrs.IPv4 = v4.GetIp() + } + v6, ok := ipv6Addrs[0].Val() + if ok { + dutlo0Attrs.IPv6 = v6.GetIp() + } + t.Logf("Got DUT IPv4 loopback address: %v", dutlo0Attrs.IPv4) + t.Logf("Got DUT IPv6 loopback address: %v", dutlo0Attrs.IPv6) + } + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, "port1")) + fptest.SetPortSpeed(t, dut.Port(t, "port2")) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, dut.Port(t, "port1").Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, dut.Port(t, "port2").Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, loopbackIntfName, deviations.DefaultNetworkInstance(dut), 0) + } + +} + +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 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 == maskLen128 { + 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.Fatal(err) + } + stmt5.GetOrCreateConditions().GetOrCreateMatchPrefixSet().PrefixSet = ygot.String(prefixSet) + stmt5.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + return rp +} + +func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + nbr1v4 := &bgpNeighbor{as: ateAS, neighborip: atePort1.IPv4, isV4: true, peerGrp: peerGrpName1} + nbr2v4 := &bgpNeighbor{as: dutAS, neighborip: otgIsisPort2LoopV4, isV4: true, peerGrp: peerGrpName2, localAddress: dutlo0Attrs.IPv4} + nbr1v6 := &bgpNeighbor{as: ateAS, neighborip: atePort1.IPv6, isV4: false, peerGrp: peerGrpName3} + nbr2v6 := &bgpNeighbor{as: dutAS, neighborip: otgIsisPort2LoopV6, isV4: false, peerGrp: peerGrpName4, localAddress: dutlo0Attrs.IPv6} + nbrs := []*bgpNeighbor{nbr1v4, nbr2v4, nbr1v6, nbr2v6} + + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutlo0Attrs.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + + // 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. + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) + pg1.PeerAs = ygot.Uint32(ateAS) + pg1.PeerGroupName = ygot.String(peerGrpName1) + + pg2 := bgp.GetOrCreatePeerGroup(peerGrpName2) + pg2.PeerAs = ygot.Uint32(dutAS) + pg2.PeerGroupName = ygot.String(peerGrpName2) + + pg3 := bgp.GetOrCreatePeerGroup(peerGrpName3) + pg3.PeerAs = ygot.Uint32(ateAS) + pg3.PeerGroupName = ygot.String(peerGrpName3) + + pg4 := bgp.GetOrCreatePeerGroup(peerGrpName4) + pg4.PeerAs = ygot.Uint32(dutAS) + pg4.PeerGroupName = ygot.String(peerGrpName4) + + for _, nbr := range nbrs { + bgpNbr := bgp.GetOrCreateNeighbor(nbr.neighborip) + bgpNbr.PeerGroup = ygot.String(nbr.peerGrp) + bgpNbr.PeerAs = ygot.Uint32(nbr.as) + bgpNbr.Enabled = ygot.Bool(true) + if nbr.localAddress != "" { + bgpNbrT := bgpNbr.GetOrCreateTransport() + bgpNbrT.LocalAddress = ygot.String(nbr.localAddress) + } + if nbr.isV4 == true { + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(false) + } else { + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(false) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + } + } + return niProto +} + +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + var nbrIP = []string{atePort1.IPv4, otgIsisPort2LoopV4, atePort1.IPv6, otgIsisPort2LoopV6} + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + for _, nbr := range nbrIP { + nbrPath := bgpPath.Neighbor(nbr) + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", nbr, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", nbr, state, want) + } + } +} + +func configureOTG(t *testing.T, otg *otg.OTG) { + t.Helper() + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + // Port1 Configuration. + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + // Port2 Configuration. + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + // Port2 Loopback Configuration. + iDut2LoopV4 := iDut2Dev.Ipv4Loopbacks().Add().SetName("Port2LoopV4").SetEthName(iDut2Eth.Name()) + iDut2LoopV4.SetAddress(otgIsisPort2LoopV4) + iDut2LoopV6 := iDut2Dev.Ipv6Loopbacks().Add().SetName("Port2LoopV6").SetEthName(iDut2Eth.Name()) + iDut2LoopV6.SetAddress(otgIsisPort2LoopV6) + + // ISIS configuration on Port2 for iBGP session establishment. + isisDut2 := iDut2Dev.Isis().SetName("ISIS2").SetSystemId(otgSysID2) + isisDut2.Basic().SetIpv4TeRouterId(atePort2.IPv4).SetHostname(isisDut2.Name()).SetLearnedLspFilter(true) + isisDut2.Interfaces().Add().SetEthName(iDut2Dev.Ethernets().Items()[0].Name()). + SetName("devIsisInt2"). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + // Advertise OTG Port2 loopback address via ISIS. + isisPort2V4 := iDut2Dev.Isis().V4Routes().Add().SetName("ISISPort2V4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(otgIsisPort2LoopV4).SetPrefix(32) + isisPort2V6 := iDut2Dev.Isis().V6Routes().Add().SetName("ISISPort2V6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(otgIsisPort2LoopV6).SetPrefix(uint32(128)) + + // eBGP v4 seesion 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(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + // eBGP v6 seesion on Port1. + iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") + iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut1Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + // iBGP - v4 seesion on Port2. + iDut2Bgp := iDut2Dev.Bgp().SetRouterId(otgIsisPort2LoopV4) + iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2LoopV4.Name()).Peers().Add().SetName(atePort2.Name + ".BGP4.peer") + iDut2Bgp4Peer.SetPeerAddress(dutlo0Attrs.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDut2Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + // iBGP - v6 seesion on Port2. + iDut2Bgp6Peer := iDut2Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut2LoopV6.Name()).Peers().Add().SetName(atePort2.Name + ".BGP6.peer") + iDut2Bgp6Peer.SetPeerAddress(dutlo0Attrs.IPv6).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + iDut2Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut2Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + // eBGP V4 routes from Port1. + bgpNeti1Bgp4PeerRoutes := iDut1Bgp4Peer.V4Routes().Add().SetName(atePort1.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(iDut1Ipv4.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp4PeerRoutes.Addresses().Add(). + SetAddress(ipv4Prefix1).SetPrefix(32) + bgpNeti1Bgp4PeerRoutes.AddPath().SetPathId(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add(). + SetAddress(ipv4Prefix2).SetPrefix(32) + bgpNeti1Bgp4PeerRoutes.AddPath().SetPathId(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add(). + SetAddress(ipv4Prefix3).SetPrefix(32) + bgpNeti1Bgp4PeerRoutes.AddPath().SetPathId(1) + + // eBGP V6 routes from Port1. + bgpNeti1Bgp6PeerRoutes := iDut1Bgp6Peer.V6Routes().Add().SetName(atePort1.Name + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(iDut1Ipv6.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp6PeerRoutes.Addresses().Add(). + SetAddress(ipv6Prefix1).SetPrefix(128) + bgpNeti1Bgp6PeerRoutes.AddPath().SetPathId(1) + bgpNeti1Bgp6PeerRoutes.Addresses().Add(). + SetAddress(ipv6Prefix2).SetPrefix(128) + bgpNeti1Bgp6PeerRoutes.AddPath().SetPathId(1) + bgpNeti1Bgp6PeerRoutes.Addresses().Add(). + SetAddress(ipv6Prefix3).SetPrefix(128) + bgpNeti1Bgp6PeerRoutes.AddPath().SetPathId(1) + + // iBGP V4 routes from Port2. + bgpNeti2Bgp4PeerRoutes := iDut2Bgp4Peer.V4Routes().Add().SetName(atePort2.Name + ".BGP4.Route") + bgpNeti2Bgp4PeerRoutes.SetNextHopIpv4Address(iDut2Ipv4.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti2Bgp4PeerRoutes.Addresses().Add(). + SetAddress(ipv4Prefix4).SetPrefix(32) + bgpNeti2Bgp4PeerRoutes.AddPath().SetPathId(1) + bgpNeti2Bgp4PeerRoutes.Addresses().Add(). + SetAddress(ipv4Prefix5).SetPrefix(32) + bgpNeti2Bgp4PeerRoutes.AddPath().SetPathId(1) + bgpNeti2Bgp4PeerRoutes.Addresses().Add(). + SetAddress(ipv4Prefix6).SetPrefix(32) + bgpNeti2Bgp4PeerRoutes.AddPath().SetPathId(1) + + // iBGP V6 routes from Port2. + bgpNeti2Bgp6PeerRoutes := iDut2Bgp6Peer.V6Routes().Add().SetName(atePort2.Name + ".BGP6.Route") + bgpNeti2Bgp6PeerRoutes.SetNextHopIpv6Address(iDut2Ipv6.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + bgpNeti2Bgp6PeerRoutes.Addresses().Add(). + SetAddress(ipv6Prefix4).SetPrefix(128) + bgpNeti2Bgp6PeerRoutes.AddPath().SetPathId(1) + bgpNeti2Bgp6PeerRoutes.Addresses().Add(). + SetAddress(ipv6Prefix5).SetPrefix(128) + bgpNeti2Bgp6PeerRoutes.AddPath().SetPathId(1) + bgpNeti2Bgp6PeerRoutes.Addresses().Add(). + SetAddress(ipv6Prefix6).SetPrefix(128) + bgpNeti2Bgp6PeerRoutes.AddPath().SetPathId(1) + + t.Logf("Pushing config to OTG and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(40 * time.Second) + otg.StartProtocols(t) + time.Sleep(40 * time.Second) +} + +func verifyBGPCapabilities(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Log("Verifying BGP capabilities.") + var nbrIP = []string{atePort1.IPv4, otgIsisPort2LoopV4, atePort1.IPv6, otgIsisPort2LoopV6} + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + for _, nbr := range nbrIP { + nbrPath := statePath.Neighbor(nbr) + capabilities := map[oc.E_BgpTypes_BGP_CAPABILITY]bool{ + oc.BgpTypes_BGP_CAPABILITY_ROUTE_REFRESH: false, + oc.BgpTypes_BGP_CAPABILITY_MPBGP: false, + oc.BgpTypes_BGP_CAPABILITY_ASN32: 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) + } + } + } +} + +func verifyPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr string, wantInstalled, wantRx, wantSent uint32, isV4 bool) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + t.Logf("Prefix telemetry on DUT for peer %v", nbr) + + var prefixPath *netinstbgp.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_PrefixesPath + if isV4 { + prefixPath = statePath.Neighbor(nbr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes() + } else { + prefixPath = statePath.Neighbor(nbr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Prefixes() + } + if gotInstalled, ok := gnmi.Watch(t, dut, prefixPath.Installed().State(), 10*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotInstalled, ok := val.Val() + return ok && gotInstalled == wantInstalled + }).Await(t); !ok { + t.Errorf("Installed prefixes mismatch: got %v, want %v", gotInstalled, wantInstalled) + } + + if !deviations.MissingPrePolicyReceivedRoutes(dut) { + if gotRx, ok := gnmi.Watch(t, dut, prefixPath.ReceivedPrePolicy().State(), 10*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotRx, ok := val.Val() + return ok && gotRx == wantRx + }).Await(t); !ok { + t.Errorf("Received prefixes mismatch: got %v, want %v", gotRx, wantRx) + } + } + if gotSent, ok := gnmi.Watch(t, dut, prefixPath.Sent().State(), 10*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotSent, ok := val.Val() + return ok && gotSent == wantSent + }).Await(t); !ok { + t.Errorf("Sent prefixes mismatch: got %v, want %v", gotSent, wantSent) + } +} + +type bgpNeighbor struct { + as uint32 + neighborip string + isV4 bool + peerGrp string + localAddress string +} + +func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName []string, dutAreaAddress, dutSysID string) { + t.Helper() + d := &oc.Root{} + dutConfIsisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + if deviations.ISISLevelEnabled(dut) { + isisLevel2.Enabled = ygot.Bool(true) + } + + for _, intf := range intfName { + isisIntf := isis.GetOrCreateInterface(intf) + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + isisIntfLevelAfi := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfi.Metric = ygot.Uint32(200) + isisIntfLevelAfi.Enabled = ygot.Bool(true) + isisIntfLevelAfi6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfi6.Metric = ygot.Uint32(200) + isisIntfLevelAfi6.Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfi.Enabled = nil + isisIntfLevelAfi6.Enabled = nil + } + } + gnmi.Replace(t, dut, dutConfIsisPath.Config(), prot) +} + +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, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", intfName) + t.Fatal("No IS-IS adjacencies reported.") + } + } +} + +type bgpNbrList struct { + nbrAddr string + isV4 bool + afiSafi oc.E_BgpTypes_AFI_SAFI_TYPE +} + +func configureBGPDefaultPolicy(t *testing.T, dut *ondatra.DUTDevice, polType oc.E_RoutingPolicy_DefaultPolicyType) { + t.Helper() + nbrList := []*bgpNbrList{ebgpNbrV4, ebgpNbrV6, ibgpNbrV4, ibgpNbrV6} + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + for _, nbr := range nbrList { + nbrPolPath := bgpPath.Neighbor(nbr.nbrAddr).AfiSafi(nbr.afiSafi).ApplyPolicy() + gnmi.Replace(t, dut, nbrPolPath.DefaultImportPolicy().Config(), polType) + gnmi.Replace(t, dut, nbrPolPath.DefaultExportPolicy().Config(), polType) + } +} + +func deleteBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, nbrList []*bgpNbrList) { + t.Helper() + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + for _, nbr := range nbrList { + nbrAfiSafiPath := bgpPath.Neighbor(nbr.nbrAddr).AfiSafi(nbr.afiSafi) + b := &gnmi.SetBatch{} + gnmi.BatchDelete(b, nbrAfiSafiPath.ApplyPolicy().ImportPolicy().Config()) + gnmi.BatchDelete(b, nbrAfiSafiPath.ApplyPolicy().ExportPolicy().Config()) + b.Set(t, dut) + } +} + +func configurePrefixMatchAndDefaultStatement(t *testing.T, dut *ondatra.DUTDevice, prefixSet, prefixSubnetRange, maskLen string, ipPrefixSet []string, action string, defaultStatementOnly bool) *oc.RoutingPolicy { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + if !defaultStatementOnly { + pset := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(prefixSet) + for _, pref := range ipPrefixSet { + pset.GetOrCreatePrefix(pref+"/"+maskLen, prefixSubnetRange) + mode := oc.PrefixSet_Mode_IPV4 + if maskLen == maskLen128 { + mode = oc.PrefixSet_Mode_IPV6 + } + if !deviations.SkipPrefixSetMode(dut) { + pset.SetMode(mode) + } + } + } + + pdef := rp.GetOrCreatePolicyDefinition(prefixSet) + if !defaultStatementOnly { + stmt1, err := pdef.AppendNewStatement("10") + if err != nil { + t.Fatal(err) + } + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().PrefixSet = ygot.String(prefixSet) + stmt1.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + } + stmt2, err := pdef.AppendNewStatement("50") + if err != nil { + t.Fatal(err) + } + stmt2.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + if action == "reject" { + stmt2.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE + } + + return rp +} + +func configureRoutingPolicyDefaultAction(t *testing.T, dut *ondatra.DUTDevice, action string, defaultStatementOnly bool) { + t.Helper() + batchConfig := &gnmi.SetBatch{} + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + t.Logf("Delete prefix set policies") + deleteBGPPolicy(t, dut, []*bgpNbrList{ebgpNbrV4, ebgpNbrV6, ibgpNbrV4, ibgpNbrV6}) + gnmi.BatchDelete(batchConfig, gnmi.OC().RoutingPolicy().Config()) + batchConfig.Set(t, dut) + time.Sleep(20 * 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)) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ebgpExportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix4}, action, defaultStatementOnly)) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ebgpExportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix4}, action, defaultStatementOnly)) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ibgpImportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix4, ipv4Prefix5}, action, defaultStatementOnly)) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ibgpImportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix4, ipv6Prefix5}, action, defaultStatementOnly)) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ibgpExportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix1}, action, defaultStatementOnly)) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ibgpExportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix1}, action, defaultStatementOnly)) + + // Apply the above policies to the respective peering at the respective AFI-SAFI levels + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV4.nbrAddr).AfiSafi(ebgpNbrV4.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ebgpImportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV4.nbrAddr).AfiSafi(ebgpNbrV4.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ebgpExportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV6.nbrAddr).AfiSafi(ebgpNbrV6.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ebgpImportIPv6}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV6.nbrAddr).AfiSafi(ebgpNbrV6.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ebgpExportIPv6}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV4.nbrAddr).AfiSafi(ibgpNbrV4.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ibgpImportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV4.nbrAddr).AfiSafi(ibgpNbrV4.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ibgpExportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV6.nbrAddr).AfiSafi(ibgpNbrV6.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ibgpImportIPv6}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV6.nbrAddr).AfiSafi(ibgpNbrV6.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ibgpExportIPv6}) + + batchConfig.Set(t, dut) + + time.Sleep(20 * time.Second) +} + +func testDefaultPolicyRejectRouteAction(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + + t.Run("Create and apply default-policy REJECT-ALL with action as REJECT_ROUTE", func(t *testing.T) { + if deviations.BgpDefaultPolicyUnsupported(dut) { + configureRoutingPolicyDefaultAction(t, dut, "reject", !defaultStatementOnly) + } else { + configureBGPDefaultPolicy(t, dut, rejectRoute) + } + }) + + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv4, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{ebgpExportIPv4}, importPol: []string{ebgpImportIPv4}, + wantInstalled: 2, wantRx: 2, wantRxPrePolicy: 3, wantSent: 1, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv6, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{ebgpExportIPv6}, importPol: []string{ebgpImportIPv6}, + wantInstalled: 2, wantRx: 2, wantRxPrePolicy: 3, wantSent: 1, isV4: false}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV4, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{ibgpExportIPv4}, importPol: []string{ibgpImportIPv4}, + wantInstalled: 2, wantRx: 2, wantRxPrePolicy: 3, wantSent: 1, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV6, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{ibgpExportIPv6}, importPol: []string{ibgpImportIPv6}, + wantInstalled: 2, wantRx: 2, wantRxPrePolicy: 3, wantSent: 1, isV4: false}) +} + +func testDefaultPolicyAcceptRouteAction(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Run("Create and apply default-policy ACCEPT-ALL with action as ACCEPT_ROUTE", func(t *testing.T) { + if deviations.BgpDefaultPolicyUnsupported(dut) { + configureRoutingPolicyDefaultAction(t, dut, "accept", !defaultStatementOnly) + } else { + configureBGPDefaultPolicy(t, dut, acceptRoute) + } + }) + + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv4, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{ebgpExportIPv4}, importPol: []string{ebgpImportIPv4}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv6, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{ebgpExportIPv6}, importPol: []string{ebgpImportIPv6}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: false}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV4, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{ibgpExportIPv4}, importPol: []string{ibgpImportIPv4}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV6, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{ibgpExportIPv6}, importPol: []string{ibgpImportIPv6}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: false}) +} + +func testDefaultPolicyAcceptRouteActionOnly(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + + t.Run("Create and apply default-policy ACCEPT-ALL with action as ACCEPT_ROUTE", func(t *testing.T) { + if deviations.BgpDefaultPolicyUnsupported(dut) { + configureRoutingPolicyDefaultAction(t, dut, "accept", defaultStatementOnly) + } else { + configureBGPDefaultPolicy(t, dut, acceptRoute) + t.Run("Delete prefix set policies", func(t *testing.T) { + deleteBGPPolicy(t, dut, []*bgpNbrList{ebgpNbrV4, ebgpNbrV6, ibgpNbrV4, ibgpNbrV6}) + }) + } + }) + + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv4, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv6, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: false}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV4, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV6, defExportPol: acceptRoute, + defImportPol: acceptRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: false}) +} + +func testNoPolicyConfiguredIBGPPeer(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + // TODO: RT-7.5 should be automated only after the expected behavior is confirmed in + // https://github.com/openconfig/public/issues/981 +} + +func testNoPolicyConfigured(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + // TODO: RT-7.6 should be automated only after the expected behavior is confirmed in + // https://github.com/openconfig/public/issues/981 +} + +func testDefaultPolicyRejectRouteActionOnly(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + + t.Run("Create and apply default-policy REJECT-ALL with action as REJECT_ROUTE", func(t *testing.T) { + if deviations.BgpDefaultPolicyUnsupported(dut) { + configureRoutingPolicyDefaultAction(t, dut, "reject", defaultStatementOnly) + } else { + configureBGPDefaultPolicy(t, dut, rejectRoute) + } + }) + + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv4, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 0, wantRx: 0, wantRxPrePolicy: 3, wantSent: 0, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: atePort1.IPv6, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 0, wantRx: 0, wantRxPrePolicy: 3, wantSent: 0, isV4: false}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV4, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 0, wantRx: 0, wantRxPrePolicy: 3, wantSent: 0, isV4: true}) + verifyPostPolicyPrefixTelemetry(t, dut, &peerDetails{ipAddr: otgIsisPort2LoopV6, defExportPol: rejectRoute, + defImportPol: rejectRoute, exportPol: []string{}, importPol: []string{}, + wantInstalled: 0, wantRx: 0, wantRxPrePolicy: 3, wantSent: 0, isV4: false}) +} + +func configureRoutePolicies(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + 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, ebgpImportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix1, ipv4Prefix2})) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, ebgpImportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix1, ipv6Prefix2})) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, ebgpExportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix4})) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, ebgpExportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix4})) + + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, ibgpImportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix4, ipv4Prefix5})) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, ibgpImportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix4, ipv6Prefix5})) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, ibgpExportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix1})) + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, ibgpExportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix1})) + + // Apply the above policies to the respective peering at the repective AFI-SAFI levels + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV4.nbrAddr).AfiSafi(ebgpNbrV4.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ebgpImportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV4.nbrAddr).AfiSafi(ebgpNbrV4.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ebgpExportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV6.nbrAddr).AfiSafi(ebgpNbrV6.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ebgpImportIPv6}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ebgpNbrV6.nbrAddr).AfiSafi(ebgpNbrV6.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ebgpExportIPv6}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV4.nbrAddr).AfiSafi(ibgpNbrV4.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ibgpImportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV4.nbrAddr).AfiSafi(ibgpNbrV4.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ibgpExportIPv4}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV6.nbrAddr).AfiSafi(ibgpNbrV6.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{ibgpImportIPv6}) + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ibgpNbrV6.nbrAddr).AfiSafi(ibgpNbrV6.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{ibgpExportIPv6}) + + batchConfig.Set(t, dut) +} + +type peerDetails struct { + ipAddr string + defExportPol, defImportPol oc.E_RoutingPolicy_DefaultPolicyType + exportPol, importPol []string + wantInstalled, wantRx, wantRxPrePolicy, wantSent uint32 + isV4 bool +} + +func verifyPostPolicyPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr *peerDetails) { + t.Helper() + + t.Logf("Prefix telemetry validation for the neighbor %v", nbr.ipAddr) + + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + var afiSafiPath *netinstbgp.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafiPath + if nbr.isV4 { + afiSafiPath = statePath.Neighbor(nbr.ipAddr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + } else { + afiSafiPath = statePath.Neighbor(nbr.ipAddr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + } + + peerTel := gnmi.Get(t, dut, afiSafiPath.State()) + + if !deviations.BgpDefaultPolicyUnsupported(dut) { + if gotDefExPolicy := peerTel.GetApplyPolicy().GetDefaultExportPolicy(); gotDefExPolicy != nbr.defExportPol { + t.Errorf("Default export policy type mismatch: got %v, want %v", gotDefExPolicy, nbr.defExportPol) + } + + if gotDefImPolicy := peerTel.GetApplyPolicy().GetDefaultImportPolicy(); gotDefImPolicy != nbr.defImportPol { + t.Errorf("Default import policy type mismatch: got %v, want %v", gotDefImPolicy, nbr.defImportPol) + } + } + if len(nbr.exportPol) != 0 { + if gotExportPol := peerTel.GetApplyPolicy().GetExportPolicy(); cmp.Diff(gotExportPol, nbr.exportPol) != "" { + t.Errorf("Export policy type mismatch: got %v, want %v", gotExportPol, nbr.exportPol) + } + } + if len(nbr.importPol) != 0 { + if gotImportPol := peerTel.GetApplyPolicy().GetImportPolicy(); cmp.Diff(gotImportPol, nbr.importPol) != "" { + t.Errorf("Import policy type mismatch: got %v, want %v", gotImportPol, nbr.importPol) + } + } + + if gotInstalled, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().Installed().State(), 10*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotInstalled, ok := val.Val() + return ok && gotInstalled == nbr.wantInstalled + }).Await(t); !ok { + t.Errorf("Installed prefixes mismatch: got %v, want %v", gotInstalled, nbr.wantInstalled) + } + + if !deviations.MissingPrePolicyReceivedRoutes(dut) { + if gotRxPrePol, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().ReceivedPrePolicy().State(), 20*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 { + gotRx, ok := val.Val() + return ok && gotRx == nbr.wantRx + }).Await(t); !ok { + t.Errorf("Received prefixes mismatch: got %v, want %v", gotRx, nbr.wantRx) + } + + 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 { + gotSent, ok := val.Val() + return ok && gotSent == nbr.wantSent + }).Await(t); !ok { + t.Errorf("Sent prefixes mismatch: got %v, want %v", gotSent, nbr.wantSent) + } + } +} + +func configStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + staticRoute1 := fmt.Sprintf("%s/%d", ipv4Prefix7, uint32(32)) + staticRoute2 := fmt.Sprintf("%s/%d", ipv6Prefix7, uint32(128)) + staticRoute3 := fmt.Sprintf("%s/%d", ipv4Prefix8, uint32(32)) + staticRoute4 := fmt.Sprintf("%s/%d", ipv6Prefix8, uint32(128)) + + ni := oc.NetworkInstance{Name: ygot.String(deviations.DefaultNetworkInstance(dut))} + static := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + + sr1 := static.GetOrCreateStatic(staticRoute1) + nh1 := sr1.GetOrCreateNextHop("0") + nh1.NextHop = oc.UnionString(atePort1.IPv4) + + sr2 := static.GetOrCreateStatic(staticRoute2) + nh2 := sr2.GetOrCreateNextHop("0") + nh2.NextHop = oc.UnionString(atePort1.IPv6) + + sr3 := static.GetOrCreateStatic(staticRoute3) + nh3 := sr3.GetOrCreateNextHop("0") + nh3.NextHop = oc.UnionString(atePort2.IPv4) + + sr4 := static.GetOrCreateStatic(staticRoute4) + nh4 := sr4.GetOrCreateNextHop("0") + nh4.NextHop = oc.UnionString(atePort2.IPv6) + + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) +} + +// TestBGPDefaultPolicies is to test default-policies at the BGP peer-group and neighbor levels. +func TestBGPDefaultPolicies(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + t.Run("Configure DUT interfaces", func(t *testing.T) { + configureDUT(t, dut) + }) + + t.Run("Configure DEFAULT network instance", func(t *testing.T) { + fptest.ConfigureDefaultNetworkInstance(t, dut) + }) + + t.Run("Configure ISIS on DUT", func(t *testing.T) { + dutIsisIntfNames := []string{dut.Port(t, "port2").Name(), loopbackIntfName} + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + dutIsisIntfNames = []string{dut.Port(t, "port2").Name() + ".0", loopbackIntfName + ".0"} + } + configureISIS(t, dut, dutIsisIntfNames, dutAreaAddress, dutSysID) + }) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + t.Run("Configure BGP Neighbors", func(t *testing.T) { + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, ateAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + }) + + otg := ate.OTG() + t.Run("Configure OTG", func(t *testing.T) { + configureOTG(t, otg) + }) + + t.Run("Verify port status on DUT", func(t *testing.T) { + verifyPortsUp(t, dut.Device) + }) + + t.Run("Verify ISIS session status on DUT", func(t *testing.T) { + dutIsisPeerIntf := []string{dut.Port(t, "port2").Name()} + verifyISISTelemetry(t, dut, dutIsisPeerIntf) + }) + + t.Run("Verify BGP session telemetry", func(t *testing.T) { + verifyBgpTelemetry(t, dut) + }) + + t.Run("Verify BGP capabilities", func(t *testing.T) { + verifyBGPCapabilities(t, dut) + }) + + t.Run("Verify prefix telemetry on DUT for all iBGP and eBGP peers", func(t *testing.T) { + if deviations.DefaultImportExportPolicyUnsupported(dut) { + verifyPrefixesTelemetry(t, dut, atePort1.IPv4, 3, 3, 3, v4Prefixes) + verifyPrefixesTelemetry(t, dut, otgIsisPort2LoopV4, 3, 3, 3, v4Prefixes) + verifyPrefixesTelemetry(t, dut, atePort1.IPv6, 3, 3, 3, !v4Prefixes) + verifyPrefixesTelemetry(t, dut, otgIsisPort2LoopV6, 3, 3, 3, !v4Prefixes) + } else { + verifyPrefixesTelemetry(t, dut, atePort1.IPv4, 0, 3, 0, v4Prefixes) + verifyPrefixesTelemetry(t, dut, otgIsisPort2LoopV4, 3, 3, 0, v4Prefixes) + verifyPrefixesTelemetry(t, dut, atePort1.IPv6, 0, 3, 0, !v4Prefixes) + verifyPrefixesTelemetry(t, dut, otgIsisPort2LoopV6, 3, 3, 0, !v4Prefixes) + } + }) + + t.Run("Add static routes for ip prefixes IPv4/v6-prefix7 and IPv4/v6-prefix8", func(t *testing.T) { + configStaticRoute(t, dut) + }) + + t.Run("Create and apply prefix based import and export route policies", func(t *testing.T) { + configureRoutePolicies(t, dut) + }) + + cases := []struct { + desc string + funcName func() + skipMsg string + }{{ + desc: "RT-7.1.1: Policy definition in policy chain is not satisfied and Default Policy has REJECT_ROUTE action", + funcName: func() { testDefaultPolicyRejectRouteAction(t, dut) }, + }, { + desc: "RT-7.1.2 : Policy definition in policy chain is not satisfied and Default Policy has ACCEPT_ROUTE action", + funcName: func() { testDefaultPolicyAcceptRouteAction(t, dut) }, + }, { + desc: "RT-7.1.3 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has ACCEPT_ROUTE action", + funcName: func() { testDefaultPolicyAcceptRouteActionOnly(t, dut) }, + }, { + desc: "RT-7.1.4 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has REJECT_ROUTE action", + funcName: func() { testDefaultPolicyRejectRouteActionOnly(t, dut) }, + }, { + desc: "RT-7.1.5 : No policy including the default-policy is attached either at the Peer-group or at the neighbor level for only IBGP peer", + funcName: func() { testNoPolicyConfiguredIBGPPeer(t, dut) }, + skipMsg: "TODO: RT-7.1.5 should be automated only after the expected behavior is confirmed in issue num 981", + }, { + desc: "RT-7.1.6 : No policy including the default-policy is attached either at the Peer-group or at the neighbor level for both EBGP and IBGP peers", + funcName: func() { testNoPolicyConfigured(t, dut) }, + skipMsg: "TODO: RT-7.1.6 should be automated only after the expected behavior is confirmed in issue num 981", + }} + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + if len(tc.skipMsg) > 0 { + t.Skip(tc.skipMsg) + } + tc.funcName() + }) + } +} diff --git a/feature/bgp/policybase/otg_tests/default_policies_test/metadata.textproto b/feature/bgp/policybase/otg_tests/default_policies_test/metadata.textproto new file mode 100644 index 00000000000..21a048a2b80 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/default_policies_test/metadata.textproto @@ -0,0 +1,55 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1d21e3cf-25c0-4e44-8988-582188e37452" +plan_id: "RT-7.1" +description: "BGP default policies" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + skip_non_bgp_route_export_check: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_isis_interface_afi_safi_enable: true + isis_interface_afi_unsupported: true + isis_instance_enabled_required: true + default_import_export_policy_unsupported: true + bgp_default_policy_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + explicit_port_speed: true + interface_enabled: true + static_protocol_name: "static" + skip_non_bgp_route_export_check: true + missing_isis_interface_afi_safi_enable: true + bgp_default_policy_unsupported: true + skip_prefix_set_mode: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + missing_isis_interface_afi_safi_enable: true + bgp_default_policy_unsupported: true + prepolicy_received_routes: true + } +} diff --git a/feature/bgp/policybase/otg_tests/import_export_multi_test/README.md b/feature/bgp/policybase/otg_tests/import_export_multi_test/README.md new file mode 100644 index 00000000000..6e8fca19c0c --- /dev/null +++ b/feature/bgp/policybase/otg_tests/import_export_multi_test/README.md @@ -0,0 +1,245 @@ +# RT-7.11: BGP Policy - Import/Export Policy Action Using Multiple Criteria + +## Summary + +The purpose of this test is to verify a combination of bgp conditions using +matching and policy nesting as well as and actions in a single BGP import +policy. Additional combinations may be added in the future as additonal +subtests. + +## Testbed type + +* [2 port ATE to DUT](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Testbed common configuration + +This configuration initializes the testbed with configurations that are a +pre-requisite for the test. This configuration should not be part of the test +functions. + +* Testbed configuration - Setup eBGP sessions and prefixes + * Generate config for 2 DUT and ATE ports where + * DUT port 1 connects to ATE port 1. + * DUT port 2 connects to ATE port 2. + * Configure ATE port 1 with an external type BGP session to DUT port 1 + * DUT ASN 65000 + * ATE port 1 ASN 65100 + * ATE port 2 ASN 65200 + * Advertise ipv4 and ipv6 prefixes from ATE port 1 to DUT port 1 using + the following communities: + * prefix-set-1 with 2 ipv4 and 2 ipv6 routes with communities [ "10:1" ] + * prefix-set-2 with 2 ipv4 and 2 ipv6 routes with communities [ "20:1" ] + * prefix-set-3 with 2 ipv4 and 2 ipv6 routes with communities [ "30:1" ] + * prefix-set-4 with 2 ipv4 and 2 ipv6 routes with communities [ "20:2", "30:3" ] + * prefix-set-5 with 2 ipv4 and 2 ipv6 routes with communities [ "40:1" ] + * prefix-set-6 with 2 ipv4 and 2 ipv6 routes with communities [ "50:1" ] + * Configure accept_all policy + * Create policy-definitions/policy-definition/config/name = "accept_all" + * statements/statement/config/name = "accept" + * actions/config/policy-result = "ACCEPT_ROUTE" + * apply as an export and import policy on the DUT + eBGP session to ATE port 1 and port 2. + +* Configure the following community sets on the DUT: + * /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config + * name = "reject_communities" + * community-member = [ "10:1" ] + * name = "accept_communities" + * community-member = [ "20:1" ] + * name = "regex_community" + * community-member = [ "^30:.*$" ] + * name = "add_communities" + * community-member = [ "40:1", "40:2" ] + * name "my_community" + * community-member = [ "50:1" ] + * name = "add_comm_60" + * community-member = [ "60:1" ] + * name = "add_comm_70" + * community-member = [ "70:1" ] + +* Create an as-path-set on the DUT as follows + * /routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/ + * as-path-set-name = "my_aspath" + * as-path-set-member = "65100" + +* Validate bgp sessions and traffic + * For IPv4 and IPv6 prefixes: + * Observe received prefixes at ATE port-2. + * Generate traffic from ATE port-2 to ATE port-1. + * Validate + * Traffic can be received on ATE port-1 for all installed routes. + * Communities on ATE Port 2 are equal to those sent by ATE Port1 + * as-path shall be "65100 65000" + * Local-Preference should be not present + * MED should be not present + +## Procedure + +### RT-7.11.1 - Create a bgp policy containing the following conditions and actions + +* Summary of this policy + * Reject route matching any communities in a community-set. + * Reject route matching another policy (nested) and not matching a community-set. + * Add a community-set if missing that same community-set. + * Add two communities and set localpref if matching a community and prefix-set. + * Set MED if matching an aspath + +* Define a policy that will be called from another policy + * policy-definitions/policy-definition/config/name: "match_community_regex" + * statements/statement/config/name: "match_community_regex" + * conditions/bgp-conditions/match-community-set/config/ + * community-set: "regex-community" + * match-set-options: "ANY" + * actions/config/policy-result = "NEXT_STATEMENT" + +* Create policy-definitions/policy-definition/config/name = "multi_policy" + * statements/statement/config/name = "reject_route_community" + * conditions/bgp-conditions/match-community-set/config + * community-set = "reject_communities" + * match-set-options = "ANY" + * actions/config/policy-result = "REJECT_ROUTE" + + * statements/statement/config/name = "if_30:.*_and_not_20:1_nested_reject" + * conditions/config/call-policy = "match_community_regex" + * conditions/bgp-conditions/match-community-set/config/ + * community-set = "accept_communities" + * match-set-options = "INVERT" + * actions/config/policy-result = "REJECT_ROUTE" + + * statements/statement/config/name = "add_communities_if_missing" + * conditions/bgp-conditions/match-community-set/config/ + * community-set-refs = "add-communities" + * match-set-options: "INVERT" + * actions/bgp-actions/set-community/reference/config/ + * community-set-refs = "add-communities" + * method = "REFERENCE" + * option = "ADD" + * actions/config/policy-result = "NEXT_STATEMENT" + + * statements/statement/config/name: "match_comm_and_prefix_add_2_community_sets" + * conditions/bgp-conditions/match-community-set/config + * community-set = "my_community" + * match-set-options = "ANY" + * conditions/match-prefix-set/config + * prefix-set = "prefix-set-5" + * match-set-options = "ANY" + * actions/bgp-actions/set-community/config + * method = "REFERENCE" + * option = "ADD" + * community-set-refs = "add_comm_60", "add_comm_70" + * actions/bgp-actions/config/set-local-pref = 5 + * actions/config/policy-result = "NEXT_STATEMENT" + + * statements/statement/config/name: "match_aspath_set_med" + * conditions/bgp-conditions/match-as-path-set/config/ + * as-path-set = "my_aspath" + * match-set-options = "ANY" + * actions/bgp-actions/config/ + * set-med = 100 + * actions/config/policy-result = "ACCEPT_ROUTE" + +* Use gnmi Set REPLACE option to configure the policies above on the DUT at this subtree level: + * `/routing-policy/policy-definitions` + +#### RT-7.11.2 Attach multi_policy as import policy + +* Use gnmi Set REPLACE option to apply the policy on the DUT bgp neighbor to the ATE port 1. + * at this subtree level: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy + * Set the value `config/import-policy` = "multi-policy" + +#### RT-7.11.3 Verify import_multi_policy expected attributes are present + +* Verify expected attributes are present in ATE. + +> NOTE: (At the time of writing, the APIs necesary to do this validation are not yet available via the OTG API. A feature enhancement has been submitted.) + +#### RT-7.11.4 Configure export_multi_policy + +This replace method should guarantee that the previous step's import-policy is removed. + +* Use gnmi Set REPLACE option to apply the policy on the DUT bgp neighbor to the ATE port 1. + * at this subtree level: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy + * Set the value `config/export-policy` = "multi-policy" + +#### RT-7.11.5 Verify export_multi_policy expected attributes are present + +* Verify expected attributes are present in ATE. + +#### Multi_policy results observed on ATE port 2 for both import and export cases + + | | Received | Communities | as-path | lpref | med | Notes | + | ------------ | -------- | --------------------------------- | ----------- | ----- | --- | --------------------------------------------------------- | + | prefix-set-1 | False | n/a | n/a | n/a | n/a | rejected by statement reject_route_community | + | prefix-set-2 | True | [ "20:1", "40:1", "40:2" ] | 65000 65100 | n/a | 100 | accepted | + | prefix-set-3 | False | n/a | n/a | n/a | n/a | rejected by statement if_30:.*_and_not_20:1_nested_reject | + | prefix-set-4 | False | n/a | n/a | n/a | n/a | rejected by statement if_30:.*_and_not_20:1_nested_reject | + | prefix-set-5 | True | [ "40:1","40:2", "60:1", "70:1" ] | 65000 65100 | 5 | 100 | accepted and match_comm_and_prefix_add_2_community_sets | + | prefix-set-6 | True | [ "10:1", "40:1", "40:2" ] | 65000 65100 | n/a | 100 | accepted | + +## 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 ## + # Policy definition + /routing-policy/policy-definitions/policy-definition/config/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/config/name: + + # Policy for community-set configuration + /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config/ext-community-set-name: + /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config/ext-community-member: + + # Policy for match configuration + /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/conditions/bgp-conditions/match-as-path-set/config/as-path-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-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/conditions/match-prefix-set/config/match-set-options: + + # Policy for bgp actions + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/config/method: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/config/options: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-refs: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med: + + # Policy for bgp attachment + /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/export-policy: + + ## State Paths ## + # Policy definition state + /routing-policy/policy-definitions/policy-definition/state/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/state/name: + + # Policy for community-set match state + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-ext-community-set/state/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/community-set: + + # Paths to verify policy state + /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/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy: + + # Paths to verify prefixes sent and received + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy: + /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/installed: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` + +## Minimum DUT Required + +vRX - Virtual Router Device diff --git a/feature/bgp/policybase/otg_tests/import_export_multi_test/import_export_multi_test.go b/feature/bgp/policybase/otg_tests/import_export_multi_test/import_export_multi_test.go new file mode 100644 index 00000000000..0e20c80a60c --- /dev/null +++ b/feature/bgp/policybase/otg_tests/import_export_multi_test/import_export_multi_test.go @@ -0,0 +1,905 @@ +// 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 import_export_test covers RT-7.11: BGP Policy - Import/Export Policy Action Using Multiple Criteria +package import_export_multi_test + +import ( + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "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" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + otg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + prefixV4Len = 30 + prefixV6Len = 126 + trafficPps = 100 + totalPackets = 1200 + localPref = 5 + medValue = 100 + bgpName = "BGP" + otglocalPref = "local-pref" + otgMED = "med" + otgASPath = "as-path" + otgCommunity = "community" + parentPolicy = "multiPolicy" + callPolicy = "match_community_regex" + rejectStatement = "reject_route_community" + nestedRejectStatement = "if_30_and_not_20_nested_reject" + callPolicyStatement = "match_community_regex" + addMissingCommunitiesStatement = "add_communities_if_missing" + matchCommPrefixAddCommuStatement = "match_comm_and_prefix_add_2_community_sets" + matchAspathSetMedStatement = "match_aspath_set_med" + rejectCommunitySet = "reject_communities" + nestedRejectCommunitySet = "accept_communities" + regexCommunitySet = "regex-community" + addCommunitiesSetRefs = "add-communities" + myCommunitySet = "my_community" + prefixSetName = "prefix-set-5" + myAsPathName = "my_aspath" + bgpActionMethod = oc.SetCommunity_Method_REFERENCE + bgpSetCommunityOptionType = oc.BgpPolicy_BgpSetCommunityOptionType_ADD + prefixSetNameSetOptions = oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY + matchAny = oc.BgpPolicy_MatchSetOptionsType_ANY + matchInvert = oc.BgpPolicy_MatchSetOptionsType_INVERT + rejectResult = oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE + nextstatementResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT +) + +var prefixesV4 = [][]string{ + {"198.51.100.0", "198.51.100.4"}, + {"198.51.100.8", "198.51.100.12"}, + {"198.51.100.16", "198.51.100.20"}, + {"198.51.100.24", "198.51.100.28"}, + {"198.51.100.32", "198.51.100.36"}, + {"198.51.100.40", "198.51.100.44"}, +} + +var prefixesV6 = [][]string{ + {"2048:db1:64:64::0", "2048:db1:64:64::4"}, + {"2048:db1:64:64::8", "2048:db1:64:64::c"}, + {"2048:db1:64:64::10", "2048:db1:64:64::14"}, + {"2048:db1:64:64::18", "2048:db1:64:64::1c"}, + {"2048:db1:64:64::20", "2048:db1:64:64::24"}, + {"2048:db1:64:64::28", "2048:db1:64:64::2c"}, +} + +var communityMembers = [][][]int{ + { + {10, 1}, {11, 1}, + }, + { + {20, 1}, {21, 1}, + }, + { + {30, 1}, {31, 1}, + }, + { + {20, 2}, {30, 3}, + }, + { + {40, 1}, {50, 1}, + }, + { + {50, 1}, {51, 1}, + }, +} + +var communityReceived [][][]int + +type bgpNbrList struct { + nbrAddr string + afiSafi oc.E_BgpTypes_AFI_SAFI_TYPE +} + +// TestMain triggers the test run +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func deleteBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, nbrList []*bgpNbrList) { + t.Helper() + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + for _, nbr := range nbrList { + nbrAfiSafiPath := bgpPath.Neighbor(nbr.nbrAddr).AfiSafi(nbr.afiSafi) + b := &gnmi.SetBatch{} + gnmi.BatchDelete(b, nbrAfiSafiPath.ApplyPolicy().ImportPolicy().Config()) + gnmi.BatchDelete(b, nbrAfiSafiPath.ApplyPolicy().ExportPolicy().Config()) + b.Set(t, dut) + } +} + +func configureImportExportAcceptAllBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4 string, ipv6 string) { + // Delete PERMIT-ALL policy applied to neighbor + deleteBGPPolicy(t, dut, []*bgpNbrList{ + { + nbrAddr: ipv4, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, + }, + { + nbrAddr: ipv6, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST, + }, + }) + + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition("routePolicy") + stmt1, err := pdef1.AppendNewStatement("routePolicyStatement") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement", err) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + + dni := deviations.DefaultNetworkInstance(dut) + pathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policyV6.SetImportPolicy([]string{"routePolicy"}) + policyV6.SetExportPolicy([]string{"routePolicy"}) + gnmi.Replace(t, dut, pathV6.Config(), policyV6) + + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policyV4 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policyV4.SetImportPolicy([]string{"routePolicy"}) + policyV4.SetExportPolicy([]string{"routePolicy"}) + gnmi.Replace(t, dut, pathV4.Config(), policyV4) +} + +func configureImportExportMultifacetMatchActionsBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, ipv4 string, ipv6 string, ipv41 string, ipv61 string) { + rejectCommunities := []string{"10:1"} + acceptCommunities := []string{"20:1"} + regexCommunities := []string{"^30:.*$"} + addCommunitiesRefs := []string{"40:1", "40:2"} + addCommunitiesSetRefsAction := []string{"add-communities"} + setCommunitySetRefs := []string{"add_comm_60", "add_comm_70"} + myCommunitySets := []string{"50:1"} + if deviations.BgpCommunityMemberIsAString(dut) { + regexCommunities = []string{"(^|\\s)30:[0-9]+($|\\s)"} + } + + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + + // Configure the policy match_community_regex which will be called from multi_policy + + pdef2 := rp.GetOrCreatePolicyDefinition(callPolicy) + + // Configure match_community_regex:STATEMENT1:match_community_regex statement + + pd2stmt1, err := pdef2.AppendNewStatement(callPolicyStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", callPolicyStatement, err) + } + + // Configure regex_community:["^30:.*$"] to match_community_regex statement + if !(deviations.CommunityMemberRegexUnsupported(dut)) { + communitySetRegex := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(regexCommunitySet) + + pd2cs1 := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + for _, commMatchPd2Cs1 := range regexCommunities { + if commMatchPd2Cs1 != "" { + pd2cs1 = append(pd2cs1, oc.UnionString(commMatchPd2Cs1)) + } + } + communitySetRegex.SetCommunityMember(pd2cs1) + communitySetRegex.SetMatchSetOptions(matchAny) + } + + var communitySetCLIConfig string + if deviations.CommunityMemberRegexUnsupported(dut) { + switch dut.Vendor() { + case ondatra.CISCO: + communitySetCLIConfig = fmt.Sprintf("community-set %v\n ios-regex '(%v)'\n end-set", regexCommunitySet, regexCommunities[0]) + default: + t.Fatalf("Unsupported vendor %s for deviation 'CommunityMemberRegexUnsupported'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) + } + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + pd2stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(regexCommunitySet) + } else { + pd2stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(regexCommunitySet) + } + + if !deviations.SkipSettingStatementForPolicy(dut) { + pd2stmt1.GetOrCreateActions().SetPolicyResult(nextstatementResult) + } + + // Configure the parent policy multi_policy. + + pdef1 := rp.GetOrCreatePolicyDefinition(parentPolicy) + + // Configure multi_policy:STATEMENT1: reject_route_community + stmt1, err := pdef1.AppendNewStatement(rejectStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", rejectStatement, err) + } + + // Configure reject_communities:[10:1] to reject_route_community statement + communitySetReject := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(rejectCommunitySet) + + cs1 := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + for _, commMatch1 := range rejectCommunities { + if commMatch1 != "" { + cs1 = append(cs1, oc.UnionString(commMatch1)) + } + } + communitySetReject.SetCommunityMember(cs1) + communitySetReject.SetMatchSetOptions(matchAny) + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(rejectCommunitySet) + } else { + stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(rejectCommunitySet) + } + + stmt1.GetOrCreateActions().SetPolicyResult(rejectResult) + + // Configure multi_policy:STATEMENT2:if_30:.*_and_not_20:1_nested_reject + + stmt2, err := pdef1.AppendNewStatement(nestedRejectStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", nestedRejectStatement, err) + } + + // Call child policy match_community_regex from parent policy multi_policy + + statPath := rp.GetOrCreatePolicyDefinition(parentPolicy).GetStatement(nestedRejectStatement) + statPath.GetOrCreateConditions().SetCallPolicy(callPolicy) + + // Configure accept_communities:[20:1] to if_30_and_not_20_nested_reject statement + communitySetNestedReject := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(nestedRejectCommunitySet) + + cs2 := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + for _, commMatch2 := range acceptCommunities { + if commMatch2 != "" { + cs2 = append(cs2, oc.UnionString(commMatch2)) + } + } + communitySetNestedReject.SetCommunityMember(cs2) + communitySetNestedReject.SetMatchSetOptions(matchInvert) + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(nestedRejectCommunitySet) + } else { + stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(nestedRejectCommunitySet) + } + + stmt2.GetOrCreateActions().SetPolicyResult(rejectResult) + + // Configure multi_policy:STATEMENT3: add_communities_if_missing statement + stmt3, err := pdef1.AppendNewStatement(addMissingCommunitiesStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", addMissingCommunitiesStatement, err) + } + + // Configure add-communities: [ "40:1", "40:2" ] to add_communities_if_missing statement + + communitySetRefsAddCommunities := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(addCommunitiesSetRefs) + + cs3 := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + for _, commMatch4 := range addCommunitiesRefs { + if commMatch4 != "" { + cs3 = append(cs3, oc.UnionString(commMatch4)) + } + } + communitySetRefsAddCommunities.SetCommunityMember(cs3) + communitySetRefsAddCommunities.SetMatchSetOptions(matchInvert) + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(addCommunitiesSetRefs) + } else { + if deviations.BgpCommunitySetRefsUnsupported(dut) { + t.Logf("TODO: community-set-refs not supported b/316833803") + stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(addCommunitiesSetRefs) + } + } + + if deviations.BgpCommunitySetRefsUnsupported(dut) { + t.Logf("TODO: community-set-refs not supported b/316833803") + } else { + stmt3.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().GetOrCreateReference().SetCommunitySetRefs(addCommunitiesSetRefsAction) + stmt3.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().SetMethod(bgpActionMethod) + stmt3.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().SetOptions(bgpSetCommunityOptionType) + } + + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt3.GetOrCreateActions().SetPolicyResult(nextstatementResult) + } + + // Configure my_community: [ "50:1" ] to match_comm_and_prefix_add_2_community_sets statement + communitySetMatchCommPrefixAddCommu := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(myCommunitySet) + + cs4 := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + for _, commMatch5 := range myCommunitySets { + if commMatch5 != "" { + cs4 = append(cs4, oc.UnionString(commMatch5)) + } + } + communitySetMatchCommPrefixAddCommu.SetCommunityMember(cs4) + communitySetMatchCommPrefixAddCommu.SetMatchSetOptions(matchAny) + + // Configure multi_policy:STATEMENT4: match_comm_and_prefix_add_2_community_sets statement + + stmt4, err := pdef1.AppendNewStatement(matchCommPrefixAddCommuStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", matchCommPrefixAddCommuStatement, err) + } + stmt6, err := pdef1.AppendNewStatement(matchCommPrefixAddCommuStatement + "_V6") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", matchCommPrefixAddCommuStatement, err) + } + + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + stmt4.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(myCommunitySet) + stmt6.GetOrCreateConditions().GetOrCreateBgpConditions().SetCommunitySet(myCommunitySet) + } else { + stmt4.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(myCommunitySet) + stmt6.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet(myCommunitySet) + } + + // configure match-prefix-set: prefix-set-5 to match_comm_and_prefix_add_2_community_sets statement + stmt4.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(prefixSetName) + stmt4.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(prefixSetNameSetOptions) + stmt6.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(prefixSetName + "_V6") + stmt6.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(prefixSetNameSetOptions) + + pset := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(prefixSetName) + pset.GetOrCreatePrefix(prefixesV4[4][0]+"/29", "29..30") + if !deviations.SkipPrefixSetMode(dut) { + pset.SetMode(oc.PrefixSet_Mode_IPV4) + } + + psetV6 := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(prefixSetName + "_V6") + psetV6.GetOrCreatePrefix(prefixesV6[4][0]+"/125", "125..126") + if !deviations.SkipPrefixSetMode(dut) { + psetV6.SetMode(oc.PrefixSet_Mode_IPV6) + } + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetName).Config(), pset) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetName+"_V6").Config(), psetV6) + + if deviations.BgpCommunitySetRefsUnsupported(dut) { + t.Logf("TODO: community-set-refs not supported b/316833803") + } else { + // TODO: Add bgp-actions: community-set-refs to match_comm_and_prefix_add_2_community_sets statement + stmt4.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().GetOrCreateReference().SetCommunitySetRefs(setCommunitySetRefs) + stmt4.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().SetMethod(oc.SetCommunity_Method_REFERENCE) + stmt4.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + + stmt6.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().GetOrCreateReference().SetCommunitySetRefs(setCommunitySetRefs) + stmt6.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().SetMethod(oc.SetCommunity_Method_REFERENCE) + stmt6.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetCommunity().SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + } + // set-local-pref = 5 + stmt4.GetOrCreateActions().GetOrCreateBgpActions().SetSetLocalPref(localPref) + stmt6.GetOrCreateActions().GetOrCreateBgpActions().SetSetLocalPref(localPref) + + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt4.GetOrCreateActions().SetPolicyResult(nextstatementResult) + stmt6.GetOrCreateActions().SetPolicyResult(nextstatementResult) + } + + // Configure multi_policy:STATEMENT5: match_aspath_set_med statement + stmt5, err := pdef1.AppendNewStatement(matchAspathSetMedStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", matchAspathSetMedStatement, err) + } + + // TODO create as-path-set on the DUT, match-as-path-set not support. + // Configure set-med 100 + stmt5.GetOrCreateActions().GetOrCreateBgpActions().SetMed = oc.UnionUint32(medValue) + + stmt5.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + if deviations.CommunityMemberRegexUnsupported(dut) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } else { + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } + + // Configure the parent BGP import and export policy. + dni := deviations.DefaultNetworkInstance(dut) + pathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policyV6.SetImportPolicy([]string{parentPolicy}) + policyV6.SetExportPolicy([]string{parentPolicy}) + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV6.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + policyV6.SetDefaultExportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + gnmi.Replace(t, dut, pathV6.Config(), policyV6) + + if !deviations.SkipBgpSendCommunityType(dut) { + n6 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv6) + n6.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_BOTH}) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv6).Config(), n6) + } + + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policyV4 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policyV4.SetImportPolicy([]string{parentPolicy}) + policyV4.SetExportPolicy([]string{parentPolicy}) + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV4.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + policyV4.SetDefaultExportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + gnmi.Replace(t, dut, pathV4.Config(), policyV4) + + if !deviations.SkipBgpSendCommunityType(dut) { + n4 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv4) + n4.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_BOTH}) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv4).Config(), n4) + } + + pathV61 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv61).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV61 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv61).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policyV61.SetExportPolicy([]string{parentPolicy}) + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV6.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + policyV6.SetDefaultExportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + gnmi.Update(t, dut, pathV61.Config(), policyV61) + + pathV41 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(ipv41).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policyV41 := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(ipv41).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policyV41.SetExportPolicy([]string{parentPolicy}) + if !deviations.DefaultRoutePolicyUnsupported(dut) { + policyV4.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + policyV4.SetDefaultExportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } + gnmi.Update(t, dut, pathV41.Config(), policyV41) +} + +func configureOTG(t *testing.T, bs *cfgplugins.BGPSession, prefixesV4 [][]string, prefixesV6 [][]string, communityMembers [][][]int) { + t.Logf("configure OTG") + devices := bs.ATETop.Devices().Items() + + ipv4 := devices[1].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := devices[1].Bgp().Ipv4Interfaces().Items()[0].Peers().Items()[0] + + ipv6 := devices[1].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := devices[1].Bgp().Ipv6Interfaces().Items()[0].Peers().Items()[0] + + for index, prefixes := range prefixesV4 { + bgp4PeerRoute := bgp4Peer.V4Routes().Add() + bgp4PeerRoute.SetName(bs.ATEPorts[1].Name + ".BGP4.peer.dut." + strconv.Itoa(index)) + bgp4PeerRoute.SetNextHopIpv4Address(ipv4.Address()) + + route4Address1 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[0]) + route4Address1.SetPrefix(prefixV4Len) + route4Address2 := bgp4PeerRoute.Addresses().Add().SetAddress(prefixes[1]) + route4Address2.SetPrefix(prefixV4Len) + + bgp6PeerRoute := bgp6Peer.V6Routes().Add() + bgp6PeerRoute.SetName(bs.ATEPorts[1].Name + ".BGP6.peer.dut." + strconv.Itoa(index)) + bgp6PeerRoute.SetNextHopIpv6Address(ipv6.Address()) + + route6Address1 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][0]) + route6Address1.SetPrefix(prefixV6Len) + route6Address2 := bgp6PeerRoute.Addresses().Add().SetAddress(prefixesV6[index][1]) + route6Address2.SetPrefix(prefixV6Len) + + for _, commu := range communityMembers[index] { + if commu[0] != 0 { + commv4 := bgp4PeerRoute.Communities().Add() + commv4.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv4.SetAsNumber(uint32(commu[0])) + commv4.SetAsCustom(uint32(commu[1])) + + commv6 := bgp6PeerRoute.Communities().Add() + commv6.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv6.SetAsNumber(uint32(commu[0])) + commv6.SetAsCustom(uint32(commu[1])) + } + } + } +} + +func configureFlowV4(t *testing.T, bs *cfgplugins.BGPSession) { + t.Logf("configure V4 Flow on traffic generator") + for index, prefixPairV4 := range prefixesV4 { + flow := bs.ATETop.Flows().Add().SetName("flow" + "ipv4" + strconv.Itoa(index)) + flow.Metrics().SetEnable(true) + + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv4"}). + SetRxNames([]string{bs.ATEPorts[1].Name + ".BGP4.peer.dut." + strconv.Itoa(index)}) + + flow.Duration().FixedPackets().SetPackets(totalPackets) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(trafficPps) + + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(bs.ATEPorts[1].MAC) + + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(bs.ATEPorts[0].IPv4) + v4.Dst().SetValues(prefixPairV4) + } +} + +func configureFlowV6(t *testing.T, bs *cfgplugins.BGPSession) { + t.Logf("configure V6 Flow on traffic generator") + for index, prefixPairV6 := range prefixesV6 { + flow := bs.ATETop.Flows().Add().SetName("flow" + "ipv6" + strconv.Itoa(index)) + flow.Metrics().SetEnable(true) + + flow.TxRx().Device(). + SetTxNames([]string{bs.ATEPorts[0].Name + ".IPv6"}). + SetRxNames([]string{bs.ATEPorts[1].Name + ".BGP6.peer.dut." + strconv.Itoa(index)}) + + flow.Duration().FixedPackets().SetPackets(totalPackets) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(trafficPps) + + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(bs.ATEPorts[1].MAC) + + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(bs.ATEPorts[0].IPv6) + v6.Dst().SetValues(prefixPairV6) + } +} + +func verifyTrafficV4AndV6(t *testing.T, bs *cfgplugins.BGPSession, testResults [6]bool) { + + sleepTime := time.Duration(totalPackets/trafficPps) + 2 + bs.ATE.OTG().StartTraffic(t) + time.Sleep(time.Second * sleepTime) + bs.ATE.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + otgutils.LogPortMetrics(t, bs.ATE.OTG(), bs.ATETop) + + for index, prefixPairV4 := range prefixesV4 { + t.Logf("Running traffic test for IPv4 prefixes: [%s, %s]. Expected Result: [%t]", prefixPairV4[0], prefixPairV4[1], testResults[index]) + t.Logf("Running traffic test for IPv6 prefixes: [%s, %s]. Expected Result: [%t]", prefixesV6[index][0], prefixesV6[index][1], testResults[index]) + + t.Log("Checking flow telemetry for v4...") + recvMetric := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Flow("flow"+"ipv4"+strconv.Itoa(index)).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + + t.Log("Checking flow telemetry for v6...") + recvMetric6 := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Flow("flow"+"ipv6"+strconv.Itoa(index)).State()) + txPackets6 := recvMetric6.GetCounters().GetOutPkts() + rxPackets6 := recvMetric6.GetCounters().GetInPkts() + lostPackets6 := txPackets6 - rxPackets6 + lossPct6 := lostPackets6 * 100 / txPackets6 + + if txPackets != rxPackets && testResults[index] { + t.Errorf("FAIL- got %v%% packet loss for %s flow and prefixes: [%s, %s]; want < 0%% traffic loss", lossPct, "flow"+"ipv4"+strconv.Itoa(index), prefixPairV4[0], prefixPairV4[1]) + } else if rxPackets != 0 && !testResults[index] { + t.Errorf("FAIL- got %v%% packet loss for %s flow and prefixes: [%s, %s]; want >100%% traffic loss", lossPct, "flow"+"ipv4"+strconv.Itoa(index), prefixPairV4[0], prefixPairV4[1]) + } else if txPackets6 != rxPackets6 && testResults[index] { + t.Errorf("FAIL- got %v%% packet loss for %s flow and prefixes: [%s, %s]; want < 0%% traffic loss", lossPct6, "flow"+"ipv6"+strconv.Itoa(index), prefixesV6[index][0], prefixesV6[index][1]) + } else if rxPackets6 != 0 && !testResults[index] { + t.Errorf("FAIL- got %v%% packet loss for %s flow and prefixes: [%s, %s]; want >100%% traffic loss", lossPct6, "flow"+"ipv6"+strconv.Itoa(index), prefixesV6[index][0], prefixesV6[index][1]) + } else { + t.Logf("Traffic validation successful for Prefixes: [%s, %s]. Result: [%t] PacketsTx: %d PacketsRx: %d", prefixesV6[index][0], prefixesV6[index][1], testResults[index], txPackets6, rxPackets6) + } + + } +} + +func validateLocalPreferenceV4(t *testing.T, dut *ondatra.DUTDevice, prefix string, metricValue uint32) { + dni := deviations.DefaultNetworkInstance(dut) + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv4Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + if prefixAddr[0] == prefix { + found = true + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + if !deviations.SkipCheckingAttributeIndex(dut) { + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != metricValue { + t.Errorf("No local pref found for prefix %s", prefix) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == metricValue { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), prefix) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", prefix) + } + } + } + } + + if !found { + t.Errorf("No Route found for prefix %s", prefix) + } +} + +func validateLocalPreferenceV6(t *testing.T, dut *ondatra.DUTDevice, prefix string, metricValue uint32) { + dni := deviations.DefaultNetworkInstance(dut) + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv6Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Ipv6Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + if prefixAddr[0] == prefix { + found = true + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + if !deviations.SkipCheckingAttributeIndex(dut) { + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != metricValue { + t.Errorf("No local pref found for prefix %s", prefix) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == metricValue { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), prefix) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", prefix) + } + } + } + } + + if !found { + t.Errorf("No Route found for prefix %s", prefix) + } +} + +func validateOTGBgpPrefixV6AndASLocalPrefMED(t *testing.T, otg *otg.OTG, dut *ondatra.DUTDevice, config gosnappi.Config, peerName, ipAddr string, prefixLen uint32, pathAttr string, metric []uint32) { + // t.Helper() + _, ok := gnmi.WatchAll(t, + otg, + gnmi.OTG().BgpPeer(peerName).UnicastIpv6PrefixAny().State(), + 30*time.Second, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv6Prefix]) bool { + _, present := v.Val() + return present + }).Await(t) + var foundPrefix = false + if ok { + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv6Prefix](t, otg, gnmi.OTG().BgpPeer(peerName).UnicastIpv6PrefixAny().State()) + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == ipAddr && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == prefixLen { + foundPrefix = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix, ipAddr) + switch pathAttr { + case otgMED: + if bgpPrefix.GetMultiExitDiscriminator() != metric[0] { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } else { + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } + case otgASPath: + if len(bgpPrefix.AsPath[0].GetAsNumbers()) != len(metric) { + t.Logf("AS number: %v", bgpPrefix.AsPath[0].GetAsNumbers()) + t.Logf("Metric: %v", metric) + t.Errorf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), len(metric)) + } else { + for index, asPath := range bgpPrefix.AsPath[0].GetAsNumbers() { + if asPath == metric[index] { + t.Logf("Comparing if got AS Path %v, want AS Path %v, are equal", bgpPrefix.AsPath[0].GetAsNumbers()[index], metric[index]) + } else { + t.Errorf("For Prefix %v, got AS Path %d want AS Path %d", bgpPrefix.GetAddress(), bgpPrefix.AsPath[0].GetAsNumbers(), metric) + } + } + t.Logf("For Prefix %v, got AS Path %d want AS Path %d", bgpPrefix.GetAddress(), bgpPrefix.AsPath[0].GetAsNumbers(), metric) + } + case otglocalPref: + validateLocalPreferenceV6(t, dut, ipAddr, metric[0]) + case otgCommunity: + t.Logf("For Prefix %v, Community received on OTG: %v", bgpPrefix.GetAddress(), bgpPrefix.Community) + for _, gotCommunity := range bgpPrefix.Community { + // TODO: add check for community + t.Logf("community AS:%d val: %d", gotCommunity.GetCustomAsNumber(), gotCommunity.GetCustomAsValue()) + } + default: + t.Errorf("Incorrect Routing Policy. Expected MED, Local Pref or AS Path Prepend!!!!") + } + break + } + } + } + if !foundPrefix { + t.Errorf("Prefix %v not received on OTG", ipAddr) + } +} + +// validateOTGBgpPrefixV4AndASLocalPrefMED verifies that the IPv4 prefix is received on OTG. +func validateOTGBgpPrefixV4AndASLocalPrefMED(t *testing.T, otg *otg.OTG, dut *ondatra.DUTDevice, config gosnappi.Config, peerName, ipAddr string, prefixLen uint32, pathAttr string, metric []uint32) { + // t.Helper() + _, ok := gnmi.WatchAll(t, + otg, + gnmi.OTG().BgpPeer(peerName).UnicastIpv4PrefixAny().State(), + 30*time.Second, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + _, present := v.Val() + return present + }).Await(t) + var foundPrefix = false + if ok { + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv4Prefix](t, otg, gnmi.OTG().BgpPeer(peerName).UnicastIpv4PrefixAny().State()) + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == ipAddr && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == prefixLen { + foundPrefix = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix.Address, ipAddr) + switch pathAttr { + case otgMED: + if bgpPrefix.GetMultiExitDiscriminator() != metric[0] { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } else { + t.Logf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), metric) + } + case otgASPath: + if len(bgpPrefix.AsPath[0].GetAsNumbers()) != len(metric) { + t.Logf("AS number: %v", bgpPrefix.AsPath[0].GetAsNumbers()) + t.Logf("Metric: %v", metric) + t.Errorf("For Prefix %v, got AS Path Prepend %d want AS Path Prepend %d", bgpPrefix.GetAddress(), len(bgpPrefix.AsPath[0].GetAsNumbers()), len(metric)) + } else { + for index, asPath := range bgpPrefix.AsPath[0].GetAsNumbers() { + if asPath == metric[index] { + t.Logf("Comparing if got AS Path %v, want AS Path %v, are equal", bgpPrefix.AsPath[0].GetAsNumbers()[index], metric[index]) + } else { + t.Errorf("For Prefix %v, got AS Path %d want AS Path %d", bgpPrefix.GetAddress(), bgpPrefix.AsPath[0].GetAsNumbers(), metric) + } + } + t.Logf("For Prefix %v, got AS Path %d want AS Path %d are equal", bgpPrefix.GetAddress(), bgpPrefix.AsPath[0].GetAsNumbers(), metric) + } + case otglocalPref: + validateLocalPreferenceV4(t, dut, ipAddr, metric[0]) + case otgCommunity: + t.Logf("For Prefix %v, Community received on OTG: %v", bgpPrefix.GetAddress(), bgpPrefix.Community) + for _, gotCommunity := range bgpPrefix.Community { + // TODO: add check for community + t.Logf("community AS:%d val: %d", gotCommunity.GetCustomAsNumber(), gotCommunity.GetCustomAsValue()) + } + default: + t.Errorf("Incorrect BGP Path Attribute. Expected MED, Local Pref or AS Path Prepend!!!!") + } + break + } + } + } + if !foundPrefix { + t.Errorf("Prefix %v not received on OTG", ipAddr) + } +} + +// TestImportExportMultifacetMatchActionsBGPPolicy covers RT-7.11 +func TestImportExportMultifacetMatchActionsBGPPolicy(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + var otgConfig gosnappi.Config + + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount2, nil) + bs.WithEBGP(t, []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST}, []string{ + "port1", "port2"}, true, false) + + configureOTG(t, bs, prefixesV4, prefixesV6, communityMembers) + bs.PushAndStart(t) + + t.Log("Verify DUT BGP sessions up") + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) + t.Log("Verify OTG BGP sessions up") + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) + + ipv4 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv4Addresses().Items()[0].Address() + ipv6 := bs.ATETop.Devices().Items()[1].Ethernets().Items()[0].Ipv6Addresses().Items()[0].Address() + + ipv41 := bs.ATETop.Devices().Items()[0].Ethernets().Items()[0].Ipv4Addresses().Items()[0].Address() + ipv61 := bs.ATETop.Devices().Items()[0].Ethernets().Items()[0].Ipv6Addresses().Items()[0].Address() + + t.Logf("Verify Import Export Accept all bgp policy") + configureImportExportAcceptAllBGPPolicy(t, bs.DUT, ipv4, ipv6) + + configureFlowV4(t, bs) + configureFlowV6(t, bs) + + bs.PushAndStartATE(t) + + testResults := [6]bool{true, true, true, true, true, true} + verifyTrafficV4AndV6(t, bs, testResults) + + configureImportExportMultifacetMatchActionsBGPPolicy(t, bs.DUT, ipv4, ipv6, ipv41, ipv61) + time.Sleep(time.Second * 120) + + testResults1 := [6]bool{false, true, false, false, true, true} + verifyTrafficV4AndV6(t, bs, testResults1) + + testMedResults := [6]bool{false, true, false, false, true, true} + testASPathResults := [6]bool{false, true, false, false, true, true} + testLocalPrefResults := [6]bool{false, false, false, false, true, false} + testCommunityResults := [6]bool{false, true, false, false, true, true} + + medValue := []uint32{medValue} + asPathValue := []uint32{cfgplugins.DutAS, cfgplugins.AteAS2} + localPrefValue := []uint32{localPref} + communityResultValue := []uint32{} + + if deviations.BgpCommunitySetRefsUnsupported(dut) { + for index, cm := range communityMembers { + if testCommunityResults[index] { + communityReceived = append(communityReceived, cm) + } + } + } else { + communityReceived = [][][]int{ + append(communityMembers[1], []int{40, 1}, []int{40, 2}), + append(communityMembers[4], []int{40, 2}, []int{60, 1}, []int{70, 1}), + append(communityMembers[5], []int{40, 1}, []int{40, 2})} + } + + for index, prefix := range prefixesV4 { + if testMedResults[index] { + for idx, pref := range prefix { + validateOTGBgpPrefixV4AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP4.peer", pref, prefixV4Len, otgMED, medValue) + validateOTGBgpPrefixV6AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP6.peer", prefixesV6[index][idx], prefixV6Len, otgMED, medValue) + } + } + if testLocalPrefResults[index] { + for idx, pref := range prefix { + validateOTGBgpPrefixV4AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP4.peer", pref, prefixV4Len, otglocalPref, localPrefValue) + validateOTGBgpPrefixV6AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP6.peer", prefixesV6[index][idx], prefixV6Len, otglocalPref, localPrefValue) + } + } + if testASPathResults[index] { + for idx, pref := range prefix { + validateOTGBgpPrefixV4AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP4.peer", pref, prefixV4Len, otgASPath, asPathValue) + validateOTGBgpPrefixV6AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP6.peer", prefixesV6[index][idx], prefixV6Len, otgASPath, asPathValue) + } + } + if testCommunityResults[index] && !deviations.SkipBgpSendCommunityType(dut) { + for idx, pref := range prefix { + validateOTGBgpPrefixV4AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP4.peer", pref, prefixV4Len, otgCommunity, communityResultValue) + validateOTGBgpPrefixV6AndASLocalPrefMED(t, otg, dut, otgConfig, bs.ATEPorts[0].Name+".BGP6.peer", prefixesV6[index][idx], prefixV6Len, otgCommunity, communityResultValue) + } + } + } +} 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 new file mode 100644 index 00000000000..1efb0711660 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/import_export_multi_test/metadata.textproto @@ -0,0 +1,38 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +plan_id: "RT-7.11" +description: "BGP Policy - Import/Export Policy Action Using Multiple Criteria" +uuid: "520f6013-0188-45a3-b5be-ce13c55ce7cd" + +testbed: TESTBED_DUT_ATE_2LINKS +tags: [TAGS_DATACENTER_EDGE] +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + missing_value_for_defaults: true + skip_set_rp_match_set_options: true + bgp_community_set_refs_unsupported: true + bgp_community_member_is_a_string: true + skip_bgp_send_community_type: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + bgp_conditions_match_community_set_unsupported: true + bgp_community_set_refs_unsupported: true + community_member_regex_unsupported: true + skip_setting_statement_for_policy: true + default_route_policy_unsupported: true + skip_checking_attribute_index: true + skip_bgp_send_community_type: true + } +} diff --git a/feature/bgp/policybase/otg_tests/link_bandwidth_test/README.md b/feature/bgp/policybase/otg_tests/link_bandwidth_test/README.md new file mode 100644 index 00000000000..c725c5e4c7d --- /dev/null +++ b/feature/bgp/policybase/otg_tests/link_bandwidth_test/README.md @@ -0,0 +1,177 @@ +# RT-7.5: BGP Policy - Match and Set Link Bandwidth Community + +## Summary + +Configure bgp policy to match, add and delete statically configured BGP link +bandwidth communities to routes based on a prefix match. + +## Testbed type + +* [2 port ATE to DUT](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +* Testbed configuration - Setup external BGP sessions and prefixes. + * Generate config for 2 DUT and ATE ports where: + * DUT port 1 to ATE port 1 EBGP session. + * DUT port 2 to ATE port 2 IBGP session. + * Configure dummy accept policies and attach it to both sessions on DUT. + * Create a `/routing-policy/policy-definitions/policy-definition/policy-definition` + named 'allow_all' with the following `statements` + * statement[name='allow-all']/ + * actions/config/policy-result = ACCEPT_ROUTE + * Use `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy` + to apply the policy on the DUT bgp neighbor to the ATE port 1. + * Configure ATE port 1 with a BGP session to DUT port 1. + * Advertise ipv4 and ipv6 prefixes to DUT port 1 using the following communities: + * prefix-set-1 with 2 ipv4 and 2 ipv6 routes without communities. + * prefix-set-2 with 2 ipv4 and 2 ipv6 routes with communities `[ "100:100" ]`. + * [TODO value change] prefix-set-3 with 2 ipv4 and 2 ipv6 routes with extended communities `[ "link-bandwidth:23456:1000" ]`. + * Configure Send community knob to IBGP neigbour to advertise the communities to IBGP peer + * [TODO] use `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/send-community-type` leaf-list with value `[STANDARD EXTENDED]`. +* RT-7.5.1 - Validate bgp sessions and traffic + * For IPv4 and IPv6 prefixes: + * Observe received prefixes at ATE port-2. + * Generate traffic from ATE port-2 to ATE port-1. + * Verify traffic is received on ATE port 1 for advertised prefixes. + routes. + +* RT-7.5.2 - Validate adding and removing link-bandwidth ext-community-sets using OC model release 3.x + * Configure the following extended community sets on the DUT: + (prefix: `routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set`) + * Create an ext-community-set named 'linkbw_1M' with members as follows: + * ext-community-member = [ "link-bandwidth:23456:1M" ] + * Create an ext-community-set named 'linkbw_2G' with members as follows: + * ext-community-member = [ "link-bandwidth:23456:2G" ] + * Create an community-set named 'regex_match_comm100' with members as follows: + * community-member = [ "^100:.*$" ] + * Create an ext-community-set named 'linkbw_any' with members as follows: + * ext-community-member = [ "^link-bandwidth:.*:.*$" ] + + * Create a `/routing-policy/policy-definitions/policy-definition/policy-definition` + named **'not_match_100_set_linkbw_1M'** with the following `statements` + * statement[name='1-megabit-match']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'regex_match_comm100' + * conditions/bgp-conditions/match-community-set/config/match-set-options = INVERT + * actions/bgp-actions/set-ext-community/reference/config/ext-community-set-refs = 'linkbw_1M' + * actions/config/policy-result = NEXT_STATEMENT + * statement[name='accept_all_routes']/ + * actions/config/policy-result = ACCEPT_ROUTE + + * Create a `/routing-policy/policy-definitions/policy-definition/policy-definition` + named **'match_100_set_linkbw_2G'** with the following `statements` + * statement[name='2-gigabit-match']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'regex_match_comm100' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/bgp-actions/set-ext-community/reference/config/ext-community-set-refs = 'linkbw_2G' + * actions/config/policy-result = NEXT_STATEMENT + * statement[name='accept_all_routes']/ + * actions/config/policy-result = ACCEPT_ROUTE + + * Create a `/routing-policy/policy-definitions/policy-definition/policy-definition` + named **'del_linkbw'** with the following `statements` + * statement[name='del_linkbw']/ + * actions/bgp-actions/set-ext-community/config/options = 'REMOVE' + * actions/bgp-actions/set-ext-community/config/method = 'REFERENCE' + * actions/bgp-actions/set-ext-community/reference/config/ext-community-set-refs = 'linkbw_any' + * actions/config/policy-result = NEXT_STATEMENT + * statement[name='accept_all_routes']/ + * actions/config/policy-result = ACCEPT_ROUTE + + * For each policy-definition created, run a subtest (RT-7.5.3.x--import) to + * Use gnmi Set REPLACE option for: + * `/routing-policy/policy-definitions` to configure the policy + * Use `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy` + to apply the policy on the DUT bgp neighbor to the ATE port 1. + * Verify expected communities are present in ATE. + * Verify expected communities are present in DUT state. + * Do not fail test if this path is not supported, only log results + * `/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/ext-community-index` + * `/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/ext-community-index` + * Mark test as passing if Global Administartive valuee (ASN) of link-bakdwidth extended community **send by DUT** is either `23456` or ASN of DUT. + + * Expected community values for each policy + | | set_linkbw_0 | not_match_100_set_linkbw_1M | + | ------------ | -------------------------------------- | --------------------------- | + | prefix-set-1 | *DEPRECATED* | [ "link-bandwidth:23456:1000000" ] | + | prefix-set-2 | *DEPRECATED* | [ "100:100" ] | + | prefix-set-3 | *DEPRECATED* | [ "link-bandwidth:23456:1000000" ] | + + | | match_100_set_linkbw_2G | del_linkbw | rm_any_zero_bw_set_LocPref_5 | + | ------------ | ------------------------------------------------- | ------------- | ---------------------------- | + | prefix-set-1 | [ none ] | [none] | *DEPRECATED* | + | prefix-set-2 | [ "100:100", "link-bandwidth:23456:2000000000" ] | [ "100:100" ] | *DEPRECATED* | + | prefix-set-3 | [ "link-bandwidth:23456:1000" ] | [ none ] | *DEPRECATED* | + + * Regarding prefix-set-3 and policy "nomatch_100_set_linkbw_2G" + * prefix-set-3 is advertised to the DUT with community "link-bandwidth:100:0" set. + * The DUT evaluates a match for "regex_nomatch_as100". This does not match because the regex pattern does not include the link-bandwidth community type. + * Community linkbw_2G should be added. + +[TODO] For each policy-definition created, run a subtest (RT-7.5.4.x--export) to + + * Use gnmi Set REPLACE option to attach `allow_all` policy using `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy` to apply the policy on the DUT bgp neighbor to the ATE port 1. + * Use gnmi Set REPLACE option for: + * `/routing-policy/policy-definitions` to configure the policy + * Use `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy` + to apply the policy on the DUT bgp neighbor to the ATE port 2. + * Verify expected communities are present in ATE port 2. + * Mark test as passing if Global Administartive valuee (ASN) of link-bakdwidth extended community **send by DUT** is either `23456` or ASN of DUT. + + * Expected community values for each policy + + | | set_linkbw_0 | not_match_100_set_linkbw_1M | + | ------------ | -------------------------------------- | --------------------------- | + | prefix-set-1 | *DEPRECATED* | [ "link-bandwidth:23456:1000000" ] | + | prefix-set-2 | *DEPRECATED* | [ "100:100" ] | + | prefix-set-3 | *DEPRECATED* | [ "link-bandwidth:23456:1000000" ] | + + | | match_100_set_linkbw_2G | del_linkbw | rm_any_zero_bw_set_LocPref_5 | + | ------------ | ------------------------------------------------- | ------------- | ---------------------------- | + | prefix-set-1 | [ none ] | [none] | *DEPRECATED* | + | prefix-set-2 | [ "100:100", "link-bandwidth:23456:2000000000" ] | [ "100:100" ] | *DEPRECATED* | + | prefix-set-3 | [ "link-bandwidth:23456:1000" ] | [ none ] | *DEPRECATED* | + + * Regarding prefix-set-3 and policy "nomatch_100_set_linkbw_2G" + * prefix-set-3 is advertised to the DUT with community "link-bandwidth:100:0" set. + * The DUT evaluates a match for "regex_nomatch_as100". This does not match because the regex pattern does not include the link-bandwidth community type. + * Community linkbw_2G should be added. + +## 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 Parameter Coverage + ## Configuration to enable advertise communities to bgp peer + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/send-community-type: + ## Policy for community-set configuration + /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config/ext-community-set-name: + /routing-policy/defined-sets/bgp-defined-sets/ext-community-sets/ext-community-set/config/ext-community-member: + ## Policy action configuration + /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/actions/bgp-actions/set-ext-community/config/options: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/config/method: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-ext-community/reference/config/ext-community-set-refs: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref: + ## Policy for community-set match configuration + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-ext-community-set/config/ext-community-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-ext-community-set/config/match-set-options: + ## Policy attachment point configuration + /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/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: + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/ext-community-index: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/state/ext-community-index: +rpcs: + gnmi: + gNMI.Subscribe: +``` +## 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 new file mode 100644 index 00000000000..a1205cfa0c0 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/link_bandwidth_test/link_bandwidth_test.go @@ -0,0 +1,1115 @@ +// 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 link_bandwidth_test + +import ( + "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/helpers" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + v41Route = "203.0.113.0" + v41TrafficStart = "203.0.113.1" + v42Route = "203.0.114.0" + v42TrafficStart = "203.0.114.1" + v43Route = "203.0.115.0" + v43TrafficStart = "203.0.115.1" + v4RoutePrefix = uint32(24) + v61Route = "2001:db8:128:128::0" + v61RouteOtg = "2001:db8:128:128::" + v61RouteAdvertise = "2001:db8:128:128::/64" + v61TrafficStart = "2001:db8:128:128::1" + v62Route = "2001:db8:128:129::0" + v62RouteAdvertise = "2001:db8:128:129::/64" + v62RouteOtg = "2001:db8:128:129::" + v62TrafficStart = "2001:db8:128:129::1" + v63Route = "2001:db8:128:130::0" + v63RouteAdvertise = "2001:db8:128:130::/64" + v63RouteOtg = "2001:db8:128:130::" + v63TrafficStart = "2001:db8:128:130::1" + v6RoutePrefix = uint32(64) + dutAS = uint32(32001) + ateAS = uint32(32002) + bgpName = "BGP" + maskLenExact = "exact" + localPref = 200 + v4Flow = "flow-v4" + v6Flow = "flow-v6" + localPerfCfg = 5 +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:1", + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:2", + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:5", + IPv6Len: ipv6PrefixLen, + } + + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:01:01:01:02", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + advertisedIPv41 = ipAddr{address: v41Route, prefix: v4RoutePrefix} + advertisedIPv42 = ipAddr{address: v42Route, prefix: v4RoutePrefix} + advertisedIPv43 = ipAddr{address: v43Route, prefix: v4RoutePrefix} + advertisedIPv61 = ipAddr{address: v61Route, prefix: v6RoutePrefix} + advertisedIPv62 = ipAddr{address: v62Route, prefix: v6RoutePrefix} + advertisedIPv63 = ipAddr{address: v63Route, prefix: v6RoutePrefix} + + extCommunitySet = map[string]string{ + "linkbw_1M": "link-bandwidth:23456:1M", + "linkbw_2G": "link-bandwidth:23456:2G", + "linkbw_any": "^link-bandwidth:.*:.*$", + } + + extCommunitySetCisco = map[string]string{ + "linkbw_1M": "23456:1000000", + "linkbw_2G": "23456:2000000000", + "linkbw_any": "^.*:.*$", + } + + communitySet = map[string]string{ + "regex_match_comm100": "^100:.*$", + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type ipAddr struct { + address string + prefix uint32 +} + +type testData struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + otgP1 gosnappi.Device + otgP2 gosnappi.Device +} + +type extCommunity struct { + prefixSet1Comm string + prefixSet2Comm string + prefixSet3Comm string +} + +type flowConfig struct { + src attrs.Attributes + dstNw string + dstIP string +} + +func TestBGPLinkBandwidth(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + devs := configureOTG(t, ate, top) + td := testData{ + dut: dut, + ate: ate, + top: top, + otgP1: devs[0], + otgP2: devs[1], + } + type testCase struct { + name string + policyName string + applyPolicy func(t *testing.T, dut *ondatra.DUTDevice, policyName string) + validate func(t *testing.T, dut *ondatra.DUTDevice, policyName string) + routeCommunity extCommunity + localPerf bool + validateRouteCommunityV4 func(t *testing.T, td testData, ec extCommunity) + validateRouteCommunityV6 func(t *testing.T, td testData, ec extCommunity) + } + baseSetupConfigAndVerification(t, td) + configureExtCommunityRoutingPolicy(t, dut) + enableExtCommunityCLIConfig(t, dut) + + testCases := []testCase{ + { + name: "ImportPolicy set not_match_100_set_linkbw_1M", + policyName: "not_match_100_set_linkbw_1M", + applyPolicy: applyImportPolicyDut, + validate: validateImportPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "link-bandwidth:23456:1000000", prefixSet2Comm: "100:100", prefixSet3Comm: "link-bandwidth:23456:1000000"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + { + name: "ExportPolicy set not_match_100_set_linkbw_1M", + policyName: "not_match_100_set_linkbw_1M", + applyPolicy: applyExportPolicyDut, + validate: validateExportPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "link-bandwidth:23456:1000000", prefixSet2Comm: "100:100", prefixSet3Comm: "link-bandwidth:23456:1000000"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + { + name: "ImportPolicy set match_100_set_linkbw_2G", + policyName: "match_100_set_linkbw_2G", + applyPolicy: applyImportPolicyDut, + validate: validateImportPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "none", prefixSet2Comm: "link-bandwidth:23456:2000000000", prefixSet3Comm: "link-bandwidth:23456:1000"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + + { + name: "ExportPolicy set match_100_set_linkbw_2G", + policyName: "match_100_set_linkbw_2G", + applyPolicy: applyExportPolicyDut, + validate: validateExportPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "none", prefixSet2Comm: "link-bandwidth:23456:2000000000", prefixSet3Comm: "link-bandwidth:23456:1000"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + { + name: "ImportPolicy set del_linkbw", + policyName: "del_linkbw", + applyPolicy: applyImportPolicyDut, + validate: validateImportPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "none", prefixSet2Comm: "100:100", prefixSet3Comm: "none"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + { + name: "ExportPolicy set del_linkbw", + policyName: "del_linkbw", + applyPolicy: applyExportPolicyDut, + validate: validateExportPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "none", prefixSet2Comm: "100:100", prefixSet3Comm: "none"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Logf("Description: %s", tc.name) + tc.applyPolicy(t, dut, tc.policyName) + tc.validate(t, dut, tc.policyName) + tc.validateRouteCommunityV4(t, td, tc.routeCommunity) + tc.validateRouteCommunityV6(t, td, tc.routeCommunity) + }) + } +} + +func enableExtCommunityCLIConfig(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.BgpExplicitExtendedCommunityEnable(dut) { + var extCommunityEnableCLIConfig string + switch dut.Vendor() { + case ondatra.CISCO: + extCommunityEnableCLIConfig = fmt.Sprintf("router bgp %v instance BGP neighbor-group %v \n ebgp-recv-extcommunity-dmz \n ebgp-send-extcommunity-dmz\n", dutAS, cfgplugins.BGPPeerGroup1) + default: + t.Fatalf("Unsupported vendor %s for deviation 'BgpExplicitExtendedCommunityEnable'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, extCommunityEnableCLIConfig) + } +} + +func removeImportAndExportPolicy(t *testing.T, dut *ondatra.DUTDevice) { + dni := deviations.DefaultNetworkInstance(dut) + + bd := &gnmi.SetBatch{} + path1 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + path2 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort2.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + path3 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + path4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort2.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + gnmi.BatchDelete(bd, path1.Config()) + gnmi.BatchDelete(bd, path2.Config()) + gnmi.BatchDelete(bd, path3.Config()) + gnmi.BatchDelete(bd, path4.Config()) + bd.Set(t, dut) +} + +func applyImportPolicyDut(t *testing.T, dut *ondatra.DUTDevice, policyName string) { + root := &oc.Root{} + dni := deviations.DefaultNetworkInstance(dut) + removeImportAndExportPolicy(t, dut) + + // Apply ipv4 policy to bgp neighbour. + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{policyName}) + gnmi.Replace(t, dut, path.Config(), policy) + + // Apply ipv6 policy to bgp neighbour. + path = gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policy = root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{policyName}) + gnmi.Replace(t, dut, path.Config(), policy) + + ni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + bgpNbrV4 := bgp.GetOrCreateNeighbor(atePort1.IPv4) + bgpNbrV4.PeerGroup = ygot.String(cfgplugins.BGPPeerGroup1) + bgpNbrV6 := bgp.GetOrCreateNeighbor(atePort1.IPv6) + bgpNbrV6.PeerGroup = ygot.String(cfgplugins.BGPPeerGroup1) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config(), niProto) +} + +func applyExportPolicyDut(t *testing.T, dut *ondatra.DUTDevice, policyName string) { + root := &oc.Root{} + dni := deviations.DefaultNetworkInstance(dut) + removeImportAndExportPolicy(t, dut) + + // Apply ipv4 policy to bgp neighbour. + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort2.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort2.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policy.SetExportPolicy([]string{policyName}) + gnmi.Replace(t, dut, path.Config(), policy) + + // Apply ipv6 policy to bgp neighbour. + path = gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort2.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policy = root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort2.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policy.SetExportPolicy([]string{policyName}) + gnmi.Replace(t, dut, path.Config(), policy) + + ni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + bgpNbrV4 := bgp.GetOrCreateNeighbor(atePort1.IPv4) + bgpNbrV4.PeerGroup = ygot.String(cfgplugins.BGPPeerGroup1) + bgpNbrV6 := bgp.GetOrCreateNeighbor(atePort1.IPv6) + bgpNbrV6.PeerGroup = ygot.String(cfgplugins.BGPPeerGroup1) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config(), niProto) +} + +func validateImportPolicyDut(t *testing.T, dut *ondatra.DUTDevice, policyName string) { + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + _, ok := gnmi.Watch(t, dut, path.State(), 30*time.Second, func(v *ygnmi.Value[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy]) bool { + value, ok := v.Val() + if !ok { + return false + } + importPolicies := value.GetImportPolicy() + if len(importPolicies) != 1 || importPolicies[0] != policyName { + return false + } + return true + }).Await(t) + if !ok { + t.Fatalf("invalid import policy") + } +} + +func validateExportPolicyDut(t *testing.T, dut *ondatra.DUTDevice, policyName string) { + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort2.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + _, ok := gnmi.Watch(t, dut, path.State(), 30*time.Second, func(v *ygnmi.Value[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy]) bool { + value, ok := v.Val() + if !ok { + return false + } + exportPolicies := value.GetExportPolicy() + if len(exportPolicies) != 1 || exportPolicies[0] != policyName { + return false + } + return true + }).Await(t) + if !ok { + t.Fatalf("invalid export policy") + } +} + +func validateRouteCommunityV4(t *testing.T, td testData, ec extCommunity) { + prefixes := map[string]string{ + v41Route: ec.prefixSet1Comm, + v42Route: ec.prefixSet2Comm, + v43Route: ec.prefixSet3Comm, + } + for prefix, community := range prefixes { + validateRouteCommunityV4Prefix(t, td, community, prefix) + } +} + +func validateRouteCommunityV4Prefix(t *testing.T, td testData, community, v4Prefix string) { + _, ok := gnmi.WatchAll(t, + td.ate.OTG(), + gnmi.OTG().BgpPeer(td.otgP2.Name()+".BGP4.peer").UnicastIpv4PrefixAny().State(), + time.Minute, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + _, present := v.Val() + return present + }).Await(t) + if ok { + bgpPrefixes := gnmi.GetAll(t, td.ate.OTG(), gnmi.OTG().BgpPeer(td.otgP2.Name()+".BGP4.peer").UnicastIpv4PrefixAny().State()) + t.Logf("bgp prefix:%v", bgpPrefixes) + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.GetAddress() == v4Prefix { + t.Logf("Prefix recevied on OTG is correct, got Address %s, want prefix %v", bgpPrefix.GetAddress(), v4Prefix) + switch community { + case "none": + if len(bgpPrefix.Community) != 0 || len(bgpPrefix.ExtendedCommunity) != 0 { + t.Errorf("community and ext communituy are not empty it should be none") + } + case "100:100": + for _, gotCommunity := range bgpPrefix.Community { + t.Logf("community AS:%d val: %d", gotCommunity.GetCustomAsNumber(), gotCommunity.GetCustomAsValue()) + if gotCommunity.GetCustomAsNumber() != 100 || gotCommunity.GetCustomAsValue() != 100 { + t.Errorf("community is not 100:100 got AS number:%d AS value:%d", gotCommunity.GetCustomAsNumber(), gotCommunity.GetCustomAsValue()) + } + } + default: + if len(bgpPrefix.ExtendedCommunity) == 0 { + t.Errorf("ERROR extended community is empty, expected %v", community) + return + } + for _, ec := range bgpPrefix.ExtendedCommunity { + lbSubType := ec.Structured.NonTransitive_2OctetAsType.LinkBandwidthSubtype + listCommunity := strings.Split(community, ":") + bandwidth := listCommunity[2] + if lbSubType.GetGlobal_2ByteAs() != 23456 && lbSubType.GetGlobal_2ByteAs() != 32002 && lbSubType.GetGlobal_2ByteAs() != 32001 { + t.Errorf("ERROR AS number should be 23456 or %d got %d", ateAS, lbSubType.GetGlobal_2ByteAs()) + return + } + if bandwidth == "1000" && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) == 0 { + t.Errorf("ERROR lb Bandwidth want 1000, got:=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } else if bandwidth == "1000000" && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 125000 && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 1000000 { + t.Errorf("ERROR lb Bandwidth want :1M, got=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } else if bandwidth == "2000000000" && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 2.5e+08 && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 2000000000 { + t.Errorf("ERROR lb Bandwidth want :2G, got=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } + + if !deviations.BgpExtendedCommunityIndexUnsupported(td.dut) { + verifyExtCommunityIndexV4(t, td, v4Prefix) + } + } + } + } + } + } +} + +func validateRouteCommunityV6(t *testing.T, td testData, ec extCommunity) { + prefixes := map[string]string{ + v61RouteOtg: ec.prefixSet1Comm, + v62RouteOtg: ec.prefixSet2Comm, + v63RouteOtg: ec.prefixSet3Comm, + } + for prefix, community := range prefixes { + validateRouteCommunityV6Prefix(t, td, community, prefix) + } +} + +func validateRouteCommunityV6Prefix(t *testing.T, td testData, community, v6Prefix string) { + // This function to verify received route communities on ATE ports. + _, ok := gnmi.WatchAll(t, + td.ate.OTG(), + gnmi.OTG().BgpPeer(td.otgP2.Name()+".BGP6.peer").UnicastIpv6PrefixAny().State(), + time.Minute, + func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv6Prefix]) bool { + _, present := v.Val() + return present + }).Await(t) + if ok { + bgpPrefixes := gnmi.GetAll(t, td.ate.OTG(), gnmi.OTG().BgpPeer(td.otgP2.Name()+".BGP6.peer").UnicastIpv6PrefixAny().State()) + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.GetAddress() == v6Prefix { + t.Logf("Prefix recevied on OTG is correct, got prefix:%v , want prefix %v", bgpPrefix.GetAddress(), v6Prefix) + switch community { + case "none": + if len(bgpPrefix.Community) != 0 || len(bgpPrefix.ExtendedCommunity) != 0 { + t.Errorf("community and ext community are not empty it should be none") + } + case "100:100": + for _, gotCommunity := range bgpPrefix.Community { + t.Logf("community AS:%d val: %d", gotCommunity.GetCustomAsNumber(), gotCommunity.GetCustomAsValue()) + if gotCommunity.GetCustomAsNumber() != 100 || gotCommunity.GetCustomAsValue() != 100 { + t.Errorf("community is not 100:100 got AS number:%d AS value:%d", gotCommunity.GetCustomAsNumber(), gotCommunity.GetCustomAsValue()) + } + } + default: + if len(bgpPrefix.ExtendedCommunity) == 0 { + t.Errorf("ERROR extended community is empty, expected %v", community) + return + } + for _, ec := range bgpPrefix.ExtendedCommunity { + lbSubType := ec.Structured.NonTransitive_2OctetAsType.LinkBandwidthSubtype + listCommunity := strings.Split(community, ":") + bandwidth := listCommunity[2] + if lbSubType.GetGlobal_2ByteAs() != 23456 && lbSubType.GetGlobal_2ByteAs() != 32002 && lbSubType.GetGlobal_2ByteAs() != 32001 { + t.Errorf("ERROR AS number should be 23456 or %d got %d", ateAS, lbSubType.GetGlobal_2ByteAs()) + return + } + if bandwidth == "1000" && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) == 0 { + t.Errorf("ERROR lb Bandwidth want 1000, got:=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } else if bandwidth == "1000000" && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 125000 && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 1000000 { + t.Errorf("ERROR lb Bandwidth want :1M, got=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } else if bandwidth == "2000000000" && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 2.5e+08 && ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 2000000000 { + t.Errorf("ERROR lb Bandwidth want :2G, got=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } + if !deviations.BgpExtendedCommunityIndexUnsupported(td.dut) { + verifyExtCommunityIndexV6(t, td, v6Prefix) + } + } + } + } + } + } +} + +func configureImportRoutingPolicyAllowAll(t *testing.T, dut *ondatra.DUTDevice) { + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition("allow-all") + stmt1, err := pdef1.AppendNewStatement("allow-all") + if err != nil { + t.Fatalf("AppendNewStatement failed: %v", err) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + + // Apply ipv4 policy to bgp neighbour. + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4). + AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName). + GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{"allow-all"}) + gnmi.Replace(t, dut, path.Config(), policy) + + // Apply ipv6 policy to bgp neighbour. + path = gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp(). + Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + + policy = root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName). + GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{"allow-all"}) + gnmi.Replace(t, dut, path.Config(), policy) +} + +func validateImportRoutingPolicyAllowAll(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + dni := deviations.DefaultNetworkInstance(dut) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp(). + Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + + policy := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy](t, dut, path.State()) + importPolicies := policy.GetImportPolicy() + if len(importPolicies) != 1 { + t.Fatalf("ImportPolicy Ipv4 = %v, want %v", importPolicies, []string{"allow-all"}) + } + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv4Unicast_LocRib](t, dut, bgpRIBPath. + AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast().LocRib().State()) + found := 0 + expected := map[string]bool{ + advertisedIPv41.address + "/" + strconv.Itoa(int(advertisedIPv41.prefix)): true, + advertisedIPv42.address + "/" + strconv.Itoa(int(advertisedIPv42.prefix)): true, + advertisedIPv43.address + "/" + strconv.Itoa(int(advertisedIPv43.prefix)): true, + } + for route, prefix := range locRib.Route { + if expected[prefix.GetPrefix()] { + found++ + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", route.Prefix, route.Origin, route.PathId, prefix.GetPrefix()) + } + } + if found != len(expected) { + t.Fatalf("Not all V4 routes found. expected:%d got:%d", len(expected), found) + } + + // Verify ipv6 policy. + pathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi_ApplyPolicy](t, dut, pathV6.State()) + importPolicies = policyV6.GetImportPolicy() + if len(importPolicies) != 1 { + t.Errorf("ImportPolicy Ipv6 got= %v, want= %v", importPolicies, []string{"allow-all"}) + } + bgpRIBPathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRibv6 := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv6Unicast_LocRib](t, dut, bgpRIBPathV6.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Ipv6Unicast().LocRib().State()) + found = 0 + expectedV6 := map[string]bool{ + v61RouteAdvertise: true, + v62RouteAdvertise: true, + v63RouteAdvertise: true, + } + for route, prefix := range locRibv6.Route { + if expectedV6[prefix.GetPrefix()] { + found++ + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", route.Prefix, route.Origin, route.PathId, prefix.GetPrefix()) + } + } + if found != len(expectedV6) { + t.Fatalf("Not all v6 Routes found expected: %d got: %d", len(expectedV6), found) + } +} + +func configureExtCommunityRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice) { + root := &oc.Root{} + var communitySetCLIConfig string + var extCommunitySetCLIConfig string + 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) + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) + } else { + extCommunitySetCLIConfig = fmt.Sprintf("extcommunity-set bandwidth %v \n %v \n end-set", name, community) + helpers.GnmiCLIConfig(t, dut, extCommunitySetCLIConfig) + } + } + default: + t.Fatalf("Unsupported vendor %s for native command support for deviation 'BgpExtendedCommunitySetUnsupported'", dut.Vendor()) + } + } else { + for name, community := range extCommunitySet { + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets() + stmt, err := pdef.NewExtCommunitySet(name) + if err != nil { + t.Fatalf("NewExtCommunitySet failed: %v", err) + } + stmt.SetExtCommunityMember([]string{community}) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } + } + + if deviations.CommunityMemberRegexUnsupported(dut) { + switch dut.Vendor() { + case ondatra.CISCO: + for name, community := range communitySet { + communitySetCLIConfig = fmt.Sprintf("community-set %v\n dfa-regex '%v' \n end-set", name, community) + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) + } + default: + t.Fatalf("Unsupported vendor %s for native cmd support for deviation 'CommunityMemberRegexUnsupported'", dut.Vendor()) + } + } else { + for name, community := range communitySet { + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets() + stmt, err := pdef.NewCommunitySet(name) + if err != nil { + t.Fatalf("NewCommunitySet failed: %v", err) + } + cs := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + cs = append(cs, oc.UnionString(community)) + stmt.SetCommunityMember(cs) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } + } + + // Configure routing Policy not_match_100_set_linkbw_1M. + rpNotMatch := root.GetOrCreateRoutingPolicy() + pdef2 := rpNotMatch.GetOrCreatePolicyDefinition("not_match_100_set_linkbw_1M") + pdef2Stmt1, err := pdef2.AppendNewStatement("regex_match_comm100_rm_lbw") + if err != nil { + t.Fatalf("AppendNewStatement regex_match_comm100_rm_lbw failed: %v", err) + } + if !deviations.BgpSetExtCommunitySetRefsUnsupported(dut) { + ref := pdef2Stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetExtCommunity() + ref.GetOrCreateReference().SetExtCommunitySetRefs([]string{"linkbw_any"}) + ref.SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_REMOVE) + ref.SetMethod(oc.SetCommunity_Method_REFERENCE) + } + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + name1, community1 := "regex_match_comm100_deviation1", "^100:.*$" + rpDev1 := root.GetOrCreateRoutingPolicy() + pdefDev1 := rpDev1.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets() + stmtDev1, err := pdefDev1.NewCommunitySet(name1) + if err != nil { + t.Fatalf("NewCommunitySet failed: %v", err) + } + cs := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + cs = append(cs, oc.UnionString(community1)) + stmtDev1.SetCommunityMember(cs) + stmtDev1.SetMatchSetOptions(oc.BgpPolicy_MatchSetOptionsType_INVERT) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpDev1) + ref1 := pdef2Stmt1.GetOrCreateConditions().GetOrCreateBgpConditions() + ref1.SetCommunitySet("regex_match_comm100_deviation1") + } + } else { + ref1 := pdef2Stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet() + ref1.SetCommunitySet("regex_match_comm100") + ref1.SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_INVERT) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + pdef2Stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT) + } + + pdef2Stmt2, err := pdef2.AppendNewStatement("regex_match_comm100_add_lbw") + if err != nil { + t.Fatalf("AppendNewStatement regex_match_comm100_add_lbw failed: %v", err) + } + if !deviations.BgpSetExtCommunitySetRefsUnsupported(dut) { + ref := pdef2Stmt2.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetExtCommunity() + ref.GetOrCreateReference().SetExtCommunitySetRefs([]string{"linkbw_1M"}) + ref.SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + ref.SetMethod(oc.SetCommunity_Method_REFERENCE) + } + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + ref1 := pdef2Stmt2.GetOrCreateConditions().GetOrCreateBgpConditions() + ref1.SetCommunitySet("regex_match_comm100_deviation1") + } + } else { + ref1 := pdef2Stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet() + ref1.SetCommunitySet("regex_match_comm100") + ref1.SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_INVERT) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + pdef2Stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT) + } + + pdef2Stmt3, err := pdef2.AppendNewStatement("accept_all_routes") + if err != nil { + t.Fatalf("AppendNewStatement accept_all_routes failed: %v", err) + } + pdef2Stmt3.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + if deviations.BgpSetExtCommunitySetRefsUnsupported(dut) { + switch dut.Vendor() { + case ondatra.CISCO: + var communityCLIConfig string + communityCLIConfig = fmt.Sprintf("community-set %v\n dfa-regex '%v', \n match invert \n end-set", "regex_match_comm100", communitySet["regex_match_comm100"]) + policySetCLIConfig := fmt.Sprintf("route-policy %v \n #statement-1 1-megabit-match \n if community is-empty then \n pass \n elseif community in %v then \n set extcommunity bandwidth %v \n endif \n pass \n #statement-2 accept_all_routes \n done \n end-policy", "not_match_100_set_linkbw_1M", "regex_match_comm100", "linkbw_1M") + helpers.GnmiCLIConfig(t, dut, communityCLIConfig) + helpers.GnmiCLIConfig(t, dut, policySetCLIConfig) + default: + t.Fatalf("Unsupported vendor %s for native cmd support for deviation 'BgpSetExtCommunitySetRefsUnsupported'", dut.Vendor()) + } + } else { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpNotMatch) + } + + // Configure routing policy match_100_set_linkbw_2G. + rpMatch := root.GetOrCreateRoutingPolicy() + pdef3 := rpMatch.GetOrCreatePolicyDefinition("match_100_set_linkbw_2G") + pdef3Stmt1, err := pdef3.AppendNewStatement("2-gigabit-match") + if err != nil { + t.Fatalf("AppendNewStatement match_100_set_linkbw_2G failed: %v", err) + } + if !deviations.BgpSetExtCommunitySetRefsUnsupported(dut) { + ref := pdef3Stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetExtCommunity() + ref.GetOrCreateReference().SetExtCommunitySetRefs([]string{"linkbw_2G"}) + ref.SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + ref.SetMethod(oc.SetCommunity_Method_REFERENCE) + } + if deviations.BGPConditionsMatchCommunitySetUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + name2, community2 := "regex_match_comm100_deviation2", "^100:.*$" + rpDev2 := root.GetOrCreateRoutingPolicy() + pdefDev2 := rpDev2.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets() + stmtDev2, err := pdefDev2.NewCommunitySet(name2) + if err != nil { + t.Fatalf("NewCommunitySet failed: %v", err) + } + cs := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{} + cs = append(cs, oc.UnionString(community2)) + stmtDev2.SetCommunityMember(cs) + stmtDev2.SetMatchSetOptions(oc.BgpPolicy_MatchSetOptionsType_ANY) + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpDev2) + ref1 := pdef3Stmt1.GetOrCreateConditions().GetOrCreateBgpConditions() + ref1.SetCommunitySet("regex_match_comm100_deviation2") + } + } else { + ref1 := pdef3Stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet() + ref1.SetCommunitySet("regex_match_comm100") + ref1.SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + pdef3Stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT) + } + pdef3Stmt2, err := pdef3.AppendNewStatement("accept_all_routes") + if err != nil { + t.Fatalf("AppendNewStatement accept_all_routes failed: %v", err) + } + pdef3Stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + if deviations.BgpSetExtCommunitySetRefsUnsupported(dut) { + switch dut.Vendor() { + case ondatra.CISCO: + communitySetCLIConfig = fmt.Sprintf("community-set %v\n dfa-regex '%v', \n match any \n end-set", "regex_match_any_comm100", communitySet["regex_match_comm100"]) + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) + communitySetCLIConfig = fmt.Sprintf("route-policy %v \n #statement-1 2-gigabit-match \n if community in %v then \n set extcommunity bandwidth %v \n endif \n pass \n #statement-2 accept_all_routes \n done \n end-policy", "match_100_set_linkbw_2G", "regex_match_any_comm100", "linkbw_2G") + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) + default: + t.Fatalf("Unsupported vendor %s for native cmd support for deviation 'BgpSetExtCommunitySetRefsUnsupported' and 'BGPConditionsMatchCommunitySetUnsupported' and 'SkipSettingStatementForPolicy'", dut.Vendor()) + } + } else { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpMatch) + } + + // Configure routing policy del_linkbw. + rpDelLinkbw := root.GetOrCreateRoutingPolicy() + pdef4 := rpDelLinkbw.GetOrCreatePolicyDefinition("del_linkbw") + pdef4Stmt1, err := pdef4.AppendNewStatement("del_linkbw") + if err != nil { + t.Fatalf("AppendNewStatement del_linkbw failed: %v", err) + } + if !deviations.BgpDeleteLinkBandwidthUnsupported(dut) { + ref := pdef4Stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetExtCommunity() + ref.GetOrCreateReference().SetExtCommunitySetRefs([]string{"linkbw_any"}) + ref.SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_REMOVE) + ref.SetMethod(oc.SetCommunity_Method_REFERENCE) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + pdef4Stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT) + } + pdef4Stmt2, err := pdef4.AppendNewStatement("accept_all_routes") + if err != nil { + t.Fatalf("AppendNewStatement accept_all_routes failed: %v", err) + } + pdef4Stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + if deviations.BgpDeleteLinkBandwidthUnsupported(dut) { + var delLinkbwCLIConfig string + switch dut.Vendor() { + case ondatra.CISCO: + delLinkbwCLIConfig = fmt.Sprintf("route-policy %v\n delete extcommunity bandwidth all\n end-policy", "del_linkbw") + default: + t.Fatalf("Unsupported vendor %s for native cmd support for deviation 'BgpDeleteLinkBandwidthUnsupported'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, delLinkbwCLIConfig) + } else { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpDelLinkbw) + } + + fptest.LogQuery(t, "", gnmi.OC().RoutingPolicy().Config(), root) +} + +func createFlow(t *testing.T, td testData, fc flowConfig) { + td.top.Flows().Clear() + v4Flow := td.top.Flows().Add().SetName(v4Flow) + v4Flow.Metrics().SetEnable(true) + v4Flow.TxRx().Device(). + SetTxNames([]string{fc.src.Name + ".IPv4"}). + SetRxNames([]string{fc.dstNw}) + v4Flow.Size().SetFixed(512) + v4Flow.Rate().SetPps(100) + v4Flow.Duration().Continuous() + e1 := v4Flow.Packet().Add().Ethernet() + e1.Src().SetValue(fc.src.MAC) + v4 := v4Flow.Packet().Add().Ipv4() + v4.Src().SetValue(fc.src.IPv4) + v4.Dst().Increment().SetStart(fc.dstIP).SetCount(1) + + td.ate.OTG().PushConfig(t, td.top) + td.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv4") +} + +func createFlowV6(t *testing.T, td testData, fc flowConfig) { + td.top.Flows().Clear() + v6Flow := td.top.Flows().Add().SetName(v6Flow) + v6Flow.Metrics().SetEnable(true) + v6Flow.TxRx().Device(). + SetTxNames([]string{fc.src.Name + ".IPv6"}). + SetRxNames([]string{fc.dstNw}) + v6Flow.Size().SetFixed(512) + v6Flow.Rate().SetPps(100) + v6Flow.Duration().Continuous() + e1 := v6Flow.Packet().Add().Ethernet() + e1.Src().SetValue(fc.src.MAC) + v6 := v6Flow.Packet().Add().Ipv6() + v6.Src().SetValue(fc.src.IPv6) + v6.Dst().Increment().SetStart(fc.dstIP).SetCount(1) + td.ate.OTG().PushConfig(t, td.top) + td.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv6") +} + +func checkTraffic(t *testing.T, td testData, flowName string) { + td.ate.OTG().StartTraffic(t) + time.Sleep(time.Second * 30) + td.ate.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + otgutils.LogPortMetrics(t, td.ate.OTG(), td.top) + recvMetric := gnmi.Get(t, td.ate.OTG(), gnmi.OTG().Flow(flowName).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + + if lossPct > 1 { + t.Errorf("FAIL in checkTraffic - Got %v%% packet loss for %s ; expected < 1%%", lossPct, flowName) + } +} + +func (td *testData) advertiseRoutesWithEBGP(t *testing.T) { + t.Helper() + + root := &oc.Root{} + ni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(td.dut)) + bgpP := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName) + bgpP.SetEnabled(true) + bgp := bgpP.GetOrCreateBgp() + + g := bgp.GetOrCreateGlobal() + g.SetAs(dutAS) + g.SetRouterId(dutPort1.IPv4) + 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) + + nV41 := bgp.GetOrCreateNeighbor(atePort1.IPv4) + nV41.SetPeerAs(ateAS) + nV41.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV42 := bgp.GetOrCreateNeighbor(atePort2.IPv4) + nV42.SetPeerAs(dutAS) + if !deviations.SkipBgpSendCommunityType(td.dut) { + nV42.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD, oc.Bgp_CommunityType_EXTENDED}) + } + nV42.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV61 := bgp.GetOrCreateNeighbor(atePort1.IPv6) + nV61.SetPeerAs(ateAS) + nV61.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nV62 := bgp.GetOrCreateNeighbor(atePort2.IPv6) + nV62.SetPeerAs(dutAS) + if !deviations.SkipBgpSendCommunityType(td.dut) { + nV62.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD, oc.Bgp_CommunityType_EXTENDED}) + } + nV62.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + gnmi.Update(t, td.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Config(), ni) + + // Configure eBGP on OTG port1. + ipv41 := td.otgP1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dev1BGP := td.otgP1.Bgp().SetRouterId(atePort1.IPv4) + bgp4Peer1 := dev1BGP.Ipv4Interfaces().Add().SetIpv4Name(ipv41.Name()).Peers().Add().SetName(td.otgP1.Name() + ".BGP4.peer") + bgp4Peer1.SetPeerAddress(dutPort1.IPv4).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + ipv61 := td.otgP1.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer1 := dev1BGP.Ipv6Interfaces().Add().SetIpv6Name(ipv61.Name()).Peers().Add().SetName(td.otgP1.Name() + ".BGP6.peer") + bgp6Peer1.SetPeerAddress(dutPort1.IPv6).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + // Configure emulated network on ATE port1. + netv41 := bgp4Peer1.V4Routes().Add().SetName("v4-bgpNet-dev1") + netv41.Addresses().Add().SetAddress(advertisedIPv41.address).SetPrefix(advertisedIPv41.prefix) + netv61 := bgp6Peer1.V6Routes().Add().SetName("v6-bgpNet-dev1") + netv61.Addresses().Add().SetAddress(advertisedIPv61.address).SetPrefix(advertisedIPv61.prefix) + + // Configure routes with BGP community. + netv42 := bgp4Peer1.V4Routes().Add().SetName("v4-bgpNet-dev2") + netv42.Addresses().Add().SetAddress(advertisedIPv42.address).SetPrefix(advertisedIPv42.prefix) + commv4 := netv42.Communities().Add() + commv4.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv4.SetAsNumber(100) + commv4.SetAsCustom(100) + netv62 := bgp6Peer1.V6Routes().Add().SetName("v6-bgpNet-dev2") + netv62.Addresses().Add().SetAddress(advertisedIPv62.address).SetPrefix(advertisedIPv62.prefix) + commv6 := netv62.Communities().Add() + commv6.SetType(gosnappi.BgpCommunityType.MANUAL_AS_NUMBER) + commv6.SetAsNumber(100) + commv6.SetAsCustom(100) + + // Configure routes with Link bandwidth community. + netv43 := bgp4Peer1.V4Routes().Add().SetName("v4-bgpNet-dev3") + netv43.Addresses().Add().SetAddress(advertisedIPv43.address).SetPrefix(advertisedIPv43.prefix) + extcommv4 := netv43.ExtendedCommunities().Add().NonTransitive2OctetAsType().LinkBandwidthSubtype() + extcommv4.SetGlobal2ByteAs(23456) + extcommv4.SetBandwidth(1000) + netv63 := bgp6Peer1.V6Routes().Add().SetName("v6-bgpNet-dev3") + netv63.Addresses().Add().SetAddress(advertisedIPv63.address).SetPrefix(advertisedIPv63.prefix) + extcommv6 := netv63.ExtendedCommunities().Add().NonTransitive2OctetAsType().LinkBandwidthSubtype() + extcommv6.SetGlobal2ByteAs(23456) + extcommv6.SetBandwidth(1000) + + // Configure iBGP on OTG port2. + ipv42 := td.otgP2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dev2BGP := td.otgP2.Bgp().SetRouterId(atePort2.IPv4) + bgp4Peer2 := dev2BGP.Ipv4Interfaces().Add().SetIpv4Name(ipv42.Name()).Peers().Add().SetName(td.otgP2.Name() + ".BGP4.peer") + bgp4Peer2.SetPeerAddress(dutPort2.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + bgp4Peer2.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + bgp4Peer2.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + ipv62 := td.otgP2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer2 := dev2BGP.Ipv6Interfaces().Add().SetIpv6Name(ipv62.Name()).Peers().Add().SetName(td.otgP2.Name() + ".BGP6.peer") + bgp6Peer2.SetPeerAddress(dutPort2.IPv6).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + bgp6Peer2.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true).SetExtendedNextHopEncoding(true) + bgp6Peer2.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) +} + +func (td *testData) verifyDUTBGPEstablished(t *testing.T) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().NeighborAny().SessionState().State() + watch := gnmi.WatchAll(t, td.dut, sp, 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if !ok || state != oc.Bgp_Neighbor_SessionState_ESTABLISHED { + return false + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established in verifyDUTBGPEstablished : got %v", val) + } +} + +// verifyOTGBGPEstablished verifies on OTG BGP peer establishment. +func (td *testData) verifyOTGBGPEstablished(t *testing.T) { + sp := gnmi.OTG().BgpPeerAny().SessionState().State() + watch := gnmi.WatchAll(t, td.ate.OTG(), sp, 2*time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + state, ok := val.Val() + if !ok || state != otgtelemetry.BgpPeer_SessionState_ESTABLISHED { + return false + } + return true + }) + state, ok := watch.Await(t) + if !ok { + t.Fatalf("BGP sessions not established : verifyOTGBGPEstablished (%v)", state) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + b := &gnmi.SetBatch{} + gnmi.BatchReplace(b, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.BatchReplace(b, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + b.Set(t, dut) + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p1) + fptest.SetPortSpeed(t, p2) + } + + 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) + } +} + +func configureOTG(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) []gosnappi.Device { + t.Helper() + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + + d1 := atePort1.AddToOTG(top, p1, &dutPort1) + d2 := atePort2.AddToOTG(top, p2, &dutPort2) + return []gosnappi.Device{d1, d2} +} + +// TODO to move base setup config in helper. +func baseSetupConfigAndVerification(t *testing.T, td testData) { + td.advertiseRoutesWithEBGP(t) + td.ate.OTG().PushConfig(t, td.top) + td.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv4") + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv6") + td.verifyDUTBGPEstablished(t) + td.verifyOTGBGPEstablished(t) + configureImportRoutingPolicyAllowAll(t, td.dut) + validateImportRoutingPolicyAllowAll(t, td.dut, td.ate) + createFlow(t, td, flowConfig{src: atePort2, dstNw: "v4-bgpNet-dev1", dstIP: v41TrafficStart}) + createFlow(t, td, flowConfig{src: atePort2, dstNw: "v4-bgpNet-dev2", dstIP: v42TrafficStart}) + createFlow(t, td, flowConfig{src: atePort2, dstNw: "v4-bgpNet-dev3", dstIP: v43TrafficStart}) + checkTraffic(t, td, v4Flow) + createFlowV6(t, td, flowConfig{src: atePort2, dstNw: "v6-bgpNet-dev1", dstIP: v61TrafficStart}) + createFlowV6(t, td, flowConfig{src: atePort2, dstNw: "v6-bgpNet-dev2", dstIP: v62TrafficStart}) + createFlowV6(t, td, flowConfig{src: atePort2, dstNw: "v6-bgpNet-dev3", dstIP: v63TrafficStart}) + checkTraffic(t, td, v6Flow) +} + +func verifyExtCommunityIndexV4(t *testing.T, td testData, v4Address string) { + dni := deviations.DefaultNetworkInstance(td.dut) + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv4Unicast_LocRib](t, td.dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast().LocRib().State()) + t.Logf("RIB: %v", locRib) + for route, prefix := range locRib.Route { + if prefix.GetPrefix() != v4Address { + continue + } + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", route.Prefix, route.Origin, route.PathId, prefix.GetPrefix()) + if prefix.ExtCommunityIndex == nil { + t.Fatalf("No V4 community index found") + } + extCommunity := bgpRIBPath.ExtCommunity(prefix.GetExtCommunityIndex()).ExtCommunity().State() + if extCommunity == nil { + t.Fatalf("No V4 community found at given index: %v", prefix.GetExtCommunityIndex()) + } + } +} + +func verifyExtCommunityIndexV6(t *testing.T, td testData, v6Address string) { + dni := deviations.DefaultNetworkInstance(td.dut) + bgpRIBPathV6 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRibv6 := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv6Unicast_LocRib](t, td.dut, bgpRIBPathV6.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Ipv6Unicast().LocRib().State()) + t.Logf("RIB: %v", locRibv6) + for route, prefix := range locRibv6.Route { + if prefix.GetPrefix() != v6Address { + continue + } + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", route.Prefix, route.Origin, route.PathId, prefix.GetPrefix()) + if prefix.ExtCommunityIndex == nil { + t.Fatalf("No V6 community index found") + } + extCommunity := bgpRIBPathV6.ExtCommunity(prefix.GetExtCommunityIndex()).ExtCommunity().State() + if extCommunity == nil { + t.Fatalf("No V6 community found at given index: %v", prefix.GetExtCommunityIndex()) + } + } +} diff --git a/feature/bgp/policybase/otg_tests/link_bandwidth_test/metadata.textproto b/feature/bgp/policybase/otg_tests/link_bandwidth_test/metadata.textproto new file mode 100644 index 00000000000..e5e9757204d --- /dev/null +++ b/feature/bgp/policybase/otg_tests/link_bandwidth_test/metadata.textproto @@ -0,0 +1,40 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "a8344612-0db0-42a1-96cf-38846a7f1603" +plan_id: "RT-7.5" +description: "BGP Policy - Match and Set Link Bandwidth Community" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + skip_set_rp_match_set_options: true + skip_setting_disable_metric_propagation: true + bgp_extended_community_index_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + bgp_extended_community_set_unsupported: true + community_member_regex_unsupported: true + skip_setting_statement_for_policy: true + bgp_set_ext_community_set_refs_unsupported: true + bgp_delete_link_bandwidth_unsupported: true + skip_bgp_send_community_type: true + bgp_extended_community_index_unsupported: true + bgp_conditions_match_community_set_unsupported: true + bgp_explicit_extended_community_enable: true + } +} +tags: TAGS_AGGREGATION +tags: TAGS_DATACENTER_EDGE diff --git a/feature/bgp/policybase/otg_tests/nested_policies/README.md b/feature/bgp/policybase/otg_tests/nested_policies/README.md new file mode 100644 index 00000000000..393028ea104 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/nested_policies/README.md @@ -0,0 +1,305 @@ +# RT-1.30: BGP nested import/export policy attachment + +## Summary + +- A policy calling another policy to be attached to a neighbor's import-policy +- A policy calling another policy to be attached to a neighbor's export-policy +- Applicable to both IPv4 and IPv6 BGP neighbors +- Single level nesting is sufficient + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed + +## Procedure + +### Applying configuration + +For each section of configuration below, prepare a gnmi.SetBatch with all the configuration items appended to one SetBatch. Then apply the configuration to the DUT in one gnmi.Set using the `replace` option + +#### Initial Setup: + +* Connect DUT port-1, 2 to ATE port-1, 2 +* Configure IPv4/IPv6 addresses on the ports +* Create an IPv4 networks i.e. ```ipv4-network-1 = 192.168.10.0/24``` attached to ATE port-1 +* Create an IPv6 networks i.e. ```ipv6-network-1 = 2024:db8:128:128::/64``` attached to ATE port-1 +* Create an IPv4 networks i.e. ```ipv4-network-2 = 192.168.20.0/24``` attached to ATE port-2 +* Create an IPv6 networks i.e. ```ipv6-network-2 = 2024:db8:64:64::/64``` attached to ATE port-2 +* Configure IPv4 and IPv6 eBGP between DUT Port-1 and ATE Port-1 + * Note: Nested policies will be applied to this eBGP session later in the test to validate the results + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:128:128::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-1 +* Configure IPv4 and IPv6 eBGP between DUT Port-2 and ATE Port-2 + * Note: This eBGP session is only used to advertise prefixes to DUT and receive prefixes from DUT + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * Advertise ```ipv4-network-2 = 192.168.20.0/24``` and ```ipv6-network-2 = 2024:db8:64:64::/64``` from ATE to DUT over the IPv4 and IPv6 eBGP session on port-2 + * Set default import and export policy to ```NEXT_STATEMENT``` for this eBGP session only + * /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/default-export-policy + +### RT-1.30.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2608] +#### IPv4 BGP nested import policy test +--- +##### Configure a route-policy to set the local preference +* Configure an IPv4 route-policy definition with the name ```lp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```lp-policy-v4``` configure a statement with the name ```lp-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```lp-policy-v4``` statement ```lp-statement-v4``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set local-pref +* For routing-policy ```lp-policy-v4``` statement ```lp-statement-v4``` set local-preference to ```200``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref + +##### Configure a route-policy to match the prefix +* Configure an IPv4 route-policy definition with the name ```match-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-policy-v4``` configure a statement with the name ```match-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-policy-v4``` statement ```match-statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```ipv4-network-1``` i.e. ```192.168.10.0/24``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-policy-v4``` statement ```match-statement-v4``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-policy-v4``` statement ```match-statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + +##### Configure a nested policy +* For routing-policy ```lp-policy-v4``` call the policy ```match-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + +##### Configure the parent bgp import policy for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Apply the parent policy ```lp-policy-v4``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + +##### Verification +* Use gNMI `replace` to send the configuration to the DUT. +* Use gNMI `subscribe` with mode `once` to retrieve the configuration `state` from the DUT. +* Verify that the parent ```lp-policy-v4``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy +* Verify that the parent ```lp-policy-v4``` policy has a child policy ```match-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + +##### Validate test results +* Validate that the DUT receives the prefix ```ipv4-network-1``` i.e. ```192.168.10.0/24``` from BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv4-network-1``` i.e. ```192.168.10.0/24``` from BGP neighbor on ATE Port-1 has local preference set to 200 + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-2 towards the DUT destined to ```ipv4-network-1``` i.e. ```192.168.10.0/24``` + * Validate that the traffic is received on ATE Port-1 + +### RT-1.30.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2608] +#### IPv4 BGP nested export policy test +--- +##### Configure a route-policy to prepend AS-PATH +* Configure an IPv4 route-policy definition with the name ```asp-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```asp-policy-v4``` configure a statement with the name ```asp-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```asp-policy-v4``` statement ```asp-statement-v4``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to prepend AS +* For routing-policy ```asp-policy-v4``` statement ```asp-statement-v4``` set AS-PATH prepend to the ASN of the DUT + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn + +##### Configure another route-policy to set the MED +* Configure an IPv4 route-policy definition with the name ```med-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy-v4``` configure a statement with the name ```med-statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy-v4``` statement ```med-statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set MED +* For routing-policy ```med-policy-v4``` statement ```med-statement-v4``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med + +##### Configure a nested policy +* For routing-policy ```asp-policy-v4``` attach the policy ```med-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + +##### Configure the parent bgp import policy for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Apply the parent policy ```asp-policy-v4``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + +##### Verification +* Use gNMI `subscribe` with mode `once` to retrieve the configuration `state` from the DUT. +* Verify that the parent ```asp-policy-v4``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy +* Verify that the parent ```asp-policy-v4``` policy has a child policy ```med-policy-v4``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + +##### Validate test results +* Validate that the ATE receives the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` from BGP neighbor on DUT Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` on ATE from BGP neighbor on DUT Port-1 has AS-PATH with the ASN of DUT occuring twice + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member +* Validate that the prefix ```ipv4-network-2``` i.e. ```192.168.20.0/24``` from BGP neighbor on DUT Port-1 has MED set to ```1000``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-1 towards the DUT destined ```ipv4-network-2``` i.e. ```192.168.20.0/24``` + * Validate that the traffic is received on ATE Port-2 + +### RT-1.30.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2608] +#### IPv6 BGP nested import policy test +--- +##### Configure a route-policy to set the local preference +* Configure an IPv6 route-policy definition with the name ```lp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```lp-policy-v6``` configure a statement with the name ```lp-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```lp-policy-v6``` statement ```lp-statement-v6``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set local-pref +* For routing-policy ```lp-policy-v6``` statement ```lp-statement-v6``` set local-preference to ```200``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref + +##### Configure a route-policy to match the prefix +* Configure an IPv6 route-policy definition with the name ```match-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```match-policy-v6``` configure a statement with the name ```match-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```match-policy-v6``` statement ```match-statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route filtering/matching +* Configure a prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v6``` set the ip-prefix to ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```match-policy-v6``` statement ```match-statement-v6``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```match-policy-v6``` statement ```match-statement-v6``` set prefix set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + +##### Configure a nested policy +* For routing-policy ```lp-policy-v6``` call the policy ```match-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + +##### Configure the parent bgp import policy for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Apply the parent policy ```lp-policy-v6``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + +##### Verification +* Use gNMI `subscribe` with mode `once` to retrieve the configuration `state` from the DUT. +* Verify that the parent ```lp-policy-v6``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy +* Verify that the parent ```lp-policy-v6``` policy has a child policy ```match-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + +##### Validate test results +* Validate that the DUT receives the prefix ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` from BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` from BGP neighbor on ATE Port-1 has local preference set to 200 + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-2 towards the DUT destined to ```ipv6-network-1``` i.e. ```2024:db8:128:128::/64``` + * Validate that the traffic is received on ATE Port-1 + +### RT-1.30.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2608] +#### IPv6 BGP nested export policy test +--- +##### Configure a route-policy to prepend AS-PATH +* Configure an IPv6 route-policy definition with the name ```asp-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```asp-policy-v6``` configure a statement with the name ```asp-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```asp-policy-v6``` statement ```asp-statement-v6``` set policy-result as ```NEXT_STATEMENT``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to prepend AS +* For routing-policy ```asp-policy-v6``` statement ```asp-statement-v6``` set AS-PATH prepend to the ASN of the DUT + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn + +##### Configure another route-policy to set the MED +* Configure an IPv6 route-policy definition with the name ```med-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```med-policy-v6``` configure a statement with the name ```med-statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```med-policy-v6``` statement ```med-statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure BGP actions to set MED +* For routing-policy ```med-policy-v6``` statement ```med-statement-v6``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med + +##### Configure a nested policy +* For routing-policy ```asp-policy-v6``` call the policy ```med-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy + +##### Configure the parent bgp import policy for the DUT BGP neighbor on ATE Port-1 +* Set default import policy to ```REJECT_ROUTE``` (Note: even though this is the OC default, the DUT should still accept this configuration) + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy +* Apply the parent policy ```asp-policy-v6``` to the BGP neighbor + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy + +##### Verification +* Use gNMI `subscribe` with mode `once` to retrieve the configuration `state` from the DUT. +* Verify that the parent ```asp-policy-v6``` policy is successfully applied to the DUT BGP neighbor on ATE Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy +* Verify that the parent ```asp-policy-v6``` policy has a child policy ```med-policy-v6``` attached + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy + +##### Validate test results +* Validate that the ATE receives the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` from BGP neighbor on DUT Port-1 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Validate that the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` on ATE from BGP neighbor on DUT Port-1 has AS-PATH with the ASN of DUT occuring twice + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member +* Validate that the prefix ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` from BGP neighbor on DUT Port-1 has MED set to ```1000``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med +* Initiate traffic from ATE Port-1 towards the DUT destined to ```ipv6-network-1``` 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 +* /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 + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: +``` + +## Required DUT platform + +* vRX diff --git a/feature/bgp/policybase/otg_tests/nested_policies/metadata.textproto b/feature/bgp/policybase/otg_tests/nested_policies/metadata.textproto new file mode 100644 index 00000000000..beed0cdd3fd --- /dev/null +++ b/feature/bgp/policybase/otg_tests/nested_policies/metadata.textproto @@ -0,0 +1,39 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "aa9b83cb-fd87-4098-ab3d-db05c66fc3c0" +plan_id: "RT-1.30" +description: "BGP nested import/export policy attachment" +testbed: TESTBED_DUT_ATE_2LINKS +tags: TAGS_DATACENTER_EDGE +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + missing_value_for_defaults: true + skip_set_rp_match_set_options: true + routing_policy_chaining_unsupported: true + } +} + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + skip_setting_statement_for_policy: true + skip_checking_attribute_index: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_rib_oc_path_unsupported: true + } +} + 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 new file mode 100644 index 00000000000..34727c8d907 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/nested_policies/nested_policies_test.go @@ -0,0 +1,896 @@ +// 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 nested_policies_test + +import ( + "fmt" + "net" + "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" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + v41Route = "203.0.113.0" + v41TrafficStart = "203.0.113.1" + v42Route = "198.51.100.0" + v42TrafficStart = "198.51.100.1" + v4RoutePrefix = uint32(24) + v61Route = "2001:db8:128:128::" + v61TrafficStart = "2001:db8:128:128::1" + v62Route = "2001:db8:128:129::" + v62TrafficStart = "2001:db8:128:129::1" + v6RoutePrefix = uint32(64) + dutAS = uint32(65656) + ateAS1 = uint32(65657) + ateAS2 = uint32(65658) + bgpName = "BGP" + maskLenExact = "exact" + localPref = 200 + med = 1000 + v4Flow = "flow-v4" + v4PrefixPolicy = "prefix-policy-v4" + v4PrefixStatement = "prefix-statement-v4" + v4PrefixSet = "prefix-set-v4" + v4LPPolicy = "lp-policy-v4" + v4LPStatement = "lp-statement-v4" + v4ASPPolicy = "asp-policy-v4" + v4ASPStatement = "asp-statement-v4" + v4MedPolicy = "med-policy-v4" + v4MedStatement = "med-statement-v4" + v6Flow = "flow-v6" + v6PrefixPolicy = "prefix-policy-v6" + v6PrefixStatement = "prefix-statement-v6" + v6PrefixSet = "prefix-set-v6" + v6LPPolicy = "lp-policy-v6" + v6LPStatement = "lp-statement-v6" + v6ASPPolicy = "asp-policy-v6" + v6ASPStatement = "asp-statement-v6" + v6MedPolicy = "med-policy-v6" + v6MedStatement = "med-statement-v6" + peerGrpNamev4 = "BGP-PEER-GROUP-V4" + peerGrpNamev6 = "BGP-PEER-GROUP-V6" + permitAll = "PERMIT-ALL" + permitAllStmtName = "20" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:1", + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:2", + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:5", + IPv6Len: ipv6PrefixLen, + } + + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:01:01:01:02", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + advertisedIPv41 = Prefix{address: v41Route, prefix: v4RoutePrefix} + advertisedIPv42 = Prefix{address: v42Route, prefix: v4RoutePrefix} + advertisedIPv61 = Prefix{address: v61Route, prefix: v6RoutePrefix} + advertisedIPv62 = Prefix{address: v62Route, prefix: v6RoutePrefix} +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type Prefix struct { + address string + prefix uint32 +} + +func (ip *Prefix) 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() +} + +type testData struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + otgP1 gosnappi.Device + otgP2 gosnappi.Device +} + +type testCase struct { + name string + desc string + applyPolicy func(t *testing.T, dut *ondatra.DUTDevice) + validate func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) + ipv4 bool + flowConfig flowConfig +} + +type flowConfig struct { + src attrs.Attributes + dstNw string + dstIP string +} + +func TestBGPNestedPolicies(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + devs := configureOTG(t, ate, top) + td := testData{ + dut: dut, + ate: ate, + top: top, + otgP1: devs[0], + otgP2: devs[1], + } + td.advertiseRoutesWithEBGP(t) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + defer ate.OTG().StopProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + td.verifyDUTBGPEstablished(t) + td.verifyOTGBGPEstablished(t) + + testCases := []testCase{ + { + name: "IPv4BGPNestedmportPolicy", + desc: "IPv4 BGP Nested import policy test", + applyPolicy: configureImportRoutingPolicy, + validate: validateImportRoutingPolicy, + ipv4: true, + flowConfig: flowConfig{src: atePort2, dstNw: "v4-bgpNet-dev1", dstIP: v41TrafficStart}, + }, + { + name: "IPv4BGPNestedExportPolicy", + desc: "IPv4 BGP Nested export policy test", + applyPolicy: configureExportRoutingPolicy, + validate: validateExportRoutingPolicy, + ipv4: true, + flowConfig: flowConfig{src: atePort1, dstNw: "v4-bgpNet-dev2", dstIP: v42TrafficStart}, + }, + { + name: "IPv6BGPNestedImportPolicy", + desc: "IPv6 BGP Nested import policy test", + applyPolicy: configureImportRoutingPolicyV6, + validate: validateImportRoutingPolicyV6, + ipv4: false, + flowConfig: flowConfig{src: atePort2, dstNw: "v6-bgpNet-dev1", dstIP: v61TrafficStart}, + }, + { + name: "IPv6BGPNestedExportPolicy", + desc: "IPv6 BGP Nested export policy test", + applyPolicy: configureExportRoutingPolicyV6, + validate: validateExportRoutingPolicyV6, + ipv4: false, + flowConfig: flowConfig{src: atePort1, dstNw: "v6-bgpNet-dev2", dstIP: v62TrafficStart}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Logf("Description: %s", tc.desc) + tc.applyPolicy(t, dut) + tc.validate(t, dut, ate) + if tc.ipv4 { + createFlow(t, td, tc.flowConfig) + checkTraffic(t, td, v4Flow) + } else { + createFlowV6(t, td, tc.flowConfig) + checkTraffic(t, td, v6Flow) + } + }) + } +} + +// configureImportRoutingPolicy configures the dut for IPv4 BGP nested import policy test. +func configureImportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + + // Configure PERMIT-ALL policy + pdef := rp.GetOrCreatePolicyDefinition(permitAll) + stmt, err := pdef.AppendNewStatement(permitAllStmtName) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", permitAllStmtName, err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + + // Configure a route-policy to set the local preference. + pdef1 := rp.GetOrCreatePolicyDefinition(v4LPPolicy) + stmt1, err := pdef1.AppendNewStatement(v4LPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4LPStatement, err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + t.Logf("Setting statement for policy") + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + stmt1.GetOrCreateActions().GetOrCreateBgpActions().SetSetLocalPref(localPref) + + // Configure a route-policy to match the prefix. + pdef2 := rp.GetOrCreatePolicyDefinition(v4PrefixPolicy) + stmt2, err := pdef2.AppendNewStatement(v4PrefixStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4PrefixStatement, err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + + t.Logf("Configuring nested policy") + // Configure a prefix-set for route filtering/matching. + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v4PrefixSet) + prefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + prefixSet.GetOrCreatePrefix(advertisedIPv41.cidr(t), maskLenExact) + + if !deviations.SkipSetRpMatchSetOptions(dut) { + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v4PrefixSet) + statPath := rp.GetOrCreatePolicyDefinition(v4LPPolicy).GetStatement(v4LPStatement).GetOrCreateConditions() + statPath.SetCallPolicy(v4PrefixPolicy) + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + + dni := deviations.DefaultNetworkInstance(dut) + // Configure the parent BGP import policy. + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + //policy under peer group + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().PeerGroup(peerGrpNamev4).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreatePeerGroup(peerGrpNamev4).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{v4LPPolicy}) + gnmi.BatchReplace(batch, path.Config(), policy) + + } else { + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{v4LPPolicy}) + if !deviations.RoutingPolicyChainingUnsupported(dut) { + gnmi.BatchUpdate(batch, path.Config(), policy) + } else { + gnmi.BatchReplace(batch, path.Config(), policy) + } + } + batch.Set(t, dut) +} + +func validateImportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + if !deviations.BGPRibOcPathUnsupported(dut) { + dni := deviations.DefaultNetworkInstance(dut) + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv4Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + if prefixAddr[0] == advertisedIPv41.address { + found = true + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + if !deviations.SkipCheckingAttributeIndex(dut) { + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != localPref { + t.Errorf("No local pref found for prefix %s", advertisedIPv41.address) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == localPref { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), advertisedIPv41.address) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", advertisedIPv41.address) + } + } + } + } + if !found { + t.Errorf("No Route found for prefix %s", advertisedIPv41.address) + } + } +} + +// configureExportRoutingPolicy configures the dut for IPv4 BGP nested export policy test. +func configureExportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + gnmi.BatchDelete(batch, gnmi.OC().RoutingPolicy().Config()) + rp := root.GetOrCreateRoutingPolicy() + + pdef := rp.GetOrCreatePolicyDefinition(permitAll) + stmt, err := pdef.AppendNewStatement(permitAllStmtName) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", permitAllStmtName, err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + + t.Logf("Configuring export routing policy") + pdef1 := rp.GetOrCreatePolicyDefinition(v4ASPPolicy) + stmt1, err := pdef1.AppendNewStatement(v4ASPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4ASPStatement, err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend().SetAsn(dutAS) + + pdef2 := rp.GetOrCreatePolicyDefinition(v4MedPolicy) + stmt2, err := pdef2.AppendNewStatement(v4MedStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v4MedStatement, err) + } + + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(med)) + statPath := rp.GetOrCreatePolicyDefinition(v4ASPPolicy).GetStatement(v4ASPStatement).GetOrCreateConditions() + statPath.SetCallPolicy(v4MedPolicy) + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + + // Configure the nested policy. + dni := deviations.DefaultNetworkInstance(dut) + time.Sleep(time.Second * 60) + + // Configure the parent BGP import policy. + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + //policy under peer group + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().PeerGroup(peerGrpNamev4).ApplyPolicy() + gnmi.BatchDelete(batch, path.Config()) + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreatePeerGroup(peerGrpNamev4).GetOrCreateApplyPolicy() + policy.SetExportPolicy([]string{v4ASPPolicy}) + gnmi.BatchReplace(batch, path.Config(), policy) + } else { + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + gnmi.BatchDelete(batch, path.Config()) + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv4).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policy.SetExportPolicy([]string{v4ASPPolicy}) + if !deviations.RoutingPolicyChainingUnsupported(dut) { + gnmi.BatchUpdate(batch, path.Config(), policy) + } else { + gnmi.BatchReplace(batch, path.Config(), policy) + } + gnmi.BatchReplace(batch, path.Config(), policy) + } + batch.Set(t, dut) + time.Sleep(time.Second * 60) +} + +func validateExportRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + t.Logf("Validating Export Routing Policy, waiting for 120 seconds") + time.Sleep(time.Second * 120) + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv4Prefix](t, ate.OTG(), gnmi.OTG().BgpPeer("atePort1.BGP4.peer").UnicastIpv4PrefixAny().State()) + found := false + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == v42Route && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == v4RoutePrefix { + found = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix, v42Route) + t.Logf("Prefix MED %d", bgpPrefix.GetMultiExitDiscriminator()) + if bgpPrefix.GetMultiExitDiscriminator() != med { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), med) + } + asPaths := bgpPrefix.AsPath + for _, ap := range asPaths { + count := 0 + for _, an := range ap.AsNumbers { + if an == dutAS { + count++ + } + } + if count == 2 { + t.Logf("ASP for prefix %v is correct, got ASP %v", bgpPrefix.GetAddress(), ap.AsNumbers) + } + } + break + } + } + if !found { + t.Errorf("No Route found for prefix %s", v42Route) + } +} + +// configureImportRoutingPolicyV6 configures the dut for IPv6 BGP nested import policy test. +func configureImportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + gnmi.BatchDelete(batch, gnmi.OC().RoutingPolicy().Config()) + rp := root.GetOrCreateRoutingPolicy() + + pdef := rp.GetOrCreatePolicyDefinition(permitAll) + stmt, err := pdef.AppendNewStatement(permitAllStmtName) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", permitAllStmtName, err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + + t.Logf("Configuring import routing policy") + pdef1 := rp.GetOrCreatePolicyDefinition(v6LPPolicy) + stmt1, err := pdef1.AppendNewStatement(v6LPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6LPStatement, err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + t.Logf("Setting statement for policy") + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + stmt1.GetOrCreateActions().GetOrCreateBgpActions().SetSetLocalPref(localPref) + + pdef2 := rp.GetOrCreatePolicyDefinition(v6PrefixPolicy) + stmt2, err := pdef2.AppendNewStatement(v6PrefixStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6PrefixStatement, err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v6PrefixSet) + prefixSet.SetMode(oc.PrefixSet_Mode_IPV6) + prefixSet.GetOrCreatePrefix(advertisedIPv61.cidr(t), maskLenExact) + + if !deviations.SkipSetRpMatchSetOptions(dut) { + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v6PrefixSet) + statPath := rp.GetOrCreatePolicyDefinition(v6LPPolicy).GetStatement(v6LPStatement).GetOrCreateConditions() + statPath.SetCallPolicy(v6PrefixPolicy) + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + + // Configure the nested policy. + dni := deviations.DefaultNetworkInstance(dut) + + // Configure the parent BGP import policy. + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + //policy under peer group + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().PeerGroup(peerGrpNamev4).ApplyPolicy() + gnmi.BatchDelete(batch, pathV4.Config()) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().PeerGroup(peerGrpNamev6).ApplyPolicy() + gnmi.BatchDelete(batch, path.Config()) + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreatePeerGroup(peerGrpNamev6).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{v6LPPolicy}) + gnmi.BatchReplace(batch, path.Config(), policy) + } else { + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + gnmi.BatchDelete(batch, pathV4.Config()) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + gnmi.BatchDelete(batch, path.Config()) + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policy.SetImportPolicy([]string{v6LPPolicy}) + if !deviations.RoutingPolicyChainingUnsupported(dut) { + gnmi.BatchUpdate(batch, path.Config(), policy) + } else { + gnmi.BatchReplace(batch, path.Config(), policy) + } + } + batch.Set(t, dut) +} + +func validateImportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + if !deviations.BGPRibOcPathUnsupported(dut) { + dni := deviations.DefaultNetworkInstance(dut) + bgpRIBPath := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Rib() + locRib := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AfiSafi_Ipv6Unicast_LocRib](t, dut, bgpRIBPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Ipv6Unicast().LocRib().State()) + found := false + for k, lr := range locRib.Route { + prefixAddr := strings.Split(lr.GetPrefix(), "/") + t.Logf("lr.GetPrefix() -> %s, prefixAddr[0] -> %s, advertisedIPv61.address = %s", lr.GetPrefix(), prefixAddr[0], advertisedIPv61.address) + if prefixAddr[0] == advertisedIPv61.address { + found = true + t.Logf("Found Route(prefix %s, origin: %v, pathid: %d) => %s", k.Prefix, k.Origin, k.PathId, lr.GetPrefix()) + if !deviations.SkipCheckingAttributeIndex(dut) { + attrSet := gnmi.Get[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSet(lr.GetAttrIndex()).State()) + if attrSet == nil || attrSet.GetLocalPref() != localPref { + t.Errorf("No local pref found for prefix %s", advertisedIPv61.address) + } + break + } else { + attrSetList := gnmi.GetAll[*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet](t, dut, bgpRIBPath.AttrSetAny().State()) + foundLP := false + for _, attrSet := range attrSetList { + if attrSet.GetLocalPref() == localPref { + foundLP = true + t.Logf("Found local pref %d for prefix %s", attrSet.GetLocalPref(), advertisedIPv61.address) + break + } + } + if !foundLP { + t.Errorf("No local pref found for prefix %s", advertisedIPv41.address) + } + } + } + } + if !found { + t.Errorf("No Route found for prefix %s", advertisedIPv61.address) + } + } +} + +// configureExportRoutingPolicyV6 configures the dut for IPv6 BGP nested export policy test. +func configureExportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice) { + batch := &gnmi.SetBatch{} + root := &oc.Root{} + gnmi.BatchDelete(batch, gnmi.OC().RoutingPolicy().Config()) + rp := root.GetOrCreateRoutingPolicy() + + pdef := rp.GetOrCreatePolicyDefinition(permitAll) + stmt, err := pdef.AppendNewStatement(permitAllStmtName) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", permitAllStmtName, err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + + t.Logf("Configuring export routing policy") + pdef1 := rp.GetOrCreatePolicyDefinition(v6ASPPolicy) + stmt1, err := pdef1.AppendNewStatement(v6ASPStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6ASPStatement, err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + stmt1.GetOrCreateActions().GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend().SetAsn(dutAS) + + pdef2 := rp.GetOrCreatePolicyDefinition(v6MedPolicy) + stmt2, err := pdef2.AppendNewStatement(v6MedStatement) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", v6MedStatement, err) + } + if !deviations.SkipSettingStatementForPolicy(dut) { + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + } + stmt2.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(med)) + statPath := rp.GetOrCreatePolicyDefinition(v6ASPPolicy).GetStatement(v6ASPStatement).GetOrCreateConditions() + statPath.SetCallPolicy(v6MedPolicy) + gnmi.BatchReplace(batch, gnmi.OC().RoutingPolicy().Config(), rp) + + // Configure the nested policy. + dni := deviations.DefaultNetworkInstance(dut) + time.Sleep(time.Second * 60) + + // Configure the parent BGP export policy. + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + //policy under peer group + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().PeerGroup(peerGrpNamev4).ApplyPolicy() + gnmi.BatchDelete(batch, pathV4.Config()) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().PeerGroup(peerGrpNamev6).ApplyPolicy() + gnmi.BatchDelete(batch, path.Config()) + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreatePeerGroup(peerGrpNamev6).GetOrCreateApplyPolicy() + policy.SetExportPolicy([]string{v6LPPolicy}) + gnmi.BatchReplace(batch, path.Config(), policy) + } else { + pathV4 := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + gnmi.BatchDelete(batch, pathV4.Config()) + path := gnmi.OC().NetworkInstance(dni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + gnmi.BatchDelete(batch, path.Config()) + policy := root.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).GetOrCreateBgp().GetOrCreateNeighbor(atePort1.IPv6).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policy.SetExportPolicy([]string{v6ASPPolicy}) + if !deviations.RoutingPolicyChainingUnsupported(dut) { + gnmi.BatchUpdate(batch, path.Config(), policy) + } else { + gnmi.BatchReplace(batch, path.Config(), policy) + } + batch.Set(t, dut) + } + time.Sleep(time.Second * 60) +} + +func validateExportRoutingPolicyV6(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv6Prefix](t, ate.OTG(), gnmi.OTG().BgpPeer("atePort1.BGP6.peer").UnicastIpv6PrefixAny().State()) + + found := false + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == v62Route && + bgpPrefix.PrefixLength != nil && bgpPrefix.GetPrefixLength() == v6RoutePrefix { + found = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix, v62Route) + if bgpPrefix.GetMultiExitDiscriminator() != med { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), med) + } + asPaths := bgpPrefix.AsPath + for _, ap := range asPaths { + count := 0 + for _, an := range ap.AsNumbers { + if an == dutAS { + count++ + } + } + if count == 2 { + t.Logf("ASP for prefix %v is correct, got ASP %v", bgpPrefix.GetAddress(), ap.AsNumbers) + } + } + break + } + } + + if !found { + t.Errorf("No Route found for prefix %s", v62Route) + } +} + +func createFlow(t *testing.T, td testData, fc flowConfig) { + td.top.Flows().Clear() + + t.Log("Configuring v4 traffic flow") + v4Flow := td.top.Flows().Add().SetName(v4Flow) + v4Flow.Metrics().SetEnable(true) + v4Flow.TxRx().Device(). + SetTxNames([]string{fc.src.Name + ".IPv4"}). + SetRxNames([]string{fc.dstNw}) + v4Flow.Size().SetFixed(512) + v4Flow.Rate().SetPps(100) + v4Flow.Duration().Continuous() + e1 := v4Flow.Packet().Add().Ethernet() + e1.Src().SetValue(fc.src.MAC) + v4 := v4Flow.Packet().Add().Ipv4() + v4.Src().SetValue(fc.src.IPv4) + v4.Dst().Increment().SetStart(fc.dstIP).SetCount(1) + + td.ate.OTG().PushConfig(t, td.top) + td.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv4") +} + +func createFlowV6(t *testing.T, td testData, fc flowConfig) { + td.top.Flows().Clear() + + t.Log("Configuring v6 traffic flow") + v6Flow := td.top.Flows().Add().SetName(v6Flow) + v6Flow.Metrics().SetEnable(true) + v6Flow.TxRx().Device(). + SetTxNames([]string{fc.src.Name + ".IPv6"}). + SetRxNames([]string{fc.dstNw}) + v6Flow.Size().SetFixed(512) + v6Flow.Rate().SetPps(100) + v6Flow.Duration().Continuous() + e1 := v6Flow.Packet().Add().Ethernet() + e1.Src().SetValue(fc.src.MAC) + v6 := v6Flow.Packet().Add().Ipv6() + v6.Src().SetValue(fc.src.IPv6) + v6.Dst().Increment().SetStart(fc.dstIP).SetCount(1) + + td.ate.OTG().PushConfig(t, td.top) + td.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, td.ate.OTG(), td.top, "IPv6") +} + +func checkTraffic(t *testing.T, td testData, flowName string) { + td.ate.OTG().StartTraffic(t) + time.Sleep(time.Second * 30) + td.ate.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + otgutils.LogPortMetrics(t, td.ate.OTG(), td.top) + + t.Log("Checking flow telemetry...") + recvMetric := gnmi.Get(t, td.ate.OTG(), gnmi.OTG().Flow(flowName).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + + if lossPct > 1 { + t.Errorf("FAIL- got %v%% packet loss for %s ; want < 1%%", lossPct, flowName) + } +} + +func (td *testData) advertiseRoutesWithEBGP(t *testing.T) { + t.Helper() + + root := &oc.Root{} + ni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(td.dut)) + bgpP := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName) + bgpP.SetEnabled(true) + bgp := bgpP.GetOrCreateBgp() + + g := bgp.GetOrCreateGlobal() + g.SetAs(dutAS) + g.SetRouterId(dutPort1.IPv4) + 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) + + t.Logf("Configuring route-policy for BGP on DUT") + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(permitAll) + stmt, err := pdef.AppendNewStatement(permitAllStmtName) + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", permitAllStmtName, err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Update(t, td.dut, gnmi.OC().RoutingPolicy().Config(), rp) + + pgv4 := bgp.GetOrCreatePeerGroup(peerGrpNamev4) + pgv4.PeerGroupName = ygot.String(peerGrpNamev4) + pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNamev6) + pgv6.PeerGroupName = ygot.String(peerGrpNamev6) + nV41 := bgp.GetOrCreateNeighbor(atePort1.IPv4) + nV41.SetPeerAs(ateAS1) + nV41.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV41.PeerGroup = ygot.String(peerGrpNamev4) + afisafiv41 := nV41.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afisafiv41.GetOrCreateApplyPolicy().SetImportPolicy([]string{permitAll}) + afisafiv41.GetOrCreateApplyPolicy().SetExportPolicy([]string{permitAll}) + + nV42 := bgp.GetOrCreateNeighbor(atePort2.IPv4) + nV42.SetPeerAs(ateAS2) + nV42.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nV42.PeerGroup = ygot.String(peerGrpNamev4) + afisafiv42 := nV42.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afisafiv42.GetOrCreateApplyPolicy().SetImportPolicy([]string{permitAll}) + afisafiv42.GetOrCreateApplyPolicy().SetExportPolicy([]string{permitAll}) + + nV61 := bgp.GetOrCreateNeighbor(atePort1.IPv6) + nV61.SetPeerAs(ateAS1) + nV61.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nV61.PeerGroup = ygot.String(peerGrpNamev6) + afisafiv61 := nV61.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + afisafiv61.GetOrCreateApplyPolicy().SetImportPolicy([]string{permitAll}) + afisafiv61.GetOrCreateApplyPolicy().SetExportPolicy([]string{permitAll}) + + nV62 := bgp.GetOrCreateNeighbor(atePort2.IPv6) + nV62.SetPeerAs(ateAS2) + nV62.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + nV62.PeerGroup = ygot.String(peerGrpNamev6) + afisafiv62 := nV62.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + afisafiv62.GetOrCreateApplyPolicy().SetImportPolicy([]string{permitAll}) + afisafiv62.GetOrCreateApplyPolicy().SetExportPolicy([]string{permitAll}) + + gnmi.Update(t, td.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Config(), ni) + + // Configure eBGP on OTG port1. + ipv41 := td.otgP1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dev1BGP := td.otgP1.Bgp().SetRouterId(atePort1.IPv4) + bgp4Peer1 := dev1BGP.Ipv4Interfaces().Add().SetIpv4Name(ipv41.Name()).Peers().Add().SetName(td.otgP1.Name() + ".BGP4.peer") + bgp4Peer1.SetPeerAddress(dutPort1.IPv4).SetAsNumber(ateAS1).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + bgp4Peer1.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + + ipv61 := td.otgP1.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer1 := dev1BGP.Ipv6Interfaces().Add().SetIpv6Name(ipv61.Name()).Peers().Add().SetName(td.otgP1.Name() + ".BGP6.peer") + bgp6Peer1.SetPeerAddress(dutPort1.IPv6).SetAsNumber(ateAS1).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + bgp6Peer1.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // Configure emulated network on ATE port1. + netv41 := bgp4Peer1.V4Routes().Add().SetName("v4-bgpNet-dev1") + netv41.Addresses().Add().SetAddress(advertisedIPv41.address).SetPrefix(advertisedIPv41.prefix) + netv61 := bgp6Peer1.V6Routes().Add().SetName("v6-bgpNet-dev1") + netv61.Addresses().Add().SetAddress(advertisedIPv61.address).SetPrefix(advertisedIPv61.prefix) + + // Configure eBGP on OTG port2. + ipv42 := td.otgP2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dev2BGP := td.otgP2.Bgp().SetRouterId(atePort2.IPv4) + bgp4Peer2 := dev2BGP.Ipv4Interfaces().Add().SetIpv4Name(ipv42.Name()).Peers().Add().SetName(td.otgP2.Name() + ".BGP4.peer") + bgp4Peer2.SetPeerAddress(dutPort2.IPv4).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + bgp4Peer2.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + + ipv62 := td.otgP2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer2 := dev2BGP.Ipv6Interfaces().Add().SetIpv6Name(ipv62.Name()).Peers().Add().SetName(td.otgP2.Name() + ".BGP6.peer") + bgp6Peer2.SetPeerAddress(dutPort2.IPv6).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + bgp6Peer2.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // Configure emulated network on ATE port2. + netv42 := bgp4Peer2.V4Routes().Add().SetName("v4-bgpNet-dev2") + netv42.Addresses().Add().SetAddress(advertisedIPv42.address).SetPrefix(advertisedIPv42.prefix) + netv62 := bgp6Peer2.V6Routes().Add().SetName("v6-bgpNet-dev2") + netv62.Addresses().Add().SetAddress(advertisedIPv62.address).SetPrefix(advertisedIPv62.prefix) +} + +// verifyDUTBGPEstablished verifies on dut for BGP peer establishment. +func (td *testData) verifyDUTBGPEstablished(t *testing.T) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, bgpName).Bgp().NeighborAny().SessionState().State() + watch := gnmi.WatchAll(t, td.dut, sp, 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if !ok || state != oc.Bgp_Neighbor_SessionState_ESTABLISHED { + return false + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established: got %v", val) + } + t.Log("DUT BGP sessions established") +} + +// VerifyOTGBGPEstablished verifies on OTG for BGP peer establishment. +func (td *testData) verifyOTGBGPEstablished(t *testing.T) { + sp := gnmi.OTG().BgpPeerAny().SessionState().State() + watch := gnmi.WatchAll(t, td.ate.OTG(), sp, 2*time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + state, ok := val.Val() + if !ok || state != otgtelemetry.BgpPeer_SessionState_ESTABLISHED { + return false + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established: got %v", val) + } + t.Log("OTG BGP sessions established") +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + b := &gnmi.SetBatch{} + gnmi.BatchReplace(b, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.BatchReplace(b, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + b.Set(t, dut) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p1) + fptest.SetPortSpeed(t, p2) + } + + 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) + } +} + +func configureOTG(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) []gosnappi.Device { + t.Helper() + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + + d1 := atePort1.AddToOTG(top, p1, &dutPort1) + d2 := atePort2.AddToOTG(top, p2, &dutPort2) + return []gosnappi.Device{d1, d2} +} diff --git a/feature/bgp/policybase/otg_tests/prefix_set_test/README.md b/feature/bgp/policybase/otg_tests/prefix_set_test/README.md new file mode 100644 index 00000000000..f4cdf076f65 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/prefix_set_test/README.md @@ -0,0 +1,79 @@ +# RT-1.33: BGP Policy with prefix-set matching + +## Summary + +BGP policy configuration with prefix-set matching + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed + +## Procedure +Establish eBGP sessions between: + • ATE port-1 and DUT port-1 + • ATE port-2 and DUT port-2 + • Configure Route-policy under BGP neighbor/session address-family + +For IPv4: +Create two prefix-sets as below: +IPv4-prefix-set-1 - exact match on 10.23.15.0/26 +IPv4-prefix-set-2 - match on 10.23.0.0/16 +For IPv6: +Create two prefix-sets as below: +IPv6-prefix-set-1 - exact match on 2001:4860:f804::/48 +IPv6-prefix-set-2 - 65-128 match on ::/0 +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 + • Configure BGP policy on DUT to allow routes based on IPv6-prefix-set-1 + • and reject routes based on IPv6-prefix-set-2 + • Validate that the prefixes are accepted after policy application. + • DUT conditionally advertises prefixes received from ATE port-1 to ATE port-2 after policy application. Ensure that multiple routes are accepted and advertised to the neighbor on ATE port-2. + +## Config Parameter Coverage +/routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +/routing-policy/defined-sets/prefix-sets/prefix-set/config/name +/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/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix +/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +/routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +/routing-policy/defined-sets/prefix-sets/prefix-set/state/name + +/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 + +## Telemetry Parameter coverage +N/A +Protocol/RPC Parameter coverage +N/A +Minimum DUT platform requirement +vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths + /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode: + /routing-policy/defined-sets/prefix-sets/prefix-set/config/name: + /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: + + ## State paths + /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/installed: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: + /routing-policy/policy-definitions/policy-definition/statements/statement/state/name: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` 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 new file mode 100644 index 00000000000..07a0db917a5 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/prefix_set_test/bgp_prefix_set_test.go @@ -0,0 +1,508 @@ +// 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 bgp_prefix_set_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/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" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + peerGrpName = "BGP-PEER-GROUP" + dutAS = 65501 + ateAS = 65502 + ateAS2 = 65503 + plenIPv4 = 30 + plenIPv6 = 126 + v4Prefixes = true + acceptPolicy = "PERMIT-ALL" + rejectPolicy = "REJECT-ALL" + bgpImportIPv4 = "IPv4-IMPORT" + bgpImportIPv6 = "IPv6-IMPORT" + bgpExportIPv4 = "IPv4-ExPORT" + bgpExportIPv6 = "IPv6-ExPORT" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE Port1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + MAC: "02:00:01:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE Port2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + IPv6: "2001:db8::192:0:2:6", + MAC: "02:00:02:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + ebgp1NbrV4 = &bgpNeighbor{ + nbrAddr: atePort1.IPv4, + isV4: true, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, + as: ateAS} + ebgp1NbrV6 = &bgpNeighbor{ + nbrAddr: atePort1.IPv6, + isV4: false, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST, + as: ateAS} + ebgp2NbrV4 = &bgpNeighbor{ + nbrAddr: atePort2.IPv4, + isV4: true, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, + as: ateAS2} + ebgp2NbrV6 = &bgpNeighbor{ + nbrAddr: atePort2.IPv6, + isV4: false, + afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST, + as: ateAS2} + ebgpNbrs = []*bgpNeighbor{ebgp1NbrV4, ebgp1NbrV6, ebgp2NbrV4, ebgp2NbrV6} + + route1 = &route{prefix: "10.23.15.1", maskLen: 32, isV4: true} + route2 = &route{prefix: "10.23.15.2", maskLen: 16, isV4: true} + route3 = &route{prefix: "10.23.15.60", maskLen: 26, isV4: true} + route4 = &route{prefix: "10.23.15.70", maskLen: 26, isV4: true} + route5 = &route{prefix: "20.23.15.2", maskLen: 16, isV4: true} + + route6 = &route{prefix: "2001:4860:f804::1", maskLen: 48, isV4: false} + route7 = &route{prefix: "2001:4860:f804::2", maskLen: 128, isV4: false} + route8 = &route{prefix: "2001:4860:f804:1111::1", maskLen: 64, isV4: false} + route9 = &route{prefix: "2001:4860:f804::10", maskLen: 70, isV4: false} + route10 = &route{prefix: "2001:5555:f804::1", maskLen: 48, isV4: false} + + routes = []*route{route1, route2, route3, route4, route5, route6, route7, route8, route9, route10} + + prefixSet1V4 = &prefixSetPolicy{ + name: "IPv4-prefix-set-1", + ipPrefix: "10.23.15.0/26", + maskLenRange: "exact", + statement: "10", + actionAccept: false, + isV4: true} + prefixSet2V4 = &prefixSetPolicy{ + name: "IPv4-prefix-set-2", + ipPrefix: "10.23.0.0/16", + maskLenRange: "16..32", + statement: "20", + actionAccept: true, + isV4: true} + prefixSet1V6 = &prefixSetPolicy{ + name: "IPv6-prefix-set-1", + ipPrefix: "2001:4860:f804::/48", + maskLenRange: "exact", + statement: "10", + actionAccept: true, + isV4: false} + prefixSet2V6 = &prefixSetPolicy{ + name: "IPv6-prefix-set-2", + ipPrefix: "::/0", + maskLenRange: "65..128", + statement: "20", + actionAccept: false, + isV4: false} +) + +type route struct { + prefix string + maskLen uint32 + isV4 bool +} + +type prefixSetPolicy struct { + name string + ipPrefix string + maskLenRange string + statement string + actionAccept bool + isV4 bool +} + +type bgpNeighbor struct { + as uint32 + nbrAddr string + isV4 bool + afiSafi oc.E_BgpTypes_AFI_SAFI_TYPE +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, "port1")) + fptest.SetPortSpeed(t, dut.Port(t, "port2")) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, dut.Port(t, "port1").Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, dut.Port(t, "port2").Name(), deviations.DefaultNetworkInstance(dut), 0) + } +} + +func configurePrefixSet(t *testing.T, dut *ondatra.DUTDevice, prefixSet []*prefixSetPolicy) { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + for _, ps := range prefixSet { + pset := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(ps.name) + pset.GetOrCreatePrefix(ps.ipPrefix, ps.maskLenRange) + if !deviations.SkipPrefixSetMode(dut) { + if ps.isV4 { + pset.SetMode(oc.PrefixSet_Mode_IPV4) + } else { + pset.SetMode(oc.PrefixSet_Mode_IPV6) + } + } + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(ps.name).Config(), pset) + } +} + +func applyPrefixSetPolicy(t *testing.T, dut *ondatra.DUTDevice, prefixSet []*prefixSetPolicy, policyName string, bgpNbr bgpNeighbor, importPolicy bool) { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + + // Associate prefix-set with routing-policy + pdef := rp.GetOrCreatePolicyDefinition(policyName) + for _, pSet := range prefixSet { + stmt, err := pdef.AppendNewStatement(pSet.statement) + if err != nil { + t.Fatal(err) + } + ps := stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet() + ps.SetPrefixSet(pSet.name) + if !deviations.SkipSetRpMatchSetOptions(dut) { + ps.SetMatchSetOptions(oc.E_RoutingPolicy_MatchSetOptionsRestrictedType(oc.RoutingPolicy_MatchSetOptionsType_ANY)) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + if !pSet.actionAccept { + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE + } + } + + batchConfig := &gnmi.SetBatch{} + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), rp) + + // Apply routing-policy with BGP neighbor + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + if importPolicy { + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(bgpNbr.nbrAddr).AfiSafi(bgpNbr.afiSafi).ApplyPolicy().ImportPolicy().Config(), []string{policyName}) + } else { + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(bgpNbr.nbrAddr).AfiSafi(bgpNbr.afiSafi).ApplyPolicy().ExportPolicy().Config(), []string{policyName}) + } + batchConfig.Set(t, dut) +} + +func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + + // Configure BGP on DUT + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(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) + + pg := bgp.GetOrCreatePeerGroup(peerGrpName) + pg.PeerAs = ygot.Uint32(peerAs) + pg.PeerGroupName = ygot.String(peerGrpName) + + for _, nbr := range ebgpNbrs { + bgpNbr := bgp.GetOrCreateNeighbor(nbr.nbrAddr) + bgpNbr.PeerGroup = ygot.String(peerGrpName) + bgpNbr.PeerAs = ygot.Uint32(nbr.as) + bgpNbr.Enabled = ygot.Bool(true) + + if nbr.isV4 == true { + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(false) + } else { + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(false) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + } + } + return niProto +} + +func verifyBgpState(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + t.Logf("Waiting for BGP neighbor to establish...") + for _, nbr := range ebgpNbrs { + nbrPath := bgpPath.Neighbor(nbr.nbrAddr) + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", nbr.nbrAddr, state, want) + } + } +} + +func configureOTG(t *testing.T, otg *otg.OTG) { + t.Helper() + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + // Port1 Configuration. Sets the ATE port + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + // Port2 Configuration. + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + // eBGP v4 seesion 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(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + // eBGP v6 seesion on Port1. + iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") + iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // eBGP v4 seesion on Port2. + iDut2Bgp := iDut2Dev.Bgp().SetRouterId(iDut2Ipv4.Address()) + iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2Ipv4.Name()).Peers().Add().SetName(atePort2.Name + ".BGP4.peer") + iDut2Bgp4Peer.SetPeerAddress(iDut2Ipv4.Gateway()).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + // eBGP v6 seesion on Port2. + iDut2Bgp6Peer := iDut2Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut2Ipv6.Name()).Peers().Add().SetName(atePort2.Name + ".BGP6.peer") + iDut2Bgp6Peer.SetPeerAddress(iDut2Ipv6.Gateway()).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut2Bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // eBGP V4 routes from Port1. + bgpNeti1Bgp4PeerRoutes := iDut1Bgp4Peer.V4Routes().Add().SetName(atePort1.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(iDut1Ipv4.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + + // eBGP V6 routes from Port1. + bgpNeti1Bgp6PeerRoutes := iDut1Bgp6Peer.V6Routes().Add().SetName(atePort1.Name + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(iDut1Ipv6.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + + for _, sendRoute := range routes { + if sendRoute.isV4 { + bgpNeti1Bgp4PeerRoutes.Addresses().Add(). + SetAddress(sendRoute.prefix).SetPrefix(sendRoute.maskLen) + } + if !sendRoute.isV4 { + bgpNeti1Bgp6PeerRoutes.Addresses().Add(). + SetAddress(sendRoute.prefix).SetPrefix(sendRoute.maskLen) + } + } + + otg.PushConfig(t, config) + otg.StartProtocols(t) +} + +func validatePrefixCount(t *testing.T, dut *ondatra.DUTDevice, nbr bgpNeighbor, wantInstalled, wantRx, wantSent uint32) { + t.Helper() + + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + t.Logf("Validating prefix count for peer %v", nbr.nbrAddr) + 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, _ := val.Val() + t.Logf("Prefix that are installed %v and want %v", gotInstalled, wantInstalled) + return gotInstalled == wantInstalled + }).Await(t) + if !ok { + t.Errorf("Installed prefixes mismatch: got %v, want %v", gotInstalled, wantInstalled) + } + + // 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, _ := val.Val() + t.Logf("Prefix that are received %v and want %v", gotRx, wantRx) + return gotRx == wantRx + }).Await(t) + if !ok { + t.Errorf("Received prefixes mismatch: got %v, want %v", gotRx, wantRx) + } + + // 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 { + 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) + return gotSent == wantSent + }).Await(t) + if !ok { + t.Errorf("Sent prefixes mismatch: got %v, want %v", gotSent, wantSent) + } +} + +// testPrefixSet is to validate prefix-set policies +func testPrefixSet(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + importPolicy := true + + // Configuring all 4 reqruired prefix-sets + t.Run("Configure prefix-set", func(t *testing.T) { + configurePrefixSet(t, dut, []*prefixSetPolicy{prefixSet1V4, prefixSet2V4, prefixSet1V6, prefixSet2V6}) + }) + + // Associating prefix-set with the required routing-policy and applying to BGP neighbors on ATE-port-1 + t.Run("Validate acceptance based on prefix-set policy - import policy on neighbor", func(t *testing.T) { + applyPrefixSetPolicy(t, dut, []*prefixSetPolicy{prefixSet1V4, prefixSet2V4}, bgpImportIPv4, *ebgp1NbrV4, importPolicy) + applyPrefixSetPolicy(t, dut, []*prefixSetPolicy{prefixSet1V6, prefixSet2V6}, bgpImportIPv6, *ebgp1NbrV6, importPolicy) + if deviations.DefaultImportExportPolicyUnsupported(dut) { + t.Logf("Validate for neighbour %v", ebgp1NbrV4) + validatePrefixCount(t, dut, *ebgp1NbrV4, 3, 5, 0) + validatePrefixCount(t, dut, *ebgp1NbrV6, 1, 5, 0) + validatePrefixCount(t, dut, *ebgp2NbrV4, 0, 0, 3) + validatePrefixCount(t, dut, *ebgp2NbrV6, 0, 0, 1) + } else { + t.Logf("Validate for neighbour %v", ebgp1NbrV4) + validatePrefixCount(t, dut, *ebgp1NbrV4, 3, 5, 0) + // only route6 is expected to accepted based on prefix-set + validatePrefixCount(t, dut, *ebgp1NbrV6, 1, 5, 0) + validatePrefixCount(t, dut, *ebgp2NbrV4, 0, 0, 0) + validatePrefixCount(t, dut, *ebgp2NbrV6, 0, 0, 0) + } + }) + + // Associating prefix-set with the required routing-policy and applying to BGP neighbors on ATE-port-2 + t.Run("Validate advertise based on prefix-set policy - export policy on neighbor", func(t *testing.T) { + applyPrefixSetPolicy(t, dut, []*prefixSetPolicy{prefixSet2V4}, bgpExportIPv4, *ebgp2NbrV4, !importPolicy) + applyPrefixSetPolicy(t, dut, []*prefixSetPolicy{prefixSet1V6}, bgpExportIPv6, *ebgp2NbrV6, !importPolicy) + + // route1, route2, route4 expected to be advertised based on prefix-set + validatePrefixCount(t, dut, *ebgp2NbrV4, 0, 0, 3) + // only route6 is expected to be advertised based on prefix-set + validatePrefixCount(t, dut, *ebgp2NbrV6, 0, 0, 1) + }) +} + +// TestBGPPrefixSet is to test prefix-set at the BGP neighbor levels. +func TestBGPPrefixSet(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + // Bring up 4 eBGP neighbors between DUT and ATE + t.Run("Establish BGP sessions", func(t *testing.T) { + configureDUT(t, dut) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, ateAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + + if deviations.MissingPrePolicyReceivedRoutes(dut) { + var enableSoftConfigInboundCLI string + switch dut.Vendor() { + case ondatra.CISCO: + enableSoftConfigInboundCLI = fmt.Sprintf("router bgp %v instance BGP neighbor-group %v \n address-family ipv4 unicast soft-reconfiguration inbound always \n address-family ipv6 unicast soft-reconfiguration inbound always", dutAS, peerGrpName) + default: + t.Fatalf("Unsupported vendor %s for deviation 'MissingPrePolicyReceivedRoutes'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, enableSoftConfigInboundCLI) + } + otg := ate.OTG() + configureOTG(t, otg) + verifyBgpState(t, dut) + }) + + if deviations.DefaultImportExportPolicyUnsupported(dut) { + t.Run("Validate initial prefix count", func(t *testing.T) { + validatePrefixCount(t, dut, *ebgp1NbrV4, 5, 5, 0) + validatePrefixCount(t, dut, *ebgp1NbrV6, 5, 5, 0) + validatePrefixCount(t, dut, *ebgp2NbrV4, 0, 0, 5) + validatePrefixCount(t, dut, *ebgp2NbrV6, 0, 0, 5) + }) + } else { + t.Run("Validate initial prefix count", func(t *testing.T) { + validatePrefixCount(t, dut, *ebgp1NbrV4, 0, 5, 0) + validatePrefixCount(t, dut, *ebgp1NbrV6, 0, 5, 0) + validatePrefixCount(t, dut, *ebgp2NbrV4, 0, 0, 0) + validatePrefixCount(t, dut, *ebgp2NbrV6, 0, 0, 0) + }) + + } + + testPrefixSet(t, dut) +} diff --git a/feature/bgp/policybase/otg_tests/prefix_set_test/metadata.textproto b/feature/bgp/policybase/otg_tests/prefix_set_test/metadata.textproto new file mode 100644 index 00000000000..159d68be6a0 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/prefix_set_test/metadata.textproto @@ -0,0 +1,43 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "3a55a01a-2a2d-404e-b397-a840192d8d67" +plan_id: "RT-1.33" +description: "BGP Policy with prefix-set matching" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + skip_set_rp_match_set_options: true + skip_prefix_set_mode: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + missing_value_for_defaults: true + skip_set_rp_match_set_options: true + default_import_export_policy_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + prepolicy_received_routes: true + } +} +tags: TAGS_AGGREGATION +tags: TAGS_TRANSIT +tags: TAGS_DATACENTER_EDGE diff --git a/feature/bgp/policybase/otg_tests/route_installation_test/README.md b/feature/bgp/policybase/otg_tests/route_installation_test/README.md index 7f9d7800334..537844a7399 100644 --- a/feature/bgp/policybase/otg_tests/route_installation_test/README.md +++ b/feature/bgp/policybase/otg_tests/route_installation_test/README.md @@ -15,7 +15,7 @@ Base BGP policy configuration and route installation. * Default accept for policies. * Default deny for policies. * Explicitly specifying local preference. - * TODO: Explicitly specifying MED value. + * Explicitly specifying MED value. * Explicitly prepending AS for advertisement with a specified AS number. * Validate that traffic can be forwarded to **all** installed routes diff --git a/feature/bgp/policybase/otg_tests/route_installation_test/route_installation_test.go b/feature/bgp/policybase/otg_tests/route_installation_test/route_installation_test.go index 948670a9662..51b4ff15589 100644 --- a/feature/bgp/policybase/otg_tests/route_installation_test/route_installation_test.go +++ b/feature/bgp/policybase/otg_tests/route_installation_test/route_installation_test.go @@ -88,6 +88,8 @@ const ( acceptPolicy = "PERMIT-ALL" setLocalPrefPolicy = "SET-LOCAL-PREF" localPrefValue = 100 + setMEDPolicy = "SET-MED-PREF" + medValue = 100 setAspathPrependPolicy = "SET-ASPATH-PREPEND" asPathRepeatValue = 3 aclStatement1 = "10" @@ -230,7 +232,7 @@ func bgpCreateNbr(localAs, peerAs uint32, policy string, dut *ondatra.DUTDevice) } // configureBGPPolicy configures a BGP routing policy to accept or reject routes based on prefix match conditions -// Additonally, it configures LocalPreference and ASPathprepend as part of the BGP policy. +// Additionally, it configures LocalPreference, ASPathprepend and MED as part of the BGP policy. func configureBGPPolicy(d *oc.Root) (*oc.RoutingPolicy, error) { rp := d.GetOrCreateRoutingPolicy() pset := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(prefixSet) @@ -282,6 +284,15 @@ func configureBGPPolicy(d *oc.Root) (*oc.RoutingPolicy, error) { aspend.RepeatN = ygot.Uint8(asPathRepeatValue) actions5.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + pdef6 := rp.GetOrCreatePolicyDefinition(setMEDPolicy) + stmt, err = pdef6.AppendNewStatement(aclStatement2) + if err != nil { + return nil, err + } + actions6 := stmt.GetOrCreateActions() + actions6.GetOrCreateBgpActions().SetMed = oc.UnionUint32(medValue) + actions6.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + return rp, nil } @@ -426,7 +437,7 @@ func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { srcDev := config.Devices().Add().SetName(ateSrc.Name) srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(srcPort.Name()) + srcEth.Connection().SetPortName(srcPort.Name()) srcIpv4 := srcEth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4") srcIpv4.SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) srcIpv6 := srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6") @@ -434,7 +445,7 @@ func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { dstDev := config.Devices().Add().SetName(ateDst.Name) dstEth := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) - dstEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(dstPort.Name()) + dstEth.Connection().SetPortName(dstPort.Name()) dstIpv4 := dstEth.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4") dstIpv4.SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) dstIpv6 := dstEth.Ipv6Addresses().Add().SetName(ateDst.Name + ".IPv6") @@ -477,7 +488,7 @@ func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { SetTxNames([]string{srcIpv4.Name()}). SetRxNames([]string{dstBgp4PeerRoutes.Name()}) flowipv4.Size().SetFixed(512) - flowipv4.Duration().SetChoice("continuous") + flowipv4.Duration().Continuous() e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(srcEth.Mac()) v4 := flowipv4.Packet().Add().Ipv4() @@ -490,7 +501,7 @@ func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { SetTxNames([]string{srcIpv6.Name()}). SetRxNames([]string{dstBgp6PeerRoutes.Name()}) flowipv6.Size().SetFixed(512) - flowipv6.Duration().SetChoice("continuous") + flowipv6.Duration().Continuous() e2 := flowipv6.Packet().Add().Ethernet() e2.Src().SetValue(srcEth.Mac()) v6 := flowipv6.Packet().Add().Ipv6() @@ -695,6 +706,13 @@ func TestBGPPolicy(t *testing.T) { received: routeCount, sent: 0, wantLoss: false, + }, { + desc: "Configure Set MED Policy", + policy: setMEDPolicy, + installed: routeCount, + received: routeCount, + sent: 0, + wantLoss: false, }} for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { diff --git a/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/README.md b/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/README.md deleted file mode 100644 index 2632792ac9d..00000000000 --- a/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# RT-1.5: BGP Prefix Limit - -## Summary - -BGP Prefix Limit - -## Procedure - -* Configure eBGP session between ATE port-1 and DUT port-1,with an Accept-route all import-policy/export-policy under the neighbor AFI/SAFI. -* With maximum prefix limits of unlimited, and N. - * Advertise prefixes of `limit - 1`, `limit`, `limit + 1`. Validate - session state meets expected value at ATE. - * Ensure that DUT marks session as prefix-limit exceeded for limit+1 - prefixes. -* Advertise prefixes to exceed configured limit, and to `limit - 1` following - session teardown, ensure session is re-established per the restart timer - (with ATE session marked as passive). -* With maximum-prefix warning-only configured, ensure that the routes that - were sent prior to the max-prefix being exceeded are retained in the routing - table by forwarding traffic to `prefix{0..n-1}` and `prefix{n}` where n is - the maximum prefix limit configured. - -## Config Parameter coverage - -For prefixes: - -* /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/ -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor - -Parameters: - -* afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/max-prefixes -* afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/restart-timer - -## Telemetry Parameter coverage - -* TODO: afi-safis/afi-safi/ipv\[46\]-unicast/prefix-limit/state/restart-timer -* TODO: - afi-safis/afi-safi/ipv\[46\]-unicast/prefix-limit/state/warning-threshold-pct -* TODO: - afi-safis/afi-safi/ipv\[46\]-unicast/prefix-limit/state/max-prefix-limit -* TODO: - afi-safis/afi-safi/ipv\[46\]-unicast/prefix-limit/state/prefix-limit-exceeded - -## Protocol/RPC Parameter coverage - -N/A - -## Minimum DUT platform requirement - -vRX diff --git a/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/bgp_prefix_limit_test.go b/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/bgp_prefix_limit_test.go deleted file mode 100644 index fc6b1797ff5..00000000000 --- a/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/bgp_prefix_limit_test.go +++ /dev/null @@ -1,612 +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 -// -// 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_prefix_limit_test - -import ( - "testing" - "time" - - "github.com/openconfig/featureprofiles/internal/attrs" - "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/ixnet" - "github.com/openconfig/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -// The testbed consists of ate:port1 -> dut:port1 and -// dut:port2 -> ate:port2. The first pair is called the "source" -// pair, and the second the "destination" pair. -// -// * Source: ate:port1 -> dut:port1 subnet 192.0.2.0/30 2001:db8::192:0:2:0/126 -// * Destination: dut:port2 -> ate:port2 subnet 192.0.2.4/30 2001:db8::192:0:2:4/126 -// -// Note that the first (.0, .3) and last (.4, .7) IPv4 addresses are -// reserved from the subnet for broadcast, so a /30 leaves exactly 2 -// usable addresses. This does not apply to IPv6 which allows /127 -// for point to point links, but we use /126 so the numbering is -// consistent with IPv4. - -const ( - grTimer = 2 * time.Minute - grRestartTime = 75 - grStaleRouteTime = 300.0 - ipv4SrcTraffic = "192.0.2.2" - ipv6SrcTraffic = "2001:db8::192:0:2:2" - ipv4DstTrafficStart = "203.0.113.1" - ipv4DstTrafficEnd = "203.0.113.254" - ipv6DstTrafficStart = "2001:db8::203:0:113:1" - ipv6DstTrafficEnd = "2001:db8::203:0:113:fe" - advertisedRoutesv4CIDR = "203.0.113.1/32" - advertisedRoutesv6CIDR = "2001:db8::203:0:113:1/128" - prefixLimit = 200 - pwarnthesholdPct = 10 - prefixTimer = 30.0 - dutAS = 64500 - ateAS = 64501 - plenIPv4 = 30 - plenIPv6 = 126 - tolerance = 50 - rplType = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - rplName = "ALLOW" - peerGrpNamev4 = "BGP-PEER-GROUP-V4" - peerGrpNamev6 = "BGP-PEER-GROUP-V6" -) - -var ( - trafficDuration = 1 * time.Minute - - dutSrc = attrs.Attributes{ - Desc: "DUT to ATE source", - IPv4: "192.0.2.1", - IPv6: "2001:db8::192:0:2:1", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - ateSrc = attrs.Attributes{ - Name: "ateSrc", - IPv4: "192.0.2.2", - IPv6: "2001:db8::192:0:2:2", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - - dutDst = attrs.Attributes{ - Desc: "DUT to ATE destination", - IPv4: "192.0.2.5", - IPv6: "2001:db8::192:0:2:5", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - - ateDst = attrs.Attributes{ - Name: "atedst", - IPv4: "192.0.2.6", - IPv6: "2001:db8::192:0:2:6", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } -) - -// configureDUT configures all the interfaces and BGP on the DUT. -func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { - dc := gnmi.OC() - p1 := dut.Port(t, "port1").Name() - i1 := dutSrc.NewOCInterface(p1, dut) - gnmi.Replace(t, dut, dc.Interface(p1).Config(), i1) - - p2 := dut.Port(t, "port2").Name() - i2 := dutDst.NewOCInterface(p2, dut) - gnmi.Replace(t, dut, dc.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, "port1")) - fptest.SetPortSpeed(t, dut.Port(t, "port2")) - } - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance(t, dut, p1, deviations.DefaultNetworkInstance(dut), 0) - fptest.AssignToNetworkInstance(t, dut, p2, deviations.DefaultNetworkInstance(dut), 0) - } - configureRoutePolicy(t, dut, rplName, rplType) - - dutConfPath := dc.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - dutConf := createBGPNeighbor(dutAS, ateAS, prefixLimit, grRestartTime, dut) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) -} - -func (tc *testCase) verifyPortsUp(t *testing.T, dev *ondatra.Device) { - for _, p := range dev.Ports() { - portStatus := gnmi.Get(t, dev, gnmi.OC().Interface(p.Name()).OperStatus().State()) - if want := oc.Interface_OperStatus_UP; portStatus != want { - t.Errorf("%s Status: got %v, want %v", p, portStatus, want) - } - } -} - -type config struct { - topo *ondatra.ATETopology - allNets []*ixnet.Network - allFlows []*ondatra.Flow -} - -// configureATE configures the interfaces and BGP on the ATE, with port2 advertising routes. -func configureATE(t *testing.T, ate *ondatra.ATEDevice, numRoutes uint32) *config { - port1 := ate.Port(t, "port1") - topo := ate.Topology().New() - iDut1 := topo.AddInterface(ateSrc.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateSrc.IPv4CIDR()).WithDefaultGateway(dutSrc.IPv4) - iDut1.IPv6().WithAddress(ateSrc.IPv6CIDR()).WithDefaultGateway(dutSrc.IPv6) - - port2 := ate.Port(t, "port2") - iDut2 := topo.AddInterface(ateDst.Name).WithPort(port2) - iDut2.IPv4().WithAddress(ateDst.IPv4CIDR()).WithDefaultGateway(dutDst.IPv4) - iDut2.IPv6().WithAddress(ateDst.IPv6CIDR()).WithDefaultGateway(dutDst.IPv6) - - // Setup ATE BGP route v4 advertisement - BGPDut1 := iDut1.BGP() - BGPDut1.AddPeer().WithPeerAddress(dutSrc.IPv4).WithLocalASN(ateAS). - WithTypeExternal() - BGPDut1.AddPeer().WithPeerAddress(dutSrc.IPv6).WithLocalASN(ateAS). - WithTypeExternal() - - BGPDut2 := iDut2.BGP() - BGPDut2.AddPeer().WithPeerAddress(dutDst.IPv4).WithLocalASN(ateAS). - WithTypeExternal() - BGPDut2.AddPeer().WithPeerAddress(dutDst.IPv6).WithLocalASN(ateAS). - WithTypeExternal() - - BGPNeti1 := iDut2.AddNetwork(advertisedRoutesv4CIDR) - BGPNeti1.IPv4().WithAddress(advertisedRoutesv4CIDR).WithCount(1) - BGPNeti1.BGP().WithNextHopAddress(ateDst.IPv4) - BGPNeti1v6 := iDut2.AddNetwork(advertisedRoutesv6CIDR) - BGPNeti1v6.IPv6().WithAddress(advertisedRoutesv6CIDR).WithCount(1) - BGPNeti1v6.BGP().WithActive(true).WithNextHopAddress(ateDst.IPv6) - - t.Logf("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) - - // ATE Traffic Configuration - t.Logf("TestBGP:start ate Traffic config") - ethHeader := ondatra.NewEthernetHeader() - // BGP V4 Traffic - ipv4Header := ondatra.NewIPv4Header() - ipv4Header.WithSrcAddress(ipv4SrcTraffic).DstAddressRange(). - WithMin(ipv4DstTrafficStart).WithMax(ipv4DstTrafficEnd). - WithCount(numRoutes) - flowIPV4 := ate.Traffic().NewFlow("Ipv4"). - WithSrcEndpoints(iDut1). - WithDstEndpoints(iDut2). - WithHeaders(ethHeader, ipv4Header). - WithFrameSize(512) - - // BGP IP V6 traffic - ipv6Header := ondatra.NewIPv6Header() - ipv6Header.WithECN(0).WithSrcAddress(ipv6SrcTraffic). - DstAddressRange().WithMin(ipv6DstTrafficStart).WithMax(ipv6DstTrafficEnd). - WithCount(numRoutes) - flowIPV6 := ate.Traffic().NewFlow("Ipv6"). - WithSrcEndpoints(iDut1). - WithDstEndpoints(iDut2). - WithHeaders(ethHeader, ipv6Header). - WithFrameSize(512) - - return &config{topo, []*ixnet.Network{BGPNeti1, BGPNeti1v6}, []*ondatra.Flow{flowIPV4, flowIPV6}} -} - -type BGPNeighbor struct { - as, pfxLimit uint32 - neighborip string - isV4 bool -} - -func setPrefixLimitv4(dut *ondatra.DUTDevice, afisafi *oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi, limit uint32) { - if deviations.BGPExplicitPrefixLimitReceived(dut) { - prefixLimitReceived := afisafi.GetOrCreateIpv4Unicast().GetOrCreatePrefixLimitReceived() - prefixLimitReceived.MaxPrefixes = ygot.Uint32(limit) - } else { - prefixLimitReceived := afisafi.GetOrCreateIpv4Unicast().GetOrCreatePrefixLimit() - prefixLimitReceived.MaxPrefixes = ygot.Uint32(limit) - } -} - -func setPrefixLimitv6(dut *ondatra.DUTDevice, afisafi *oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi, limit uint32) { - if deviations.BGPExplicitPrefixLimitReceived(dut) { - prefixLimitReceived := afisafi.GetOrCreateIpv6Unicast().GetOrCreatePrefixLimitReceived() - prefixLimitReceived.MaxPrefixes = ygot.Uint32(limit) - } else { - prefixLimitReceived := afisafi.GetOrCreateIpv6Unicast().GetOrCreatePrefixLimit() - prefixLimitReceived.MaxPrefixes = ygot.Uint32(limit) - } -} - -func createBGPNeighbor(localAs, peerAs, pLimit uint32, restartTime uint16, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { - nbrs := []*BGPNeighbor{ - {as: peerAs, pfxLimit: pLimit, neighborip: ateSrc.IPv4, isV4: true}, - {as: peerAs, pfxLimit: pLimit, neighborip: ateSrc.IPv6, isV4: false}, - {as: peerAs, pfxLimit: pLimit, neighborip: ateDst.IPv4, isV4: true}, - {as: peerAs, pfxLimit: pLimit, neighborip: ateDst.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.As = ygot.Uint32(localAs) - global.RouterId = ygot.String(dutSrc.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) - - // 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.PeerAs = ygot.Uint32(peerAs) - pgv4.PeerGroupName = ygot.String(peerGrpNamev4) - pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNamev6) - pgv6.PeerAs = ygot.Uint32(peerAs) - pgv6.PeerGroupName = ygot.String(peerGrpNamev6) - - for _, nbr := range nbrs { - if nbr.isV4 { - nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) - nv4.PeerAs = ygot.Uint32(nbr.as) - nv4.Enabled = ygot.Bool(true) - nv4.PeerGroup = ygot.String(peerGrpNamev4) - nv4.GetOrCreateTimers().RestartTime = ygot.Uint16(restartTime) - 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) - setPrefixLimitv4(dut, afisafi, nbr.pfxLimit) - if deviations.RoutePolicyUnderAFIUnsupported(dut) { - rpl := pgv4.GetOrCreateApplyPolicy() - rpl.ImportPolicy = []string{rplName} - rpl.ExportPolicy = []string{rplName} - } else { - pgafv4 := pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pgafv4.Enabled = ygot.Bool(true) - rpl := pgafv4.GetOrCreateApplyPolicy() - rpl.ImportPolicy = []string{rplName} - rpl.ExportPolicy = []string{rplName} - } - } else { - nv6 := bgp.GetOrCreateNeighbor(nbr.neighborip) - nv6.PeerAs = ygot.Uint32(nbr.as) - nv6.Enabled = ygot.Bool(true) - nv6.PeerGroup = ygot.String(peerGrpNamev6) - nv6.GetOrCreateTimers().RestartTime = ygot.Uint16(restartTime) - 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) - setPrefixLimitv6(dut, afisafi6, nbr.pfxLimit) - if deviations.RoutePolicyUnderAFIUnsupported(dut) { - rpl := pgv6.GetOrCreateApplyPolicy() - rpl.ImportPolicy = []string{rplName} - rpl.ExportPolicy = []string{rplName} - } else { - pgafv6 := pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) - pgafv6.Enabled = ygot.Bool(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(ateDst.IPv4) - nbrPathv6 := statePath.Neighbor(ateDst.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 getPrefixLimitv4(dut *ondatra.DUTDevice, neighbor *oc.NetworkInstance_Protocol_Bgp_Neighbor) (uint32, bool) { - if deviations.BGPExplicitPrefixLimitReceived(dut) { - prefixLimitReceived := neighbor.GetAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetIpv4Unicast().GetPrefixLimitReceived() - return prefixLimitReceived.GetMaxPrefixes(), prefixLimitReceived.GetPrefixLimitExceeded() - } else { - prefixLimitReceived := neighbor.GetAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetIpv4Unicast().GetPrefixLimit() - return prefixLimitReceived.GetMaxPrefixes(), prefixLimitReceived.GetPrefixLimitExceeded() - } -} - -func getPrefixLimitv6(dut *ondatra.DUTDevice, neighbor *oc.NetworkInstance_Protocol_Bgp_Neighbor) (uint32, bool) { - if deviations.BGPExplicitPrefixLimitReceived(dut) { - prefixLimitReceived := neighbor.GetAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetIpv6Unicast().GetPrefixLimitReceived() - return prefixLimitReceived.GetMaxPrefixes(), prefixLimitReceived.GetPrefixLimitExceeded() - } else { - prefixLimitReceived := neighbor.GetAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetIpv6Unicast().GetPrefixLimit() - return prefixLimitReceived.GetMaxPrefixes(), prefixLimitReceived.GetPrefixLimitExceeded() - } -} - -func verifyPrefixLimitTelemetry(t *testing.T, dut *ondatra.DUTDevice, neighbor *oc.NetworkInstance_Protocol_Bgp_Neighbor, wantEstablished bool) { - t.Run("verifyPrefixLimitTelemetry", func(t *testing.T) { - if *neighbor.NeighborAddress == ateDst.IPv4 { - maxPrefix, limitExceeded := getPrefixLimitv4(dut, neighbor) - if maxPrefix != prefixLimit { - t.Errorf("PrefixLimit max-prefixes v4 mismatch: got %d, want %d", maxPrefix, prefixLimit) - } - if (wantEstablished && limitExceeded) || (!wantEstablished && !limitExceeded) { - t.Errorf("PrefixLimitExceeded v4 mismatch: got %t, want %t", limitExceeded, !wantEstablished) - } - } else if *neighbor.NeighborAddress == ateDst.IPv6 { - maxPrefix, limitExceeded := getPrefixLimitv6(dut, neighbor) - if maxPrefix != prefixLimit { - t.Errorf("PrefixLimit max-prefixes v6 mismatch: got %d, want %d", maxPrefix, prefixLimit) - } - if (wantEstablished && limitExceeded) || (!wantEstablished && !limitExceeded) { - t.Errorf("PrefixLimitExceeded v6 mismatch: got %t, want %t", limitExceeded, !wantEstablished) - } - } - }) -} - -func (tc *testCase) verifyBGPTelemetry(t *testing.T, dut *ondatra.DUTDevice) { - t.Log("Waiting for BGPv4 neighbor to establish...") - waitForBGPSession(t, dut, tc.wantEstablished) - - installedRoutes := tc.numRoutes - if !tc.wantEstablished { - installedRoutes = 0 - } - - compare := func(val *ygnmi.Value[uint32]) bool { - c, ok := val.Val() - return ok && c == installedRoutes - } - t.Log("Verifying BGP state") - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - prefixes := statePath.Neighbor(ateDst.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes() - if got, ok := gnmi.Watch(t, dut, prefixes.Received().State(), 2*time.Minute, compare).Await(t); !ok { - t.Errorf("Received prefixes v4 mismatch: got %v, want %v", got, installedRoutes) - } - if got, ok := gnmi.Watch(t, dut, prefixes.Installed().State(), 2*time.Minute, compare).Await(t); !ok { - t.Errorf("Installed prefixes v4 mismatch: got %v, want %v", got, installedRoutes) - } - nv4 := gnmi.Get(t, dut, statePath.Neighbor(ateDst.IPv4).State()) - verifyPrefixLimitTelemetry(t, dut, nv4, tc.wantEstablished) - - prefixesv6 := statePath.Neighbor(ateDst.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Prefixes() - if got, ok := gnmi.Watch(t, dut, prefixesv6.Installed().State(), time.Minute, compare).Await(t); !ok { - t.Errorf("Installed prefixes v6 mismatch: got %v, want %v", got, installedRoutes) - } - if got, ok := gnmi.Watch(t, dut, prefixesv6.Received().State(), time.Minute, compare).Await(t); !ok { - t.Errorf("Received prefixes v6 mismatch: got %v, want %v", got, installedRoutes) - } - nv6 := gnmi.Get(t, dut, statePath.Neighbor(ateDst.IPv6).State()) - verifyPrefixLimitTelemetry(t, dut, nv6, tc.wantEstablished) -} - -func (tc *testCase) verifyNoPacketLoss(t *testing.T, ate *ondatra.ATEDevice, allFlows []*ondatra.Flow, tolerance float32) { - captureTrafficStats(t, ate) - for _, flow := range allFlows { - lossPct := gnmi.Get(t, ate, gnmi.OC().Flow(flow.Name()).LossPct().State()) - if lossPct > tolerance { - t.Errorf("Traffic Loss Pct for Flow %s: got %v, want 0", flow.Name(), lossPct) - } else { - t.Logf("Traffic Test Passed! Got %v loss", lossPct) - } - } -} - -func (tc *testCase) verifyPacketLoss(t *testing.T, ate *ondatra.ATEDevice, allFlows []*ondatra.Flow, tolerance float32) { - captureTrafficStats(t, ate) - for _, flow := range allFlows { - lossPct := gnmi.Get(t, ate, gnmi.OC().Flow(flow.Name()).LossPct().State()) - if lossPct >= (100-tolerance) && lossPct <= 100 { - t.Logf("Traffic Test Passed! Loss seen as expected: got %v, want 100%% ", lossPct) - } else { - t.Errorf("Traffic %s is expected to fail: got %v, want 100%% failure", flow.Name(), lossPct) - } - } -} - -func captureTrafficStats(t *testing.T, ate *ondatra.ATEDevice) { - ap := ate.Port(t, "port1") - aic1 := gnmi.OC().Interface(ap.Name()).Counters() - sentPkts := gnmi.Get(t, ate, aic1.OutPkts().State()) - fptest.LogQuery(t, "ate:port1 counters", aic1.State(), gnmi.Get(t, ate, aic1.State())) - - op := ate.Port(t, "port2") - aic2 := gnmi.OC().Interface(op.Name()).Counters() - rxPkts := gnmi.Get(t, ate, aic2.InPkts().State()) - fptest.LogQuery(t, "ate:port2 counters", aic2.State(), gnmi.Get(t, ate, aic2.State())) - var lostPkts uint64 - // account for control plane packets in rxPkts - if rxPkts > sentPkts { - lostPkts = rxPkts - sentPkts - } else { - lostPkts = sentPkts - rxPkts - } - t.Logf("Packets: %d sent, %d received, %d lost", sentPkts, rxPkts, lostPkts) - - if lostPkts > tolerance { - t.Logf("Lost Packets: %d", lostPkts) - } else { - t.Log("Traffic Test Passed!") - } -} - -func sendTraffic(t *testing.T, ate *ondatra.ATEDevice, allFlows []*ondatra.Flow, duration time.Duration) { - t.Log("Starting traffic") - ate.Traffic().Start(t, allFlows...) - time.Sleep(duration) - ate.Traffic().Stop(t) - t.Log("Traffic stopped") -} - -func configureBGPRoutes(t *testing.T, topo *ondatra.ATETopology, allNets []*ixnet.Network, routeCount uint32) { - for _, net := range allNets { - netName := net.EndpointPB().GetNetworkName() - net.BGP().ClearASPathSegments() - if netName == advertisedRoutesv4CIDR { - net.IPv4().WithAddress(advertisedRoutesv4CIDR).WithCount(routeCount) - net.BGP().WithActive(true).WithNextHopAddress(ateDst.IPv4) - } - if netName == advertisedRoutesv6CIDR { - net.IPv6().WithAddress(advertisedRoutesv6CIDR).WithCount(routeCount) - net.BGP().WithActive(true).WithNextHopAddress(ateDst.IPv6) - } - } - topo.UpdateNetworks(t) -} - -type testCase struct { - desc string - name string - numRoutes uint32 - wantEstablished bool - wantNoPacketLoss bool -} - -func (tc *testCase) run(t *testing.T, conf *config, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { - t.Log(tc.desc) - configureBGPRoutes(t, conf.topo, conf.allNets, tc.numRoutes) - now := time.Now() - - // Verify Port Status - t.Log(" Verifying port status") - t.Run("verifyPortsUp", func(t *testing.T) { - tc.verifyPortsUp(t, dut.Device) - }) - - // Verify BGP Parameters - t.Log("Check BGP parameters with Prefix Limit not exceeded") - t.Run("verifyBGPTelemetry", func(t *testing.T) { - tc.verifyBGPTelemetry(t, dut) - }) - // Time Duration for which maximum-prefix-restart-time has been active - elapsed := time.Since(now) - - // Starting ATE Traffic - t.Log("Verify Traffic statistics") - if tc.name == "OverLimit" { - trafficDurationOverlimit := grRestartTime - time.Duration(elapsed.Nanoseconds()) - sendTraffic(t, ate, conf.allFlows, trafficDurationOverlimit) - } else { - sendTraffic(t, ate, conf.allFlows, trafficDuration) - } - tolerance := float32(deviations.BGPTrafficTolerance(dut)) - if tc.wantNoPacketLoss { - t.Run("verifyNoPacketLoss", func(t *testing.T) { - tc.verifyNoPacketLoss(t, ate, conf.allFlows, tolerance) - }) - } else { - t.Run("verifyPacketLoss", func(t *testing.T) { - tc.verifyPacketLoss(t, ate, conf.allFlows, tolerance) - }) - } -} - -func TestTrafficBGPPrefixLimit(t *testing.T) { - cases := []testCase{{ - name: "UnderLimit", - desc: "BGP Prefixes within expected limit", - numRoutes: prefixLimit - 1, - wantEstablished: true, - wantNoPacketLoss: true, - }, { - name: "AtLimit", - desc: "BGP Prefixes at threshold of expected limit", - numRoutes: prefixLimit, - wantEstablished: true, - wantNoPacketLoss: true, - }, { - name: "OverLimit", - desc: "BGP Prefixes outside expected limit", - numRoutes: prefixLimit + 1, - wantEstablished: false, - wantNoPacketLoss: false, - }, { - name: "ReestablishedAtLimit", - desc: "BGP Session ReEstablished after prefixes are within limits", - numRoutes: prefixLimit, - wantEstablished: true, - wantNoPacketLoss: true, - }} - - dut := ondatra.DUT(t, "dut") - ate := ondatra.ATE(t, "ate") - // DUT Configuration - t.Log("Start DUT interface Config") - configureDUT(t, dut) - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - // ATE Configuration. - t.Log("Start ATE Config") - conf := configureATE(t, ate, tc.numRoutes) - time.Sleep(1 * time.Minute) - tc.run(t, conf, dut, ate) - }) - } -} diff --git a/feature/bgp/prefixlimit/feature.textproto b/feature/bgp/prefixlimit/feature.textproto index 23aec8e81c3..0ecbb09d46a 100644 --- a/feature/bgp/prefixlimit/feature.textproto +++ b/feature/bgp/prefixlimit/feature.textproto @@ -11,6 +11,9 @@ # 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" 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 789055662fa..1594facc35b 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 @@ -50,18 +50,12 @@ func TestMain(m *testing.M) { const ( trafficDuration = 1 * time.Minute grTimer = 2 * time.Minute - grRestartTime = 60 + grRestartTime = 75 grStaleRouteTime = 300.0 ipv4SrcTraffic = "192.0.2.2" - ipv6SrcTraffic = "2001:db8::192:0:2:2" - ipv4DstTrafficStart = "203.0.113.1" - ipv4DstTrafficEnd = "203.0.113.254" - ipv6DstTrafficStart = "2001:db8::203:0:113:1" - ipv6DstTrafficEnd = "2001:db8::203:0:113:fe" - advertisedRoutesv4CIDR = "203.0.113.1/32" - advertisedRoutesv6CIDR = "2001:db8::203:0:113:1/128" - advertisedRoutesv4Net = "203.0.113.1" - advertisedRoutesv6Net = "2001:db8::203:0:113:1" + ipv6SrcTraffic = "2001:DB8:1::1" + ipv4DstTraffic = "203.0.113.0" + ipv6DstTraffic = "2001:DB8:2::1" advertisedRoutesv4Prefix = 32 advertisedRoutesv6Prefix = 128 prefixLimit = 200 @@ -72,11 +66,16 @@ const ( plenIPv4 = 30 plenIPv6 = 126 tolerance = 50 - lossTolerance = 2 rplType = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE rplName = "ALLOW" peerGrpNamev4 = "BGP-PEER-GROUP-V4" peerGrpNamev6 = "BGP-PEER-GROUP-V6" + r4UnderLimit = "r4UnderLimit" + r6UnderLimit = "r6UnderLimit" + r4AtLimit = "r4AtLimit" + r6AtLimit = "r6AtLimit" + r4OverLimit = "r4OverLimit" + r6OverLimit = "r6OverLimit" ) var ( @@ -124,6 +123,7 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { p2 := dut.Port(t, "port2").Name() i2 := dutDst.NewOCInterface(p2, dut) gnmi.Replace(t, dut, dc.Interface(p2).Config(), i2) + // Configure Network instance type on DUT t.Log("Configure/update Network Instance") fptest.ConfigureDefaultNetworkInstance(t, dut) @@ -152,21 +152,14 @@ func (tc *testCase) verifyPortsUp(t *testing.T, dev *ondatra.Device) { } } -type config struct { - topo gosnappi.Config - bgpv4RR gosnappi.BgpV4RouteRange - bgpv6RR gosnappi.BgpV6RouteRange - flowV4Incr gosnappi.PatternFlowIpv4DstCounter - flowV6Incr gosnappi.PatternFlowIpv6DstCounter -} - // configureATE configures the interfaces and BGP on the ATE, with port2 advertising routes. -func configureATE(t *testing.T, ate *ondatra.ATEDevice) *config { +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + otg := ate.OTG() topo := gosnappi.NewConfig() srcPort := topo.Ports().Add().SetName("port1") srcDev := topo.Devices().Add().SetName(ateSrc.Name) srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(srcPort.Name()) + srcEth.Connection().SetPortName(srcPort.Name()) srcIpv4 := srcEth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4") srcIpv4.SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) srcIpv6 := srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6") @@ -175,7 +168,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) *config { dstPort := topo.Ports().Add().SetName("port2") dstDev := topo.Devices().Add().SetName(ateDst.Name) dstEth := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) - dstEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(dstPort.Name()) + dstEth.Connection().SetPortName(dstPort.Name()) dstIpv4 := dstEth.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4") dstIpv4.SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) dstIpv6 := dstEth.Ipv6Addresses().Add().SetName(ateDst.Name + ".IPv6") @@ -194,58 +187,67 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) *config { dstBgp6Peer := dstBgp.Ipv6Interfaces().Add().SetIpv6Name(dstIpv6.Name()).Peers().Add().SetName(ateDst.Name + ".BGP6.peer") dstBgp6Peer.SetPeerAddress(dstIpv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) - dstBgp4PeerRoutes := dstBgp4Peer.V4Routes().Add().SetName(ateDst.Name + ".BGP4.peer" + ".RR4") - dstBgp4PeerRoutes.SetNextHopIpv4Address(dstIpv4.Address()). + configureBGPv4Routes(dstBgp4Peer, dstIpv4.Address(), r4UnderLimit, ipv4DstTraffic, prefixLimit-1) + configureBGPv6Routes(dstBgp6Peer, dstIpv6.Address(), r6UnderLimit, ipv6DstTraffic, prefixLimit-1) + configureBGPv4Routes(dstBgp4Peer, dstIpv4.Address(), r4AtLimit, ipv4DstTraffic, prefixLimit) + configureBGPv6Routes(dstBgp6Peer, dstIpv6.Address(), r6AtLimit, ipv6DstTraffic, prefixLimit) + 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) + + t.Logf("Pushing config to ATE and starting protocols...") + otg.PushConfig(t, topo) + + return topo +} + +func configureBGPv4Routes(peer gosnappi.BgpV4Peer, ipv4 string, name string, prefix string, count uint32) { + routes := peer.V4Routes().Add().SetName(name) + routes.SetNextHopIpv4Address(ipv4). SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) - dstBgp4PeerRoutes.Addresses().Add(). - SetAddress(advertisedRoutesv4Net). + routes.Addresses().Add(). + SetAddress(prefix). SetPrefix(advertisedRoutesv4Prefix). - SetCount(1) - dstBgp6PeerRoutes := dstBgp6Peer.V6Routes().Add().SetName(ateDst.Name + ".BGP6.peer" + ".RR6") - dstBgp6PeerRoutes.SetNextHopIpv6Address(dstIpv6.Address()). + SetCount(count) +} + +func configureBGPv6Routes(peer gosnappi.BgpV6Peer, ipv6 string, name string, prefix string, count uint32) { + routes := peer.V6Routes().Add().SetName(name) + routes.SetNextHopIpv6Address(ipv6). SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) - dstBgp6PeerRoutes.Addresses().Add(). - SetAddress(advertisedRoutesv6Net). + routes.Addresses().Add(). + SetAddress(prefix). SetPrefix(advertisedRoutesv6Prefix). - SetCount(1) - - // ATE Traffic Configuration - t.Logf("TestBGP:start ate Traffic config") - // BGP V4 Traffic - flowipv4 := topo.Flows().Add().SetName("IPv4") - flowipv4.Metrics().SetEnable(true) - flowipv4.TxRx().Device(). - SetTxNames([]string{srcIpv4.Name()}). - SetRxNames([]string{dstBgp4PeerRoutes.Name()}) - flowipv4.Size().SetFixed(512) - flowipv4.Duration().SetChoice("continuous") - e1 := flowipv4.Packet().Add().Ethernet() - e1.Src().SetValue(srcEth.Mac()) - v4 := flowipv4.Packet().Add().Ipv4() - v4.Src().SetValue(ipv4SrcTraffic) - v4DstIncrement := v4.Dst().Increment().SetStart(advertisedRoutesv4Net).SetCount(prefixLimit) - - // BGP IP V6 traffic - flowipv6 := topo.Flows().Add().SetName("IPv6") - flowipv6.Metrics().SetEnable(true) - flowipv6.TxRx().Device(). - SetTxNames([]string{srcIpv6.Name()}). - SetRxNames([]string{dstBgp6PeerRoutes.Name()}) - flowipv6.Size().SetFixed(512) - flowipv6.Duration().SetChoice("continuous") - e2 := flowipv6.Packet().Add().Ethernet() - e2.Src().SetValue(srcEth.Mac()) - v6 := flowipv6.Packet().Add().Ipv6() - v6.Src().SetValue(ipv6SrcTraffic) - v6DstIncrement := v6.Dst().Increment().SetStart(advertisedRoutesv6Net).SetCount(prefixLimit) - - t.Logf("Pushing config to ATE and starting protocols...") - ate.OTG().PushConfig(t, topo) - ate.OTG().StartProtocols(t) + SetCount(count) +} - return &config{topo, dstBgp4PeerRoutes, dstBgp6PeerRoutes, v4DstIncrement, v6DstIncrement} +func configureFlow(topo gosnappi.Config, name, flowSrcEndPoint, flowDstEndPoint, srcMac, srcIp, dstIp, iptype string, routeCount uint32) { + flow := topo.Flows().Add().SetName(name) + flow.Metrics().SetEnable(true) + flow.TxRx().Device(). + SetTxNames([]string{flowSrcEndPoint}). + SetRxNames([]string{flowDstEndPoint}) + flow.Size().SetFixed(1500) + flow.Duration().FixedPackets().SetPackets(1000) + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(srcMac) + if iptype == "ipv4" { + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(srcIp) + v4.Dst().Increment().SetStart(dstIp).SetCount(routeCount) + } else { + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(srcIp) + v6.Dst().Increment().SetStart(dstIp).SetCount(routeCount) + } } type BGPNeighbor struct { @@ -426,16 +428,20 @@ func verifyPrefixLimitTelemetry(t *testing.T, dut *ondatra.DUTDevice, neighbor * if maxPrefix != prefixLimit { t.Errorf("PrefixLimit max-prefixes v4 mismatch: got %d, want %d", maxPrefix, prefixLimit) } - if (wantEstablished && limitExceeded) || (!wantEstablished && !limitExceeded) { - t.Errorf("PrefixLimitExceeded v4 mismatch: got %t, want %t", limitExceeded, !wantEstablished) + if !deviations.PrefixLimitExceededTelemetryUnsupported(dut) { + if (wantEstablished && limitExceeded) || (!wantEstablished && !limitExceeded) { + t.Errorf("PrefixLimitExceeded v4 mismatch: got %t, want %t", limitExceeded, !wantEstablished) + } } } else if *neighbor.NeighborAddress == ateDst.IPv6 { maxPrefix, limitExceeded := getPrefixLimitv6(dut, neighbor) if maxPrefix != prefixLimit { t.Errorf("PrefixLimit max-prefixes v6 mismatch: got %d, want %d", maxPrefix, prefixLimit) } - if (wantEstablished && limitExceeded) || (!wantEstablished && !limitExceeded) { - t.Errorf("PrefixLimitExceeded v6 mismatch: got %t, want %t", limitExceeded, !wantEstablished) + if !deviations.PrefixLimitExceededTelemetryUnsupported(dut) { + if (wantEstablished && limitExceeded) || (!wantEstablished && !limitExceeded) { + t.Errorf("PrefixLimitExceeded v6 mismatch: got %t, want %t", limitExceeded, !wantEstablished) + } } } }) @@ -477,12 +483,11 @@ func (tc *testCase) verifyBGPTelemetry(t *testing.T, dut *ondatra.DUTDevice) { verifyPrefixLimitTelemetry(t, dut, nv6, tc.wantEstablished) } -func (tc *testCase) verifyNoPacketLoss(t *testing.T, ate *ondatra.ATEDevice, conf gosnappi.Config, tolerance float32) { - captureTrafficStats(t, ate, conf) +func (tc *testCase) verifyNoPacketLoss(t *testing.T, ate *ondatra.ATEDevice, conf gosnappi.Config, tolerance float32, flowNames []string) { otg := ate.OTG() otgutils.LogFlowMetrics(t, otg, conf) - for _, flow := range conf.Flows().Items() { - recvMetric := gnmi.Get(t, otg, gnmi.OTG().Flow(flow.Name()).State()) + for _, flow := range flowNames { + recvMetric := gnmi.Get(t, otg, gnmi.OTG().Flow(flow).State()) txPackets := float32(recvMetric.GetCounters().GetOutPkts()) rxPackets := float32(recvMetric.GetCounters().GetInPkts()) if txPackets == 0 { @@ -491,19 +496,18 @@ func (tc *testCase) verifyNoPacketLoss(t *testing.T, ate *ondatra.ATEDevice, con lostPackets := txPackets - rxPackets lossPct := lostPackets * 100 / txPackets if lossPct > tolerance { - t.Errorf("Traffic Loss Pct for Flow %s: got %v, want 0", flow.Name(), lossPct) + t.Errorf("Traffic Loss Pct for Flow %s: got %v, want 0", flow, lossPct) } else { t.Logf("Traffic Test Passed! Got %v loss", lossPct) } } } -func (tc *testCase) verifyPacketLoss(t *testing.T, ate *ondatra.ATEDevice, conf gosnappi.Config, tolerance float32) { - captureTrafficStats(t, ate, conf) +func (tc *testCase) verifyPacketLoss(t *testing.T, ate *ondatra.ATEDevice, conf gosnappi.Config, tolerance float32, flowNames []string) { otg := ate.OTG() otgutils.LogFlowMetrics(t, otg, conf) - for _, flow := range conf.Flows().Items() { - recvMetric := gnmi.Get(t, otg, gnmi.OTG().Flow(flow.Name()).State()) + for _, flow := range flowNames { + recvMetric := gnmi.Get(t, otg, gnmi.OTG().Flow(flow).State()) txPackets := float32(recvMetric.GetCounters().GetOutPkts()) rxPackets := float32(recvMetric.GetCounters().GetInPkts()) if txPackets == 0 { @@ -514,39 +518,11 @@ func (tc *testCase) verifyPacketLoss(t *testing.T, ate *ondatra.ATEDevice, conf if lossPct >= (100-tolerance) && lossPct <= 100 { t.Logf("Traffic Test Passed! Loss seen as expected: got %v, want 100%% ", lossPct) } else { - t.Errorf("Traffic %s is expected to fail: got %v, want 100%% failure", flow.Name(), lossPct) + t.Errorf("Traffic %s is expected to fail: got %v, want 100%% failure", flow, lossPct) } } } -func captureTrafficStats(t *testing.T, ate *ondatra.ATEDevice, conf gosnappi.Config) { - otg := ate.OTG() - otgutils.LogPortMetrics(t, otg, conf) - ap := ate.Port(t, "port1") - aic1 := gnmi.OTG().Port(ap.ID()).Counters() - sentPkts := gnmi.Get(t, otg, aic1.OutFrames().State()) - fptest.LogQuery(t, "ate:port1 counters", aic1.State(), gnmi.Get(t, otg, aic1.State())) - - op := ate.Port(t, "port2") - aic2 := gnmi.OTG().Port(op.ID()).Counters() - rxPkts := gnmi.Get(t, otg, aic2.InFrames().State()) - fptest.LogQuery(t, "ate:port2 counters", aic2.State(), gnmi.Get(t, otg, aic2.State())) - var lostPkts uint64 - // account for control plane packets in rxPkts - if rxPkts > sentPkts { - lostPkts = rxPkts - sentPkts - } else { - lostPkts = sentPkts - rxPkts - } - t.Logf("Packets: %d sent, %d received, %d lost", sentPkts, rxPkts, lostPkts) - - if lostPkts > tolerance { - t.Logf("Lost Packets: %d", lostPkts) - } else { - t.Log("Traffic Test Passed!") - } -} - func sendTraffic(t *testing.T, ate *ondatra.ATEDevice, duration time.Duration) { otg := ate.OTG() t.Log("Starting traffic") @@ -554,31 +530,27 @@ func sendTraffic(t *testing.T, ate *ondatra.ATEDevice, duration time.Duration) { time.Sleep(duration) otg.StopTraffic(t) t.Log("Traffic stopped") + time.Sleep(20 * time.Second) } -func configureBGPRoutes(t *testing.T, configElement *config, routeCount uint32) { +func advertiseBGPRoutes(t *testing.T, conf gosnappi.Config, routeNames []string) { + ate := ondatra.ATE(t, "ate") otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Protocol().Route().SetNames(routeNames).SetState(gosnappi.StateProtocolRouteState.ADVERTISE) + otg.SetControlState(t, cs) - // Modifying the OTG BGP routes configuration - configElement.bgpv4RR.Addresses().Clear() - configElement.bgpv4RR.Addresses().Add(). - SetAddress(advertisedRoutesv4Net). - SetPrefix(advertisedRoutesv4Prefix). - SetCount(uint32(routeCount)) +} - configElement.bgpv6RR.Addresses().Clear() - configElement.bgpv6RR.Addresses().Add(). - SetAddress(advertisedRoutesv6Net). - SetPrefix(advertisedRoutesv6Prefix). - SetCount(uint32(routeCount)) +func withdrawBGPRoutes(t *testing.T, conf gosnappi.Config, routeNames []string) { - // Modifying the OTG flows - configElement.flowV4Incr.SetCount(uint32(routeCount)) - configElement.flowV6Incr.SetCount(uint32(routeCount)) + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Protocol().Route().SetNames(routeNames).SetState(gosnappi.StateProtocolRouteState.WITHDRAW) + otg.SetControlState(t, cs) - otg.PushConfig(t, configElement.topo) - otg.StartProtocols(t) } type testCase struct { @@ -587,11 +559,22 @@ type testCase struct { numRoutes uint32 wantEstablished bool wantNoPacketLoss bool + routeNames []string } -func (tc *testCase) run(t *testing.T, conf *config, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { +func (tc *testCase) run(t *testing.T, conf gosnappi.Config, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { t.Log(tc.desc) - configureBGPRoutes(t, conf, tc.numRoutes) + flowNames := []string{} + + for _, name := range []string{"IPv4", "IPv6"} { + if tc.numRoutes == prefixLimit { + flowNames = append(flowNames, name+"."+"AtLimit") + } else { + flowNames = append(flowNames, name+"."+tc.name) + } + } + + advertiseBGPRoutes(t, conf, tc.routeNames) now := time.Now() // Verify Port Status @@ -619,13 +602,15 @@ func (tc *testCase) run(t *testing.T, conf *config, dut *ondatra.DUTDevice, ate tolerance := float32(deviations.BGPTrafficTolerance(dut)) if tc.wantNoPacketLoss { t.Run("verifyNoPacketLoss", func(t *testing.T) { - tc.verifyNoPacketLoss(t, ate, conf.topo, tolerance) + tc.verifyNoPacketLoss(t, ate, conf, tolerance, flowNames) }) } else { t.Run("verifyPacketLoss", func(t *testing.T) { - tc.verifyPacketLoss(t, ate, conf.topo, tolerance) + tc.verifyPacketLoss(t, ate, conf, tolerance, flowNames) }) } + + withdrawBGPRoutes(t, conf, tc.routeNames) } func TestTrafficBGPPrefixLimit(t *testing.T) { @@ -635,24 +620,28 @@ func TestTrafficBGPPrefixLimit(t *testing.T) { numRoutes: prefixLimit - 1, wantEstablished: true, wantNoPacketLoss: true, + routeNames: []string{r4UnderLimit, r6UnderLimit}, }, { name: "AtLimit", desc: "BGP Prefixes at threshold of expected limit", numRoutes: prefixLimit, wantEstablished: true, wantNoPacketLoss: true, + routeNames: []string{r4AtLimit, r6AtLimit}, }, { name: "OverLimit", desc: "BGP Prefixes outside expected limit", numRoutes: prefixLimit + 1, wantEstablished: false, wantNoPacketLoss: false, + routeNames: []string{r4OverLimit, r6OverLimit}, }, { name: "ReestablishedAtLimit", desc: "BGP Session ReEstablished after prefixes are within limits", numRoutes: prefixLimit, wantEstablished: true, wantNoPacketLoss: true, + routeNames: []string{r4AtLimit, r6AtLimit}, }} dut := ondatra.DUT(t, "dut") @@ -665,6 +654,15 @@ func TestTrafficBGPPrefixLimit(t *testing.T) { t.Log("Start ATE Config") conf := configureATE(t, ate) + ate.OTG().StartProtocols(t) + + withdrawBGPRoutes(t, conf, []string{r4UnderLimit, + r6UnderLimit, + r4AtLimit, + r6AtLimit, + r4OverLimit, + r6OverLimit}) + for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { tc.run(t, conf, dut, ate) diff --git a/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/metadata.textproto b/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/metadata.textproto index 095a3b46531..3ffa1ab9deb 100644 --- a/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/metadata.textproto +++ b/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/metadata.textproto @@ -11,6 +11,15 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + prefix_limit_exceeded_telemetry_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + prefix_limit_exceeded_telemetry_unsupported: true } } platform_exceptions: { diff --git a/feature/bgp/routereflector/feature.textproto b/feature/bgp/routereflector/feature.textproto index 7bdb66535ea..a1b04315f79 100644 --- a/feature/bgp/routereflector/feature.textproto +++ b/feature/bgp/routereflector/feature.textproto @@ -11,6 +11,9 @@ # 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_routereflector" 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 new file mode 100644 index 00000000000..e663f722f25 --- /dev/null +++ b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md @@ -0,0 +1,639 @@ +# RT-1.27: Static route to BGP redistribution + +## Summary + +- Static routes selected for redistribution base on combination of: prefix-set, set-tag +- MED set to value of metric of static route (metric propagation) +- AS-Path prepend to contain AS with value provided in configuration (repeat prepend 'n' times) +- Local-Preference to a value provided in configuration +- Community list set to defined community set +- BGP protocol next-hop set to value provided in configuration +- Redstribute static-route with "DROP" as the next-hop + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed + +## Procedure + +#### Initial Setup: + +* Connect DUT port-1, 2 and 3 to ATE port-1, 2 and 3 respectively + +* 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-3 IPv4 address ```dp3-v4 = 192.168.1.9/30``` + * ATE port-3 IPv4 address ```ap3-v4 = 192.168.1.10/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``` + + * DUT port-3 IPv6 address ```dp3-v6 = 2001:DB8::9/126``` + * ATE port-3 IPv6 address ```ap3-v6 = 2001:DB8::10/126``` + +* Create two IPv4 networks i.e. ```ipv4-network = 192.168.10.0/24``` and ```ipv4-drop-network = 192.168.20.0/24``` attached to ATE port-2 + +* Create two IPv6 networks i.e. ```ipv6-network = 2024:db8:128:128::/64``` and ```ipv6-drop-network = 2024:db8:64:64::/64``` attached to ATE port-2 + +* Configure IPv4 and IPv6 eBGP session between ATE port-1 and DUT port-1 + * ATE ASN = 64511 + * DUT ASN = 64512 + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/send-community-type = ```STANDARD``` + +* Configure IPv4 and IPv6 iBGP session between ATE port-3 and DUT port-3 + * ATE ASN = 64512 + * DUT ASN = 64512 + * /network-instances/network-instance/protocols/protocol/bgp/global/config + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ + * /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/send-community-type = ```STANDARD``` + +* On the DUT advertise networks of ```dp2-v4``` i.e. ```192.168.1.4/30``` and ```dp2-v6``` i.e. ```2001:DB8::0/126``` through the BGP session between DUT port-1 and ATE port-1 + * Do not configure BGP between DUT port-2 and ATE port-2 + * Do not advertise ```ipv4-network 192.168.10.0/24``` or ```ipv6-network 2024:db8:128:128::/64``` + +* Configure an IPv4 static route ```ipv4-route``` on DUT destined to the ```ipv4-network``` i.e. ```192.168.10.0/24``` with the next hop set to the IPv4 address of ATE port-2 ```ap2-v4``` i.e. ```192.168.1.6/30``` + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + * Set the metric of the ```ipv4-route``` to 104 + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric + * Set a tag on the ```ipv4-route``` to 40 + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag + +* Configure an IPv6 static route on DUT destined to the ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` with the next hop set to the IPv6 address of ATE port-2 ```ap2-v6``` i.e. ```2001:DB8::5/126``` + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + * Set the metric of the ```ipv6-route``` to 106 + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric + * Set a tag on the ```ipv6-route``` to 60 + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag + + +### RT-1.27.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP with default-import-policy set to reject +--- +##### Configure default policy to reject routes +* Configure default import policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +##### Verification +* Verify default import policy is set to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +##### Validate test results +* Validate that the ATE does not receives the redistributed static route ```ipv4-route``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix + +### RT-1.27.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP matching a prefix using a route-policy +--- +##### Configure a route-policy +* Configure an IPv4 route-policy definition with the name ```route-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```route-policy-v4``` configure a statement with the name ```statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route-filtering/matching +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```ipv4-network``` i.e. ```192.168.10.0/24``` and masklength to ```exact``` + * /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 +* For prefix-set ```prefix-set-v4``` set another ip-prefix to ```ipv4-drop-network``` i.e. ```192.168.20.0/24``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set +##### Attach the route-policy to the redistribution import-policy +* Apply routing policy ```route-policy-v4``` for redistribution to BGP + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +##### Verification +* Verify IPv4 route-policy definition is configured with the name ```route-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/state/name +* Verify for routing-policy ```route-policy-v4``` a statement with the name ```statement-v4``` is configured + * /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` policy-result is set to ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result +* Verify prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` is configured + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* Verify for prefix-set ```prefix-set-v4``` the ip-prefix is set to ```192.168.10.0/24``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` match options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` prefix-set is set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set +* Verify routing policy ```route-policy-v4``` is applied as import policy for redistribution to BGP + * /network-instances/network-instance/table-connections/table-connection/state/import-policy +##### Validate the test results +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with MED of ```104``` + * /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 +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` +* Validate that the traffic is received on ATE port-2 + +### RT-1.27.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP with metric propogation diabled +--- +##### Configure redistribution +* Disable metric propogation by setting it to ```true``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +##### Verification +* Verify disable metric propogation is set to ```true``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with MED either having no value (missing) or ```0``` but not ```104``` + * /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 + +### RT-1.27.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP with metric propogation enabled +--- +##### Configure static route metric to be copied to MED +* Enable metric propogation by setting disable-metric-propagation to ```false``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +##### Verification +* Verify disable metric propogation is now ```false``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with MED of ```104``` + * /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 + +### RT-1.27.5 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP with AS-PATH prepend +--- +##### Configure BGP actions to prepend AS +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set AS-PATH prepend to the ASN ```64599``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set the prepended ASN to repeat ```3``` times + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n +##### Verification +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` AS-PATH prepend is set to the ASN ```64599``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/asn +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` the prepended ASN ```64599``` repeats ```3``` times + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/repeat-n +##### Validate the test results +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with AS-PATH of ```64599 64599 64599 64512``` + * /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/as-path/as-segment/state/member + +### RT-1.27.6 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP with MED set to ```1000``` +--- +##### Configure BGP actions to set MED +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med +##### Verification +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` MED is set to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-med +##### Validate test results +* validate that the ATE receives the redistributed static route ```ipv4-route``` with MED of ```1000``` + * /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 + +### RT-1.27.7 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP with Local-Preference set to ```100``` +--- +##### Configure BGP actions to set local-pref +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set local-preference to ```100``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref +##### Verification +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` local-preference is set to ```100``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-local-pref +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with MED of ```1000``` on the iBGP session between DUT-ATE port 3 + * /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/local-pref + +### RT-1.27.8 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv4 static routes to BGP with community set to ```64512:100``` +--- +##### Configure a community-set +* Configure a community set with name ```community-set-v4``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name +* For community set ```community-set-v4``` configure a community member value to ```64512:100``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Attach the community-set to route-policy +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` reference the community set ```community-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref +##### Verification +* Verify a community set with name ```community-set-v4``` exists + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name +* Verify for community set ```community-set-v4``` a community member value of ```64512:100``` is configured + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with a community value of ```64512:100``` + * /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/communities/community/state/index + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/community-index + +### RT-1.27.9 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribution of IPv4 static routes to BGP that does not match a conditional tag should not happen +--- +##### Configure a tag-set with incorrect tag value to validate that the route is not redistributed +* Configure a tag-set with name ```tag-set-v4``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/name +* Configure tag-set ```tag-set-v4``` with a tag value of ```100``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value +##### Attach the tag-set to route-policy conditions +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` configure match-set-tag condition to ```tag-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/tag-set +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` configure match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/match-set-options +##### Verification +* Verify a tag-set with name ```tag-set-v4``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/name +* Verify tag-set ```tag-set-v4``` with a tag value of ```100``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` tag-set is set to ```tag-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/tag-set +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` match-set-options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/match-set-options +##### Validate test results +* Verify that the ATE does not receives the redistributed static route ```ipv4-route``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix + +### RT-1.27.10 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribution of IPv4 static routes to BGP that matches a conditional tag should happen +--- +##### Configure a tag-set with correct tag value to validate that the route is redistributed +* Configure tag-set ```tag-set-v4``` with a tag value of ```40``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value +##### Verification +* Verify tag-set ```tag-set-v4``` with a tag value of ```40``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value +##### Validate test results +* Verify that the ATE receives the redistributed static route ```ipv4-route``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` +* Validate that the traffic is received on ATE port-2 + +### RT-1.27.11 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute a NULL IPv4 static routes to BGP with a next-hop configured through route-policy +--- +##### Configure a NULL static route +* Configure an IPv4 static route ```ipv4-drop-route``` on DUT destined to ```ipv4-drop-network``` i.e. ```192.168.20.0/24``` with the next hop set to ```DROP``` + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop +##### Configure a tag on the static route +* Set a tag on the ```ipv4-drop-route``` to ```40``` + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag +##### Configure BGP actions to set a next-hop +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set next-hop to ```192.168.1.9``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-next-hop +##### Verification +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` next-hop is set to ```192.168.1.9``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-next-hop +##### Validate the test results +* Validate that the ATE receives the redistributed static route ```ipv4-drop-route``` on the iBGP session between DUT-ATE port 3 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix +* Initiate traffic from ATE port-3 to the DUT and destined to ```ipv4-drop-network``` i.e. ```192.168.20.0/24``` +* Validate that the traffic is received on ATE port-2 + + + +### RT-1.27.12 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP with default-import-policy set to reject +--- +##### Configure default policy to reject routes +* Configure default import policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +##### Verification +* Verify default import policy is set to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +##### Validate test results +* Validate that the ATE does not receives the redistributed static route ```ipv4-route``` and ```ipv6-route``` + * /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/ipv6-unicast/loc-rib/routes/route/prefix + +### RT-1.27.13 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP matching a prefix using a route-policy +--- +##### Configure a route-policy +* Configure an IPv6 route-policy definition with the name ```route-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```route-policy-v6``` configure a statement with the name ```statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +##### Configure a prefix-set for route-filtering/matching +* Configure a prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` for the routing policy ```route-policy-v6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v6``` set the ip-prefix to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` and masklength to ```exact``` + * /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 +* For prefix-set ```prefix-set-v6``` set another ip-prefix to ```ipv6-drop-network``` i.e. ```2024:db8:64:64::/64``` and masklength to ```exact``` + * /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 +##### Attach the prefix-set to route-policy +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set prefix set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set +##### Attach the route-policy to the redistribution import-policy +* Apply routing policy ```route-policy-v6``` for redistribution to BGP + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +##### Verification +* Verify for routing-policy ```route-policy-v6``` a statement with the name ```statement-v6``` is configured + * /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` policy-result is set to ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` AS-PATH prepend is set to the ASN ```64512``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/asn +* Verify prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` is configured + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* Verify for prefix-set ```prefix-set-v6``` the ip-prefix is set to ```2024:db8:128:128::/64``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` match options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` prefix-set is set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set +* Verify routing policy ```route-policy-v6``` is applied as import policy for redistribution to BGP + * /network-instances/network-instance/table-connections/table-connection/state/import-policy +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with MED of ```1000``` + * /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/attr-sets/attr-set/state/med +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` +* Validate that the traffic is received on ATE port-2 + + +### RT-1.27.14 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP with metric propogation diabled +--- +##### Disable metric propogation +* Disable metric propogation by setting it to ```true``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +##### Verification +* Verify disable metric propogation is set to ```true``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with MED either having no value (missing) or ```0``` but not ```106``` + * /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/attr-sets/attr-set/state/med + +### RT-1.27.15 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP with metric propogation enabled +--- +##### Configure static route metric to be copied to MED +* Enable metric propogation by setting it to ```false``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +##### Verification +* Verify disable metric propogation is now ```false``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with MED ```106``` + * /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/attr-sets/attr-set/state/med + +### RT-1.27.16 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP with AS-PATH prepend +--- +##### Configure BGP actions to prepend AS +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set AS-PATH prepend to the ASN ```64512``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn +##### Verification +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` AS-PATH prepend is set to the ASN ```64512``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/asn +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with AS-PATH of ```64512 64512``` + * /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/attr-sets/attr-set/as-path/as-segment/state/member + +### RT-1.27.17 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP with MED set to ```1000``` +--- +##### Configure BGP actions to set MED +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set MED to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med +##### Verification +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` MED is set to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-med +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with MED of ```1000``` + * /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/attr-sets/attr-set/state/med + +### RT-1.27.18 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP with Local-Preference set to ```100``` +--- +##### Configure BGP actions to set local-pref +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set local-preference to ```100``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref +##### Verification +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` local-preference is set to ```100``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-local-pref +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with MED of ```1000``` on the iBGP session between DUT-ATE port 3 + * /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/attr-sets/attr-set/state/local-pref + +### RT-1.27.19 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute IPv6 static routes to BGP with community set to ```64512:100``` +--- +##### Configure a community-set +* Configure a community set with name ```community-set-v6``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name +* For community set ```community-set-v6``` configure a community member value to ```64512:100``` + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member +##### Attach the community-set to route-policy +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` reference the community set ```community-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref +##### Verification +* Verity a community set with name ```community-set-v6``` exists + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name +* Verify for community set ```community-set-v6``` a community member value of ```64512:100``` is configured + * /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +##### Validate test results +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with a community value of ```64512:100``` + * /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/communities/community/state/index + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/state/community-index + +### RT-1.27.20 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribution of IPv6 static routes to BGP that does not match a conditional tag should not happen +--- +##### Configure a tag-set with incorrect tag value to validate that the route is not redistributed +* Configure a tag-set with name ```tag-set-v6``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/name +* Configure tag-set ```tag-set-v6``` with a tag value of ```100``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value +##### Attach the tag-set to route-policy conditions +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` configure tag-set to ```tag-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/tag-set +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` configure match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/match-set-options +##### Verification +* Verify a tag-set with name ```tag-set-v6``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/name +* Verify tag-set ```tag-set-v6``` with a tag value of ```100``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` match-set-tag is set to ```tag-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/tag-set +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` match-set-options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/match-set-options +##### Validate test results +* Verify that the ATE does not receives the redistributed static route ```ipv6-route``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix + +### RT-1.27.21 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribution of IPv6 static routes to BGP that matches a conditional tag should happen +--- +##### Configure a tag-set with correct tag value to validate that the route is redistributed +* Configure tag-set ```tag-set-v6``` with a tag value of ```60``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value + * here we are setting correct tag value of 60, as defined in initial setup of this test, to validate that the route is now redistributed +##### Verification +* Verify tag-set ```tag-set-v6``` with a tag value of ```60``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value +##### Validate test results +* Verify that the ATE receives the redistributed static route ```ipv6-route``` + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` +* Validate that the traffic is received on ATE port-2 + +### RT-1.27.22 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +#### Redistribute a NULL IPv6 static routes to BGP with a next-hop configured through route-policy +--- +##### Configure a NULL static route +* Configure an IPv6 static route ```ipv6-drop-route``` on DUT destined to ```ipv6-drop-network``` i.e. ```2024:db8:64:64::/64``` with the next hop set to ```DROP``` + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop +##### Configure a tag on the static route +* Set a tag on the ```ipv6-drop-route``` to 60 + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag +##### Configure BGP actions to set a next-hop +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set next-hop to ```2001:DB8::9``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-next-hop +##### Verification +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` next-hop is set to ```2001:DB8::9``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-next-hop +##### Validate the test results +* Validate that the ATE receives the redistributed static route ```ipv4-drop-route``` on the iBGP session between DUT-ATE port 3 + * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix +* Initiate traffic from ATE port-3 to the DUT and destined to ```ipv4-drop-network``` 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/afi-safis/afi-safi/config/ +* /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/send-community-type +* /network-instances/network-instance/protocols/protocol/bgp/global/config + +* /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix +* /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag +* /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric +* /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + +* /network-instances/network-instance/table-connections/table-connection/config/address-family +* /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +* /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +* /network-instances/network-instance/table-connections/table-connection/config/dst-protocol +* /network-instances/network-instance/table-connections/table-connection/config/import-policy +* /network-instances/network-instance/table-connections/table-connection/config/src-protocol + +* /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/community-set-name + +* /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* /routing-policy/defined-sets/prefix-sets/prefix-set/config/name +* /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/actions/bgp-actions/set-as-path-prepend/config/repeat-n + +* /routing-policy/defined-sets/tag-sets/tag-set/config/name +* /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value + +* /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/actions/bgp-actions/config/set-local-pref +* /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/config/set-next-hop +* /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-community/reference/config/community-set-ref + +* /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/conditions/match-tag-set/config/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/tag-set + +## Telemetry parameter coverage + +* /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/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/state/community-index +* /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/state/community-index + +* /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/attr-sets/attr-set/state/local-pref +* /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med + +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member +* /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name + +* /network-instances/network-instance/protocols/protocol/bgp/rib/communities/community/state/index + +* /network-instances/network-instance/table-connections/table-connection/state/address-family +* /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +* /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +* /network-instances/network-instance/table-connections/table-connection/state/dst-protocol +* /network-instances/network-instance/table-connections/table-connection/state/import-policy +* /network-instances/network-instance/table-connections/table-connection/state/src-protocol + +* /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* /routing-policy/defined-sets/prefix-sets/prefix-set/state/name +* /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix +* /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range + +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/repeat-n + +* /routing-policy/defined-sets/tag-sets/tag-set/state/name +* /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value + +* /routing-policy/policy-definitions/policy-definition/state/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result + +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/asn +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-local-pref +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-med +* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-next-hop + +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set + +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/match-set-options +* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/tag-set + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: +``` + +## Required DUT platform + +* FFF 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 new file mode 100644 index 00000000000..a0da74fe48d --- /dev/null +++ b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/metadata.textproto @@ -0,0 +1,39 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "eb7e7ab2-5f58-4039-b862-13ad55459074" +plan_id: "RT-1.27" +description: "Static route to BGP redistribution" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + aggregate_atomic_update: true + static_protocol_name: "static" + interface_enabled: true + skip_set_rp_match_set_options: true + skip_prefix_set_mode: true + table_connections_unsupported: true + use_vendor_native_tag_set_config: true + skip_bgp_send_community_type: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + default_network_instance: "default" + interface_enabled: true + static_protocol_name: "STATIC" + skip_bgp_send_community_type: true + skip_setting_disable_metric_propagation: true + same_policy_attached_to_all_afis: true + set_metric_as_preference: 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 new file mode 100644 index 00000000000..44a3ca35150 --- /dev/null +++ b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/static_route_bgp_redistribution_test.go @@ -0,0 +1,1903 @@ +// Copyright 2023 Nokia, 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. +// +// This code is a Contribution to OpenConfig Feature Profiles project ("Work") +// made under the Google Software Grant and Corporate Contributor License +// Agreement ("CLA") and governed by the Apache License 2.0. No other rights +// or licenses in or to any of Nokia's intellectual property are granted for +// any other purpose. This code is provided on an "as is" basis without +// any warranties of any kind. +// +// SPDX-License-Identifier: Apache-2.0 + +package static_route_bgp_redistribution_test + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "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" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + subInterfaceIndex = 0 + mtu = 1500 + peerGroupName = "PEER-GROUP" + dutAsn = 64512 + atePeer1Asn = 64511 + atePeer2Asn = 64512 + acceptRoute = true + metricPropagate = true + policyResultNext = true + isV4 = true + shouldBePresent = true + replace = true + redistributeStaticPolicyNameV4 = "route-policy-v4" + redistributeStaticPolicyNameV6 = "route-policy-v6" + policyStatementNameV4 = "statement-v4" + policyStatementNameV6 = "statement-v6" + trafficDuration = 30 * time.Second + tolerancePct = 2 + medZero = 0 + medNonZero = 1000 + medIPv4 = 104 + medIPv6 = 106 + localPreference = 100 +) + +var ( + dutPort1 = &attrs.Attributes{ + Name: "dutPort1", + MAC: "00:12:01:01:01:01", + IPv4: "192.168.1.1", + IPv6: "2001:db8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + MTU: mtu, + } + + dutPort2 = &attrs.Attributes{ + Name: "dutPort2", + MAC: "00:12:02:01:01:01", + IPv4: "192.168.1.5", + IPv6: "2001:db8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + MTU: mtu, + } + + dutPort3 = &attrs.Attributes{ + Name: "dutPort3", + MAC: "00:12:03:01:01:01", + IPv4: "192.168.1.9", + IPv6: "2001:db8::9", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + MTU: mtu, + } + + 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, + MTU: mtu, + } + + 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, + MTU: mtu, + } + + atePort3 = &attrs.Attributes{ + Name: "atePort3", + MAC: "02:00:03:01:01:01", + IPv4: "192.168.1.10", + IPv6: "2001:db8::a", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + MTU: mtu, + } + + dutPorts = map[string]*attrs.Attributes{ + "port1": dutPort1, + "port2": dutPort2, + "port3": dutPort3, + } + + atePorts = map[string]*attrs.Attributes{ + "port1": atePort1, + "port2": atePort2, + "port3": atePort3, + } +) + +func configureDUTPort(t *testing.T, dut *ondatra.DUTDevice, port *ondatra.Port, portAttrs *attrs.Attributes) { + t.Helper() + + gnmi.Replace( + t, + dut, + gnmi.OC().Interface(port.Name()).Config(), + portAttrs.NewOCInterface(port.Name(), dut), + ) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, port) + } + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, port.Name(), deviations.DefaultNetworkInstance(dut), subInterfaceIndex) + } +} + +func configureDUTStatic(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + + staticPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.Delete(t, dut, staticPath.Config()) + + dutOcRoot := &oc.Root{} + networkInstance := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + networkInstanceProtocolStatic := networkInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + networkInstanceProtocolStatic.SetEnabled(true) + + ipv4StaticRoute := networkInstanceProtocolStatic.GetOrCreateStatic("192.168.10.0/24") + // TODO - we dont support, guessing table connection related? + if !deviations.UseVendorNativeTagSetConfig(dut) { + ipv4StaticRoute.SetSetTag(oc.UnionString("40")) + } else { + configureStaticRouteTagSet(t, dut) + attachTagSetToStaticRoute(t, dut, "192.168.10.0/24", "tag-static-v4") + } + + ipv4StaticRouteNextHop := ipv4StaticRoute.GetOrCreateNextHop("0") + if deviations.SetMetricAsPreference(dut) { + ipv4StaticRouteNextHop.Metric = ygot.Uint32(104) + } else { + ipv4StaticRouteNextHop.Preference = ygot.Uint32(104) + } + ipv4StaticRouteNextHop.SetNextHop(oc.LocalRouting_LOCAL_DEFINED_NEXT_HOP_DROP) + + ipv6StaticRoute := networkInstanceProtocolStatic.GetOrCreateStatic("2024:db8:128:128::/64") + if !deviations.UseVendorNativeTagSetConfig(dut) { + ipv6StaticRoute.SetSetTag(oc.UnionString("60")) + } else { + attachTagSetToStaticRoute(t, dut, "2024:db8:128:128::/64", "tag-static-v6") + } + + ipv6StaticRouteNextHop := ipv6StaticRoute.GetOrCreateNextHop("1") + if deviations.SetMetricAsPreference(dut) { + ipv6StaticRouteNextHop.Metric = ygot.Uint32(106) + } else { + ipv6StaticRouteNextHop.Preference = ygot.Uint32(106) + } + ipv6StaticRouteNextHop.SetNextHop(oc.LocalRouting_LOCAL_DEFINED_NEXT_HOP_DROP) + + gnmi.Replace(t, dut, staticPath.Config(), networkInstanceProtocolStatic) +} + +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, "BGP") + + // permit all policy + rp := dutOcRoot.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition("permit-all") + stmt, err := pdef.AppendNewStatement("accept") + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + + // setup BGP + networkInstance := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + networkInstanceProtocolBgp := networkInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + networkInstanceProtocolBgp.SetEnabled(true) + bgp := networkInstanceProtocolBgp.GetOrCreateBgp() + + bgpGlobal := bgp.GetOrCreateGlobal() + bgpGlobal.RouterId = ygot.String(dutPort1.IPv4) + bgpGlobal.As = ygot.Uint32(dutAsn) + + bgpGlobalIPv4AF := bgpGlobal.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + bgpGlobalIPv4AF.SetEnabled(true) + + bgpGlobalIPv6AF := bgpGlobal.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + bgpGlobalIPv6AF.SetEnabled(true) + + if !deviations.SkipBgpSendCommunityType(dut) { + bgpGlobalIPv6AF.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD}) + bgpGlobalIPv4AF.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD}) + } + + bgpPeerGroup := bgp.GetOrCreatePeerGroup(peerGroupName) + bgpPeerGroup.SetPeerAs(dutAsn) + + // dutPort1 -> atePort1 peer (ebgp session) + ateEBGPNeighborOne := bgp.GetOrCreateNeighbor(atePort1.IPv4) + ateEBGPNeighborOne.PeerGroup = ygot.String(peerGroupName) + ateEBGPNeighborOne.PeerAs = ygot.Uint32(atePeer1Asn) + ateEBGPNeighborOne.Enabled = ygot.Bool(true) + + ateEBGPNeighborIPv4AF := ateEBGPNeighborOne.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + ateEBGPNeighborIPv4AF.SetEnabled(true) + ateEBGPNeighborIPv4AFPolicy := ateEBGPNeighborIPv4AF.GetOrCreateApplyPolicy() + ateEBGPNeighborIPv4AFPolicy.SetImportPolicy([]string{"permit-all"}) + ateEBGPNeighborIPv4AFPolicy.SetExportPolicy([]string{"permit-all"}) + + ateEBGPNeighborTwo := bgp.GetOrCreateNeighbor(atePort1.IPv6) + ateEBGPNeighborTwo.PeerGroup = ygot.String(peerGroupName) + ateEBGPNeighborTwo.PeerAs = ygot.Uint32(atePeer1Asn) + ateEBGPNeighborTwo.Enabled = ygot.Bool(true) + + ateEBGPNeighborIPv6AF := ateEBGPNeighborTwo.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + ateEBGPNeighborIPv6AF.SetEnabled(true) + ateEBGPNeighborIPv6AFPolicy := ateEBGPNeighborIPv6AF.GetOrCreateApplyPolicy() + ateEBGPNeighborIPv6AFPolicy.SetImportPolicy([]string{"permit-all"}) + ateEBGPNeighborIPv6AFPolicy.SetExportPolicy([]string{"permit-all"}) + + // dutPort3 -> atePort3 peer (ibgp session) + ateIBGPNeighborThree := bgp.GetOrCreateNeighbor(atePort3.IPv4) + ateIBGPNeighborThree.PeerGroup = ygot.String(peerGroupName) + ateIBGPNeighborThree.PeerAs = ygot.Uint32(atePeer2Asn) + ateIBGPNeighborThree.Enabled = ygot.Bool(true) + + ateIBGPNeighborThreeIPv4AF := ateIBGPNeighborThree.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + ateIBGPNeighborThreeIPv4AF.SetEnabled(true) + ateIBGPNeighborThreeIPv4AFPolicy := ateIBGPNeighborThreeIPv4AF.GetOrCreateApplyPolicy() + ateIBGPNeighborThreeIPv4AFPolicy.SetImportPolicy([]string{"permit-all"}) + ateIBGPNeighborThreeIPv4AFPolicy.SetExportPolicy([]string{"permit-all"}) + + ateIBGPNeighborFour := bgp.GetOrCreateNeighbor(atePort3.IPv6) + ateIBGPNeighborFour.PeerGroup = ygot.String(peerGroupName) + ateIBGPNeighborFour.PeerAs = ygot.Uint32(atePeer2Asn) + ateIBGPNeighborFour.Enabled = ygot.Bool(true) + + ateIBGPNeighborFourIPv6AF := ateIBGPNeighborFour.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + ateIBGPNeighborFourIPv6AF.SetEnabled(true) + ateIBGPNeighborFourIPv6AFPolicy := ateIBGPNeighborFourIPv6AF.GetOrCreateApplyPolicy() + ateIBGPNeighborFourIPv6AFPolicy.SetImportPolicy([]string{"permit-all"}) + ateIBGPNeighborFourIPv6AFPolicy.SetExportPolicy([]string{"permit-all"}) + + gnmi.Replace(t, dut, bgpPath.Config(), networkInstanceProtocolBgp) +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + for portName, portAttrs := range dutPorts { + port := dut.Port(t, portName) + configureDUTPort(t, dut, port, portAttrs) + } + fptest.ConfigureDefaultNetworkInstance(t, dut) + + configureDUTStatic(t, dut) + configureDUTBGP(t, dut) +} + +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, "BGP"). + Bgp(). + Neighbor(neighbor). + SessionState().State(), time.Second*240, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + } +} + +func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + t.Helper() + + otgConfig := gosnappi.NewConfig() + + for portName, portAttrs := range atePorts { + port := ate.Port(t, portName) + portAttrs.AddToOTG(otgConfig, port, dutPorts[portName]) + } + + devices := otgConfig.Devices().Items() + + // eBGP v4 session on Port1. + bgp := devices[0].Bgp().SetRouterId(atePort1.IPv4) + iDut1Ipv4 := devices[0].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + iDut1Bgp := bgp.SetRouterId(iDut1Ipv4.Address()) + iDut1Bgp4Peer := iDut1Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut1Ipv4.Name()).Peers().Add().SetName(atePort1.Name + ".BGP4.peer") + iDut1Bgp4Peer.SetPeerAddress(iDut1Ipv4.Gateway()).SetAsNumber(atePeer1Asn).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + // eBGP v6 session on Port1. + iDut1Ipv6 := devices[0].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") + iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(atePeer1Asn).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + // iBGP v4 session on Port3. + bgp = devices[2].Bgp().SetRouterId(atePort3.IPv4) + iDut3Ipv4 := devices[2].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + iDut3Bgp := bgp.SetRouterId(iDut3Ipv4.Address()) + iDut3Bgp4Peer := iDut3Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut3Ipv4.Name()).Peers().Add().SetName(atePort3.Name + ".BGP4.peer") + iDut3Bgp4Peer.SetPeerAddress(iDut3Ipv4.Gateway()).SetAsNumber(atePeer2Asn).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDut3Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + // iBGP v6 session on Port3. + iDut3Ipv6 := devices[2].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + iDut3Bgp6Peer := iDut3Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut3Ipv6.Name()).Peers().Add().SetName(atePort3.Name + ".BGP6.peer") + iDut3Bgp6Peer.SetPeerAddress(iDut3Ipv6.Gateway()).SetAsNumber(atePeer2Asn).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + iDut3Bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + + return otgConfig +} + +// Configure OTG traffic-flow +func configureTrafficFlow(t *testing.T, otgConfig gosnappi.Config, isV4 bool, name, flowSrcEndPoint, flowDstEndPoint, srcMac, srcIp, dstIp string) gosnappi.Config { + t.Helper() + + // ATE Traffic Configuration. + t.Logf("TestBGP:start ate Traffic config: %v", name) + + otgConfig.Flows().Clear() + + flow := otgConfig.Flows().Add().SetName(name) + flow.Metrics().SetEnable(true) + flow.TxRx().Device(). + SetTxNames([]string{flowSrcEndPoint}). + SetRxNames([]string{flowDstEndPoint}) + flow.Size().SetFixed(1500) + flow.Duration().FixedPackets().SetPackets(1000) + e := flow.Packet().Add().Ethernet() + e.Src().SetValue(srcMac) + if isV4 { + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(srcIp) + v4.Dst().SetValue(dstIp) + } else { + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(srcIp) + v6.Dst().SetValue(dstIp) + } + + return otgConfig +} + +// Sending traffic over configured flow for fixed duration +func sendTraffic(t *testing.T, otg *otg.OTG) { + t.Logf("Starting traffic") + otg.StartTraffic(t) + time.Sleep(trafficDuration) + t.Logf("Stop traffic") + otg.StopTraffic(t) +} + +// Validate traffic flow +func verifyTraffic(t *testing.T, ate *ondatra.ATEDevice, conf gosnappi.Config) { + otg := ate.OTG() + otgutils.LogFlowMetrics(t, otg, conf) + for _, flow := range conf.Flows().Items() { + recvMetric := gnmi.Get(t, otg, gnmi.OTG().Flow(flow.Name()).State()) + txPackets := float32(recvMetric.GetCounters().GetOutPkts()) + rxPackets := float32(recvMetric.GetCounters().GetInPkts()) + if txPackets == 0 { + t.Fatalf("TxPkts = 0, want > 0") + } + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + if lossPct > tolerancePct { + t.Errorf("Traffic Loss Pct for Flow %s: got %v, want max %v pct failure", flow.Name(), lossPct, tolerancePct) + } else { + t.Logf("Traffic Test Passed! for flow %s", flow.Name()) + } + } +} + +// Configure table-connection with source as static-route and destination as bgp +func configureTableConnection(t *testing.T, dut *ondatra.DUTDevice, isV4, mPropagation bool, importPolicy string, defaultImport oc.E_RoutingPolicy_DefaultPolicyType) { + t.Helper() + + niPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + dutOcRoot := &oc.Root{} + networkInstance := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + addressFamily := oc.Types_ADDRESS_FAMILY_IPV4 + if !isV4 { + addressFamily = oc.Types_ADDRESS_FAMILY_IPV6 + } + + batchSet := &gnmi.SetBatch{} + tc := networkInstance.GetOrCreateTableConnection( + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + addressFamily, + ) + + if importPolicy != "" { + tc.SetImportPolicy([]string{importPolicy}) + } + if !deviations.SkipSettingDisableMetricPropagation(dut) { + tc.SetDisableMetricPropagation(!mPropagation) + } + 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 { + addressFamily = oc.Types_ADDRESS_FAMILY_IPV6 + } else { + addressFamily = oc.Types_ADDRESS_FAMILY_IPV4 + } + + tc1 := networkInstance.GetOrCreateTableConnection( + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + addressFamily, + ) + + if importPolicy != "" { + tc1.SetImportPolicy([]string{importPolicy}) + } + 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) + } + + batchSet.Set(t, dut) +} + +// Populate routing-policy to redistribute static-route +func redistributeStaticRoute(t *testing.T, isV4 bool, mPropagation, policyResultNext bool, routingPolicy *oc.RoutingPolicy) *oc.RoutingPolicy { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := "redistribute-static" + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + } + + apolicy := routingPolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyName) + astmt, err := apolicy.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + astmt.GetOrCreateConditions().SetInstallProtocolEq(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC) + astmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + if policyResultNext { + astmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT + } + astmt.GetOrCreateActions().GetOrCreateBgpActions().SetSetRouteOrigin(oc.E_BgpPolicy_BgpOriginAttrType(oc.BgpPolicy_BgpOriginAttrType_IGP)) + astmt.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(medZero)) + if mPropagation { + astmt.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.E_BgpActions_SetMed(oc.BgpActions_SetMed_IGP)) + } + + return routingPolicy +} + +// Configure routing-policy to redistribute static-route +func configureStaticRedistributionPolicy(t *testing.T, dut *ondatra.DUTDevice, isV4, acceptRoute, mPropagation bool) { + t.Helper() + + dutOcRoot := &oc.Root{} + rp := dutOcRoot.GetOrCreateRoutingPolicy() + rp = redistributeStaticRoute(t, isV4, mPropagation, !policyResultNext, rp) + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + } + + astmt := rp.GetPolicyDefinition(redistributeStaticPolicyName).GetStatement("redistribute-static") + astmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE + if acceptRoute { + astmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + } + + rpConfPath := gnmi.OC().RoutingPolicy() + gnmi.Replace(t, dut, rpConfPath.PolicyDefinition(redistributeStaticPolicyName).Config(), rp.GetOrCreatePolicyDefinition(redistributeStaticPolicyName)) + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyName}) +} + +// Validate configurations for table-connections and routing-policy +func validateRedistributeStatic(t *testing.T, dut *ondatra.DUTDevice, acceptRoute, isV4, mPropagation bool) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := "redistribute-static" + af := oc.Types_ADDRESS_FAMILY_IPV4 + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy().State() + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + af = oc.Types_ADDRESS_FAMILY_IPV6 + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy().State() + } + + if !deviations.TableConnectionsUnsupported(dut) { + tcState := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection( + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + af).State()) + + if tcState.GetSrcProtocol() != oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC { + t.Fatal("source protocol not static for table connection but should be") + } + + if tcState.GetDstProtocol() != oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP { + t.Fatal("destination protocol not bgp for table connection but should be") + } + + if tcState.GetAddressFamily() != af { + t.Fatal("address family not as expected or table connection but should be") + } + + if !deviations.SkipSettingDisableMetricPropagation(dut) { + if !mPropagation { + if tcState.GetDisableMetricPropagation() { + t.Fatal("Metric propagation disabled for table connection, expected enabled") + } + } else { + if !tcState.GetDisableMetricPropagation() { + t.Fatal("Metric propagation is enabled for table connection, expected disabled") + } + } + } + } else { + var foundPDef oc.RoutingPolicy_PolicyDefinition + policyDef := gnmi.GetAll(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinitionAny().State()) + for _, pDef := range policyDef { + if pDef.GetName() == redistributeStaticPolicyName { + foundPDef = *pDef + } + } + + if foundPDef.GetName() != redistributeStaticPolicyName { + t.Fatal("Expected import policy is not configured") + } + + if foundPDef.GetStatement(policyStatementName).GetOrCreateConditions().GetInstallProtocolEq() != oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC { + t.Fatal("Source protocol not static for redistribution policy.Expected static protocol") + } + + if mPropagation { + if foundPDef.GetStatement(policyStatementName).GetActions().GetBgpActions().GetSetMed() != oc.E_BgpActions_SetMed(oc.BgpActions_SetMed_IGP) { + t.Fatal("Expected metric propagation is not configured") + } + } + + found := false + bgpPolicy := gnmi.Get(t, dut, bgpPath) + for _, exPol := range bgpPolicy { + if exPol == redistributeStaticPolicyName { + found = true + t.Logf("bgp associated with routing-policy: %v", exPol) + } + } + if !found { + t.Fatal("BGP not associated with expected policy") + } + } +} + +// Validate prefix-set routing-policy configurations +func validatePrefixSetRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice, isV4 bool) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := policyStatementNameV4 + prefixSetName := "prefix-set-v4" + prefixSetMode := oc.PrefixSet_Mode_IPV4 + prefixAddress := "192.168.10.0/24" + prefixMaskLen := "exact" + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + policyStatementName = policyStatementNameV6 + prefixSetName = "prefix-set-v6" + prefixSetMode = oc.PrefixSet_Mode_IPV6 + prefixAddress = "2024:db8:128:128::/64" + } + + var foundPDef oc.RoutingPolicy_PolicyDefinition + policyDef := gnmi.GetAll(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinitionAny().State()) + for _, pDef := range policyDef { + if pDef.GetName() == redistributeStaticPolicyName { + foundPDef = *pDef + } + } + + if foundPDef.GetName() != redistributeStaticPolicyName { + t.Fatal("Expected import policy is not configured") + } + + if foundPDef.GetStatement(policyStatementName).GetActions().GetPolicyResult() != oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE { + t.Fatalf("Routing-policy result unexpectd for statement %v. It is not set to ACCEPT_ROUTE.", policyStatementName) + } + + if foundPDef.GetStatement(policyStatementName).GetConditions().GetMatchPrefixSet().GetPrefixSet() != prefixSetName { + t.Fatal("Routing-policy not associated with expected prefix-set") + } + + if !deviations.SkipSetRpMatchSetOptions(dut) { + if foundPDef.GetStatement(policyStatementName).GetConditions().GetMatchPrefixSet().GetMatchSetOptions() != oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY { + t.Fatal("Routing-policy prefix-set match-set-option not set to ANY") + } + } + + var foundPSet oc.RoutingPolicy_DefinedSets_PrefixSet + prefixSet := gnmi.GetAll(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSetAny().State()) + for _, pSet := range prefixSet { + if pSet.GetName() == prefixSetName { + foundPSet = *pSet + } + } + + if foundPSet.GetName() != prefixSetName { + t.Fatal("Expected prefix-set is not configured") + } + + if !deviations.SkipPrefixSetMode(dut) { + if foundPSet.GetMode() != prefixSetMode { + t.Fatal("Expected prefix-set mode is not configured") + } + } + + if foundPSet.GetPrefix(prefixAddress, prefixMaskLen).GetIpPrefix() != prefixAddress { + t.Fatal("Expected prefix not configured in prefix-set") + } +} + +// 1.27.3 setup function +func redistributeIPv4Static(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.TableConnectionsUnsupported(dut) { + configureStaticRedistributionPolicy(t, dut, isV4, acceptRoute, !metricPropagate) + } else { + configureTableConnection(t, dut, isV4, !metricPropagate, "", oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.3 validation function +func validateRedistributeIPv4Static(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, acceptRoute, isV4, !metricPropagate) + validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medZero, shouldBePresent) +} + +// 1.27.4 setup function +func redistributeIPv4StaticWithMetricPropagation(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.TableConnectionsUnsupported(dut) { + configureStaticRedistributionPolicy(t, dut, isV4, acceptRoute, metricPropagate) + } else { + configureTableConnection(t, dut, isV4, metricPropagate, "", oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.4 validation function +func validateRedistributeIPv4StaticWithMetricPropagation(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, acceptRoute, isV4, metricPropagate) + validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medIPv4, shouldBePresent) +} + +// 1.27.14 setup function +func redistributeIPv6Static(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.TableConnectionsUnsupported(dut) { + configureStaticRedistributionPolicy(t, dut, !isV4, acceptRoute, !metricPropagate) + } else { + configureTableConnection(t, dut, !isV4, !metricPropagate, "", oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.14 validation function +func validateRedistributeIPv6Static(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, acceptRoute, !isV4, !metricPropagate) + validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medZero, shouldBePresent) +} + +// 1.27.15 setup function +func redistributeIPv6StaticWithMetricPropagation(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.TableConnectionsUnsupported(dut) { + configureStaticRedistributionPolicy(t, dut, !isV4, acceptRoute, metricPropagate) + } else { + configureTableConnection(t, dut, !isV4, metricPropagate, "", oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.15 validation function +func validateRedistributeIPv6StaticWithMetricPropagation(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, acceptRoute, !isV4, metricPropagate) + validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medIPv6, shouldBePresent) +} + +// 1.27.1 setup function +func redistributeIPv4StaticDefaultRejectPolicy(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.TableConnectionsUnsupported(dut) { + configureStaticRedistributionPolicy(t, dut, isV4, !acceptRoute, !metricPropagate) + } else { + configureTableConnection(t, dut, isV4, !metricPropagate, "", oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } +} + +// 1.27.12 setup function +func redistributeIPv6StaticDefaultRejectPolicy(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.TableConnectionsUnsupported(dut) { + configureStaticRedistributionPolicy(t, dut, !isV4, !acceptRoute, !metricPropagate) + } else { + configureTableConnection(t, dut, !isV4, !metricPropagate, "", oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + } +} + +// 1.27.1 validation function +func validateRedistributeIPv4DefaultRejectPolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, !acceptRoute, isV4, !metricPropagate) + validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medZero, !shouldBePresent) +} + +// 1.27.12 validation function +func validateRedistributeIPv6DefaultRejectPolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, !acceptRoute, !isV4, !metricPropagate) + validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medZero, !shouldBePresent) +} + +// 1.27.2 setup function +func redistributeIPv4PrefixRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyNameV4) + + otgConfig := configureOTG(t, ate) + otgConfig = configureTrafficFlow(t, otgConfig, isV4, "StaticRoutesV4Flow", atePort1.Name+".IPv4", atePort2.Name+".IPv4", atePort1.MAC, atePort1.IPv4, "192.168.10.0") + ate.OTG().PushConfig(t, otgConfig) + ate.OTG().StartProtocols(t) + + dutOcRoot := &oc.Root{} + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + if deviations.TableConnectionsUnsupported(dut) { + redistributePolicy = redistributeStaticRoute(t, isV4, metricPropagate, policyResultNext, redistributePolicy) + } + + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyNameV4) + + v4PrefixSet := redistributePolicy.GetOrCreateDefinedSets().GetOrCreatePrefixSet("prefix-set-v4") + v4PrefixSet.GetOrCreatePrefix("192.168.10.0/24", "exact") + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + + v4PrefixSet.GetOrCreatePrefix("192.168.20.0/24", "exact") + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet("prefix-set-v4").Config(), v4PrefixSet) + + ipv4PrefixPolicyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementNameV4) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + ipv4PrefixPolicyStatementAction := ipv4PrefixPolicyStatement.GetOrCreateActions() + ipv4PrefixPolicyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + ipv4PrefixPolicyStatementConditionsPrefixes := ipv4PrefixPolicyStatement.GetOrCreateConditions().GetOrCreateMatchPrefixSet() + ipv4PrefixPolicyStatementConditionsPrefixes.SetPrefixSet("prefix-set-v4") + if !deviations.SkipSetRpMatchSetOptions(dut) { + ipv4PrefixPolicyStatementConditionsPrefixes.SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + if deviations.TableConnectionsUnsupported(dut) { + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyNameV4}) + } else { + configureTableConnection(t, dut, isV4, metricPropagate, redistributeStaticPolicyNameV4, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } + + sendTraffic(t, ate.OTG()) + verifyTraffic(t, ate, otgConfig) +} + +// 1.27.2 validation function +func validateRedistributeIPv4PrefixRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, acceptRoute, isV4, metricPropagate) + validatePrefixSetRoutingPolicy(t, dut, isV4) + validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medIPv4, shouldBePresent) +} + +// 1.27.5 and 1.27.16 setup function +func redistributeStaticRoutePolicyWithASN(t *testing.T, dut *ondatra.DUTDevice, isV4 bool) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := policyStatementNameV4 + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + policyStatementName = policyStatementNameV6 + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + } + + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyName) + + dutOcRoot := &oc.Root{} + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + + if deviations.TableConnectionsUnsupported(dut) { + redistributePolicy = redistributeStaticRoute(t, isV4, metricPropagate, policyResultNext, redistributePolicy) + } + + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyName) + policyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + policyStatementAction := policyStatement.GetOrCreateActions() + policyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend().Asn = ygot.Uint32(64512) + if isV4 { + policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend().Asn = ygot.Uint32(65499) + policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetAsPathPrepend().SetRepeatN(3) + } + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + if deviations.TableConnectionsUnsupported(dut) { + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyName}) + } else { + configureTableConnection(t, dut, isV4, metricPropagate, redistributeStaticPolicyName, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.6 and 1.27.17 setup function +func redistributeStaticRoutePolicyWithMED(t *testing.T, dut *ondatra.DUTDevice, isV4 bool, medValue uint32) { + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := policyStatementNameV4 + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + policyStatementName = policyStatementNameV6 + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + } + + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyName) + + dutOcRoot := &oc.Root{} + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + + if deviations.TableConnectionsUnsupported(dut) { + redistributePolicy = redistributeStaticRoute(t, isV4, metricPropagate, policyResultNext, redistributePolicy) + } + + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyName) + policyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + policyStatementAction := policyStatement.GetOrCreateActions() + policyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + policyStatement.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(medValue)) + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + if deviations.TableConnectionsUnsupported(dut) { + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyName}) + } else { + configureTableConnection(t, dut, isV4, metricPropagate, redistributeStaticPolicyName, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.7 and 1.27.18 setup function +func redistributeStaticRoutePolicyWithLocalPreference(t *testing.T, dut *ondatra.DUTDevice, isV4 bool, localPreferenceValue uint32) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := policyStatementNameV4 + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort3.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + policyStatementName = policyStatementNameV6 + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort3.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + } + + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyName) + + dutOcRoot := &oc.Root{} + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + + if deviations.TableConnectionsUnsupported(dut) { + redistributePolicy = redistributeStaticRoute(t, isV4, metricPropagate, policyResultNext, redistributePolicy) + } + + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyName) + policyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + policyStatementAction := policyStatement.GetOrCreateActions() + policyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + policyStatement.GetOrCreateActions().GetOrCreateBgpActions().SetLocalPref = ygot.Uint32(localPreferenceValue) + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + if deviations.TableConnectionsUnsupported(dut) { + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyName}) + } else { + configureTableConnection(t, dut, isV4, metricPropagate, redistributeStaticPolicyName, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.8 and 1.27.19 setup function +func redistributeStaticRoutePolicyWithCommunitySet(t *testing.T, dut *ondatra.DUTDevice, isV4 bool) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := policyStatementNameV4 + communitySetName := "community-set-v4" + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort3.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + policyStatementName = policyStatementNameV6 + communitySetName = "community-set-v6" + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort3.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + } + + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyName) + communityPath := gnmi.OC().RoutingPolicy().DefinedSets().BgpDefinedSets().CommunitySet(communitySetName) + + dutOcRoot := &oc.Root{} + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + if deviations.TableConnectionsUnsupported(dut) { + redistributePolicy = redistributeStaticRoute(t, isV4, metricPropagate, policyResultNext, redistributePolicy) + } + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyName) + + communitySet := dutOcRoot.GetOrCreateRoutingPolicy() + communitySetPolicyDefinition := communitySet.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySetName) + communitySetPolicyDefinition.SetCommunityMember([]oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString("64512:100")}) + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + gnmi.Replace(t, dut, communityPath.Config(), communitySetPolicyDefinition) + + policyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + policyStatementAction := policyStatement.GetOrCreateActions() + policyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetCommunity().SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetCommunity().GetOrCreateReference().SetCommunitySetRef(communitySetName) + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + if deviations.TableConnectionsUnsupported(dut) { + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyName}) + } else { + configureTableConnection(t, dut, isV4, metricPropagate, redistributeStaticPolicyName, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.9, 1.27.10, 1.27.20 and 1.27.21 setup function +func redistributeStaticRoutePolicyWithTagSet(t *testing.T, dut *ondatra.DUTDevice, isV4 bool, tagSetValue uint32) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + tagSetName := "tag-set-v4" + policyStatementName := policyStatementNameV4 + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + tagSetName = "tag-set-v6" + policyStatementName = policyStatementNameV6 + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + } + + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyName) + tagSetPath := gnmi.OC().RoutingPolicy().DefinedSets().TagSet(tagSetName) + + dutOcRoot := &oc.Root{} + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyName) + + if deviations.TableConnectionsUnsupported(dut) { + redistributeStaticRoute(t, isV4, !metricPropagate, policyResultNext, redistributePolicy) + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + configureRoutingPolicyTagSet(t, dut, isV4, tagSetValue) + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyName}) + } else { + tagSet := dutOcRoot.GetOrCreateRoutingPolicy() + tagSetPolicyDefinition := tagSet.GetOrCreateDefinedSets().GetOrCreateTagSet(tagSetName) + tagSetPolicyDefinition.SetTagValue([]oc.RoutingPolicy_DefinedSets_TagSet_TagValue_Union{oc.UnionString(fmt.Sprintf("%v", tagSetValue))}) + gnmi.Replace(t, dut, tagSetPath.Config(), tagSetPolicyDefinition) + + policyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + policyStatementCondition := policyStatement.GetOrCreateConditions() + if !deviations.SkipSetRpMatchSetOptions(dut) { + policyStatementCondition.GetOrCreateMatchTagSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + policyStatementCondition.GetOrCreateMatchTagSet().SetTagSet(tagSetName) + policyStatementAction := policyStatement.GetOrCreateActions() + policyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + configureTableConnection(t, dut, isV4, !metricPropagate, redistributeStaticPolicyName, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } +} + +// 1.27.11 and 1.27.22 setup function +func redistributeNullNextHopStaticRoute(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, isV4 bool) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + tagSetName := "tag-set-v4" + tagValue := "40" + policyStatementName := policyStatementNameV4 + ipRoute := "192.168.20.0/24" + routeNextHop := "192.168.1.9" + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ExportPolicy() + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + tagSetName = "tag-set-v6" + tagValue = "60" + policyStatementName = policyStatementNameV6 + ipRoute = "2024:db8:64:64::/64" + routeNextHop = "2001:DB8::9" + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + } + + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyName) + staticPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + + otgConfig := configureOTG(t, ate) + if isV4 { + otgConfig = configureTrafficFlow(t, otgConfig, isV4, "StaticDropRoutesV4Flow", atePort3.Name+".IPv4", atePort2.Name+".IPv4", atePort3.MAC, atePort3.IPv4, "192.168.20.0") + } else { + otgConfig = configureTrafficFlow(t, otgConfig, isV4, "StaticDropRoutesV6Flow", atePort3.Name+".IPv6", atePort2.Name+".IPv6", atePort3.MAC, atePort3.IPv6, "2024:db8:64:64::") + } + ate.OTG().PushConfig(t, otgConfig) + ate.OTG().StartProtocols(t) + + dutOcRoot := &oc.Root{} + networkInstance := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + networkInstanceProtocolStatic := networkInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + networkInstanceProtocolStatic.SetEnabled(true) + ipStaticRoute := networkInstanceProtocolStatic.GetOrCreateStatic(ipRoute) + if !deviations.UseVendorNativeTagSetConfig(dut) { + ipStaticRoute.SetSetTag(oc.UnionString(tagValue)) + } else { + configureStaticRouteTagSet(t, dut) + attachTagSetToStaticRoute(t, dut, ipRoute, tagSetName) + } + ipStaticRouteNextHop := ipStaticRoute.GetOrCreateNextHop("0") + ipStaticRouteNextHop.SetNextHop(oc.LocalRouting_LOCAL_DEFINED_NEXT_HOP_DROP) + gnmi.Update(t, dut, staticPath.Config(), networkInstanceProtocolStatic) + + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyName) + + if deviations.TableConnectionsUnsupported(dut) { + redistributeStaticRoute(t, isV4, !metricPropagate, policyResultNext, redistributePolicy) + + statement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + statement.GetOrCreateActions().GetOrCreateBgpActions().SetSetNextHop(oc.UnionString(routeNextHop)) + statement.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyName}) + } else { + ipPrefixPolicyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + ipPrefixPolicyStatementAction := ipPrefixPolicyStatement.GetOrCreateActions() + ipPrefixPolicyStatementAction.GetOrCreateBgpActions().SetSetNextHop(oc.UnionString(routeNextHop)) + ipPrefixPolicyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + configureTableConnection(t, dut, isV4, !metricPropagate, redistributeStaticPolicyName, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } + + // Sending traffic to network via dut having static-route to drop it. + // Traffic must be dropped by the dut irrespective of the bgp advertised-route + // having updated next-hop, considering existing static-route is preferred over bgp. + // Commenting traffic validation for now + /* + sendTraffic(t, ate.OTG()) + verifyTraffic(t, ate, otgConfig) + */ +} + +// 1.27.13 setup function +func redistributeIPv6StaticRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyNameV6) + + otgConfig := configureOTG(t, ate) + otgConfig = configureTrafficFlow(t, otgConfig, !isV4, "StaticRoutesV6Flow", atePort1.Name+".IPv6", atePort2.Name+".IPv6", atePort1.MAC, atePort1.IPv6, "2024:db8:128:128::") + ate.OTG().PushConfig(t, otgConfig) + ate.OTG().StartProtocols(t) + + dutOcRoot := &oc.Root{} + redistributePolicy := dutOcRoot.GetOrCreateRoutingPolicy() + if deviations.TableConnectionsUnsupported(dut) { + redistributePolicy = redistributeStaticRoute(t, !isV4, metricPropagate, policyResultNext, redistributePolicy) + } + redistributePolicyDefinition := redistributePolicy.GetOrCreatePolicyDefinition(redistributeStaticPolicyNameV6) + + v6PrefixSet := redistributePolicy.GetOrCreateDefinedSets().GetOrCreatePrefixSet("prefix-set-v6") + v6PrefixSet.GetOrCreatePrefix("2024:db8:128:128::/64", "exact") + if !deviations.SkipPrefixSetMode(dut) { + v6PrefixSet.SetMode(oc.PrefixSet_Mode_IPV6) + } + + v6PrefixSet.GetOrCreatePrefix("2024:db8:64:64::/64", "exact") + if !deviations.SkipPrefixSetMode(dut) { + v6PrefixSet.SetMode(oc.PrefixSet_Mode_IPV6) + } + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet("prefix-set-v6").Config(), v6PrefixSet) + + ipv6PrefixPolicyStatement, err := redistributePolicyDefinition.AppendNewStatement(policyStatementNameV6) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + + ipv6PrefixPolicyStatementAction := ipv6PrefixPolicyStatement.GetOrCreateActions() + ipv6PrefixPolicyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + ipv6PrefixPolicyStatementConditionsPrefixes := ipv6PrefixPolicyStatement.GetOrCreateConditions().GetOrCreateMatchPrefixSet() + ipv6PrefixPolicyStatementConditionsPrefixes.SetPrefixSet("prefix-set-v6") + if !deviations.SkipSetRpMatchSetOptions(dut) { + ipv6PrefixPolicyStatementConditionsPrefixes.SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) + + if deviations.TableConnectionsUnsupported(dut) { + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(atePort1.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy().ExportPolicy() + gnmi.Replace(t, dut, bgpPath.Config(), []string{redistributeStaticPolicyNameV6}) + } else { + configureTableConnection(t, dut, !isV4, metricPropagate, redistributeStaticPolicyNameV6, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) + } + + sendTraffic(t, ate.OTG()) + verifyTraffic(t, ate, otgConfig) +} + +// 1.27.13 validation function +func validateRedistributeIPv6RoutePolicy(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { + validateRedistributeStatic(t, dut, acceptRoute, !isV4, metricPropagate) + validatePrefixSetRoutingPolicy(t, dut, !isV4) + validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medIPv6, shouldBePresent) +} + +// 1.27.5 and 1.27.16 validation function +func validatePrefixASN(t *testing.T, ate *ondatra.ATEDevice, isV4 bool, bgpPeerName, subnet string, wantASPath []uint32) { + + foundPrefix := false + + if isV4 { + prefixPath := gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv4PrefixAny() + prefix, ok := gnmi.WatchAll(t, ate.OTG(), prefixPath.State(), 10*time.Second, func(val *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + prefix, _ := val.Val() + if prefix.GetAddress() == subnet { + foundPrefix = true + gotASPath := prefix.AsPath[len(prefix.AsPath)-1].GetAsNumbers() + t.Logf("Prefix %v learned with ASN : %v", prefix.GetAddress(), gotASPath) + return reflect.DeepEqual(gotASPath, wantASPath) + } + return false + }).Await(t) + if !ok { + pfx, _ := prefix.Val() + t.Fatalf("Prefix not updated with required as-path. Got %v, want %v", pfx.AsPath[len(pfx.AsPath)-1].GetAsNumbers(), wantASPath) + } + } else { + prefixPath := gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv6PrefixAny() + prefix, ok := gnmi.WatchAll(t, ate.OTG(), prefixPath.State(), 10*time.Second, func(val *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv6Prefix]) bool { + prefix, _ := val.Val() + if prefix.GetAddress() == subnet { + foundPrefix = true + gotASPath := prefix.AsPath[len(prefix.AsPath)-1].GetAsNumbers() + t.Logf("Prefix %v learned with ASN : %v", prefix.GetAddress(), gotASPath) + return reflect.DeepEqual(gotASPath, wantASPath) + } + return false + }).Await(t) + if !ok { + pfx, _ := prefix.Val() + t.Fatalf("Prefix not updated with required as-path. Got %v, want %v", pfx.AsPath[len(pfx.AsPath)-1].GetAsNumbers(), wantASPath) + } + } + if !foundPrefix { + t.Fatalf("Prefix %v not present in OTG", subnet) + } +} + +// 1.27.7 and 1.27.18 validation function +func validatePrefixLocalPreference(t *testing.T, ate *ondatra.ATEDevice, isV4 bool, bgpPeerName, subnet string, wantLocalPreference uint32) { + + foundPrefix := false + if isV4 { + prefixPath := gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv4PrefixAny() + prefix, ok := gnmi.WatchAll(t, ate.OTG(), prefixPath.State(), 10*time.Second, func(val *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + prefix, _ := val.Val() + if prefix.GetAddress() == subnet { + foundPrefix = true + gotLocalPreference := prefix.GetLocalPreference() + t.Logf("Prefix %v learned with localPreference : %v", prefix.GetAddress(), gotLocalPreference) + return gotLocalPreference == wantLocalPreference + } + return false + }).Await(t) + if !ok { + pfx, _ := prefix.Val() + t.Fatalf("Prefix not updated with the local-preference. Got %v, want %v", pfx.GetLocalPreference(), wantLocalPreference) + } + } else { + prefixPath := gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv6PrefixAny() + prefix, ok := gnmi.WatchAll(t, ate.OTG(), prefixPath.State(), 10*time.Second, func(val *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv6Prefix]) bool { + prefix, _ := val.Val() + if prefix.GetAddress() == subnet { + foundPrefix = true + gotLocalPreference := prefix.GetLocalPreference() + t.Logf("Prefix %v learned with localPreference : %v", prefix.GetAddress(), gotLocalPreference) + return gotLocalPreference == wantLocalPreference + } + return false + }).Await(t) + if !ok { + pfx, _ := prefix.Val() + t.Fatalf("Prefix not updated with the local-preference. Got %v, want %v", pfx.GetLocalPreference(), wantLocalPreference) + } + } + if !foundPrefix { + t.Fatalf("Prefix %v not present in OTG", subnet) + } +} + +// 1.27.8 and 1.27.19 validation function +func validatePrefixCommunitySet(t *testing.T, ate *ondatra.ATEDevice, isV4 bool, bgpPeerName, subnet, wantCommunitySet string) { + + foundPrefix := false + if isV4 { + prefixPath := gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv4PrefixAny() + prefix, ok := gnmi.WatchAll(t, ate.OTG(), prefixPath.State(), 10*time.Second, func(val *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + prefix, _ := val.Val() + if prefix.GetAddress() == subnet { + foundPrefix = true + var gotCommunitySet string + for _, community := range prefix.Community { + gotCommunityNumber := community.GetCustomAsNumber() + gotCommunityValue := community.GetCustomAsValue() + gotCommunitySet = fmt.Sprint(gotCommunityNumber) + ":" + fmt.Sprint(gotCommunityValue) + } + t.Logf("Prefix %v learned with CommunitySet : %v", prefix.GetAddress(), gotCommunitySet) + return gotCommunitySet == wantCommunitySet + } + return false + }).Await(t) + if !ok { + pfx, _ := prefix.Val() + var gotCS string + for _, community := range pfx.Community { + gotCN := community.GetCustomAsNumber() + gotCV := community.GetCustomAsValue() + gotCS = fmt.Sprint(gotCN) + ":" + fmt.Sprint(gotCV) + } + t.Fatalf("Prefix not updated with the community-set. Got %v, want %v", gotCS, wantCommunitySet) + } + } else { + prefixPath := gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv6PrefixAny() + prefix, ok := gnmi.WatchAll(t, ate.OTG(), prefixPath.State(), 10*time.Second, func(val *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv6Prefix]) bool { + prefix, _ := val.Val() + if prefix.GetAddress() == subnet { + foundPrefix = true + var gotCommunitySet string + for _, community := range prefix.Community { + gotCommunityNumber := community.GetCustomAsNumber() + gotCommunityValue := community.GetCustomAsValue() + gotCommunitySet = fmt.Sprint(gotCommunityNumber) + ":" + fmt.Sprint(gotCommunityValue) + } + t.Logf("Prefix %v learned with CommunitySet : %v", prefix.GetAddress(), gotCommunitySet) + return gotCommunitySet == wantCommunitySet + } + return false + }).Await(t) + if !ok { + pfx, _ := prefix.Val() + var gotCS string + for _, community := range pfx.Community { + gotCN := community.GetCustomAsNumber() + gotCV := community.GetCustomAsValue() + gotCS = fmt.Sprint(gotCN) + ":" + fmt.Sprint(gotCV) + } + t.Fatalf("Prefix not updated with the community-set. Got %v, want %v", gotCS, wantCommunitySet) + } + } + + if !foundPrefix { + t.Fatalf("Prefix %v not present in OTG", subnet) + } +} + +// 1.27.9, 1.27.10, 1.27.20 and 1.27.21 validation function +func validateRedistributeRouteWithTagSet(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, isV4, shouldBePresent bool) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + af := oc.Types_ADDRESS_FAMILY_IPV4 + tagSet := "tag-set-v4" + policyStatementName := policyStatementNameV4 + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + af = oc.Types_ADDRESS_FAMILY_IPV6 + tagSet = "tag-set-v6" + policyStatementName = policyStatementNameV6 + } + + if !deviations.TableConnectionsUnsupported(dut) { + tcState := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection( + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + af).State()) + + importPolicies := tcState.GetImportPolicy() + found := false + for _, iPolicy := range importPolicies { + if iPolicy == redistributeStaticPolicyName { + found = true + } + } + + if !found { + t.Fatal("expected import policy is not configured") + } + } + + var foundPDef oc.RoutingPolicy_PolicyDefinition + policyDef := gnmi.GetAll(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinitionAny().State()) + for _, pDef := range policyDef { + if pDef.GetName() == redistributeStaticPolicyName { + foundPDef = *pDef + } + } + + if foundPDef.GetName() != redistributeStaticPolicyName { + t.Fatal("Expected import policy is not configured") + } + if !deviations.UseVendorNativeTagSetConfig(dut) { + if foundPDef.GetStatement(policyStatementName).GetConditions().GetOrCreateMatchTagSet().GetTagSet() != tagSet { + t.Fatal("Expected tag-set is not configured") + } + if foundPDef.GetStatement(policyStatementName).GetConditions().GetOrCreateMatchTagSet().GetMatchSetOptions() != oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY { + t.Fatal("Expected match-set-option for tag-set is not configured") + } + } + + if isV4 { + validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medZero, shouldBePresent) + } else { + validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medZero, shouldBePresent) + } +} + +// 1.27.11 and 1.27.22 validation function +func validateRedistributeNullNextHopStaticRoute(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, isV4 bool) { + + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := policyStatementNameV4 + addressFamily := oc.Types_ADDRESS_FAMILY_IPV4 + nextHop := "192.168.1.9" + if !isV4 { + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + policyStatementName = policyStatementNameV6 + addressFamily = oc.Types_ADDRESS_FAMILY_IPV6 + nextHop = "2001:db8::9" + } + + if !deviations.TableConnectionsUnsupported(dut) { + tcState := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection( + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + addressFamily).State()) + + importPolicies := tcState.GetImportPolicy() + found := false + for _, iPolicy := range importPolicies { + if iPolicy == redistributeStaticPolicyName { + found = true + } + } + + if !found { + t.Fatal("expected import policy is not configured") + } + } + + var foundPDef oc.RoutingPolicy_PolicyDefinition + policyDef := gnmi.GetAll(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinitionAny().State()) + for _, pDef := range policyDef { + if pDef.GetName() == redistributeStaticPolicyName { + foundPDef = *pDef + } + } + + if foundPDef.GetName() != redistributeStaticPolicyName { + t.Fatal("Expected import policy is not configured") + } + + if foundPDef.GetStatement(policyStatementName).GetActions().GetBgpActions().GetSetNextHop() != oc.UnionString(nextHop) { + t.Fatal("Expected next-hop is not configured") + } + + if isV4 { + validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.20.0", medZero, shouldBePresent) + } else { + validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:64:64::", medZero, shouldBePresent) + } +} + +// Used by multiple IPv4 test validations for route presence and MED value +func validateLearnedIPv4Prefix(t *testing.T, ate *ondatra.ATEDevice, bgpPeerName, subnet string, expectedMED uint32, shouldBePresent bool) { + + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv4PrefixAny().State(), + time.Minute, func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + return v.IsPresent() + }).Await(t) + + if !ok { + t.Errorf("No BGP prefixes learnt") + } + + bgpPrefixes := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv4PrefixAny().State()) + found := false + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == subnet { + found = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix, subnet) + t.Logf("Prefix MED %d", bgpPrefix.GetMultiExitDiscriminator()) + if bgpPrefix.GetMultiExitDiscriminator() != expectedMED { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), expectedMED) + } + break + } + } + + if !found { + t.Errorf("No Route found for prefix %s", subnet) + } +} + +// Used by multiple IPv6 test validations for route presence and MED value +func validateLearnedIPv6Prefix(t *testing.T, ate *ondatra.ATEDevice, bgpPeerName, subnet string, expectedMED uint32, shouldBePresent bool) { + time.Sleep(5 * time.Second) + + bgpPrefixes := gnmi.GetAll[*otgtelemetry.BgpPeer_UnicastIpv6Prefix](t, ate.OTG(), gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv6PrefixAny().State()) + found := false + for _, bgpPrefix := range bgpPrefixes { + if bgpPrefix.Address != nil && bgpPrefix.GetAddress() == subnet { + found = true + t.Logf("Prefix recevied on OTG is correct, got prefix %v, want prefix %v", bgpPrefix, subnet) + t.Logf("Prefix MED %d", bgpPrefix.GetMultiExitDiscriminator()) + if bgpPrefix.GetMultiExitDiscriminator() != expectedMED { + t.Errorf("For Prefix %v, got MED %d want MED %d", bgpPrefix.GetAddress(), bgpPrefix.GetMultiExitDiscriminator(), expectedMED) + } + break + } + } + + if !found { + t.Errorf("No Route found for prefix %s", subnet) + } +} + +func TestBGPStaticRouteRedistribution(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + configureDUT(t, dut) + otgConfig := configureOTG(t, ate) + ate.OTG().PushConfig(t, otgConfig) + ate.OTG().StartProtocols(t) + + awaitBGPEstablished(t, dut, []string{atePort1.IPv4, atePort3.IPv4}) + + type testCase struct { + name string + setup func() + validate func() + } + + testCases := []testCase{ + // 1.27.1 + { + name: "1.27.1 redistribute-ipv4-ipv6-default-reject-policy", + setup: func() { redistributeIPv4StaticDefaultRejectPolicy(t, dut) }, + validate: func() { validateRedistributeIPv4DefaultRejectPolicy(t, dut, ate) }, + }, + // 1.27.2 + { + name: "1.27.2 redistribute-ipv4-prefix-route-policy", + setup: func() { redistributeIPv4PrefixRoutePolicy(t, dut, ate) }, + validate: func() { validateRedistributeIPv4PrefixRoutePolicy(t, dut, ate) }, + }, + // 1.27.3 + { + name: "1.27.3 redistribute-ipv4-static-routes-with-metric-propagation-disabled", + setup: func() { redistributeIPv4Static(t, dut) }, + validate: func() { validateRedistributeIPv4Static(t, dut, ate) }, + }, + // 1.27.4 + { + name: "1.27.4 redistribute-ipv4-static-routes-with-metric-propagation-enabled", + setup: func() { redistributeIPv4StaticWithMetricPropagation(t, dut) }, + validate: func() { validateRedistributeIPv4StaticWithMetricPropagation(t, dut, ate) }, + }, + // 1.27.5 + { + name: "1.27.5 redistribute-ipv4-route-policy-as-prepend", + setup: func() { redistributeStaticRoutePolicyWithASN(t, dut, isV4) }, + validate: func() { + validatePrefixASN(t, ate, isV4, atePort1.Name+".BGP4.peer", "192.168.10.0", []uint32{64512, 65499, 65499, 65499}) + }, + }, + // 1.27.6 + { + name: "1.27.6 redistribute-ipv4-route-policy-med", + setup: func() { redistributeStaticRoutePolicyWithMED(t, dut, isV4, medNonZero) }, + validate: func() { + validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medNonZero, shouldBePresent) + }, + }, + // 1.27.7 + { + name: "1.27.7 redistribute-ipv4-route-policy-local-preference", + setup: func() { redistributeStaticRoutePolicyWithLocalPreference(t, dut, isV4, localPreference) }, + validate: func() { + validatePrefixLocalPreference(t, ate, isV4, atePort3.Name+".BGP4.peer", "192.168.10.0", localPreference) + }, + }, + // 1.27.8 + { + name: "1.27.8 redistribute-ipv4-route-policy-community-set", + setup: func() { redistributeStaticRoutePolicyWithCommunitySet(t, dut, isV4) }, + validate: func() { + validatePrefixCommunitySet(t, ate, isV4, atePort3.Name+".BGP4.peer", "192.168.10.0", "64512:100") + }, + }, + // 1.27.9 + { + name: "1.27.9 redistribute-ipv4-route-policy-unmatched-tag", + setup: func() { redistributeStaticRoutePolicyWithTagSet(t, dut, isV4, 100) }, + validate: func() { validateRedistributeRouteWithTagSet(t, dut, ate, isV4, !shouldBePresent) }, + }, + // 1.27.10 + { + name: "1.27.10 redistribute-ipv4-route-policy-matched-set", + setup: func() { redistributeStaticRoutePolicyWithTagSet(t, dut, isV4, 40) }, + validate: func() { validateRedistributeRouteWithTagSet(t, dut, ate, isV4, shouldBePresent) }, + }, + // 1.27.11 + { + name: "1.27.11 redistribute-ipv4-route-policy-nexthop", + setup: func() { redistributeNullNextHopStaticRoute(t, dut, ate, isV4) }, + validate: func() { validateRedistributeNullNextHopStaticRoute(t, dut, ate, isV4) }, + }, + // 1.27.12 + { + name: "1.27.12 redistribute-ipv6-default-reject-policy", + setup: func() { redistributeIPv6StaticDefaultRejectPolicy(t, dut) }, + validate: func() { validateRedistributeIPv6DefaultRejectPolicy(t, dut, ate) }, + }, + // 1.27.13 + { + name: "1.27.13 redistribute-ipv6-route-policy", + setup: func() { redistributeIPv6StaticRoutePolicy(t, dut, ate) }, + validate: func() { validateRedistributeIPv6RoutePolicy(t, dut, ate) }, + }, + // 1.27.14 + { + name: "1.27.14 redistribute-ipv6-static-routes-with-metric-propagation-disabled", + setup: func() { redistributeIPv6Static(t, dut) }, + validate: func() { validateRedistributeIPv6Static(t, dut, ate) }, + }, + // 1.27.15 + { + name: "1.27.15 redistribute-ipv6-static-routes-with-metric-propagation-enabled", + setup: func() { redistributeIPv6StaticWithMetricPropagation(t, dut) }, + validate: func() { validateRedistributeIPv6StaticWithMetricPropagation(t, dut, ate) }, + }, + // 1.27.16 + { + name: "1.27.16 redistribute-ipv6-route-policy-as-prepend", + setup: func() { redistributeStaticRoutePolicyWithASN(t, dut, !isV4) }, + validate: func() { + validatePrefixASN(t, ate, !isV4, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", []uint32{64512, 64512}) + }, + }, + // 1.27.17 + { + name: "1.27.17 redistribute-ipv6-route-policy-med", + setup: func() { redistributeStaticRoutePolicyWithMED(t, dut, !isV4, medNonZero) }, + validate: func() { + validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medNonZero, shouldBePresent) + }, + }, + // 1.27.18 + { + name: "1.27.18 redistribute-ipv6-route-policy-local-preference", + setup: func() { redistributeStaticRoutePolicyWithLocalPreference(t, dut, !isV4, localPreference) }, + validate: func() { + validatePrefixLocalPreference(t, ate, !isV4, atePort3.Name+".BGP6.peer", "2024:db8:128:128::", localPreference) + }, + }, + // 1.27.19 + { + name: "1.27.19 redistribute-ipv6-route-policy-community-set", + setup: func() { redistributeStaticRoutePolicyWithCommunitySet(t, dut, !isV4) }, + validate: func() { + validatePrefixCommunitySet(t, ate, !isV4, atePort3.Name+".BGP6.peer", "2024:db8:128:128::", "64512:100") + }, + }, + // 1.27.20 + { + name: "1.27.20 redistribute-ipv6-route-policy-unmatched-tag", + setup: func() { redistributeStaticRoutePolicyWithTagSet(t, dut, !isV4, 100) }, + validate: func() { validateRedistributeRouteWithTagSet(t, dut, ate, !isV4, !shouldBePresent) }, + }, + // 1.27.21 + { + name: "1.27.21 redistribute-ipv6-route-policy-matched-set", + setup: func() { redistributeStaticRoutePolicyWithTagSet(t, dut, !isV4, 60) }, + validate: func() { validateRedistributeRouteWithTagSet(t, dut, ate, !isV4, shouldBePresent) }, + }, + // 1.27.22 + { + name: "1.27.22 redistribute-ipv6-route-policy-nexthop", + setup: func() { redistributeNullNextHopStaticRoute(t, dut, ate, !isV4) }, + validate: func() { validateRedistributeNullNextHopStaticRoute(t, dut, ate, !isV4) }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + tc.validate() + }) + } +} + +// Function using native-yang to attach tag-set to static-route +func attachTagSetToStaticRoute(t *testing.T, dut *ondatra.DUTDevice, prefix, tagPolicy string) { + + tagValue, err := json.Marshal(tagPolicy) + if err != nil { + t.Fatalf("Error with json Marshal: %v", err) + } + + gpbSetRequest := &gpb.SetRequest{ + Prefix: &gpb.Path{ + Origin: "native", + }, + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "network-instance", Key: map[string]string{"name": "DEFAULT"}}, + {Name: "static-routes"}, + {Name: "route", Key: map[string]string{"prefix": prefix}}, + {Name: "tag-set"}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: tagValue, + }, + }, + }, + }, + } + + gnmiClient := dut.RawAPIs().GNMI(t) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("Unexpected error updating SRL static-route tag-set: %v", err) + } + +} + +// Function using native-yang to configure tag-set used by static-route +func configureStaticRouteTagSet(t *testing.T, dut *ondatra.DUTDevice) { + + var routingPolicyTagSetValueV4 = []any{ + map[string]any{ + "tag-value": []any{ + 40, + }, + }, + } + tagValueV4, err := json.Marshal(routingPolicyTagSetValueV4) + if err != nil { + t.Fatalf("Error with json Marshal: %v", err) + } + var routingPolicyTagSetValueV6 = []any{ + map[string]any{ + "tag-value": []any{ + 60, + }, + }, + } + tagValueV6, err := json.Marshal(routingPolicyTagSetValueV6) + if err != nil { + t.Fatalf("Error with json Marshal: %v", err) + } + + gpbSetRequest := &gpb.SetRequest{ + Prefix: &gpb.Path{ + Origin: "native", + }, + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "routing-policy"}, + {Name: "tag-set", Key: map[string]string{"name": "tag-static-v4"}}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: tagValueV4, + }, + }, + }, + { + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "routing-policy"}, + {Name: "tag-set", Key: map[string]string{"name": "tag-static-v6"}}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: tagValueV6, + }, + }, + }, + }, + } + + gnmiClient := dut.RawAPIs().GNMI(t) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("Unexpected error updating SRL routing-policy tag-set for static-route: %v", err) + } +} + +// Function using native-yang to configure tag-set with routing-policy +func configureRoutingPolicyTagSet(t *testing.T, dut *ondatra.DUTDevice, isV4 bool, tValue uint32) { + + tagSetName := "tag-set-v4" + redistributeStaticPolicyName := redistributeStaticPolicyNameV4 + policyStatementName := policyStatementNameV4 + if !isV4 { + tagSetName = "tag-set-v6" + redistributeStaticPolicyName = redistributeStaticPolicyNameV6 + policyStatementName = policyStatementNameV6 + } + + var routingPolicyTagSet = []any{ + map[string]any{ + "match": map[string]any{ + "internal-tags": map[string]any{ + "tag-set": []string{tagSetName}, + }, + }, + "action": map[string]any{ + "policy-result": "accept", + }, + }, + } + tagSetStatement, err := json.Marshal(routingPolicyTagSet) + if err != nil { + t.Fatalf("Error with json Marshal: %v", err) + } + var routingPolicyTagSetValue = []any{ + map[string]any{ + "tag-value": []any{ + tValue, + }, + }, + } + tagValue, err := json.Marshal(routingPolicyTagSetValue) + if err != nil { + t.Fatalf("Error with json Marshal: %v", err) + } + + gpbTagSetReplace := &gpb.SetRequest{ + Prefix: &gpb.Path{ + Origin: "native", + }, + Replace: []*gpb.Update{ + { + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "routing-policy"}, + {Name: "tag-set", Key: map[string]string{"name": tagSetName}}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: tagValue, + }, + }, + }, + }, + } + + gpbPolicyUpdate := &gpb.SetRequest{ + Prefix: &gpb.Path{ + Origin: "native", + }, + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "routing-policy"}, + {Name: "policy", Key: map[string]string{"name": redistributeStaticPolicyName}}, + {Name: "statement", Key: map[string]string{"name": policyStatementName}}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: tagSetStatement, + }, + }, + }, + }, + } + + gnmiClient := dut.RawAPIs().GNMI(t) + if _, err := gnmiClient.Set(context.Background(), gpbTagSetReplace); err != nil { + t.Fatalf("Unexpected error updating SRL routing-policy tag-set: %v", err) + } + if _, err := gnmiClient.Set(context.Background(), gpbPolicyUpdate); err != nil { + t.Fatalf("Unexpected error updating SRL routing-policy tag-set: %v", err) + } +} diff --git a/feature/bgp/tests/local_bgp_test/README.md b/feature/bgp/tests/local_bgp_test/README.md index 3e30b2b711b..de4b31660f8 100644 --- a/feature/bgp/tests/local_bgp_test/README.md +++ b/feature/bgp/tests/local_bgp_test/README.md @@ -13,18 +13,28 @@ Enable an Accept-route all import-policy/export-policy for eBGP session under th This test is suitable for running in a KNE environment. -## Parameter Coverage - -* /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/local-as -* /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/state/messages/received/last-notification-error-code -* /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/neighbors/neighbor/timers/config/hold-time -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/keepalive-interval +## OpenConfig Path and RPC Coverage + +```yaml +paths: + ## Parameter Coverage + + /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/local-as: + /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/state/messages/received/last-notification-error-code: + /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/neighbors/neighbor/timers/config/hold-time: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/keepalive-interval: + +rpcs: + gnmi: + gNMI.Subscribe: + 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 d582aeae44a..b3e7834d4d1 100644 --- a/feature/bgp/tests/local_bgp_test/local_bgp_test.go +++ b/feature/bgp/tests/local_bgp_test/local_bgp_test.go @@ -176,7 +176,7 @@ func TestEstablish(t *testing.T) { }, dut) gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) gnmi.Replace(t, ate, ateConfPath.Config(), ateConf) - gnmi.Await(t, dut, nbrPath.SessionState().State(), time.Second*120, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + 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) { 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 32acae86d73..43d4f97c60f 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 @@ -16,20 +16,28 @@ BGP Keepalive and HoldTimer Configuration Test * Verify that the sessions are established after soft reset. -## Config Parameter coverage - -* /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 - -## Telemetry Parameter coverage - -* /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 - -## Protocol/RPC Parameter coverage - -N/A - +## 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/bgp/neighbors/neighbor/timers/config/keepalive-interval: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/hold-time: + + ## State Paths ## + /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: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: + on_change: true + gNMI.Set: +``` + ## Minimum DUT platform requirement -vRX \ No newline at end of file +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 a2a29135bb7..4d5bf0acc90 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 @@ -208,7 +208,7 @@ func ateFlowConfig(t *testing.T, topo gosnappi.Config, srcEth gosnappi.DeviceEth SetTxNames([]string{srcIpv4.Name()}). SetRxNames([]string{dstBgp4PeerRoutes.Name()}) flowipv4.Size().SetFixed(512) - flowipv4.Duration().SetChoice("continuous") + flowipv4.Duration().Continuous() e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(srcEth.Mac()) v4 := flowipv4.Packet().Add().Ipv4() @@ -221,7 +221,7 @@ func ateFlowConfig(t *testing.T, topo gosnappi.Config, srcEth gosnappi.DeviceEth SetTxNames([]string{srcIpv6.Name()}). SetRxNames([]string{dstBgp6PeerRoutes.Name()}) flowipv6.Size().SetFixed(512) - flowipv6.Duration().SetChoice("continuous") + flowipv6.Duration().Continuous() e2 := flowipv6.Packet().Add().Ethernet() e2.Src().SetValue(srcEth.Mac()) v6 := flowipv6.Packet().Add().Ipv6() @@ -259,8 +259,13 @@ func bgpCreateNbr(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { nv4.GetOrCreateTimers().RestartTime = ygot.Uint16(bgpGlobalAttrs.grRestartTime) afisafi := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) afisafi.Enabled = ygot.Bool(true) - prefixLimit := afisafi.GetOrCreateIpv4Unicast().GetOrCreatePrefixLimit() - prefixLimit.MaxPrefixes = ygot.Uint32(uint32(nbr.pfxLimit)) + if deviations.BGPExplicitPrefixLimitReceived(dut) { + prefixLimit := afisafi.GetOrCreateIpv4Unicast().GetOrCreatePrefixLimitReceived() + prefixLimit.MaxPrefixes = ygot.Uint32(uint32(nbr.pfxLimit)) + } else { + prefixLimit := afisafi.GetOrCreateIpv4Unicast().GetOrCreatePrefixLimit() + prefixLimit.MaxPrefixes = ygot.Uint32(uint32(nbr.pfxLimit)) + } if deviations.RoutePolicyUnderAFIUnsupported(dut) { rpl := pgv4.GetOrCreateApplyPolicy() rpl.ImportPolicy = []string{bgpGlobalAttrs.rplName} @@ -281,8 +286,13 @@ func bgpCreateNbr(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { nv6.GetOrCreateTimers().RestartTime = ygot.Uint16(bgpGlobalAttrs.grRestartTime) afisafi6 := nv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) afisafi6.Enabled = ygot.Bool(true) - prefixLimit6 := afisafi6.GetOrCreateIpv6Unicast().GetOrCreatePrefixLimit() - prefixLimit6.MaxPrefixes = ygot.Uint32(nbr.pfxLimit) + if deviations.BGPExplicitPrefixLimitReceived(dut) { + prefixLimit := afisafi6.GetOrCreateIpv6Unicast().GetOrCreatePrefixLimitReceived() + prefixLimit.MaxPrefixes = ygot.Uint32(uint32(nbr.pfxLimit)) + } else { + prefixLimit := afisafi6.GetOrCreateIpv6Unicast().GetOrCreatePrefixLimit() + prefixLimit.MaxPrefixes = ygot.Uint32(uint32(nbr.pfxLimit)) + } if deviations.RoutePolicyUnderAFIUnsupported(dut) { rpl := pgv6.GetOrCreateApplyPolicy() rpl.ImportPolicy = []string{bgpGlobalAttrs.rplName} @@ -484,8 +494,7 @@ func TestBgpKeepAliveHoldTimerConfiguration(t *testing.T) { configureDUT(t, dut) t.Log("Configure RPL") configureRoutePolicy(t, dut, bgpGlobalAttrs.rplName, oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) - dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) - gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + fptest.ConfigureDefaultNetworkInstance(t, dut) t.Logf("Start DUT BGP Config") dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") dutConf := bgpCreateNbr(dut) diff --git a/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/metadata.textproto b/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/metadata.textproto index 8b25dcb8189..d9df992f174 100644 --- a/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/metadata.textproto +++ b/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/metadata.textproto @@ -21,6 +21,7 @@ platform_exceptions: { explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true + bgp_explicit_prefix_limit_received: true } } platform_exceptions: { diff --git a/feature/container/containerz/tests/container_lifecycle/README.md b/feature/container/containerz/tests/container_lifecycle/README.md new file mode 100644 index 00000000000..768d43fee2b --- /dev/null +++ b/feature/container/containerz/tests/container_lifecycle/README.md @@ -0,0 +1,113 @@ +# CNTR-1: Basic container lifecycle via `gnoi.Containerz`. + +## Summary + +Verify the correct behaviour of `gNOI.Containerz` when operating containers. + +## Procedure + +This step only applies if the reference implementation of containerz is being +tested. + +Start by pulling the reference implementation: + +```shell +$ git clone git@github.com:openconfig/containerz.git +``` + +Then `cd` into the containerz directory and build containerz: + +```shell +$ cd containerz +$ go build . +``` + +Finally start containerz: + +```shell +$ ./containerz start +``` + +You should see the following output: + +```shell +$ ./containerz start +I0620 12:02:57.408496 3615908 janitor.go:33] janitor-starting +I0620 12:02:57.408547 3615908 janitor.go:36] janitor-started +I0620 12:02:57.408583 3615908 server.go:167] server-start +I0620 12:02:57.408595 3615908 server.go:170] Starting up on Containerz server, listening on: [::]:19999 +I0620 12:02:57.408608 3615908 server.go:171] server-ready +``` + +### Build Test Container + +The test container is available in the feature profile repository under +`internal/cntrsrv`. + +Start by entering in that directory and running the following commands: + +```shell +$ cd internal/cntrsrv +$ go mod vendor +$ CGO_ENABLED=0 go build . +$ docker build -f build/Dockerfile.local -t cntrsrv:latest . +``` + +At this point you will have a container image build for the test container. + +```shell +$ docker images +REPOSITORY TAG IMAGE ID CREATED SIZE +cntrsrv latest 8d786a6eebc8 3 minutes ago 21.4MB +``` + +Now export the container to a tarball. + +```shell +$ docker save -o /tmp/cntrsrv.tar cntrsrv:latest +$ docker rmi cntrsrv:latest +``` + +This is the tarball that will be used during tests. + +## CNTR-1.1: Deploy and Start a Container + +Using the +[`gnoi.Containerz`](https://github.com/openconfig/gnoi/tree/main/containerz) API +(reference implementation to be available +[`openconfig/containerz`](https://github.com/openconfig/containerz), deploy a +container to the DUT. Using `gnoi.Containerz` start the container. + +The container should expose a simple health API. The test succeeds if is +possible to connect to the container via the gRPC API to determine its health. + +## CNTR-1.2: Retrieve a running container's logs. + +Using the container started as part of CNTR-1.1, retrieve the logs from the +container and ensure non-zero contents are returned when using +`gnoi.Containerz.Log`. + +## CNTR-1.3: List the running containers on a DUT + +Using the container started as part of CNTR-1.1, validate that the container is +included in the listed set of containers when calling `gnoi.Containerz.List`. + +## CNTR-1.4: Stop a container running on a DUT. + +Using the container started as part of CNTR-1.2, validate that the container can +be stopped, and is subsequently no longer listed in the `gnoi.Containerz.List` +API. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the RPCs intended to be covered by this test. + +```yaml +rpcs: + gnoi: + containerz.Containerz.Deploy: + containerz.Containerz.StartContainer: + containerz.Containerz.StopContainer: + containerz.Containerz.Log: + containerz.Containerz.ListContainer: +``` \ No newline at end of file diff --git a/feature/container/containerz/tests/container_lifecycle/containerz_test.go b/feature/container/containerz/tests/container_lifecycle/containerz_test.go new file mode 100644 index 00000000000..696c9a9c0fa --- /dev/null +++ b/feature/container/containerz/tests/container_lifecycle/containerz_test.go @@ -0,0 +1,69 @@ +package container_lifecycle_test + +import ( + "context" + "crypto/tls" + "flag" + "testing" + + "github.com/openconfig/containerz/client" + cpb "github.com/openconfig/featureprofiles/internal/cntrsrv/proto/cntr" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials" +) + +var ( + containerTar = flag.String("container_tar", "/tmp/cntrsrv.tar", "The container tarball to deploy.") + containerzAddr = flag.String("containerz_addr", "localhost:19999", "containerz server address") +) + +// TestDeployAndStartContainer implements CNTR-1.1 validating that it is +// possible deploy and start a container via containerz. +func TestDeployAndStartContainer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + cli, err := client.NewClient(ctx, *containerzAddr) + if err != nil { + t.Fatalf("unable to dial containerz: %v", err) + } + + progCh, err := cli.PushImage(ctx, "cntrsrv", "latest", *containerTar) + if err != nil { + t.Fatalf("unable to push image: %v", err) + } + + for prog := range progCh { + switch { + case prog.Error != nil: + t.Fatalf("failed to push image: %v", err) + case prog.Finished: + t.Logf("Pushed %s/%s\n", prog.Image, prog.Tag) + default: + t.Logf(" %d bytes pushed", prog.BytesReceived) + } + } + + ret, err := cli.StartContainer(ctx, "cntrsrv", "latest", "./cntrsrv", "test-instance", client.WithPorts([]string{"60061:60061"})) + if err != nil { + t.Fatalf("unable to start container: %v", err) + } + t.Logf("Started %s", ret) + + tlsc := credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, // NOLINT + }) + conntectionState := connectivity.Ready + conn, err := grpc.NewClient("localhost:60061", grpc.WithTransportCredentials(tlsc)) + conn.WaitForStateChange(ctx, conntectionState) + if err != nil { + t.Fatalf("Failed to dial cntrsrv, %v", err) + } + + cntrCli := cpb.NewCntrClient(conn) + if _, err = cntrCli.Ping(ctx, &cpb.PingRequest{}); err != nil { + t.Errorf("unable to reach cntrsrv: %v", err) + } + +} diff --git a/feature/container/containerz/tests/container_lifecycle/metadata.textproto b/feature/container/containerz/tests/container_lifecycle/metadata.textproto new file mode 100644 index 00000000000..7d84745fd46 --- /dev/null +++ b/feature/container/containerz/tests/container_lifecycle/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "a477c97d-c895-4af3-9c97-9327bf86d517" +plan_id: "CNTR-1" +description: "Basic container lifecycle via `gnoi.Containerz`." +testbed: TESTBED_DUT_ATE_2LINKS \ No newline at end of file diff --git a/feature/container/networking/tests/container_connectivity/README.md b/feature/container/networking/tests/container_connectivity/README.md new file mode 100644 index 00000000000..0c5156b571b --- /dev/null +++ b/feature/container/networking/tests/container_connectivity/README.md @@ -0,0 +1,60 @@ +# CNTR-2: Container network connectivity tests + +Tests within this directory ensure that a container deployed on a network +device is able to connect to external services via gRPC. + +## CNTR-2.1: Connect to container from external client. + +Deploy a container to a DUT that is listening on `[::]:60061`. Validate that the +test can connect to tcp/60061 via gRPC and receive a response on a simple +"dummy" service. + +## CNTR-2.2: Connect to locally running service. + +For a DUT configured with gNMI running on tcp/9339 (IANA standard), and gRIBI +running on tcp/9340 (IANA standard), the test should: + +* Instruct the container to make a gRPC `Dial` call to the running gNMI + instance, with a specified timeout. The test succeeds if the connection + succeeds within the timeout, otherwise it fails. +* Instruct the container to make a gRPC `Dial` call to the running gRIBI + instance with the same pass/fail logic. + +## CNTR-2.3: Connect to a remote node. + +Deploy two DUTs running in the following configuration: + +``` + [ c1 ] [ c2 ] + --------- -------- + [ DUT 1 ] ---- port1 ---- [ DUT 2 ] +``` + +where c1 is an instance of the "listener" container image, and c2 is an instance +of the "dialer" image. + +The test should: * ensure that c1 is listening on `[::]:60071` running a gRPC +service. * use gNMI to configure and/or discover the link local addresses +configured on DUT1 port 1 and DUT2 port1. * instruct c2 to make a dial call and +isue a simple RPC to the address configured by c1. If the dial call succeeds +within a specified timeout, the test passes, otherwise it fails. + +## CNTR-2.4: Connect to another container on a local node + +Deploy a single DUT with two containers C1 and C2 running on them. C1 should +listen on a gRPC service on `tcp/[::]:60061` and C2 should listen on a gRPC +service on `tcp/[::]60062`. + +* Instruct C1 to make a gRPC dial call to C2's listen port with a specified + timeout, ensure that an RPC response is received. +* Instruct C2 to make a gRPC dial call to C2's listen port with a specified + timeout, ensure that an RPC response is received. + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/container/networking/tests/container_connectivity/cntr_test.go b/feature/container/networking/tests/container_connectivity/cntr_test.go new file mode 100644 index 00000000000..e1d57a59c3a --- /dev/null +++ b/feature/container/networking/tests/container_connectivity/cntr_test.go @@ -0,0 +1,230 @@ +/* + 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 cntr_test implements an ONDATRA test for container functionalities +// as described in the CNTR-[234] tests in README.md. +package cntr_test + +import ( + "context" + "crypto/tls" + "fmt" + "strings" + "testing" + "time" + + "github.com/kr/pretty" + "github.com/openconfig/featureprofiles/internal/fptest" + "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" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/encoding/prototext" + + cpb "github.com/openconfig/featureprofiles/internal/cntrsrv/proto/cntr" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func DialService(ctx context.Context, t *testing.T, name string, dut *ondatra.DUTDevice) (*grpc.ClientConn, func()) { + t.Helper() + + var dialer interface { + DialGRPC(context.Context, string, ...grpc.DialOption) (*grpc.ClientConn, error) + } + if err := binding.DUTAs(dut.RawAPIs().BindingDUT(), &dialer); err != nil { + t.Fatalf("DUT does not support DialGRPC function: %v", err) + } + + tlsc := credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, // NOLINT + }) + conn, err := dialer.DialGRPC(ctx, name, grpc.WithTransportCredentials(tlsc)) + conn.WaitForStateChange(ctx, connectivity.Ready) + if err != nil { + t.Fatalf("Failed to dial %s, %v", name, err) + } + return conn, func() { conn.Close() } +} + +// TestDial implements CNTR-2, validating that it is possible for an external caller to dial into a service +// running in a container on a DUT. The service used is the cntr service defined by cntr.proto. +func TestDial(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + _, stop := DialService(ctx, t, "cntr", ondatra.DUT(t, "r0")) + stop() +} + +// TestDialLocal implements CNTR-3, validating that it is possible for a +// container running on the device to connect to local gRPC services that are +// running on the DUT. +func TestDialLocal(t *testing.T) { + + tests := []struct { + desc string + inMsg *cpb.DialRequest + wantResp bool + wantErr bool + }{{ + desc: "dial gNMI", + inMsg: &cpb.DialRequest{ + Addr: "localhost:9339", + Request: &cpb.DialRequest_Srv{ + Srv: cpb.Service_ST_GNMI, + }, + }, + wantResp: true, + }, { + desc: "dial gRIBI", + inMsg: &cpb.DialRequest{ + Addr: "localhost:9340", + Request: &cpb.DialRequest_Srv{ + Srv: cpb.Service_ST_GRIBI, + }, + }, + wantResp: true, + }, { + desc: "dial something not listening", + inMsg: &cpb.DialRequest{ + Addr: "localhost:4242", + Request: &cpb.DialRequest_Srv{ + Srv: cpb.Service_ST_GRIBI, + }, + }, + wantErr: true, + }} + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + conn, stop := DialService(ctx, t, "cntr", ondatra.DUT(t, "r0")) + defer stop() + + client := cpb.NewCntrClient(conn) + got, err := client.Dial(ctx, tt.inMsg) + if (err != nil) != tt.wantErr { + t.Fatalf("DialContext(): got unexpected error, err: %v, wantErr? %v", err, tt.wantErr) + } + + t.Logf("got response: %s", prototext.Format(got)) + if (got != nil) != tt.wantResp { + t.Fatalf("Dial: did not get correct response, got: %s, wantResponse? %v", prototext.Format(got), tt.wantResp) + } + }) + } +} + +// TestConnectRemote implements CNTR-4, validating that it is possible for a container to connect to a container +// on an adjacent node via gRPC using IPv6 link local addresses. r0 and r1 in the topology are configured with +// IPv6 link-local addresses via gNMI, and the CNTR service is used to trigger a connection between the two addresses. +// +// The test is repeated for r0 --> r1 and r1 --> r0. +func TestConnectRemote(t *testing.T) { + configureIPv6Addr := func(dut *ondatra.DUTDevice, name, addr string) { + pn := dut.Port(t, "port1").Name() + + d := &oc.Interface{ + Name: ygot.String(pn), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Enabled: ygot.Bool(true), + } + s := d.GetOrCreateSubinterface(0) + s.GetOrCreateIpv4().Enabled = ygot.Bool(true) + v6 := s.GetOrCreateIpv6() + // TODO(robjs): Clarify whether IPv4 enabled is required here for multiple + // targets, otherwise add a deviation. + v6.Enabled = ygot.Bool(true) + a := v6.GetOrCreateAddress(addr) + a.PrefixLength = ygot.Uint8(64) + a.Type = oc.IfIp_Ipv6AddressType_LINK_LOCAL_UNICAST + gnmi.Replace(t, dut, gnmi.OC().Interface(pn).Config(), d) + + time.Sleep(1 * time.Second) + } + + configureIPv6Addr(ondatra.DUT(t, "r0"), "port1", "fe80::cafe:1") + configureIPv6Addr(ondatra.DUT(t, "r1"), "port1", "fe80::cafe:2") + + validateIPv6Present := func(dut *ondatra.DUTDevice, name string) { + // Check that there is a configured IPv6 address on the interface. + // TODO(robjs): Validate expectations as to whether autoconf link-local is returned + // here. + v6addr := gnmi.GetAll(t, dut, gnmi.OC().Interface(dut.Port(t, name).Name()).SubinterfaceAny().Ipv6().AddressAny().State()) + if len(v6addr) < 1 { + t.Fatalf("%s: did not get a configured IPv6 address, got: %d (%s), want: 1", dut.Name(), len(v6addr), pretty.Sprint(v6addr)) + } + } + + validateIPv6Present(ondatra.DUT(t, "r0"), "port1") + validateIPv6Present(ondatra.DUT(t, "r1"), "port1") + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + conn, stop := DialService(ctx, t, "cntr", ondatra.DUT(t, "r1")) + defer stop() + + containerInterfaceName := func(t *testing.T, d *ondatra.DUTDevice, port *ondatra.Port) string { + switch d.Vendor() { + case ondatra.ARISTA: + switch { + case strings.HasPrefix(port.Name(), "Ethernet"): + num, _ := strings.CutPrefix(port.Name(), "Ethernet") + return fmt.Sprintf("eth%s", num) + } + } + t.Fatalf("cannot resolve interface name into Linux interface name, %s -> %s", d.Vendor(), port.Name()) + return "" + } + + tests := []struct { + inDUT *ondatra.DUTDevice + inRemoteAddr string + }{{ + inDUT: ondatra.DUT(t, "r1"), + inRemoteAddr: "fe80::cafe:1", + }, { + inDUT: ondatra.DUT(t, "r0"), + inRemoteAddr: "fe80::cafe:2", + }} + + for _, tt := range tests { + t.Run(fmt.Sprintf("dial from %s to %s", tt.inDUT, tt.inRemoteAddr), func(t *testing.T) { + dialAddr := fmt.Sprintf("[%s%%25%s]:60061", tt.inRemoteAddr, containerInterfaceName(t, tt.inDUT, tt.inDUT.Port(t, "port1"))) + t.Logf("dialing remote address %s", dialAddr) + client := cpb.NewCntrClient(conn) + got, err := client.Dial(ctx, &cpb.DialRequest{ + Addr: dialAddr, + Request: &cpb.DialRequest_Ping{ + Ping: &cpb.PingRequest{}, + }, + }) + if err != nil { + t.Fatalf("could not make request to remote device, got err: %v", err) + } + t.Logf("got response, %s", prototext.Format(got)) + }) + } +} diff --git a/feature/container/networking/tests/container_connectivity/metadata.textproto b/feature/container/networking/tests/container_connectivity/metadata.textproto new file mode 100644 index 00000000000..e7fa94d3853 --- /dev/null +++ b/feature/container/networking/tests/container_connectivity/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "dc9a9682-8ec1-4ca0-8f54-635d690bb488" +plan_id: "CNTR-2" +description: "Container network connectivity tests" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/example/tests/topology_test/topology_test.go b/feature/example/tests/topology_test/topology_test.go index 7f635620201..2e688c08809 100644 --- a/feature/example/tests/topology_test/topology_test.go +++ b/feature/example/tests/topology_test/topology_test.go @@ -145,7 +145,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { in := top.Ports().Add().SetName(ap.ID()) dev := top.Devices().Add().SetName(ap.Name() + ".dev") eth := dev.Ethernets().Add().SetName(ap.Name() + ".eth").SetMac(atePortMac(i)) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(in.Name()) + eth.Connection().SetPortName(in.Name()) ipv4Addr := strings.Split(atePortCIDR(i), "/")[0] eth.Ipv4Addresses().Add().SetName(dev.Name() + ".ipv4"). SetAddress(ipv4Addr).SetGateway(dutPortIP(i)). diff --git a/feature/experimental/basic_entries_installed_in_gribi/gribi_ip4_entry/feature.textproto b/feature/experimental/basic_entries_installed_in_gribi/gribi_ip4_entry/feature.textproto deleted file mode 100644 index ed5c0b71946..00000000000 --- a/feature/experimental/basic_entries_installed_in_gribi/gribi_ip4_entry/feature.textproto +++ /dev/null @@ -1,67 +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. - -id { - name: "experimental_basic_entries_installed_in_gribi_gribi_ip4_entry" - version: 1 -} - -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/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/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/state/id" -} -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/interface-ref/state/interface" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/subinterface" -} -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/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/ipv4-unicast/ipv4-entry/state/decapsulate-header" -} -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/counters/packets-forwarded" -} diff --git a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/README.md b/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/README.md deleted file mode 100644 index 724e759f578..00000000000 --- a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# RT-1.19: BGP 2-Byte and 4-Byte ASN support - -## Summary - -BGP 2-Byte and 4-Byte ASN support - -## Procedure - -* Establish BGP sessions as follows and verify all the sessions are established - * ATE (2-byte) - DUT (4-byte) - eBGP IPv4 with ASN < 65535 on DUT side - * ATE (2-byte) - DUT (4-byte) - eBGP IPv6 with ASN < 65535 on DUT side - * ATE (4-byte) - DUT (4-byte) - eBGP IPv4 - * ATE (4-byte) - DUT (4-byte) - eBGP IPv6 - * ATE (2-byte) - DUT (4-byte) - iBGP IPv4 with ASN < 65535 on DUT side - * ATE (4-byte) - DUT (4-byte) - iBGP IPv6 with ASN < 65535 on DUT side - * ATE (4-byte) - DUT (4-byte) - iBGP IPv4 - * ATE (4-byte) - DUT (4-byte) - iBGP IPv6 - -## Config Parameter Coverage - -* /global/config/as -* /neighbors/neighbor/config/peer-as -* /neighbors/neighbor/config/local-as - -## Telemetry Parameter Coverage - -* /global/config/as -* /neighbors/neighbor/config/peer-as -* /neighbors/neighbor/config/local-as diff --git a/feature/experimental/bgp/ate_tests/bgp_always_compare_med/README.md b/feature/experimental/bgp/ate_tests/bgp_always_compare_med/README.md deleted file mode 100644 index a31cfab6f28..00000000000 --- a/feature/experimental/bgp/ate_tests/bgp_always_compare_med/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# RT-1.12: BGP always compare MED - -## Summary - -BGP always compare MED - -## Procedure - -* Establish BGP sessions as follows between ATE and DUT. - * ATE emulates three eBGP neighbors peering the DUT. - * DUT Port1 (AS 65501) ---iBGP 1--- ATE Port1 (AS 65501) - * DUT Port2 (AS 65501) ---eBGP 1--- ATE Port2 (AS 65502) - * DUT Port3 (AS 65501) ---eBGP 2--- ATE Port3 (AS 65503) -* Associate eBGP neighbors #1 and #2 with MED values of 100 and 50 on the advertised routes. -* Enable “always-compare-med” knob on the DUT. -* Validate traffic flowing to the prefixes received from eBGP neighbor #2 from DUT (ATE Port3). -* Disable MED settings on DUT and ATE ports. -* Validate the change of traffic flow because of the change (ATE Port2). -* Validate session state and capabilities received on DUT using telemetry. - -## Config Parameter coverage - -* /route-selection-options/config/always-compare-med -* /global/afi-safis/afi-safi/route-selection-options/config/always-compare-med -* /global/route-selection-options/config/always-compare-med - -## Telemetry Parameter coverage - -* /global/afi-safis/afi-safi/route-selection-options/state/always-compare-med -* /global/route-selection-options/state/always-compare-med - -## Protocol/RPC Parameter coverage - -N/A - -## Minimum DUT platform requirement - -N/A diff --git a/feature/experimental/bgp/ate_tests/bgp_always_compare_med/bgp_always_compare_med_test.go b/feature/experimental/bgp/ate_tests/bgp_always_compare_med/bgp_always_compare_med_test.go deleted file mode 100644 index a47e8dfbed3..00000000000 --- a/feature/experimental/bgp/ate_tests/bgp_always_compare_med/bgp_always_compare_med_test.go +++ /dev/null @@ -1,563 +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 -// -// 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_always_compare_med_test - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/openconfig/featureprofiles/internal/attrs" - "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/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -const ( - trafficDuration = 1 * time.Minute - ipv4SrcTraffic = "192.0.2.2" - advertisedRoutesv4CIDR = "203.0.113.1/32" - ipv4DstTrafficStart = "203.0.113.1" - ipv4DstTrafficEnd = "203.0.113.254" - peerGrpName1 = "BGP-PEER-GROUP1" - peerGrpName2 = "BGP-PEER-GROUP2" - peerGrpName3 = "BGP-PEER-GROUP3" - tolerancePct = 2 - tolerance = 50 - routeCount = 254 - dutAS = 65501 - ateAS1 = 65501 - ateAS2 = 65502 - ateAS3 = 65503 - plenIPv4 = 30 - plenIPv6 = 126 - setMEDPolicy100 = "SET-MED-100" - setMEDPolicy50 = "SET-MED-50" - rplAllowPolicy = "ALLOW" - aclStatement20 = "20" - aclStatement30 = "30" - bgpMED100 = 100 - bgpMED50 = 50 - wantLoss = true - flow1 = "flowPort1toPort2" - flow2 = "flowPort1toPort3" -) - -var ( - dutSrc = attrs.Attributes{ - Desc: "DUT to ATE source", - IPv4: "192.0.2.1", - IPv6: "2001:db8::192:0:2:1", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - ateSrc = attrs.Attributes{ - Name: "ateSrc", - IPv4: "192.0.2.2", - IPv6: "2001:db8::192:0:2:2", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - dutDst1 = attrs.Attributes{ - Desc: "DUT to ATE destination 1", - IPv4: "192.0.2.5", - IPv6: "2001:db8::192:0:2:5", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - ateDst1 = attrs.Attributes{ - Name: "atedst1", - IPv4: "192.0.2.6", - IPv6: "2001:db8::192:0:2:6", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - dutDst2 = attrs.Attributes{ - Desc: "DUT to ATE destination 2", - IPv4: "192.0.2.9", - IPv6: "2001:db8::192:0:2:9", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - ateDst2 = attrs.Attributes{ - Name: "atedst2", - IPv4: "192.0.2.10", - IPv6: "2001:db8::192:0:2:10", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } -) - -// configureDUT configures all the interfaces on the DUT. -func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { - dc := gnmi.OC() - i1 := dutSrc.NewOCInterface(dut.Port(t, "port1").Name(), dut) - gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) - - i2 := dutDst1.NewOCInterface(dut.Port(t, "port2").Name(), dut) - gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) - - i3 := dutDst2.NewOCInterface(dut.Port(t, "port3").Name(), dut) - gnmi.Replace(t, dut, dc.Interface(i3.GetName()).Config(), i3) - - if deviations.ExplicitPortSpeed(dut) { - fptest.SetPortSpeed(t, dut.Port(t, "port1")) - fptest.SetPortSpeed(t, dut.Port(t, "port2")) - fptest.SetPortSpeed(t, dut.Port(t, "port3")) - } - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance(t, dut, i1.GetName(), deviations.DefaultNetworkInstance(dut), 0) - fptest.AssignToNetworkInstance(t, dut, i2.GetName(), deviations.DefaultNetworkInstance(dut), 0) - fptest.AssignToNetworkInstance(t, dut, i3.GetName(), deviations.DefaultNetworkInstance(dut), 0) - } -} - -// 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) - } - } -} - -// bgpCreateNbr creates a BGP object with neighbors pointing to ateSrc and ateDst. -func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { - nbr1v4 := &bgpNeighbor{as: ateAS1, neighborip: ateSrc.IPv4, isV4: true, peerGrp: peerGrpName1} - nbr2v4 := &bgpNeighbor{as: ateAS2, neighborip: ateDst1.IPv4, isV4: true, peerGrp: peerGrpName2} - nbr3v4 := &bgpNeighbor{as: ateAS3, neighborip: ateDst2.IPv4, isV4: true, peerGrp: peerGrpName3} - nbrs := []*bgpNeighbor{nbr1v4, nbr2v4, nbr3v4} - - dutOcRoot := &oc.Root{} - ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) - niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - bgp := niProto.GetOrCreateBgp() - global := bgp.GetOrCreateGlobal() - global.RouterId = ygot.String(dutDst2.IPv4) - global.As = ygot.Uint32(localAs) - 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. - pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) - pg1.PeerAs = ygot.Uint32(ateAS1) - pg1.PeerGroupName = ygot.String(peerGrpName1) - - pg2 := bgp.GetOrCreatePeerGroup(peerGrpName2) - pg2.PeerAs = ygot.Uint32(ateAS2) - pg2.PeerGroupName = ygot.String(peerGrpName2) - - pg3 := bgp.GetOrCreatePeerGroup(peerGrpName3) - pg3.PeerAs = ygot.Uint32(ateAS3) - pg3.PeerGroupName = ygot.String(peerGrpName3) - - if deviations.RoutePolicyUnderAFIUnsupported(dut) { - rp2 := pg2.GetOrCreateApplyPolicy() - rp2.SetImportPolicy([]string{rplAllowPolicy}) - - rp3 := pg3.GetOrCreateApplyPolicy() - rp3.SetImportPolicy([]string{rplAllowPolicy}) - - } else { - - pg2af4 := pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pg2af4.Enabled = ygot.Bool(true) - - pg2rpl4 := pg2af4.GetOrCreateApplyPolicy() - pg2rpl4.SetImportPolicy([]string{rplAllowPolicy}) - - pg3af4 := pg3.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pg3af4.Enabled = ygot.Bool(true) - - pg3rpl4 := pg3af4.GetOrCreateApplyPolicy() - pg3rpl4.SetImportPolicy([]string{rplAllowPolicy}) - } - - for _, nbr := range nbrs { - if nbr.isV4 { - nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) - nv4.PeerGroup = ygot.String(nbr.peerGrp) - nv4.PeerAs = ygot.Uint32(nbr.as) - nv4.Enabled = ygot.Bool(true) - af4 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - af4.Enabled = ygot.Bool(true) - af6 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) - af6.Enabled = ygot.Bool(false) - } - } - return niProto -} - -// verifyBgpTelemetry checks that the dut has an established BGP session with reasonable settings. -func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { - var nbrIP = []string{ateSrc.IPv4, ateDst1.IPv4, ateDst2.IPv4} - t.Logf("Verifying BGP state.") - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - - for _, nbr := range nbrIP { - nbrPath := bgpPath.Neighbor(nbr) - // Get BGP adjacency state. - t.Logf("Waiting for BGP neighbor to establish...") - var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] - status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { - state, ok := val.Val() - return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED - }).Await(t) - if !ok { - fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) - t.Fatal("No BGP neighbor formed") - } - state, _ := status.Val() - t.Logf("BGP adjacency for %s: %v", nbr, state) - if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { - t.Errorf("BGP peer %s status got %d, want %d", nbr, state, want) - } - } -} - -// configureATE configures the interfaces and BGP protocols on an ATE, including -// advertising some(faked) networks over BGP. -func configureATE(t *testing.T, ate *ondatra.ATEDevice) []*ondatra.Flow { - topo := ate.Topology().New() - - port1 := ate.Port(t, "port1") - iDut1 := topo.AddInterface(ateSrc.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateSrc.IPv4CIDR()).WithDefaultGateway(dutSrc.IPv4) - - port2 := ate.Port(t, "port2") - iDut2 := topo.AddInterface(ateDst1.Name).WithPort(port2) - iDut2.IPv4().WithAddress(ateDst1.IPv4CIDR()).WithDefaultGateway(dutDst1.IPv4) - - port3 := ate.Port(t, "port3") - iDut3 := topo.AddInterface(ateDst2.Name).WithPort(port3) - iDut3.IPv4().WithAddress(ateDst2.IPv4CIDR()).WithDefaultGateway(dutDst2.IPv4) - - // Setup ATE BGP route v4 advertisement. - bgpDut1 := iDut1.BGP() - bgpDut1.AddPeer().WithPeerAddress(dutSrc.IPv4).WithLocalASN(ateAS1). - WithTypeInternal() - - bgpDut2 := iDut2.BGP() - bgpDut2.AddPeer().WithPeerAddress(dutDst1.IPv4).WithLocalASN(ateAS2). - WithTypeExternal() - - bgpDut3 := iDut3.BGP() - bgpDut3.AddPeer().WithPeerAddress(dutDst2.IPv4).WithLocalASN(ateAS3). - WithTypeExternal() - - bgpNeti1 := iDut2.AddNetwork("bgpNeti1") // Advertise same prefixes from both eBGP Peers. - bgpNeti1.IPv4().WithAddress(advertisedRoutesv4CIDR).WithCount(routeCount) - bgpNeti1.BGP().WithNextHopAddress(ateDst1.IPv4) - - bgpNeti2 := iDut3.AddNetwork("bgpNeti2") // Advertise same prefixes from both eBGP Peers. - bgpNeti2.IPv4().WithAddress(advertisedRoutesv4CIDR).WithCount(routeCount) - bgpNeti2.BGP().WithNextHopAddress(ateDst2.IPv4) - - t.Logf("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) - - // ATE Traffic Configuration. - ethHeader := ondatra.NewEthernetHeader() - // BGP V4 Traffic. - ipv4Header := ondatra.NewIPv4Header() - ipv4Header.WithSrcAddress(ipv4SrcTraffic).DstAddressRange(). - WithMin(ipv4DstTrafficStart).WithMax(ipv4DstTrafficEnd). - WithCount(routeCount) - flowipv41 := ate.Traffic().NewFlow(flow1). - WithSrcEndpoints(iDut1). - WithDstEndpoints(iDut2). - WithHeaders(ethHeader, ipv4Header). - WithFrameSize(512) - flowipv42 := ate.Traffic().NewFlow(flow2). - WithSrcEndpoints(iDut1). - WithDstEndpoints(iDut3). - WithHeaders(ethHeader, ipv4Header). - WithFrameSize(512) - return []*ondatra.Flow{flowipv41, flowipv42} -} - -// verifyTraffic confirms that every traffic flow has the expected amount of loss (0% or 100% -// depending on wantLoss, +- 2%). -func verifyTraffic(t *testing.T, ate *ondatra.ATEDevice, flowName string, wantLoss bool) { - // Compare traffic loss based on wantLoss. - lossPct := gnmi.Get(t, ate, gnmi.OC().Flow(flowName).LossPct().State()) - if wantLoss { - if lossPct < 100-tolerancePct { - t.Errorf("Traffic is expected to fail %s\n got %v, want 100%% failure", flowName, lossPct) - } else { - t.Logf("Traffic Loss Test Passed!") - } - } else { - if lossPct > tolerancePct { - t.Errorf("Traffic Loss Pct for Flow: %s\n got %v, want 0", flowName, lossPct) - } else { - t.Logf("Traffic Test Passed!") - } - } -} - -// sendTraffic is used to send traffic. -func sendTraffic(t *testing.T, ate *ondatra.ATEDevice, allFlows []*ondatra.Flow) { - t.Logf("Starting traffic.") - ate.Traffic().Start(t, allFlows...) - time.Sleep(trafficDuration) - ate.Traffic().Stop(t) - t.Logf("Stop traffic.") -} - -// setMED is used to configure routing policy to set BGP MED on DUT. -func setMED(t *testing.T, dut *ondatra.DUTDevice, d *oc.Root) { - - dutPolicyConfPath2 := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)). - Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp(). - PeerGroup(peerGrpName2) - - dutPolicyConfPath3 := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)). - Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp(). - PeerGroup(peerGrpName3) - - // Apply setMed import policy on eBGP Peer1 - ATE Port2 - with MED 100. - // Apply setMed Import policy on eBGP Peer2 - ATE Port3 - with MED 50. - if deviations.RoutePolicyUnderAFIUnsupported(dut) { - gnmi.Replace(t, dut, dutPolicyConfPath2.ApplyPolicy().ImportPolicy().Config(), []string{setMEDPolicy100}) - gnmi.Replace(t, dut, dutPolicyConfPath3.ApplyPolicy().ImportPolicy().Config(), []string{setMEDPolicy50}) - } else { - gnmi.Replace(t, dut, dutPolicyConfPath2.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST). - ApplyPolicy().ImportPolicy().Config(), []string{setMEDPolicy100}) - gnmi.Replace(t, dut, dutPolicyConfPath3.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST). - ApplyPolicy().ImportPolicy().Config(), []string{setMEDPolicy50}) - } -} - -// configPolicy is used to configure routing policies on the DUT. -func configPolicy(t *testing.T, dut *ondatra.DUTDevice, d *oc.Root) { - - rp := d.GetOrCreateRoutingPolicy() - - pdef1 := rp.GetOrCreatePolicyDefinition(setMEDPolicy100) - st, err := pdef1.AppendNewStatement(aclStatement20) - if err != nil { - t.Fatal(err) - } - actions1 := st.GetOrCreateActions() - actions1.GetOrCreateBgpActions().SetMed = oc.UnionUint32(bgpMED100) - if deviations.BGPSetMedRequiresEqualOspfSetMetric(dut) { - actions1.GetOrCreateOspfActions().GetOrCreateSetMetric().SetMetric(bgpMED100) - } - actions1.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - - pdef2 := rp.GetOrCreatePolicyDefinition(setMEDPolicy50) - st, err = pdef2.AppendNewStatement(aclStatement20) - if err != nil { - t.Fatal(err) - } - actions2 := st.GetOrCreateActions() - actions2.GetOrCreateBgpActions().SetMed = oc.UnionUint32(bgpMED50) - if deviations.BGPSetMedRequiresEqualOspfSetMetric(dut) { - actions2.GetOrCreateOspfActions().GetOrCreateSetMetric().SetMetric(bgpMED50) - } - actions2.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - - pdef3 := rp.GetOrCreatePolicyDefinition(rplAllowPolicy) - st, err = pdef3.AppendNewStatement("id-1") - if err != nil { - t.Fatal(err) - } - action3 := st.GetOrCreateActions() - action3.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) -} - -// verifySetMed is used to validate MED on received prefixes at ATE Port1. -func verifySetMed(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, wantMEDValue uint32) { - at := gnmi.OC() - - rib := at.NetworkInstance(ateSrc.Name).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "0").Bgp().Rib() - prefixPath := rib.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast(). - NeighborAny().AdjRibInPre().RouteAny().WithPathId(0).Prefix() - - gnmi.WatchAll(t, ate, prefixPath.State(), time.Minute, func(v *ygnmi.Value[string]) bool { - _, present := v.Val() - return present - }).Await(t) - - wantMED := []uint32{} - // Build wantMED to compare the diff. - for i := 0; i < routeCount; i++ { - wantMED = append(wantMED, uint32(wantMEDValue)) - } - - gotMED := gnmi.GetAll(t, ate, rib.AttrSetAny().Med().State()) - if diff := cmp.Diff(wantMED, gotMED); diff != "" { - t.Errorf("Obtained MED on ATE is not as expected, got %v, want %v", gotMED, wantMED) - } -} - -// verifyBGPCapabilities is used to Verify BGP capabilities like route refresh as32 and mpbgp. -func verifyBGPCapabilities(t *testing.T, dut *ondatra.DUTDevice) { - t.Log("Verifying BGP capabilities.") - var nbrIP = []string{ateSrc.IPv4, ateDst1.IPv4, ateDst2.IPv4} - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - for _, nbr := range nbrIP { - nbrPath := statePath.Neighbor(nbr) - capabilities := map[oc.E_BgpTypes_BGP_CAPABILITY]bool{ - oc.BgpTypes_BGP_CAPABILITY_ROUTE_REFRESH: 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) - } - } - } -} - -// verifyPrefixesTelemetry confirms that the dut shows the correct numbers of installed, -// sent and received IPv4 prefixes. -func verifyPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, wantInstalled, wantSent uint32) { - 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, 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) - } - if gotSent, ok := gnmi.Watch(t, dut, prefixesv4.Sent().State(), time.Minute, func(v *ygnmi.Value[uint32]) bool { - got, ok := v.Val() - return ok && got == wantSent - }).Await(t); !ok { - t.Errorf("Sent prefixes mismatch: got %v, want %v", gotSent, wantSent) - } -} - -type bgpNeighbor struct { - as uint32 - neighborip string - isV4 bool - peerGrp string -} - -// TestRemovePrivateAS is to Validate that private AS numbers are stripped -// before advertisement to the eBGP neighbor. -func TestAlwaysCompareMED(t *testing.T) { - t.Logf("Start DUT config load.") - dut := ondatra.DUT(t, "dut") - ate := ondatra.ATE(t, "ate") - d := &oc.Root{} - - t.Run("Configure DUT interfaces", func(t *testing.T) { - t.Logf("Start DUT interface Config.") - configureDUT(t, dut) - }) - - t.Run("Configure DEFAULT network instance", func(t *testing.T) { - t.Log("Configure Network Instance type.") - fptest.ConfigureDefaultNetworkInstance(t, dut) - }) - - dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - t.Run("Configure BGP Neighbors", func(t *testing.T) { - t.Logf("Start DUT BGP Config.") - gnmi.Delete(t, dut, dutConfPath.Config()) - configPolicy(t, dut, d) - dutConf := bgpCreateNbr(dutAS, ateAS1, dut) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) - }) - - var allFlows []*ondatra.Flow - t.Run("Configure ATE", func(t *testing.T) { - t.Logf("Start ATE Config.") - allFlows = configureATE(t, ate) - }) - - t.Run("Verify port status on DUT", func(t *testing.T) { - t.Log("Verifying port status.") - verifyPortsUp(t, dut.Device) - }) - - t.Run("Verify BGP telemetry", func(t *testing.T) { - t.Log("Check BGP parameters.") - verifyBgpTelemetry(t, dut) - t.Log("Check BGP Capabilities") - verifyBGPCapabilities(t, dut) - }) - - t.Run("Configure SET MED on DUT", func(t *testing.T) { - setMED(t, dut, d) - }) - - t.Run("Configure always compare med on DUT", func(t *testing.T) { - t.Log("Configure always compare med on DUT.") - gnmi.Replace(t, dut, dutConfPath.Bgp().Global().RouteSelectionOptions().AlwaysCompareMed().Config(), true) - }) - - t.Run("Verify received BGP routes at ATE Port 1 have lowest MED", func(t *testing.T) { - t.Log("Verify BGP prefix telemetry.") - verifyPrefixesTelemetry(t, dut, 0, routeCount) - t.Log("Verify best route advertised to atePort1 is Peer with lowest MED 50 - eBGP Peer2.") - verifySetMed(t, dut, ate, bgpMED50) - }) - - t.Run("Send and validate traffic from ATE Port1", func(t *testing.T) { - t.Log("Validate traffic flowing to the prefixes received from eBGP neighbor #2 from DUT (lowest MED-50).") - sendTraffic(t, ate, allFlows) - verifyTraffic(t, ate, flow1, wantLoss) - verifyTraffic(t, ate, flow2, !wantLoss) - }) - - t.Run("Remove MED settings on DUT", func(t *testing.T) { - t.Log("Disable MED settings on DUT.") - dutPolicyConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - if deviations.RoutePolicyUnderAFIUnsupported(dut) { - gnmi.Replace(t, dut, dutPolicyConfPath.PeerGroup(peerGrpName2).ApplyPolicy().ImportPolicy().Config(), []string{rplAllowPolicy}) - gnmi.Replace(t, dut, dutPolicyConfPath.PeerGroup(peerGrpName3).ApplyPolicy().ImportPolicy().Config(), []string{rplAllowPolicy}) - } else { - gnmi.Replace(t, dut, dutPolicyConfPath.PeerGroup(peerGrpName2).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ImportPolicy().Config(), []string{rplAllowPolicy}) - gnmi.Replace(t, dut, dutPolicyConfPath.PeerGroup(peerGrpName3).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ImportPolicy().Config(), []string{rplAllowPolicy}) - } - - }) - - t.Run("Verify MED on received routes at ATE Port1 after removing MED settings", func(t *testing.T) { - t.Log("Verify BGP prefix telemetry.") - verifyPrefixesTelemetry(t, dut, 0, routeCount) - t.Log("Verify best route advertised to atePort1.") - verifySetMed(t, dut, ate, uint32(0)) - }) - - t.Run("Send and verify traffic after removing MED settings on DUT", func(t *testing.T) { - t.Log("Validate traffic change due to change in MED settings - Best route changes.") - sendTraffic(t, ate, allFlows) - verifyTraffic(t, ate, flow1, !wantLoss) - verifyTraffic(t, ate, flow2, wantLoss) - }) -} diff --git a/feature/experimental/bgp/ate_tests/bgp_remove_private_as/README.md b/feature/experimental/bgp/ate_tests/bgp_remove_private_as/README.md deleted file mode 100644 index 3cf8f73d886..00000000000 --- a/feature/experimental/bgp/ate_tests/bgp_remove_private_as/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# RT-1.11: BGP remove private AS  - -## Summary - -BGP remove private AS - -## Procedure - -* Establish BGP sessions as follows between ATE and DUT. - * ATE emulates two eBGP neighbors peering with the DUT using public AS numbers. - * DUT Port1 (AS 500) ---eBGP--- ATE Port1 (AS 100) - * DUT Port2 (AS 500) ---eBGP--- ATE Port2 (AS 200) - * Inject routes with AS_PATH modified to have private AS number 65501 from eBGP neighbor #1 - (ATE Port1). - * Validate received routes on ATE Port2 should have AS Path "500 100 65501". - * Configure "remove private AS" with type PRIVATE_AS_REMOVE_ALL on DUT. - * Validate that private AS numbers are stripped before advertisement to the eBGP peer ATE Port2. - * AS path for received routes on ATE Port2 should be "500 100". - * TODO: different patterns of private AS should be tested. - * AS Path SEQ - 65501, 65507, 65554 - * AS Path SEQ - 65501, 600 - * AS Path SEQ - 800, 65501, 600 - ## TODO : https://github.com/openconfig/featureprofiles/issues/1659 - ## SET mode is not working in ATE. - * AS Path SET - 800, 65505, 600 - -## Config Parameter coverage - -* /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/remove-private-as - -## Telemetry Parameter coverage - -* /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as4-path/as4-segment/state - -## Protocol/RPC Parameter coverage - -N/A - -## Minimum DUT platform requirement - -N/A \ No newline at end of file diff --git a/feature/experimental/bgp/ate_tests/bgp_remove_private_as/bgp_remove_private_as_test.go b/feature/experimental/bgp/ate_tests/bgp_remove_private_as/bgp_remove_private_as_test.go deleted file mode 100644 index 0d785ad59c7..00000000000 --- a/feature/experimental/bgp/ate_tests/bgp_remove_private_as/bgp_remove_private_as_test.go +++ /dev/null @@ -1,394 +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 -// -// 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_remove_private_as_test - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/openconfig/featureprofiles/internal/attrs" - "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/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -// The testbed consists of ate:port1 -> dut:port1 and -// dut:port2 -> ate:port2. The first pair is called the "source" -// pair, and the second the "destination" pair. -// -// Source: ate:port1 -> dut:port1 subnet 192.0.2.0/30 -// Destination: dut:port2 -> ate:port2 subnet 192.0.2.4/30 -// -// Note that the first (.0, .3) and last (.4, .7) IPv4 addresses are -// reserved from the subnet for broadcast, so a /30 leaves exactly 2 -// usable addresses. This does not apply to IPv6 which allows /127 -// for point to point links, but we use /126 so the numbering is -// consistent with IPv4. - -const ( - trafficDuration = 1 * time.Minute - ipv4SrcTraffic = "192.0.2.2" - advertisedRoutesv4CIDR = "203.0.113.1/32" - peerGrpName1 = "BGP-PEER-GROUP1" - peerGrpName2 = "BGP-PEER-GROUP2" - policyName = "ALLOW" - routeCount = 254 - dutAS = 500 - ateAS1 = 100 - ateAS2 = 200 - plenIPv4 = 30 - plenIPv6 = 126 - removeASPath = true -) - -var ( - dutSrc = attrs.Attributes{ - Desc: "DUT to ATE source", - IPv4: "192.0.2.1", - IPv6: "2001:db8::192:0:2:1", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - ateSrc = attrs.Attributes{ - Name: "ateSrc", - IPv4: "192.0.2.2", - IPv6: "2001:db8::192:0:2:2", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - dutDst = attrs.Attributes{ - Desc: "DUT to ATE destination", - IPv4: "192.0.2.5", - IPv6: "2001:db8::192:0:2:5", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - ateDst = attrs.Attributes{ - Name: "atedst", - IPv4: "192.0.2.6", - IPv6: "2001:db8::192:0:2:6", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } -) - -// configureDUT configures all the interfaces on the DUT. -func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { - dc := gnmi.OC() - i1 := dutSrc.NewOCInterface(dut.Port(t, "port1").Name(), dut) - gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) - - i2 := dutDst.NewOCInterface(dut.Port(t, "port2").Name(), dut) - gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) -} - -// 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) - } - } -} - -// bgpCreateNbr creates a BGP object with neighbors pointing to ateSrc and ateDst. -func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { - nbr1v4 := &bgpNeighbor{as: ateAS1, neighborip: ateSrc.IPv4, isV4: true, peerGrp: peerGrpName1} - nbr2v4 := &bgpNeighbor{as: ateAS2, neighborip: ateDst.IPv4, isV4: true, peerGrp: peerGrpName2} - nbrs := []*bgpNeighbor{nbr1v4, nbr2v4} - - 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.RouterId = ygot.String(dutDst.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) - - // 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. - pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) - pg1.PeerAs = ygot.Uint32(ateAS1) - pg1.PeerGroupName = ygot.String(peerGrpName1) - - pg2 := bgp.GetOrCreatePeerGroup(peerGrpName2) - pg2.PeerAs = ygot.Uint32(ateAS2) - pg2.PeerGroupName = ygot.String(peerGrpName2) - - if deviations.RoutePolicyUnderAFIUnsupported(dut) { - rpl := pg1.GetOrCreateApplyPolicy() - rpl.ImportPolicy = []string{policyName} - rpl.ExportPolicy = []string{policyName} - - rp2 := pg2.GetOrCreateApplyPolicy() - rp2.ImportPolicy = []string{policyName} - rp2.ExportPolicy = []string{policyName} - } else { - pgaf := pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pgaf.Enabled = ygot.Bool(true) - rpl := pgaf.GetOrCreateApplyPolicy() - rpl.ImportPolicy = []string{policyName} - rpl.ExportPolicy = []string{policyName} - - pgaf2 := pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pgaf2.Enabled = ygot.Bool(true) - rp2 := pgaf2.GetOrCreateApplyPolicy() - rp2.ImportPolicy = []string{policyName} - rp2.ExportPolicy = []string{policyName} - } - - for _, nbr := range nbrs { - nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) - nv4.PeerGroup = ygot.String(nbr.peerGrp) - nv4.PeerAs = ygot.Uint32(nbr.as) - nv4.Enabled = ygot.Bool(true) - af4 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - af4.Enabled = ygot.Bool(true) - af6 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) - af6.Enabled = ygot.Bool(false) - } - return niProto -} - -// verifyBGPTelemetry checks that the dut has an established BGP session with reasonable settings. -func verifyBGPTelemetry(t *testing.T, dut *ondatra.DUTDevice) { - var nbrIP = []string{ateSrc.IPv4, ateDst.IPv4} - t.Logf("Verifying BGP state.") - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - for _, nbr := range nbrIP { - var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] - nbrPath := statePath.Neighbor(nbr) - t.Logf("Waiting for BGP neighbor to establish...") - status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { - state, ok := val.Val() - return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED - }).Await(t) - if !ok { - fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) - t.Fatal("No BGP neighbor formed") - } - state, _ := status.Val() - t.Logf("BGP adjacency for %s: %s", nbr, state) - if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { - t.Errorf("BGP peer %s status got %v, want %d", nbr, status, want) - } - } -} - -// verifyPrefixesTelemetry confirms that the dut shows the correct numbers of installed, -// sent and received IPv4 prefixes. -func verifyPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr string, wantInstalled, wantSent uint32) { - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - prefixesv4 := statePath.Neighbor(nbr).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) - } - if gotSent := gnmi.Get(t, dut, prefixesv4.Sent().State()); gotSent != wantSent { - t.Errorf("Sent prefixes mismatch: got %v, want %v", gotSent, wantSent) - } -} - -// configureATE configures the interfaces and BGP protocols on an ATE, including -// advertising some(faked) networks over BGP. -func configureATE(t *testing.T, ate *ondatra.ATEDevice, asSeg []uint32, asSEQMode bool) *ondatra.ATETopology { - port1 := ate.Port(t, "port1") - topo := ate.Topology().New() - iDut1 := topo.AddInterface(ateSrc.Name).WithPort(port1) - iDut1.IPv4().WithAddress(ateSrc.IPv4CIDR()).WithDefaultGateway(dutSrc.IPv4) - - port2 := ate.Port(t, "port2") - iDut2 := topo.AddInterface(ateDst.Name).WithPort(port2) - iDut2.IPv4().WithAddress(ateDst.IPv4CIDR()).WithDefaultGateway(dutDst.IPv4) - - // Setup ATE BGP route v4 advertisement. - bgpDut1 := iDut1.BGP() - bgpDut1.AddPeer().WithPeerAddress(dutSrc.IPv4).WithLocalASN(ateAS1). - WithTypeExternal() - - bgpDut2 := iDut2.BGP() - bgpDut2.AddPeer().WithPeerAddress(dutDst.IPv4).WithLocalASN(ateAS2). - WithTypeExternal() - - bgpNeti1 := iDut1.AddNetwork("bgpNeti1") - bgpNeti1.IPv4().WithAddress(advertisedRoutesv4CIDR).WithCount(routeCount) - bgpNeti1.BGP().WithNextHopAddress(ateSrc.IPv4) - - if asSEQMode { - bgpNeti1.BGP().AddASPathSegment(asSeg...).WithTypeSEQ() - } else { - // TODO : SET mode is not working - // https://github.com/openconfig/featureprofiles/issues/1659 - bgpNeti1.BGP().AddASPathSegment(asSeg...).WithTypeSET() - } - - t.Logf("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) - - return topo -} - -type bgpNeighbor struct { - as uint32 - neighborip string - isV4 bool - peerGrp string -} - -// verifyBGPAsPath is to Validate AS Path attribute using bgp rib telemetry on ATE. -func verifyBGPAsPath(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, asSeg []uint32, removeASPath bool) { - at := gnmi.OC() - rib := at.NetworkInstance(ateDst.Name).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "0").Bgp().Rib() - prefixPath := rib.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast(). - NeighborAny().AdjRibInPre().RouteAny().WithPathId(0).Prefix() - - gnmi.WatchAll(t, ate, prefixPath.State(), time.Minute, func(v *ygnmi.Value[string]) bool { - _, present := v.Val() - return present - }).Await(t) - - var wantASSeg = []uint32{dutAS, ateAS1} - - if removeASPath { - for _, as := range asSeg { - if as < 64512 { - wantASSeg = append(wantASSeg, as) - } - } - } else { - wantASSeg = append(wantASSeg, asSeg...) - } - - gotASSeg, ok := gnmi.WatchAll(t, ate, rib.AttrSetAny().AsSegmentMap().State(), 1*time.Minute, func(v *ygnmi.Value[map[uint32]*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet_AsSegment]) bool { - val, present := v.Val() - if present { - for _, as := range val { - if cmp.Equal(as.Member, wantASSeg) { - return true - } - } - } - return false - }).Await(t) - if !ok { - t.Errorf("Obtained AS path on ATE is not as expected, gotASSeg %v, wantASSeg %v", gotASSeg, wantASSeg) - } -} - -// 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) - } - stc := st.GetOrCreateConditions() - stc.InstallProtocolEq = oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP - st.GetOrCreateActions().PolicyResult = pr - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) -} - -// TestRemovePrivateAS is to Validate that private AS numbers are stripped -// before advertisement to the eBGP neighbor. -func TestRemovePrivateAS(t *testing.T) { - t.Logf("Start DUT config load.") - dut := ondatra.DUT(t, "dut") - - t.Run("Configure DUT interfaces", func(t *testing.T) { - t.Logf("Start DUT interface Config.") - configureDUT(t, dut) - }) - - t.Run("Configure DEFAULT network instance.", func(t *testing.T) { - t.Log("Configure Network Instance type to DEFAULT.") - dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) - gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) - }) - - dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - t.Run("Configure BGP Neighbors.", func(t *testing.T) { - t.Logf("Start DUT BGP Config.") - gnmi.Delete(t, dut, dutConfPath.Config()) - configureRoutePolicy(t, dut, policyName, oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) - dutConf := bgpCreateNbr(dutAS, ateAS1, dut) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) - }) - - cases := []struct { - desc string - asSeg []uint32 - asSEQMode bool - }{{ - desc: "AS Path SEQ - 65501, 65507, 65534", - asSeg: []uint32{65501, 65507, 65534}, - asSEQMode: true, - }, { - desc: "AS Path SEQ - 65501, 600", - asSeg: []uint32{65501, 600}, - asSEQMode: true, - }, { - desc: "AS Path SEQ - 800, 65501, 600", - asSeg: []uint32{800, 65501, 600}, - asSEQMode: true, - }} - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - t.Logf("Start ATE Config.") - ate := ondatra.ATE(t, "ate") - topo := configureATE(t, ate, tc.asSeg, tc.asSEQMode) - - t.Log("Verifying port status.") - verifyPortsUp(t, dut.Device) - - t.Log("Check BGP parameters.") - verifyBGPTelemetry(t, dut) - - t.Log("Verify BGP prefix telemetry.") - verifyPrefixesTelemetry(t, dut, ateSrc.IPv4, routeCount, 0) - verifyPrefixesTelemetry(t, dut, ateDst.IPv4, 0, routeCount) - - t.Log("Verify AS Path list received at ate Port2 including private AS number.") - verifyBGPAsPath(t, dut, ate, tc.asSeg, !removeASPath) - - t.Log("Configure remove private AS on DUT.") - gnmi.Update(t, dut, dutConfPath.Bgp().PeerGroup(peerGrpName2).RemovePrivateAs().Config(), oc.Bgp_RemovePrivateAsOption_PRIVATE_AS_REMOVE_ALL) - - t.Log("Private AS numbers should be stripped off while advertising BGP routes into public AS.") - verifyBGPAsPath(t, dut, ate, tc.asSeg, removeASPath) - - topo.StopProtocols(t) - - t.Log("Remove remove-private-AS on DUT.") - gnmi.Delete(t, dut, dutConfPath.Bgp().PeerGroup(peerGrpName2).RemovePrivateAs().Config()) - }) - } -} diff --git a/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/README.md b/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/README.md deleted file mode 100644 index b82f7c4bc20..00000000000 --- a/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# RT-1.23: BGP AFI SAFI OC DEFAULTS - -## Summary - -BGP AFI SAFI OC DEFAULTS TEST - -## Procedure - -* 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) - -* [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-3] 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. - -## Config Parameter coverage - -* /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/session-state -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/ - supported-capabilities -* /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/ - 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 - -## Protocol/RPC Parameter coverage - -N/A - -## Minimum DUT platform requirement - -N/A diff --git a/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/bgp_afi_safi_defaults_test.go b/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/bgp_afi_safi_defaults_test.go deleted file mode 100644 index 75c5ad1ae20..00000000000 --- a/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/bgp_afi_safi_defaults_test.go +++ /dev/null @@ -1,427 +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 -// -// 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_afi_safi_defaults_test - -import ( - "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/ondatra" - "github.com/openconfig/ondatra/gnmi" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ondatra/gnmi/oc/netinstbgp" - otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" - otg "github.com/openconfig/ondatra/otg" - "github.com/openconfig/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -const ( - trafficDuration = 1 * time.Minute - ipv4SrcTraffic = "192.0.2.2" - advertisedRoutesv4CIDR = "203.0.113.1/32" - advertisedRoutesv4Net = "203.0.113.1" - advertisedRoutesv4Prefix = 32 - ipv4DstTrafficStart = "203.0.113.1" - ipv4DstTrafficEnd = "203.0.113.254" - peerGrpName1 = "BGP-PEER-GROUP1" - peerGrpName2 = "BGP-PEER-GROUP2" - tolerancePct = 2 - tolerance = 50 - routeCount = 254 - dutAS = 65501 - ateAS = 65502 - plenIPv4 = 30 - plenIPv6 = 126 - nbrLevel = "NEIGHBOR" - peerGrpLevel = "PEER-GROUP" - globalLevel = "GLOBAL" -) - -var ( - dutPort1 = attrs.Attributes{ - Desc: "DUT to ATE Port1", - IPv4: "192.0.2.1", - IPv6: "2001:db8::192:0:2:1", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - atePort1 = attrs.Attributes{ - Name: "atePort1", - IPv4: "192.0.2.2", - IPv6: "2001:db8::192:0:2:2", - MAC: "02:00:01:01:01:01", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - dutPort2 = attrs.Attributes{ - Desc: "DUT to ATE Port2", - IPv4: "192.0.2.5", - IPv6: "2001:db8::192:0:2:5", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - atePort2 = attrs.Attributes{ - Name: "atePort2", - IPv4: "192.0.2.6", - IPv6: "2001:db8::192:0:2:6", - MAC: "02:00:02:01:01:01", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - - nbr1 = &bgpNeighbor{as: ateAS, neighborip: atePort1.IPv4, isV4: true, peerGrp: peerGrpName1} - nbr2 = &bgpNeighbor{as: ateAS, neighborip: atePort1.IPv6, isV4: false, peerGrp: peerGrpName2} - nbr3 = &bgpNeighbor{as: dutAS, neighborip: atePort2.IPv4, isV4: true, peerGrp: peerGrpName1} - nbr4 = &bgpNeighbor{as: dutAS, neighborip: atePort2.IPv6, isV4: false, peerGrp: peerGrpName2} -) - -// configureDUT configures all the interfaces on the DUT. -func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { - t.Helper() - dc := gnmi.OC() - i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) - gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) - - i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) - gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) -} - -// 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) - } - } -} - -// bgpCreateNbr creates a BGP object with neighbors pointing to ateSrc and ateDst. -func bgpCreateNbr(t *testing.T, localAs, peerAs uint32, dut *ondatra.DUTDevice, afiSafiLevel string) *oc.NetworkInstance_Protocol { - t.Helper() - nbrs := []*bgpNeighbor{nbr1, nbr2, nbr3, nbr4} - dutOcRoot := &oc.Root{} - ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) - niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - bgp := niProto.GetOrCreateBgp() - global := bgp.GetOrCreateGlobal() - global.RouterId = ygot.String(dutPort2.IPv4) - global.As = ygot.Uint32(localAs) - - // 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. - pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) // V4 peer group - pg1.PeerAs = ygot.Uint32(ateAS) - pg1.PeerGroupName = ygot.String(peerGrpName1) - - pg2 := bgp.GetOrCreatePeerGroup(peerGrpName2) // V6 peer group - pg2.PeerAs = ygot.Uint32(dutAS) - pg2.PeerGroupName = ygot.String(peerGrpName2) - - for _, nbr := range nbrs { - nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) - nv4.PeerGroup = ygot.String(nbr.peerGrp) - nv4.PeerAs = ygot.Uint32(nbr.as) - nv4.Enabled = ygot.Bool(true) - - switch afiSafiLevel { - case globalLevel: - 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) - extNh := global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateIpv4Unicast() - extNh.ExtendedNextHopEncoding = ygot.Bool(true) - if deviations.BGPGlobalExtendedNextHopEncodingUnsupported(dut) { - global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast = nil - } - case nbrLevel: - if nbr.isV4 == true { - 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) - } - case peerGrpLevel: - pg1af4 := pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pg1af4.Enabled = ygot.Bool(true) - ext1Nh := pg1af4.GetOrCreateIpv4Unicast() - ext1Nh.ExtendedNextHopEncoding = ygot.Bool(true) - pg1af6 := pg1.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) - pg1af6.Enabled = ygot.Bool(true) - - pg2af4 := pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pg2af4.Enabled = ygot.Bool(true) - ext2Nh := pg2af4.GetOrCreateIpv4Unicast() - ext2Nh.ExtendedNextHopEncoding = ygot.Bool(true) - pg2af6 := pg2.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) - pg2af6.Enabled = ygot.Bool(true) - } - } - return niProto -} - -func verifyOtgBgpTelemetry(t *testing.T, otg *otg.OTG, c gosnappi.Config, state string) { - t.Helper() - for _, d := range c.Devices().Items() { - for _, ip := range d.Bgp().Ipv4Interfaces().Items() { - for _, configPeer := range ip.Peers().Items() { - nbrPath := gnmi.OTG().BgpPeer(configPeer.Name()) - _, ok := gnmi.Watch(t, otg, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { - currState, ok := val.Val() - return ok && currState.String() == state - }).Await(t) - if !ok { - t.Errorf("No BGP neighbor formed for peer %s", configPeer.Name()) - } - } - } - for _, ip := range d.Bgp().Ipv6Interfaces().Items() { - for _, configPeer := range ip.Peers().Items() { - nbrPath := gnmi.OTG().BgpPeer(configPeer.Name()) - _, ok := gnmi.Watch(t, otg, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { - currState, ok := val.Val() - return ok && currState.String() == state - }).Await(t) - if !ok { - t.Errorf("No BGP neighbor formed for peer %s", configPeer.Name()) - } - } - } - } -} - -// verifyBgpTelemetry checks that the dut has an established BGP session with reasonable settings. -func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { - t.Helper() - var nbrIP = []string{atePort1.IPv4, atePort2.IPv4, atePort1.IPv6, atePort2.IPv6} - t.Logf("Verifying BGP state.") - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - - for _, nbr := range nbrIP { - nbrPath := bgpPath.Neighbor(nbr) - // Get BGP adjacency state. - t.Logf("Waiting for BGP neighbor to establish...") - status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { - state, ok := val.Val() - return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED - }).Await(t) - if !ok { - t.Fatal("No BGP neighbor formed") - } - state, _ := status.Val() - t.Logf("BGP adjacency for %s: %v", nbr, state) - if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { - t.Errorf("BGP peer %s status got %d, want %d", nbr, state, want) - } - } -} - -// configureOTG configures the interfaces and BGP protocols on an ATE, including -// advertising some(faked) networks over BGP. -func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { - t.Helper() - config := gosnappi.NewConfig() - port1 := config.Ports().Add().SetName("port1") - port2 := config.Ports().Add().SetName("port2") - - iDut1Dev := config.Devices().Add().SetName(atePort1.Name) - iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - iDut1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port1.Name()) - iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") - iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) - iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") - iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) - - iDut2Dev := config.Devices().Add().SetName(atePort2.Name) - iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) - iDut2Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port2.Name()) - iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") - iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) - iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") - iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) - - // BGP seesion - 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(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) - iDut1Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) - iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) - - iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") - iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) - iDut1Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) - iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) - - iDut2Bgp := iDut2Dev.Bgp().SetRouterId(iDut2Ipv4.Address()) - iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2Ipv4.Name()).Peers().Add().SetName(atePort2.Name + ".BGP4.peer") - iDut2Bgp4Peer.SetPeerAddress(iDut2Ipv4.Gateway()).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) - iDut2Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) - iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) - - iDut2Bgp6Peer := iDut2Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut2Ipv6.Name()).Peers().Add().SetName(atePort2.Name + ".BGP6.peer") - iDut2Bgp6Peer.SetPeerAddress(iDut2Ipv6.Gateway()).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) - iDut2Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) - iDut2Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) - - t.Logf("Pushing config to OTG and starting protocols...") - otg.PushConfig(t, config) - time.Sleep(30 * time.Second) - otg.StartProtocols(t) - time.Sleep(30 * time.Second) - - return config -} - -// verifyBGPCapabilities is used to Verify BGP capabilities like route refresh as32 and mpbgp. -func verifyBgpCapabilities(t *testing.T, dut *ondatra.DUTDevice, afiSafiLevel string) { - t.Helper() - t.Log("Verifying BGP AFI-SAFI capabilities.") - nbrs := []*bgpNeighbor{nbr1, nbr2, nbr3, nbr4} - - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - var nbrPath *netinstbgp.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafiPathAny - - for _, nbr := range nbrs { - nbrPath = statePath.Neighbor(nbr.neighborip).AfiSafiAny() - - capabilities := map[oc.E_BgpTypes_AFI_SAFI_TYPE]bool{ - oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST: false, - oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST: false, - } - - for _, cap := range gnmi.GetAll(t, dut, nbrPath.State()) { - capabilities[cap.GetAfiSafiName()] = cap.GetActive() - } - - switch afiSafiLevel { - case nbrLevel: - if nbr.isV4 && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST] { - t.Errorf("AFI_SAFI_TYPE_IPV6_UNICAST should not be enabled for v4 Peer: %v, %v", capabilities, nbr.neighborip) - } - if !nbr.isV4 && capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST] { - t.Errorf("AFI_SAFI_TYPE_IPV4_UNICAST should not be for v6 Peer: %v, %v", capabilities, nbr.neighborip) - } - t.Logf("Capabilities for peer %v are %v", nbr.neighborip, capabilities) - case peerGrpLevel: - if capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST] == true && - capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST] == true { - t.Logf("Both V4 and V6 AFI-SAFI are inherited from peer-group level for peer: %v, %v", nbr.neighborip, capabilities) - } else { - t.Errorf("Both V4 and V6 AFI-SAFI are not inherited from peer-group level for peer: %v, %v", nbr.neighborip, capabilities) - } - case globalLevel: - if capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST] == true && - capabilities[oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST] == true { - t.Logf("Both V4 and V6 AFI-SAFI are inherited from global level for peer: %v, %v", nbr.neighborip, capabilities) - } else { - t.Errorf("Both V4 and V6 AFI-SAFI are not inherited from gloval level for peer: %v, %v", nbr.neighborip, capabilities) - } - } - } -} - -// 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) -} - -type bgpNeighbor struct { - as uint32 - neighborip string - isV4 bool - peerGrp string -} - -// TestAfiSafiOcDefaults validates AFI-SAFI configuration enabled at neighbor, -// peer group and global levels. -func TestAfiSafiOcDefaults(t *testing.T) { - t.Logf("Start DUT config load.") - dut := ondatra.DUT(t, "dut") - ate := ondatra.ATE(t, "ate") - - t.Run("Configure DUT interfaces", func(t *testing.T) { - configureDUT(t, dut) - }) - - t.Run("Configure DEFAULT network instance", func(t *testing.T) { - dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) - gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) - }) - - dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - - cases := []struct { - desc string - afiSafiLevel string - }{{ - desc: "Validate AFI-SAFI OC defaults at neighbor level", - afiSafiLevel: nbrLevel, - }, { - desc: "Validate AFI-SAFI OC defaults at peer group level", - afiSafiLevel: peerGrpLevel, - }, { - desc: "Validate AFI-SAFI OC defaults at global level", - afiSafiLevel: globalLevel, - }} - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - t.Run("Configure BGP Neighbors", func(t *testing.T) { - bgpClearConfig(t, dut) - dutConf := bgpCreateNbr(t, dutAS, ateAS, dut, tc.afiSafiLevel) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) - }) - - otg := ate.OTG() - var otgConfig gosnappi.Config - t.Run("Configure OTG", func(t *testing.T) { - otgConfig = configureOTG(t, otg) - }) - - t.Run("Verify port status on DUT", func(t *testing.T) { - verifyPortsUp(t, dut.Device) - }) - - t.Run("Verify BGP telemetry", func(t *testing.T) { - verifyBgpTelemetry(t, dut) - verifyOtgBgpTelemetry(t, otg, otgConfig, "ESTABLISHED") - verifyBgpCapabilities(t, dut, tc.afiSafiLevel) - }) - }) - } -} diff --git a/feature/experimental/gnmi_service/benchmarking_drained_configuration_convergence_time/feature.textproto b/feature/experimental/gnmi_service/benchmarking_drained_configuration_convergence_time/feature.textproto deleted file mode 100644 index 9893a032bf4..00000000000 --- a/feature/experimental/gnmi_service/benchmarking_drained_configuration_convergence_time/feature.textproto +++ /dev/null @@ -1,28 +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. - -id { - name: "experimental_gnmi_service_benchmarking_drained_configuration_convergence_time" - version: 1 -} - -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med" -} -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n" -} -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn" -} diff --git a/feature/experimental/gnmi_service/telemetry_port_speed/feature.textproto b/feature/experimental/gnmi_service/telemetry_port_speed/feature.textproto deleted file mode 100644 index 29b1af29b18..00000000000 --- a/feature/experimental/gnmi_service/telemetry_port_speed/feature.textproto +++ /dev/null @@ -1,28 +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. - -id { - name: "experimental_gnmi_service_telemetry_port_speed" - version: 1 -} - -telemetry_path { - path: "/interfaces/interface/state/oper-status" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/port-speed" -} -telemetry_path { - path: "/interfaces/interface/aggregation/state/lag-speed" -} diff --git a/feature/experimental/hierarchical_gribi_entries/base_hierarchical_route_installation/feature.textproto b/feature/experimental/hierarchical_gribi_entries/base_hierarchical_route_installation/feature.textproto deleted file mode 100644 index 36675d5be9c..00000000000 --- a/feature/experimental/hierarchical_gribi_entries/base_hierarchical_route_installation/feature.textproto +++ /dev/null @@ -1,70 +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. - -id { - name: "experimental_hierarchical_gribi_entries_base_hierarchical_route_installation" - version: 1 -} - -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/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/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/state/id" -} -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/interface-ref/state/interface" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/subinterface" -} -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/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-hop-groups/next-hop-group/state/backup-next-hop-group" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/color" -} -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/pushed-mpls-label-stack" -} diff --git a/feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md b/feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md deleted file mode 100644 index c3996d72bcf..00000000000 --- a/feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# RT-5.6: Interface Loopback mode - -## Summary - -Ensure Interface mode can be set to loopback mode and can be added as part of static LAG. - -## Procedure - -### TestCase-1: - -* 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”. -* 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”. - -## Config Parameter Coverage - -* /interfaces/interface/config/loopback-mode -* /interfaces/interface/ethernet/config/port-speed -* /interfaces/interface/ethernet/config/duplex-mode -* /interfaces/interface/ethernet/config/aggregate-id -* /interfaces/interface/aggregation/config/lag-type -* /interfaces/interface/aggregation/config/min-links - -## Telemetry Parameter Coverage - -* /interfaces/interface/state/loopback-mode - -## Protocol/RPC Parameter Coverage - -None - -## Minimum DUT Platform Requirement - -vRX diff --git a/feature/experimental/isis/ate_tests/base_adjacencies_test/README.md b/feature/experimental/isis/ate_tests/base_adjacencies_test/README.md deleted file mode 100644 index ee4bd9875ad..00000000000 --- a/feature/experimental/isis/ate_tests/base_adjacencies_test/README.md +++ /dev/null @@ -1,168 +0,0 @@ -# RT-2.1: Base IS-IS Process and Adjacencies - -## Summary - -Base IS-IS functionality and adjacency establishment. - -## Procedure - -* Basic fields test - * Configure DUT:port1 for an IS-IS session with ATE:port1. - * Read back the configuration to ensure that all fields are readable and - have been set properly (or correctly have their default value). - * Check that all relevant counters are readable and are 0 since the - adjacency has not yet been established. - * Push ATE configuration for the other end of the adjacency, and wait for - the adjacency to form. - * Check that the various state fields of the adjacency are reported - correctly. - * Check that error counters are still 0 and that packet counters have all - increased. -* Hello padding test - * Configure IS-IS between DUT:port1 and ATE:port1 for each possible value - of hello padding (DISABLED, STRICT, etc.) - * Confirm in each case that that adjacency forms and the correct values - are reported back by the device. - * TODO: LOOSE padding test -* Authentication test - * Configure IS-IS between DUT:port1 and ATE:port1 With authentication - disabled, then enabled in TEXT mode, then enabled in MD5 mode. - * Confirm in each case that that adjacency forms and the correct values - are reported back by the device. -* Routing test - * With ISIS level authentication enabled and hello authentication enabled: - * Ensure that IPv4 and IPv6 prefixes that are advertised as attached - prefixes within each LSP are correctly installed into the DUT - routing table, by ensuring that packets are received to the attached - prefix when forwarded from ATE port-1. - * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an - (emulated) neighboring system are installed into the DUT routing - table, and validate that packets are sent and received to them. - * With a known LSP content, ensure that the telemetry received from the - device for the LSP matches the expected content. - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * TODO: global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/metric - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safis/afi-safi/state/metric - * interfaces/interface/levels/level/packet-counters/cnsp/dropped - * interfaces/interface/levels/level/packet-counters/cnsp/processed - * interfaces/interface/levels/level/packet-counters/cnsp/received - * interfaces/interface/levels/level/packet-counters/cnsp/sent - * interfaces/interface/levels/level/packet-counters/iih/dropped - * interfaces/interface/levels/level/packet-counters/iih/processed - * interfaces/interface/levels/level/packet-counters/iih/received - * interfaces/interface/levels/level/packet-counters/iih/retransmit - * interfaces/interface/levels/level/packet-counters/iih/sent - * interfaces/interface/levels/level/packet-counters/lsp/dropped - * interfaces/interface/levels/level/packet-counters/lsp/processed - * interfaces/interface/levels/level/packet-counters/lsp/received - * interfaces/interface/levels/level/packet-counters/lsp/retransmit - * interfaces/interface/levels/level/packet-counters/lsp/sent - * interfaces/interface/levels/level/packet-counters/psnp/dropped - * interfaces/interface/levels/level/packet-counters/psnp/processed - * interfaces/interface/levels/level/packet-counters/psnp/received - * interfaces/interface/levels/level/packet-counters/psnp/retransmit - * interfaces/interface/levels/level/packet-counters/psnp/sent - * interfaces/interfaces/circuit-counters/state/adj-changes - * interfaces/interfaces/circuit-counters/state/adj-number - * interfaces/interfaces/circuit-counters/state/auth-fails - * interfaces/interfaces/circuit-counters/state/auth-type-fails - * interfaces/interfaces/circuit-counters/state/id-field-len-mismatches - * interfaces/interfaces/circuit-counters/state/lan-dis-changes - * interfaces/interfaces/circuit-counters/state/max-area-address-mismatch - * interfaces/interfaces/circuit-counters/state/rejected-adj - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/local-extended-system-id - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-extended-system-id - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/priority - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/remaining-hold-time - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/restart-suppress - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceeded-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs - -* For LSDB - subpaths of - - * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/... - -## Protocol/RPC Parameter coverage - -* IS-IS: - * LSP messages - * TLV 1 (Area Addresses) - * TLV 10 (Authentication) - * TLV 22 (Extended IS reach) - * TLV 135 (Extended IP Reachability) - * TLV 137 (Dynamic Name) - * TLV 232 (IPv6 Reachability) - -## Minimum DUT platform requirement - -vRX diff --git a/feature/experimental/isis/ate_tests/base_adjacencies_test/base_adjacencies_test.go b/feature/experimental/isis/ate_tests/base_adjacencies_test/base_adjacencies_test.go deleted file mode 100644 index 24727551d4f..00000000000 --- a/feature/experimental/isis/ate_tests/base_adjacencies_test/base_adjacencies_test.go +++ /dev/null @@ -1,566 +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 -// -// 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 base_adjacencies_test - -import ( - "context" - "fmt" - "net" - "strings" - "testing" - "time" - - "github.com/openconfig/featureprofiles/feature/experimental/isis/ate_tests/internal/session" - "github.com/openconfig/featureprofiles/internal/attrs" - "github.com/openconfig/featureprofiles/internal/check" - "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/ixnet" - "github.com/openconfig/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -// EqualToDefault is the same as check.Equal unless the AllowNilForDefaults -// deviation is set, in which case it uses check.EqualOrNil to allow the device -// to return a nil value. This should only be used when `val` is the default -// for this particular query. -func EqualToDefault[T any](query ygnmi.SingletonQuery[T], val T, missingValueForDefaults bool) check.Validator { - if missingValueForDefaults { - return check.EqualOrNil(query, val) - } - return check.Equal(query, val) -} - -// CheckPresence check for the leaf presense only when missingValueForDefaults is false. -func CheckPresence(query ygnmi.SingletonQuery[uint32], missingValueForDefaults bool) check.Validator { - if !missingValueForDefaults { - return check.Present[uint32](query) - } - return check.Validate(query, func(vgot *ygnmi.Value[uint32]) error { - return nil - }) -} - -// TestBasic configures IS-IS on the DUT and confirms that the various values and defaults propagate -// then configures the ATE as well, waits for the adjacency to form, and checks that numerous -// counters and other values now have sensible values. -func TestBasic(t *testing.T) { - ts := session.MustNew(t).WithISIS() - // Only push DUT config - no adjacency established yet - if err := ts.PushDUT(context.Background(), t); err != nil { - t.Fatalf("Unable to push initial DUT config: %v", err) - } - isisRoot := session.ISISPath(ts.DUT) - port1ISIS := isisRoot.Interface(ts.DUTPort1.Name()) - if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { - port1ISIS = isisRoot.Interface(ts.DUTPort1.Name() + ".0") - } - // There might be lag between when the instance name is set and when the - // other parameters are set; we expect the total lag to be under one minute - // There are about 14 RPCs executed in quick succession in this block. - // Increasing the wait-time to 1 minute value to accommodate this. - deadline := time.Now().Add(time.Minute) - - t.Run("read_config", func(t *testing.T) { - checks := []check.Validator{ - check.Equal(isisRoot.Global().Net().State(), []string{"49.0001.1920.0000.2001.00"}), - check.Equal(isisRoot.Global().LevelCapability().State(), oc.Isis_LevelType_LEVEL_2), - check.Equal(port1ISIS.Enabled().State(), true), - check.Equal(port1ISIS.CircuitType().State(), oc.Isis_CircuitType_POINT_TO_POINT), - } - - // if MissingIsisInterfaceAfiSafiEnable is set, ignore enable flag check for AFI, SAFI at global level - // and validate enable at interface level - if deviations.MissingIsisInterfaceAfiSafiEnable(ts.DUT) { - checks = append(checks, - check.Equal(port1ISIS.Af(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled().State(), true), - check.Equal(port1ISIS.Af(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled().State(), true)) - } else { - checks = append(checks, - check.Equal(isisRoot.Global().Af(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled().State(), true), - 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 { - t.Error(err) - } - }) - } - }) - - missingValueForDefaults := deviations.MissingValueForDefaults(ts.DUT) - t.Run("read_auth", func(t *testing.T) { - // TODO: Enable these tests once supported - t.Skip("Authentication not supported") - l2auth := isisRoot.Level(2).Authentication() - for _, vd := range []check.Validator{ - check.Equal(isisRoot.Global().AuthenticationCheck().State(), true), - check.Equal(l2auth.DisableCsnp().State(), false), - check.Equal(l2auth.DisablePsnp().State(), false), - check.Equal(l2auth.DisableLsp().State(), false), - } { - t.Run(vd.RelPath(isisRoot), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - }) - var spfBefore uint32 - t.Run("counters_before_any_adjacencies", func(t *testing.T) { - if val, err := ygnmi.Lookup(context.Background(), ts.DUTClient, isisRoot.Level(2).SystemLevelCounters().SpfRuns().State()); err != nil { - t.Errorf("Unable to read spf run counter before adjancencies: %v", err) - } else { - v, present := val.Val() - if present { - spfBefore = v - } - } - - t.Run("packet_counters", func(t *testing.T) { - pCounts := port1ISIS.Level(2).PacketCounters() - for _, vd := range []check.Validator{ - EqualToDefault(pCounts.Csnp().Dropped().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Csnp().Processed().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Csnp().Received().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Csnp().Sent().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Psnp().Dropped().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Psnp().Processed().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Psnp().Received().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Psnp().Sent().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Lsp().Dropped().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Lsp().Processed().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Lsp().Received().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Lsp().Sent().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Iih().Dropped().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Iih().Processed().State(), uint32(0), missingValueForDefaults), - EqualToDefault(pCounts.Iih().Received().State(), uint32(0), missingValueForDefaults), - // Don't check IIH sent - the device can send hellos even if the other - // end is offline. - } { - t.Run(vd.RelPath(pCounts), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - }) - - t.Run("circuit_counters", func(t *testing.T) { - cCounts := port1ISIS.CircuitCounters() - for _, vd := range []check.Validator{ - EqualToDefault(cCounts.AdjChanges().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.AdjNumber().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.AuthFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.AuthTypeFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.IdFieldLenMismatches().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.LanDisChanges().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.MaxAreaAddressMismatches().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.RejectedAdj().State(), uint32(0), missingValueForDefaults), - } { - t.Run(vd.RelPath(cCounts), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - }) - t.Run("level_counters", func(t *testing.T) { - sysCounts := isisRoot.Level(2).SystemLevelCounters() - for _, vd := range []check.Validator{ - EqualToDefault(sysCounts.AuthFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.AuthTypeFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.CorruptedLsps().State(), uint32(0), missingValueForDefaults), - CheckPresence(sysCounts.DatabaseOverloads().State(), missingValueForDefaults), - EqualToDefault(sysCounts.ExceedMaxSeqNums().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.IdLenMismatch().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.LspErrors().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.MaxAreaAddressMismatches().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.OwnLspPurges().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.SeqNumSkips().State(), uint32(0), missingValueForDefaults), - } { - t.Run(vd.RelPath(sysCounts), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - }) - }) - - // Form the adjacency - ts.PushAndStartATE(t) - systemID, err := ts.AwaitAdjacency() - if err != nil { - t.Fatalf("No IS-IS adjacency formed: %v", err) - } - - // Allow 1 Minute of lag between adjacency appearing and all data being populated - - t.Run("adjacency_state", func(t *testing.T) { - // There are about 16 RPCs executed in quick succession in this block. - // Increasing the wait-time value to accommodate this. - deadline = time.Now().Add(time.Minute) - adj := port1ISIS.Level(2).Adjacency(systemID) - for _, vd := range []check.Validator{ - check.Equal(adj.AdjacencyState().State(), oc.Isis_IsisInterfaceAdjState_UP), - check.Equal(adj.SystemId().State(), systemID), - check.UnorderedEqual(adj.AreaAddress().State(), []string{session.ATEAreaAddress, session.DUTAreaAddress}, func(a, b string) bool { return a < b }), - check.EqualOrNil(adj.DisSystemId().State(), "0000.0000.0000"), - check.NotEqual(adj.LocalExtendedCircuitId().State(), uint32(0)), - check.Equal(adj.MultiTopology().State(), false), - check.Equal(adj.NeighborCircuitType().State(), oc.Isis_LevelType_LEVEL_2), - check.NotEqual(adj.NeighborExtendedCircuitId().State(), uint32(0)), - check.Equal(adj.NeighborIpv4Address().State(), session.ATEISISAttrs.IPv4), - check.Predicate(adj.NeighborSnpa().State(), "Need a valid MAC address", func(got string) bool { - mac, err := net.ParseMAC(got) - return mac != nil && err == nil - }), - check.Equal(adj.Nlpid().State(), []oc.E_Adjacency_Nlpid{oc.Adjacency_Nlpid_IPV4, oc.Adjacency_Nlpid_IPV6}), - check.Predicate(adj.NeighborIpv6Address().State(), "want a valid IPv6 address", func(got string) bool { - ip := net.ParseIP(got) - return ip != nil && ip.To16() != nil - }), - check.Present[uint8](adj.Priority().State()), - check.Present[bool](adj.RestartStatus().State()), - check.Present[bool](adj.RestartSupport().State()), - check.Present[bool](adj.RestartSuppress().State()), - } { - t.Run(vd.RelPath(adj), func(t *testing.T) { - if strings.Contains(vd.Path(), "multi-topology") { - if deviations.ISISMultiTopologyUnsupported(ts.DUT) { - t.Skip("Multi-Topology Unsupported") - } - } - if strings.Contains(vd.Path(), "restart-suppress") { - if deviations.ISISRestartSuppressUnsupported(ts.DUT) { - t.Skip("Restart-Suppress Unsupported") - } - } - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - - }) - - t.Run("counters_after_adjacency", func(t *testing.T) { - // Wait for at least one CSNP, PSNP, and LSP to have gone by, then confirm - // the corresponding processed/received/sent counters are nonzero while all - // the error and dropped counters remain at 0. - pCounts := port1ISIS.Level(2).PacketCounters() - - // Note: This is not a subtest because a failure here means checking the - // rest of the counters is pointless - none of them will change if we - // haven't been exchanging IS-IS messages. - // There are about 3 RPCs executed in quick succession in this block. - // Increasing the wait-time value to accommodate this. - deadline = time.Now().Add(time.Second * 30) - for _, vd := range []check.Validator{ - check.NotEqual(pCounts.Csnp().Processed().State(), uint32(0)), - check.NotEqual(pCounts.Lsp().Processed().State(), uint32(0)), - } { - t.Run(vd.RelPath(pCounts), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Fatalf("No messages in active adjacency after 30s: %v", err) - } - }) - } - - // There are about 14 RPCs executed in quick succession in this block. - // Increasing the wait-time value to accommodate this. - deadline = time.Now().Add(time.Minute) - t.Run("packet_counters", func(t *testing.T) { - pCounts := port1ISIS.Level(2).PacketCounters() - for _, vd := range []check.Validator{ - check.NotEqual(pCounts.Csnp().Processed().State(), uint32(0)), - check.NotEqual(pCounts.Csnp().Received().State(), uint32(0)), - check.NotEqual(pCounts.Csnp().Sent().State(), uint32(0)), - check.NotEqual(pCounts.Psnp().Sent().State(), uint32(0)), - check.NotEqual(pCounts.Lsp().Processed().State(), uint32(0)), - check.NotEqual(pCounts.Lsp().Received().State(), uint32(0)), - check.NotEqual(pCounts.Lsp().Sent().State(), uint32(0)), - check.NotEqual(pCounts.Iih().Processed().State(), uint32(0)), - check.NotEqual(pCounts.Iih().Received().State(), uint32(0)), - check.NotEqual(pCounts.Iih().Sent().State(), uint32(0)), - // No dropped messages - check.Equal(pCounts.Csnp().Dropped().State(), uint32(0)), - check.Equal(pCounts.Psnp().Dropped().State(), uint32(0)), - check.Equal(pCounts.Lsp().Dropped().State(), uint32(0)), - check.Equal(pCounts.Iih().Dropped().State(), uint32(0)), - } { - t.Run(vd.RelPath(pCounts), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - }) - - t.Run("circuit_counters", func(t *testing.T) { - // Only adjChanges and adjNumber should have gone up - others should still be 0 - cCounts := port1ISIS.CircuitCounters() - for _, vd := range []check.Validator{ - check.NotEqual(cCounts.AdjChanges().State(), uint32(0)), - check.NotEqual(cCounts.AdjNumber().State(), uint32(0)), - EqualToDefault(cCounts.AuthFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.AuthTypeFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.IdFieldLenMismatches().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.LanDisChanges().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.MaxAreaAddressMismatches().State(), uint32(0), missingValueForDefaults), - EqualToDefault(cCounts.RejectedAdj().State(), uint32(0), missingValueForDefaults), - } { - t.Run(vd.RelPath(cCounts), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - }) - - t.Run("level_counters", func(t *testing.T) { - // Error counters should still be zero - sysCounts := isisRoot.Level(2).SystemLevelCounters() - for _, vd := range []check.Validator{ - EqualToDefault(sysCounts.AuthFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.AuthTypeFails().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.CorruptedLsps().State(), uint32(0), missingValueForDefaults), - CheckPresence(sysCounts.DatabaseOverloads().State(), missingValueForDefaults), - EqualToDefault(sysCounts.ExceedMaxSeqNums().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.IdLenMismatch().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.LspErrors().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.MaxAreaAddressMismatches().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.OwnLspPurges().State(), uint32(0), missingValueForDefaults), - EqualToDefault(sysCounts.SeqNumSkips().State(), uint32(0), missingValueForDefaults), - check.Predicate(sysCounts.SpfRuns().State(), fmt.Sprintf("want > %v", spfBefore), func(got uint32) bool { - return got > spfBefore - }), - } { - t.Run(vd.RelPath(sysCounts), func(t *testing.T) { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - }) - } - }) - }) -} - -// TestHelloPadding tests several different hello padding modes to confirm they all work. -func TestHelloPadding(t *testing.T) { - for _, tc := range []struct { - name string - mode oc.E_Isis_HelloPaddingType - skip string - }{ - { - name: "disabled", - mode: oc.Isis_HelloPaddingType_DISABLE, - }, { - name: "strict", - mode: oc.Isis_HelloPaddingType_STRICT, - }, { - name: "adaptive", - mode: oc.Isis_HelloPaddingType_ADAPTIVE, - }, { - name: "loose", - mode: oc.Isis_HelloPaddingType_LOOSE, - // TODO: Skip based on deviations. - skip: "Unsupported", - }, - } { - t.Run(tc.name, func(t *testing.T) { - if tc.skip != "" { - t.Skip(tc.skip) - } - ts := session.MustNew(t).WithISIS() - ts.ConfigISIS(func(isis *oc.NetworkInstance_Protocol_Isis) { - global := isis.GetOrCreateGlobal() - global.HelloPadding = tc.mode - }, func(isis *ixnet.ISIS) { - isis.WithHelloPaddingEnabled(tc.mode != oc.Isis_HelloPaddingType_DISABLE) - }) - ts.PushAndStart(t) - _, err := ts.AwaitAdjacency() - if err != nil { - t.Fatalf("No IS-IS adjacency formed: %v", err) - } - telemPth := session.ISISPath(ts.DUT).Global() - var vd check.Validator - missingValueForDefaults := deviations.MissingValueForDefaults(ts.DUT) - if tc.mode == oc.Isis_HelloPaddingType_STRICT { - vd = EqualToDefault(telemPth.HelloPadding().State(), oc.Isis_HelloPaddingType_STRICT, missingValueForDefaults) - } else { - vd = check.Equal(telemPth.HelloPadding().State(), tc.mode) - } - if err := vd.Check(ts.DUTClient); err != nil { - t.Error(err) - } - }) - } -} - -// TestAuthentication verifies that with authentication enabled or disabled we can still establish -// an IS-IS session with the ATE. -func TestAuthentication(t *testing.T) { - const password = "google" - for _, tc := range []struct { - name string - mode oc.E_IsisTypes_AUTH_MODE - enabled bool - }{ - {name: "enabled:md5", mode: oc.IsisTypes_AUTH_MODE_MD5, enabled: true}, - {name: "enabled:text", mode: oc.IsisTypes_AUTH_MODE_TEXT, enabled: true}, - {name: "disabled", mode: oc.IsisTypes_AUTH_MODE_TEXT, enabled: false}, - } { - t.Run(tc.name, func(t *testing.T) { - ts := session.MustNew(t).WithISIS() - ts.ConfigISIS(func(isis *oc.NetworkInstance_Protocol_Isis) { - level := isis.GetOrCreateLevel(2) - level.Enabled = ygot.Bool(true) - auth := level.GetOrCreateAuthentication() - auth.Enabled = ygot.Bool(true) - auth.AuthMode = tc.mode - auth.AuthType = oc.KeychainTypes_AUTH_TYPE_SIMPLE_KEY - auth.AuthPassword = ygot.String(password) - for _, intf := range isis.Interface { - intf.GetOrCreateLevel(2).GetOrCreateHelloAuthentication().Enabled = ygot.Bool(tc.enabled) - if tc.enabled { - intf.GetLevel(2).GetHelloAuthentication().AuthPassword = ygot.String("google") - intf.GetLevel(2).GetHelloAuthentication().AuthMode = tc.mode - intf.GetLevel(2).GetHelloAuthentication().AuthType = oc.KeychainTypes_AUTH_TYPE_SIMPLE_KEY - } - } - }, func(isis *ixnet.ISIS) { - if tc.enabled { - switch tc.mode { - case oc.IsisTypes_AUTH_MODE_TEXT: - isis.WithAuthPassword(password) - case oc.IsisTypes_AUTH_MODE_MD5: - isis.WithAuthMD5(password) - default: - t.Fatalf("test case has bad mode: %v", tc.mode) - } - } else { - isis.WithAuthDisabled() - } - }) - ts.PushAndStart(t) - ts.MustAdjacency(t) - }) - } -} - -// TestTraffic has the ATE advertise some routes and verifies that traffic sent to the DUT is routed -// appropriately. -func TestTraffic(t *testing.T) { - ts := session.MustNew(t).WithISIS() - targetNetwork := &attrs.Attributes{ - Desc: "External network (simulated by ATE)", - IPv4: "198.51.100.0", - IPv4Len: 24, - IPv6: "2001:db8::198:51:100:0", - IPv6Len: 112, - } - deadNetwork := &attrs.Attributes{ - Desc: "Unreachable network (traffic to it should blackhole)", - IPv4: "203.0.113.0", - IPv4Len: 24, - IPv6: "2001:db8::203:0:113:0", - IPv6Len: 112, - } - - ts.ConfigISIS(func(isis *oc.NetworkInstance_Protocol_Isis) { - // disable global hello padding on the DUT - global := isis.GetOrCreateGlobal() - global.HelloPadding = oc.Isis_HelloPaddingType_DISABLE - // configuring single topology for ISIS global ipv4 AF - 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) - } - }, func(isis *ixnet.ISIS) { - // disable global hello padding on the ATE - isis.WithHelloPaddingEnabled(false) - }) - - ate := ts.ATE - // We generate traffic entering along port2 and destined for port1 - srcIntf := ts.MustATEInterface(t, "port2") - dstIntf := ts.MustATEInterface(t, "port1") - // net is a simulated network containing the addresses specified by targetNetwork - net := dstIntf.AddNetwork("net") - net.IPv4().WithAddress(targetNetwork.IPv4CIDR()).WithCount(1) - net.IPv6().WithAddress(targetNetwork.IPv6CIDR()).WithCount(1) - net.ISIS().WithIPReachabilityExternal().WithIPReachabilityMetric(10) - t.Log("Starting protocols on ATE...") - ts.PushAndStart(t) - defer ts.ATETop.StopProtocols(t) - ts.MustAdjacency(t) - t.Log("Configuring traffic from ATE through DUT...") - v4Header := ondatra.NewIPv4Header() - v4Header.DstAddressRange().WithMin(targetNetwork.IPv4).WithCount(1) - v4Flow := ate.Traffic().NewFlow("v4Flow"). - WithSrcEndpoints(srcIntf).WithDstEndpoints(dstIntf). - WithHeaders(ondatra.NewEthernetHeader(), v4Header) - v6Header := ondatra.NewIPv6Header() - v6Header.DstAddressRange().WithMin(targetNetwork.IPv6).WithCount(1) - v6Flow := ate.Traffic().NewFlow("v6Flow"). - WithSrcEndpoints(srcIntf).WithDstEndpoints(dstIntf). - WithHeaders(ondatra.NewEthernetHeader(), v6Header) - // deadFlow is addressed to a nonexistent network as a consistency check - - // all traffic should be blackholed. - deadHeader := ondatra.NewIPv4Header() - deadHeader.DstAddressRange().WithMin(deadNetwork.IPv4).WithCount(1) - deadFlow := ate.Traffic().NewFlow("flow2"). - WithSrcEndpoints(srcIntf).WithDstEndpoints(dstIntf). - WithHeaders(ondatra.NewEthernetHeader(), deadHeader) - t.Log("Running traffic for 30s...") - ate.Traffic().Start(t, v4Flow, v6Flow, deadFlow) - time.Sleep(time.Second * 30) - ate.Traffic().Stop(t) - t.Log("Checking telemetry...") - telem := gnmi.OC() - v4Loss := gnmi.Get(t, ate, telem.Flow(v4Flow.Name()).LossPct().State()) - v6Loss := gnmi.Get(t, ate, telem.Flow(v6Flow.Name()).LossPct().State()) - deadLoss := gnmi.Get(t, ate, telem.Flow(deadFlow.Name()).LossPct().State()) - if v4Loss > 1 { - t.Errorf("Got %v%% IPv4 packet loss; expected < 1%%", v4Loss) - } - if v6Loss > 1 { - t.Errorf("Got %v%% IPv6 packet loss; expected < 1%%", v6Loss) - } - if deadLoss != 100 { - t.Errorf("Got %v%% invalid packet loss; expected 100%%", deadLoss) - } -} diff --git a/feature/experimental/isis/ate_tests/internal/session/attrs.go b/feature/experimental/isis/ate_tests/internal/session/attrs.go deleted file mode 100644 index 06dfae73b5b..00000000000 --- a/feature/experimental/isis/ate_tests/internal/session/attrs.go +++ /dev/null @@ -1,124 +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 -// -// 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 session - -// This is identical to the internal/attrs library except it points to ygnmi -import ( - "fmt" - - "github.com/openconfig/featureprofiles/internal/deviations" - "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ygot/ygot" -) - -// Attributes bundles some common attributes for devices and/or interfaces. -// It provides helpers to generate appropriate configuration for OpenConfig -// and for an ATETopology. All fields are optional; only those that are -// non-empty will be set when configuring an interface. -type Attributes struct { - IPv4 string - IPv6 string - MAC string - Name string // Interface name, only applied to ATE ports. - Desc string // Description, only applied to DUT interfaces. - IPv4Len uint8 // Prefix length for IPv4. - IPv6Len uint8 // Prefix length for IPv6. - MTU uint16 -} - -// IPv4CIDR constructs the IPv4 CIDR notation with the given prefix -// length, e.g. "192.0.2.1/30". -func (a *Attributes) IPv4CIDR() string { - return fmt.Sprintf("%s/%d", a.IPv4, a.IPv4Len) -} - -// IPv6CIDR constructs the IPv6 CIDR notation with the given prefix -// length, e.g. "2001:db8::1/126". -func (a *Attributes) IPv6CIDR() string { - return fmt.Sprintf("%s/%d", a.IPv6, a.IPv6Len) -} - -// ConfigInterface configures an OpenConfig interface with these attributes. -func (a *Attributes) ConfigInterface(intf *oc.Interface, dut *ondatra.DUTDevice) *oc.Interface { - if a.Desc != "" { - intf.Description = ygot.String(a.Desc) - } - intf.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd - if deviations.InterfaceEnabled(dut) { - intf.Enabled = ygot.Bool(true) - } - if a.MTU > 0 && !deviations.OmitL2MTU(dut) { - intf.Mtu = ygot.Uint16(a.MTU + 14) - } - e := intf.GetOrCreateEthernet() - if a.MAC != "" { - e.MacAddress = ygot.String(a.MAC) - } - - s := intf.GetOrCreateSubinterface(0) - if a.IPv4 != "" { - s4 := s.GetOrCreateIpv4() - if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { - s4.Enabled = ygot.Bool(true) - } - if a.MTU > 0 { - s4.Mtu = ygot.Uint16(a.MTU) - } - a4 := s4.GetOrCreateAddress(a.IPv4) - if a.IPv4Len > 0 { - a4.PrefixLength = ygot.Uint8(a.IPv4Len) - } - } - - if a.IPv6 != "" { - s6 := s.GetOrCreateIpv6() - if a.MTU > 0 { - s6.Mtu = ygot.Uint32(uint32(a.MTU)) - } - if deviations.InterfaceEnabled(dut) { - s6.Enabled = ygot.Bool(true) - } - a6 := s6.GetOrCreateAddress(a.IPv6) - if a.IPv6Len > 0 { - a6.PrefixLength = ygot.Uint8(a.IPv6Len) - } - } - return intf -} - -// NewOCInterface returns a new *oc.Interface configured with these attributes -func (a *Attributes) NewOCInterface(name string, dut *ondatra.DUTDevice) *oc.Interface { - return a.ConfigInterface(&oc.Interface{Name: ygot.String(name)}, dut) -} - -// AddToATE adds a new interface to an ATETopology with these attributes. -func (a *Attributes) AddToATE(top *ondatra.ATETopology, ap *ondatra.Port, peer *Attributes) *ondatra.Interface { - i := top.AddInterface(a.Name).WithPort(ap) - if a.MTU > 0 { - i.Ethernet().WithMTU(a.MTU) - } - if a.IPv4 != "" { - i.IPv4(). - WithAddress(a.IPv4CIDR()). - WithDefaultGateway(peer.IPv4) - } - if a.IPv6 != "" { - i.IPv6(). - WithAddress(a.IPv6CIDR()). - WithDefaultGateway(peer.IPv6) - } - return i -} diff --git a/feature/experimental/isis/ate_tests/internal/session/session.go b/feature/experimental/isis/ate_tests/internal/session/session.go deleted file mode 100644 index 6fbc5caa12c..00000000000 --- a/feature/experimental/isis/ate_tests/internal/session/session.go +++ /dev/null @@ -1,338 +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 -// -// 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 session is deprecated and scoped only to be used with -// feature/experimental/isis/ate_tests/*. Do not use elsewhere. -package session - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/openconfig/featureprofiles/internal/deviations" - "github.com/openconfig/featureprofiles/internal/fptest" - "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ondatra/gnmi/oc/netinstisis" - "github.com/openconfig/ondatra/gnmi/oc/networkinstance" - "github.com/openconfig/ondatra/gnmi/oc/ocpath" - "github.com/openconfig/ondatra/ixnet" - "github.com/openconfig/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -// PTISIS is shorthand for the long oc protocol type constant -const PTISIS = oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS - -// The testbed consists of a dut and an ate with two connections, labeled ISISIntf and Intf2. -// ISISIntf links dut:port1 and ate:port1, which are assigned 192.0.2.1/30 and 192.0.2.2/30 -// respectively. Intf2 connects dut:port2 to ate:port2, which are 192.0.2.5/30 and 192.0.2.6/30. -// We establish an IS-IS adjacency over ISISIntf. For traffic testing, we configure the ATE end -// of the IS-IS adjacency to advertise 198.51.100.0/24, then generate traffic through ate:port2 with -// IPv4 headers indicating that it should go to a random address in that range; the dut should -// route this traffic to the IS-IS link, where the ATE should log it arriving on ate:port1. -const ( - DUTAreaAddress = "49.0001" - ATEAreaAddress = "49.0002" - DUTSysID = "1920.0000.2001" - ISISName = "DEFAULT" - pLen4 = 30 - pLen6 = 126 -) - -var ( - // DUTNET is the Network Entity Title for the DUT - DUTNET = fmt.Sprintf("%v.%v.00", DUTAreaAddress, DUTSysID) - // DUTISISAttrs has attributes for the DUT ISIS connection on port1 - DUTISISAttrs = &Attributes{ - Desc: "DUT to ATE with IS-IS", - IPv4: "192.0.2.1", - IPv6: "2001:db8::1", - IPv4Len: pLen4, - IPv6Len: pLen6, - } - // ATEISISAttrs has attributes for the ATE ISIS connection on port1 - ATEISISAttrs = &Attributes{ - Name: "port1", - Desc: "ATE to DUT with IS-IS", - IPv4: "192.0.2.2", - IPv6: "2001:db8::2", - IPv4Len: pLen4, - IPv6Len: pLen6, - } - // DUTTrafficAttrs has attributes for the DUT end of the traffic connection (port2) - DUTTrafficAttrs = &Attributes{ - Desc: "DUT to ATE secondary link", - IPv4: "192.0.2.5", - IPv6: "2001:db8::5", - IPv4Len: pLen4, - IPv6Len: pLen6, - } - // ATETrafficAttrs has attributes for the ATE end of the traffic connection (port2) - ATETrafficAttrs = &Attributes{ - Name: "port2", - Desc: "ATE to DUT secondary link", - IPv4: "192.0.2.6", - IPv6: "2001:db8::6", - IPv4Len: pLen4, - IPv6Len: pLen6, - } -) - -// ISISPath is shorthand for ProtocolPath().Isis(). -func ISISPath(dut *ondatra.DUTDevice) *netinstisis.NetworkInstance_Protocol_IsisPath { - return ProtocolPath(dut).Isis() -} - -// ProtocolPath returns the path to the IS-IS protocol named ISISName on the -// default network instance. -func ProtocolPath(dut *ondatra.DUTDevice) *networkinstance.NetworkInstance_ProtocolPath { - return ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(PTISIS, ISISName) -} - -// addISISOC configures basic IS-IS on a device. -func addISISOC(dev *oc.Root, areaAddress, sysID, ifaceName string, dut *ondatra.DUTDevice) { - 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 - intf := isis.GetOrCreateInterface(ifaceName) - 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 - } - -} - -// addISISTopo configures basic IS-IS on an ATETopology interface. -func addISISTopo(iface *ondatra.Interface, areaAddress, sysID string) { - isis := iface.ISIS() - isis. - WithAreaID(areaAddress). - WithTERouterID(sysID). - WithNetworkTypePointToPoint(). - WithWideMetricEnabled(true). - WithLevelL2().WithMetric(10) -} - -// TestSession is a convenience wrapper around the dut, ate, ports, and -// topology we're using. -type TestSession struct { - DUT *ondatra.DUTDevice - DUTClient *ygnmi.Client - ATE *ondatra.ATEDevice - // Rather than looking these up all the time, we fetch all the relevant ports - // and interfaces at setup time. - DUTPort1, DUTPort2, ATEPort1, ATEPort2 *ondatra.Port - ATEIntf1, ATEIntf2 *ondatra.Interface - // DUTConf and ATETop can be modified by tests; calling .Push() will apply - // them to the dut and ate. - DUTConf *oc.Root - ATETop *ondatra.ATETopology -} - -// New creates a new TestSession using the default global config, and -// configures the interfaces on the dut and the ate. -func New(t testing.TB) (*TestSession, error) { - t.Helper() - s := &TestSession{} - s.DUT = ondatra.DUT(t, "dut") - var err error - s.DUTClient, err = ygnmi.NewClient(s.DUT.RawAPIs().GNMI(t), ygnmi.WithTarget(s.DUT.ID())) - if err != nil { - return nil, fmt.Errorf("unable to connect to gNMI on %v: %w", s.DUT, err) - } - s.DUTPort1 = s.DUT.Port(t, "port1") - s.DUTPort2 = s.DUT.Port(t, "port2") - s.DUTConf = &oc.Root{} - // configure dut ports - DUTISISAttrs.ConfigInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort1.Name()), s.DUT) - DUTTrafficAttrs.ConfigInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort2.Name()), 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 - // that don't use an ATE. - if ate, ok := ondatra.ATEs(t)["ate"]; ok { - s.ATE = ate - s.ATEPort1 = s.ATE.Port(t, "port1") - s.ATEPort2 = s.ATE.Port(t, "port2") - s.ATETop = s.ATE.Topology().New() - s.ATEIntf1 = ATEISISAttrs.AddToATE(s.ATETop, s.ATEPort1, DUTISISAttrs) - s.ATEIntf2 = ATETrafficAttrs.AddToATE(s.ATETop, s.ATEPort2, DUTTrafficAttrs) - } - return s, nil -} - -// MustNew creates a new TestSession or Fatal()s if anything goes wrong. -func MustNew(t testing.TB) *TestSession { - t.Helper() - v, err := New(t) - if err != nil { - t.Fatalf("Unable to initialize topology: %v", err) - } - return v -} - -// WithISIS adds ISIS to a test session. -func (s *TestSession) WithISIS() *TestSession { - if deviations.ExplicitInterfaceInDefaultVRF(s.DUT) { - addISISOC(s.DUTConf, DUTAreaAddress, DUTSysID, s.DUTPort1.Name()+".0", s.DUT) - } else { - addISISOC(s.DUTConf, DUTAreaAddress, DUTSysID, s.DUTPort1.Name(), s.DUT) - } - if s.ATE != nil { - addISISTopo(s.ATEIntf1, ATEAreaAddress, "*") - } - return s -} - -// ConfigISIS takes two functions, one that operates on an OC IS-IS block and -// one that operates on an ondatra ATE IS-IS block. The first will be applied -// to the IS-IS block of ts.DUTConfig, and the second will be applied to the -// IS-IS configuration of ts.ATETop -func (s *TestSession) ConfigISIS(ocFn func(*oc.NetworkInstance_Protocol_Isis), ateFn func(*ixnet.ISIS)) { - ocFn(s.DUTConf.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(s.DUT)).GetOrCreateProtocol(PTISIS, ISISName).GetOrCreateIsis()) - if s.ATE != nil { - ateFn(s.ATEIntf1.ISIS()) - } -} - -// PushAndStart calls PushDUT and PushAndStartATE to send config to both -// devices. -func (s *TestSession) PushAndStart(t testing.TB) error { - t.Helper() - if err := s.PushDUT(context.Background(), t); err != nil { - return err - } - s.PushAndStartATE(t) - return nil -} - -// PushDUT replaces DUT config with s.dutConf. Only interfaces and the ISIS -// protocol are written. -func (s *TestSession) PushDUT(ctx context.Context, t testing.TB) error { - // Push the interfaces - for name, conf := range s.DUTConf.Interface { - _, err := ygnmi.Replace(ctx, s.DUTClient, ocpath.Root().Interface(name).Config(), conf) - if err != nil { - return fmt.Errorf("configuring interface %s: %w", name, err) - } - } - if deviations.ExplicitInterfaceInDefaultVRF(s.DUT) { - fptest.AssignToNetworkInstance(t, s.DUT, s.DUTPort1.Name(), deviations.DefaultNetworkInstance(s.DUT), 0) - fptest.AssignToNetworkInstance(t, s.DUT, s.DUTPort2.Name(), deviations.DefaultNetworkInstance(s.DUT), 0) - } - if deviations.ExplicitPortSpeed(s.DUT) { - fptest.SetPortSpeed(t, s.DUTPort1) - fptest.SetPortSpeed(t, s.DUTPort2) - } - - // Push the ISIS protocol - if _, err := ygnmi.Update(ctx, s.DUTClient, ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(s.DUT)).Config(), &oc.NetworkInstance{ - Name: ygot.String(deviations.DefaultNetworkInstance(s.DUT)), - Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE, - }); err != nil { - return fmt.Errorf("configuring network instance: %w", err) - } - dutConf := s.DUTConf.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(s.DUT)).GetOrCreateProtocol(PTISIS, ISISName) - _, err := ygnmi.Replace(ctx, s.DUTClient, ProtocolPath(s.DUT).Config(), dutConf) - if err != nil { - return fmt.Errorf("configuring ISIS: %w", err) - } - return nil -} - -// PushAndStartATE pushes the ATETop to the ATE and starts protocols on it. -func (s *TestSession) PushAndStartATE(t testing.TB) { - t.Helper() - if s.ATE == nil { - t.Fatal("Cannot run test without ATE") - } - s.ATETop.Push(t).StartProtocols(t) -} - -// AwaitAdjacency waits up to a minute for the dut to report that the ISISIntf -// link has formed any IS-IS adjacency, returning the adjacency ID or an error -// if one doesn't form. -func (s *TestSession) AwaitAdjacency() (string, error) { - intf := ISISPath(s.DUT).Interface(s.DUTPort1.Name()) - if deviations.ExplicitInterfaceInDefaultVRF(s.DUT) { - intf = ISISPath(s.DUT).Interface(s.DUTPort1.Name() + ".0") - } - query := intf.LevelAny().AdjacencyAny().AdjacencyState().State() - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - watcher := ygnmi.WatchAll(ctx, s.DUTClient, query, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) error { - if val == nil || !val.IsPresent() { - return ygnmi.Continue - } - v, _ := val.Val() - if v == oc.Isis_IsisInterfaceAdjState_UP { - return nil - } - return ygnmi.Continue - }) - - got, err := watcher.Await() - if err != nil { - return "", err - } - return got.Path.GetElem()[10].GetKey()["system-id"], nil -} - -// MustAdjacency waits up to a minute for an IS-IS adjacency to form between -// the DUT and the ATE; it returns the adjacency ID or calls t.Fatal no -// adjacency forms. -func (s *TestSession) MustAdjacency(t testing.TB) string { - adjID, err := s.AwaitAdjacency() - if err != nil { - t.Fatalf("Waiting for adjacency to form: %v", err) - } - return adjID -} - -// MustATEInterface returns the ATE interface for the portID, or calls t.Fatal -// if this fails. -func (s *TestSession) MustATEInterface(t testing.TB, portID string) *ondatra.Interface { - if s.ATE == nil { - t.Fatal("Cannot run test without ATE") - } - iface, ok := s.ATETop.Interfaces()[portID] - if !ok { - t.Fatalf("No ATE interface with ID %v", portID) - } - return iface -} diff --git a/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/README.md b/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/README.md deleted file mode 100644 index b11cdde6f37..00000000000 --- a/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# RT-2.10: IS-IS change LSP lifetime - -## Summary - -* Changing the lsp lifetime and verifying isis lsp parameters - -## Procedure - - * Configure IS-IS for ATE port-1 and DUT port-1. - * Modify the default lifetime of the LSP PDU. - * The default lifetime of the LSP PDU is 1200 seconds. - This parameter can be updated using the LSP lifetime parameter. - LSP lifetime indicates how long the LSP PDU originated by the DUT should remain in the network. - The DUT regenerates the LSP PDU typically ~300 seconds before its expiration. - * Verify that IS-IS adjacency for IPv4 and IPV6 address family is coming up. - * Verify that IPv4 and IPv6 prefixes that are advertised by ATE correctly installed into DUTs route and forwarding table. - * Verify that the updated LSP lifetime is reflected in isis database output. - * Verify that the remaining lifetime of the lsp is remaining lifetime = configured lifetime - time passed since the LSP PDU generation. - * Verify that once the new LSP PDU is generated the sequence number and checksum of the new LSP PDU is updated - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/timers/config/lsp-lifetime-interval - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/timers/state/lsp-lifetime-interval - * levels/level/link-state-database/lsp/state/remaining-lifetime diff --git a/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go b/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go deleted file mode 100644 index bb44c779722..00000000000 --- a/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go +++ /dev/null @@ -1,320 +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 -// -// 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 isis_change_lsp_lifetime_test - -import ( - "fmt" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/openconfig/featureprofiles/internal/attrs" - "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/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -const ( - plenIPv4 = 30 - plenIPv6 = 126 - isisInstance = "DEFAULT" - dutAreaAddress = "49.0001" - ateAreaAddress = "49.0002" - dutSysID = "1920.0000.2001" - lspLifetime = 500 - v4Route = "203.0.113.0/30" - v6Route = "2001:db8::203:0:113:0/126" - v4IP = "203.0.113.1" - v6IP = "2001:db8::203:0:113:1" -) - -var ( - dutPort1Attr = attrs.Attributes{ - Desc: "DUT to ATE port1 ", - IPv4: "192.0.2.1", - IPv6: "2001:db8::192:0:2:1", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - atePort1attr = attrs.Attributes{ - Name: "ATE to DUT port1 ", - IPv4: "192.0.2.2", - IPv6: "2001:db8::192:0:2:2", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - dutPort2Attr = attrs.Attributes{ - Desc: "DUT to ATE port2 ", - IPv4: "192.0.2.5", - IPv6: "2001:db8::192:0:2:5", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } - atePort2attr = attrs.Attributes{ - Name: "ATE to DUT port2", - IPv4: "192.0.2.6", - IPv6: "2001:db8::192:0:2:6", - IPv4Len: plenIPv4, - IPv6Len: plenIPv6, - } -) - -// configureDUT configures all the interfaces on the DUT. -func configureDUT(t *testing.T) { - t.Helper() - dc := gnmi.OC() - dut := ondatra.DUT(t, "dut") - - i1 := dutPort1Attr.NewOCInterface(dut.Port(t, "port1").Name(), dut) - t.Log("Pushing interface config on DUT port1") - gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) - - i2 := dutPort2Attr.NewOCInterface(dut.Port(t, "port2").Name(), dut) - t.Log("Pushing interface config on DUT port2") - gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) - - if deviations.ExplicitPortSpeed(dut) { - fptest.SetPortSpeed(t, dut.Port(t, "port1")) - fptest.SetPortSpeed(t, dut.Port(t, "port2")) - } -} - -// configureISIS configures isis on DUT. -func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName string, dutAreaAddress, dutSysID string) { - t.Helper() - d := &oc.Root{} - configPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) - netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) - prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) - prot.Enabled = ygot.Bool(true) - isis := prot.GetOrCreateIsis() - globalIsis := isis.GetOrCreateGlobal() - - // Global configs - globalIsis.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} - globalIsis.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) - globalIsis.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) - globalIsis.LevelCapability = oc.Isis_LevelType_LEVEL_2 - - globalIsis.GetOrCreateTimers().LspLifetimeInterval = ygot.Uint16(lspLifetime) - if deviations.ISISLspLifetimeIntervalRequiresLspRefreshInterval(dut) { - globalIsis.GetOrCreateTimers().LspRefreshInterval = ygot.Uint16(65535) - } - if deviations.ISISInstanceEnabledRequired(dut) { - globalIsis.Instance = ygot.String(isisInstance) - } - - // Interface configs - intf := isis.GetOrCreateInterface(intfName) - intf.Enabled = ygot.Bool(true) - intf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT - intf.InterfaceId = &intfName - - t.Log("Pushing isis config on DUT") - gnmi.Replace(t, dut, configPath.Config(), prot) -} - -// configureATE configures the interfaces and isis on ATE. -func configureATE(t *testing.T, ate *ondatra.ATEDevice) *ondatra.ATETopology { - t.Helper() - topo := ate.Topology().New() - port1 := ate.Port(t, "port1") - port2 := ate.Port(t, "port2") - - i1Dut := topo.AddInterface(atePort1attr.Name).WithPort(port1) - i1Dut.IPv4().WithAddress(atePort1attr.IPv4CIDR()).WithDefaultGateway(dutPort1Attr.IPv4) - i1Dut.IPv6().WithAddress(atePort1attr.IPv6CIDR()).WithDefaultGateway(dutPort1Attr.IPv6) - - i2Dut := topo.AddInterface(atePort2attr.Name).WithPort(port2) - i2Dut.IPv4().WithAddress(atePort2attr.IPv4CIDR()).WithDefaultGateway(dutPort2Attr.IPv4) - i2Dut.IPv6().WithAddress(atePort2attr.IPv6CIDR()).WithDefaultGateway(dutPort2Attr.IPv6) - - isisDut := i1Dut.ISIS() - isisDut. - WithAreaID(ateAreaAddress). - WithTERouterID(atePort1attr.IPv4). - WithNetworkTypePointToPoint(). - WithLevelL2() - - netGrp := i1Dut.AddNetwork(fmt.Sprintf("isis-%d", 1)) - netGrp.IPv4().WithAddress(v4Route) - netGrp.ISIS().WithActive(true) - netGrp.IPv6().WithAddress(v6Route) - netGrp.ISIS().WithActive(true) - - t.Log("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) - return topo -} - -// createFlow returns v4 and v6 flow from atePort2 to atePort1 -func createFlow(t *testing.T, ate *ondatra.ATEDevice, ateTopo *ondatra.ATETopology) []*ondatra.Flow { - t.Helper() - srcIntf := ateTopo.Interfaces()[atePort2attr.Name] - dstIntf := ateTopo.Interfaces()[atePort1attr.Name] - - t.Log("Configuring v4 traffic flow ") - v4Header := ondatra.NewIPv4Header() - v4Header.DstAddressRange().WithMin(v4IP).WithCount(1) - - v4Flow := ate.Traffic().NewFlow("v4Flow"). - WithSrcEndpoints(srcIntf).WithDstEndpoints(dstIntf). - WithHeaders(ondatra.NewEthernetHeader(), v4Header) - - t.Log("Configuring v6 traffic flow ") - v6Header := ondatra.NewIPv6Header() - v6Header.DstAddressRange().WithMin(v6IP).WithCount(1) - - v6Flow := ate.Traffic().NewFlow("v6Flow"). - WithSrcEndpoints(srcIntf).WithDstEndpoints(dstIntf). - WithHeaders(ondatra.NewEthernetHeader(), v6Header) - - return []*ondatra.Flow{v4Flow, v6Flow} -} - -// TestISISChangeLSPLifetime verifies isis lsp telemetry paramters with configured lsp lifetime. -func TestISISChangeLSPLifetime(t *testing.T) { - dut := ondatra.DUT(t, "dut") - ate := ondatra.ATE(t, "ate") - intfName := dut.Port(t, "port1").Name() - - // Configure interface on the DUT. - configureDUT(t) - - // Configure network Instance type on DUT. - dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) - t.Log("Pushing network Instance type config on DUT") - gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) - - // Configure isis on DUT. - configureISIS(t, dut, intfName, dutAreaAddress, dutSysID) - - // Configure interface,isis and traffic on ATE. - ateTopo := configureATE(t, ate) - flows := createFlow(t, ate, ateTopo) - - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - intfName = intfName + ".0" - } - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() - - t.Run("Isis telemetry", func(t *testing.T) { - t.Run("Verifying adjacency", func(t *testing.T) { - adjacencyPath := statePath.Interface(intfName).Level(2).AdjacencyAny().AdjacencyState().State() - - _, ok := gnmi.WatchAll(t, dut, adjacencyPath, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { - state, present := val.Val() - return present && state == oc.Isis_IsisInterfaceAdjState_UP - }).Await(t) - if !ok { - t.Fatalf("No isis adjacency reported on interface %v", intfName) - } - }) - // Getting neighbors sysid. - sysid := gnmi.GetAll(t, dut, statePath.Interface(intfName).Level(2).AdjacencyAny().SystemId().State()) - ateSysID := sysid[0] - ateLspID := ateSysID + ".00-00" - dutLspID := dutSysID + ".00-00" - - t.Run("Adjacency state checks", func(t *testing.T) { - adjPath := statePath.Interface(intfName).Level(2).Adjacency(ateSysID) - if got := gnmi.Get(t, dut, adjPath.Nlpid().State()); !cmp.Equal(got, []oc.E_Adjacency_Nlpid{oc.Adjacency_Nlpid_IPV4, oc.Adjacency_Nlpid_IPV6}) { - t.Errorf("FAIL- Expected address families not found, got %s, want %s", got, []oc.E_Adjacency_Nlpid{oc.Adjacency_Nlpid_IPV4, oc.Adjacency_Nlpid_IPV6}) - } - }) - t.Run("Lsp checks", func(t *testing.T) { - if got := gnmi.Get(t, dut, statePath.Global().Timers().LspLifetimeInterval().State()); got != lspLifetime { - t.Errorf("FAIL- Expected lsp lifetime interval not found, want %d, got %d", lspLifetime, got) - } - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(dutLspID).LspId().State()); got != dutLspID { - t.Errorf("FAIL- Expected DUT lsp id not found, want %s, got %s", dutLspID, got) - } - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(ateLspID).LspId().State()); got != ateLspID { - t.Errorf("FAIL- Expected ATE lsp not found, want %s, got %s", ateLspID, got) - } - if got := gnmi.Get(t, dut, statePath.Interface(intfName).Level(2).PacketCounters().Lsp().Sent().State()); got == 0 { - t.Errorf("FAIL- Expected lsp count is greater than 0, got %d", got) - } - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(dutLspID).RemainingLifetime().State()); got >= lspLifetime { - t.Errorf("FAIL- Expected remaining lifetime not found, got %d,want less then %d", got, lspLifetime) - } - }) - t.Run("Route checks", func(t *testing.T) { - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV4_INTERNAL_REACHABILITY).Ipv4InternalReachability().Prefix(v4Route).Prefix().State()); got != v4Route { - t.Errorf("FAIL- Expected v4 route not found in isis, got %v, want %v", got, v4Route) - } - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(v6Route).Prefix().State()); got != v6Route { - t.Errorf("FAIL- Expected v6 route not found in isis, got %v, want %v", got, v6Route) - } - if got := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Afts().Ipv4Entry(v4Route).State()).GetPrefix(); got != v4Route { - t.Errorf("FAIL- Expected v4 route not found in aft, got %v, want %v", got, v4Route) - } - if got := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Afts().Ipv6Entry(v6Route).State()).GetPrefix(); got != v6Route { - t.Errorf("FAIL- Expected v6 route not found in aft, got %v, want %v", got, v6Route) - } - }) - seqNum1 := gnmi.Get(t, dut, statePath.Level(2).Lsp(dutLspID).SequenceNumber().State()) - checksum1 := gnmi.Get(t, dut, statePath.Level(2).Lsp(dutLspID).Checksum().State()) - lspSent1 := gnmi.Get(t, dut, statePath.Interface(intfName).Level(2).PacketCounters().Lsp().Sent().State()) - - // Check the lsp's checksum/seq number/remaining lifetime once lsp refreshes periodically. - t.Run("Lsp lifetime checks", func(t *testing.T) { - _, ok := gnmi.Watch(t, dut, statePath.Interface(intfName).Level(2).PacketCounters().Lsp().Sent().State(), time.Minute*4, func(val *ygnmi.Value[uint32]) bool { - lspSent2, present := val.Val() - - if lspSent2 > lspSent1 { - time.Sleep(time.Second * 5) - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(dutLspID).SequenceNumber().State()); got <= seqNum1 { - t.Errorf("FAIL- Sequence number of new lsp should increment, got %d, want greater than %d", got, seqNum1) - } - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(dutLspID).Checksum().State()); got == checksum1 { - t.Errorf("FAIL- Checksum of new lsp should be different from %d, got %d", checksum1, got) - } - if got := gnmi.Get(t, dut, statePath.Level(2).Lsp(dutLspID).RemainingLifetime().State()); got >= lspLifetime || got < lspLifetime-50 { - t.Errorf("FAIL- Expected remaining lifetime not found, got %d,expected b/w %d and %d", got, lspLifetime, lspLifetime-50) - } - } - return present && lspSent2 > lspSent1 - }).Await(t) - if !ok { - t.Error("FAIL- Isis lsp is not refreshing periodically") - } - }) - t.Run("Traffic checks", func(t *testing.T) { - ate.Traffic().Start(t, flows...) - time.Sleep(time.Second * 15) - ate.Traffic().Stop(t) - - for _, flow := range flows { - t.Log("Checking flow telemetry...") - telem := gnmi.OC() - loss := gnmi.Get(t, ate, telem.Flow(flow.Name()).LossPct().State()) - - if loss > 1 { - t.Errorf("FAIL- Got %v%% packet loss for %s ; expected < 1%%", loss, flow.Name()) - } - } - }) - }) -} diff --git a/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/metadata.textproto b/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/metadata.textproto deleted file mode 100644 index e5832a7e139..00000000000 --- a/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/metadata.textproto +++ /dev/null @@ -1,43 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto -# proto-message: Metadata - -uuid: "7c80f692-478c-44c8-ba6e-d4d46bfebce1" -plan_id: "RT-2.10" -description: "IS-IS change LSP lifetime" -testbed: TESTBED_DUT_ATE_2LINKS -platform_exceptions: { - platform: { - vendor: NOKIA - } - deviations: { - isis_interface_level1_disable_required: true - missing_isis_interface_afi_safi_enable: true - explicit_port_speed: true - explicit_interface_in_default_vrf: true - missing_value_for_defaults: true - interface_enabled: true - } -} -platform_exceptions: { - platform: { - vendor: CISCO - } - deviations: { - ipv4_missing_enabled: true - isis_interface_level1_disable_required: true - } -} -platform_exceptions: { - platform: { - vendor: ARISTA - } - deviations: { - omit_l2_mtu: true - missing_value_for_defaults: true - interface_enabled: true - default_network_instance: "default" - isis_instance_enabled_required: true - isis_lsp_lifetime_interval_requires_lsp_refresh_interval: true - } -} -tags: TAGS_AGGREGATION diff --git a/feature/experimental/isis/ate_tests/lsp_updates_test/lsp_updates_test.go b/feature/experimental/isis/ate_tests/lsp_updates_test/lsp_updates_test.go deleted file mode 100644 index 1e234fba6e2..00000000000 --- a/feature/experimental/isis/ate_tests/lsp_updates_test/lsp_updates_test.go +++ /dev/null @@ -1,114 +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 -// -// 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 lsp_updates_test implements RT-2.2. -package lsp_updates_test - -import ( - "context" - "testing" - "time" - - "github.com/openconfig/featureprofiles/feature/experimental/isis/ate_tests/internal/session" - "github.com/openconfig/featureprofiles/internal/check" - "github.com/openconfig/featureprofiles/internal/deviations" - "github.com/openconfig/featureprofiles/internal/fptest" - "github.com/openconfig/ondatra/gnmi" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -func TestOverloadBit(t *testing.T) { - ts := session.MustNew(t).WithISIS() - // Only push DUT config - no adjacency established yet - if err := ts.PushDUT(context.Background(), t); err != nil { - t.Fatalf("Unable to push initial DUT config: %v", err) - } - isisPath := session.ISISPath(ts.DUT) - overloads := isisPath.Level(2).SystemLevelCounters().DatabaseOverloads() - //Lookup the initial value for 'database-overloads' leaf counter after config is pushed to DUT & before adjacency is formed - getDbOlInitCount := gnmi.Lookup(t, ts.DUT, overloads.State()) - olVal, present := getDbOlInitCount.Val() - if !present { - olVal = uint32(0) - } - ts.PushAndStartATE(t) - ts.MustAdjacency(t) - setBit := isisPath.Global().LspBit().OverloadBit().SetBit() - deadline := time.Now().Add(time.Second * 3) - checkSetBit := check.Equal(setBit.State(), false) - if deviations.MissingValueForDefaults(ts.DUT) { - checkSetBit = check.EqualOrNil(setBit.State(), false) - } - - for _, vd := range []check.Validator{ - checkSetBit, - check.EqualOrNil(overloads.State(), olVal), - } { - if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { - t.Error(err) - } - } - ts.DUTConf. - GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)). - GetProtocol(session.PTISIS, session.ISISName). - GetIsis(). - GetGlobal(). - GetOrCreateLspBit(). - GetOrCreateOverloadBit().SetBit = ygot.Bool(true) - ts.PushDUT(context.Background(), t) - // TODO: Verify the link state database once device support is added. - if err := check.Equal(overloads.State(), uint32(olVal+1)).AwaitFor(time.Second*10, ts.DUTClient); err != nil { - t.Error(err) - } - if err := check.Equal(setBit.State(), true).AwaitFor(time.Second*3, ts.DUTClient); err != nil { - t.Error(err) - } - // TODO: Verify the link state database on the ATE once the ATE reports this properly - // ateTelemPth := ts.ATEISISTelemetry(t) - // ateDB := ateTelemPth.Level(2).LspAny() - // for _, nbr := range ateDB.Tlv(telemetry.IsisLsdbTypes_ISIS_TLV_TYPE_IS_NEIGHBOR_ATTRIBUTE).IsisNeighborAttribute().NeighborAny().Get(t) { - // } -} - -func TestMetric(t *testing.T) { - t.Logf("Starting...") - ts := session.MustNew(t).WithISIS() - isisIntfName := ts.DUT.Port(t, "port1").Name() - if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { - isisIntfName = ts.DUT.Port(t, "port1").Name() + ".0" - } - ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(session.PTISIS, session.ISISName).GetIsis(). - GetInterface(isisIntfName). - GetOrCreateLevel(2). - GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST). - Metric = ygot.Uint32(100) - ts.PushAndStart(t) - ts.MustAdjacency(t) - - metric := session.ISISPath(ts.DUT).Interface(isisIntfName).Level(2). - Af(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Metric() - if err := check.Equal(metric.State(), uint32(100)).AwaitFor(time.Second*3, ts.DUTClient); err != nil { - t.Error(err) - } - // TODO: Verify the link state database on the ATE once the ATE reports this properly - // ateTelemPth := ts.ATEISISTelemetry(t) - // ateDB := ateTelemPth.Level(2).LspAny() - // for _, nbr := range ateDB.Tlv(telemetry.IsisLsdbTypes_ISIS_TLV_TYPE_IS_NEIGHBOR_ATTRIBUTE).IsisNeighborAttribute().NeighborAny().Get(t) { - // } -} diff --git a/feature/experimental/isis/ate_tests/lsp_updates_test/metadata.textproto b/feature/experimental/isis/ate_tests/lsp_updates_test/metadata.textproto deleted file mode 100644 index 1af1369658e..00000000000 --- a/feature/experimental/isis/ate_tests/lsp_updates_test/metadata.textproto +++ /dev/null @@ -1,42 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto -# proto-message: Metadata - -uuid: "24249426-4ec4-417e-8885-f147d04eebf2" -plan_id: "RT-2.2" -description: "IS-IS LSP Updates" -testbed: TESTBED_DUT_ATE_2LINKS -platform_exceptions: { - platform: { - vendor: NOKIA - } - deviations: { - isis_interface_level1_disable_required: true - missing_isis_interface_afi_safi_enable: true - explicit_port_speed: true - explicit_interface_in_default_vrf: true - missing_value_for_defaults: true - interface_enabled: true - } -} -platform_exceptions: { - platform: { - vendor: CISCO - } - deviations: { - ipv4_missing_enabled: true - isis_interface_level1_disable_required: true - } -} -platform_exceptions: { - platform: { - vendor: ARISTA - } - deviations: { - omit_l2_mtu: true - missing_value_for_defaults: true - interface_enabled: true - default_network_instance: "default" - isis_instance_enabled_required: true - isis_interface_afi_unsupported: true - } -} diff --git a/feature/experimental/isis/otg_tests/base_adjacencies_test/README.md b/feature/experimental/isis/otg_tests/base_adjacencies_test/README.md deleted file mode 100644 index 3448bd66fb8..00000000000 --- a/feature/experimental/isis/otg_tests/base_adjacencies_test/README.md +++ /dev/null @@ -1,167 +0,0 @@ -# RT-2.1: Base IS-IS Process and Adjacencies - -## Summary - -Base IS-IS functionality and adjacency establishment. - -## Procedure - -* Basic fields test - * Configure DUT:port1 for an IS-IS session with ATE:port1. - * Read back the configuration to ensure that all fields are readable and - have been set properly (or correctly have their default value). - * Check that all relevant counters are readable and are 0 since the - adjacency has not yet been established. - * Push ATE configuration for the other end of the adjacency, and wait for - the adjacency to form. - * Check that the various state fields of the adjacency are reported - correctly. - * Check that error counters are still 0 and that packet counters have all - increased. -* Hello padding test - * Configure IS-IS between DUT:port1 and ATE:port1 for each possible value - of hello padding (DISABLED, STRICT, etc.) - * Confirm in each case that that adjacency forms and the correct values - are reported back by the device. -* Authentication test - * Configure IS-IS between DUT:port1 and ATE:port1 With authentication - disabled, then enabled in TEXT mode, then enabled in MD5 mode. - * Confirm in each case that that adjacency forms and the correct values - are reported back by the device. -* Routing test -* With ISIS level authentication enabled and hello authentication enabled: - * Ensure that IPv4 and IPv6 prefixes that are advertised as attached - prefixes within each LSP are correctly installed into the DUT - routing table, by ensuring that packets are received to the attached - prefix when forwarded from ATE port-1. - * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an - (emulated) neighboring system are installed into the DUT routing - table, and validate that packets are sent and received to them. - * With a known LSP content, ensure that the telemetry received from the - device for the LSP matches the expected content. - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * TODO: global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/metric - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safis/afi-safi/state/metric - * interfaces/interface/levels/level/packet-counters/cnsp/dropped - * interfaces/interface/levels/level/packet-counters/cnsp/processed - * interfaces/interface/levels/level/packet-counters/cnsp/received - * interfaces/interface/levels/level/packet-counters/cnsp/sent - * interfaces/interface/levels/level/packet-counters/iih/dropped - * interfaces/interface/levels/level/packet-counters/iih/processed - * interfaces/interface/levels/level/packet-counters/iih/received - * interfaces/interface/levels/level/packet-counters/iih/retransmit - * interfaces/interface/levels/level/packet-counters/iih/sent - * interfaces/interface/levels/level/packet-counters/lsp/dropped - * interfaces/interface/levels/level/packet-counters/lsp/processed - * interfaces/interface/levels/level/packet-counters/lsp/received - * interfaces/interface/levels/level/packet-counters/lsp/retransmit - * interfaces/interface/levels/level/packet-counters/lsp/sent - * interfaces/interface/levels/level/packet-counters/psnp/dropped - * interfaces/interface/levels/level/packet-counters/psnp/processed - * interfaces/interface/levels/level/packet-counters/psnp/received - * interfaces/interface/levels/level/packet-counters/psnp/retransmit - * interfaces/interface/levels/level/packet-counters/psnp/sent - * interfaces/interfaces/circuit-counters/state/adj-changes - * interfaces/interfaces/circuit-counters/state/adj-number - * interfaces/interfaces/circuit-counters/state/auth-fails - * interfaces/interfaces/circuit-counters/state/auth-type-fails - * interfaces/interfaces/circuit-counters/state/id-field-len-mismatches - * interfaces/interfaces/circuit-counters/state/lan-dis-changes - * interfaces/interfaces/circuit-counters/state/max-area-address-mismatch - * interfaces/interfaces/circuit-counters/state/rejected-adj - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/local-extended-system-id - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-extended-system-id - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/priority - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/remaining-hold-time - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interfaces/levels/level/adjacencies/adjacency/state/restart-suppress - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceeded-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs - -* For LSDB - subpaths of - - * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/... - -## Protocol/RPC Parameter coverage - -* IS-IS: - * LSP messages - * TLV 1 (Area Addresses) - * TLV 10 (Authentication) - * TLV 22 (Extended IS reach) - * TLV 135 (Extended IP Reachability) - * TLV 137 (Dynamic Name) - * TLV 232 (IPv6 Reachability) - -## Minimum DUT platform requirement - -vRX diff --git a/feature/experimental/isis/otg_tests/internal/session/attrs.go b/feature/experimental/isis/otg_tests/internal/session/attrs.go deleted file mode 100644 index 77edb2efcf0..00000000000 --- a/feature/experimental/isis/otg_tests/internal/session/attrs.go +++ /dev/null @@ -1,146 +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 -// -// 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 session - -// This is identical to the internal/attrs library except it points to ygnmi -import ( - "fmt" - - "github.com/open-traffic-generator/snappi/gosnappi" - "github.com/openconfig/featureprofiles/internal/deviations" - "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ygot/ygot" -) - -// Attributes bundles some common attributes for devices and/or interfaces. -// It provides helpers to generate appropriate configuration for OpenConfig -// and for an ATETopology. All fields are optional; only those that are -// non-empty will be set when configuring an interface. -type Attributes struct { - IPv4 string - IPv6 string - MAC string - Name string // Interface name, only applied to ATE ports. - Desc string // Description, only applied to DUT interfaces. - IPv4Len uint8 // Prefix length for IPv4. - IPv6Len uint8 // Prefix length for IPv6. - MTU uint16 -} - -// IPv4CIDR constructs the IPv4 CIDR notation with the given prefix -// length, e.g. "192.0.2.1/30". -func (a *Attributes) IPv4CIDR() string { - return fmt.Sprintf("%s/%d", a.IPv4, a.IPv4Len) -} - -// IPv6CIDR constructs the IPv6 CIDR notation with the given prefix -// length, e.g. "2001:db8::1/126". -func (a *Attributes) IPv6CIDR() string { - return fmt.Sprintf("%s/%d", a.IPv6, a.IPv6Len) -} - -// ConfigInterface configures an OpenConfig interface with these attributes. -func (a *Attributes) ConfigInterface(intf *oc.Interface, dut *ondatra.DUTDevice) *oc.Interface { - if a.Desc != "" { - intf.Description = ygot.String(a.Desc) - } - intf.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd - if deviations.InterfaceEnabled(dut) { - intf.Enabled = ygot.Bool(true) - } - if a.MTU > 0 && !deviations.OmitL2MTU(dut) { - intf.Mtu = ygot.Uint16(a.MTU + 14) - } - e := intf.GetOrCreateEthernet() - if a.MAC != "" { - e.MacAddress = ygot.String(a.MAC) - } - - s := intf.GetOrCreateSubinterface(0) - if a.IPv4 != "" { - s4 := s.GetOrCreateIpv4() - if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { - s4.Enabled = ygot.Bool(true) - } - if a.MTU > 0 { - s4.Mtu = ygot.Uint16(a.MTU) - } - a4 := s4.GetOrCreateAddress(a.IPv4) - if a.IPv4Len > 0 { - a4.PrefixLength = ygot.Uint8(a.IPv4Len) - } - } - - if a.IPv6 != "" { - s6 := s.GetOrCreateIpv6() - if a.MTU > 0 { - s6.Mtu = ygot.Uint32(uint32(a.MTU)) - } - if deviations.InterfaceEnabled(dut) { - s6.Enabled = ygot.Bool(true) - } - a6 := s6.GetOrCreateAddress(a.IPv6) - if a.IPv6Len > 0 { - a6.PrefixLength = ygot.Uint8(a.IPv6Len) - } - } - return intf -} - -// NewOCInterface returns a new *oc.Interface configured with these attributes -func (a *Attributes) NewOCInterface(name string, dut *ondatra.DUTDevice) *oc.Interface { - return a.ConfigInterface(&oc.Interface{Name: ygot.String(name)}, dut) -} - -// AddToATE adds a new interface to an ATETopology with these attributes. -func (a *Attributes) AddToATE(top *ondatra.ATETopology, ap *ondatra.Port, peer *Attributes) *ondatra.Interface { - i := top.AddInterface(a.Name).WithPort(ap) - if a.MTU > 0 { - i.Ethernet().WithMTU(a.MTU) - } - if a.IPv4 != "" { - i.IPv4(). - WithAddress(a.IPv4CIDR()). - WithDefaultGateway(peer.IPv4) - } - if a.IPv6 != "" { - i.IPv6(). - WithAddress(a.IPv6CIDR()). - WithDefaultGateway(peer.IPv6) - } - return i -} - -// AddToOTG adds basic elements to a gosnappi configuration -func (a *Attributes) AddToOTG(top gosnappi.Config, ap *ondatra.Port, peer *Attributes) gosnappi.Device { - top.Ports().Add().SetName(ap.ID()) - dev := top.Devices().Add().SetName(a.Name) - eth := dev.Ethernets().Add().SetName(a.Name + ".Eth").SetMac(a.MAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap.ID()) - - if a.MTU > 0 { - eth.SetMtu(uint32(a.MTU)) - } - if a.IPv4 != "" { - ip := eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") - ip.SetAddress(a.IPv4).SetGateway(peer.IPv4).SetPrefix(uint32(a.IPv4Len)) - } - if a.IPv6 != "" { - ip := eth.Ipv6Addresses().Add().SetName(dev.Name() + ".IPv6") - ip.SetAddress(a.IPv6).SetGateway(peer.IPv6).SetPrefix(uint32(a.IPv6Len)) - } - return dev -} diff --git a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/README.md b/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/README.md deleted file mode 100644 index 0a0d6fe16b1..00000000000 --- a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# RT-2.6: IS-IS Hello-Padding enabled at interface level - -## Summary - -* Base IS-IS functionality and adjacency establishment. -* Verifies isis adjacency by changing MTU. - -## Procedure - -* Configure IS-IS for ATE port-1 and DUT port-1. -* Configure DUT with global hello-padding enabled. -* Ensure that adjacencies are established with: - * Interface level hello padding is enabled. - * Verify that IPv4 and IPv6 IS-ISIS adjacency comes up fine. - * Verify the output of ST path displaying the status of ISIS hello padding. - * If we change the MTU on either side, then adjacency should not come up. - * Verify that IPv4 and IPv6 prefixes that are advertised by ATE correctly installed into DUTs route and forwarding table. - * TODO-Verify the Hellos are sent with Padding during adjacency turn-up if the padding is enabled adaptively/sometimes. - * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - * levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/metric - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/state/hello-padding - * interfaces/interface/state/hello-padding - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interface/levels/level/adjacencies/adjacency/state/priority - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceed-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/manual-address-drop-from-area - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/part-changes - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs diff --git a/feature/experimental/isis/otg_tests/isis_interface_level_passive_test/README.md b/feature/experimental/isis/otg_tests/isis_interface_level_passive_test/README.md deleted file mode 100644 index 6e80f5f2985..00000000000 --- a/feature/experimental/isis/otg_tests/isis_interface_level_passive_test/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# RT-2.11: IS-IS Passive is enabled at the area level - -## Summary - -* Verify isis adjacency with passive enabled under level. - -## Topology - -* ATE:port1 <-> port1:DUT:port2 <-> ATE:port2 - -## Procedure - -* Configure IS-IS for ATE port-1 and DUT port-1. -* Configure DUT interface with IS-IS passive configured at area level 2. - * Verify that IS-IS adjacency is not coming up in level-2 area for IPv4 and IPV6 address families. -* Undo the IS-IS passive configuration under level 2 - * Verify that IS-IS adjacency for IPv4 and IPV6 address families are coming up in the level-2 area. - * Verify that IPv4 and IPv6 prefixes that are advertised by ATE are correctly installed into DUTs route and forwarding table. - * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. - * TODO-Verify the output of ST path displaying the interface as passive in ISIS database/adj table - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - * levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/config/passive - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/config/passive - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/metric - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * interfaces/interface/state/passive - * interfaces/interface/levels/level/state/passive - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interface/levels/level/adjacencies/adjacency/state/priority - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceed-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/manual-address-drop-from-area - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/part-changes - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md b/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md deleted file mode 100644 index ce29ac4d93c..00000000000 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# RT-2.7: IS-IS Passive is enabled at interface level - -## Summary - -* Base IS-IS functionality and adjacency establishment. -* Ensure that IS-IS adjacency is not coming up on the passive interface - -## Procedure - -* TestIsisInterfacePassive - - * Configure IS-IS for ATE port-1 and DUT port-1. - * Configure DUT interface as ISIS passive interface. - * Ensure that IS-IS adjacency is not coming up on the passive interface. - * TODO-Verify the output of ST path displaying the interface as passive in ISIS database/adj table - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - * levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/config/passive - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/config/passive - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * interfaces/interface/state/passive - * interfaces/interface/levels/level/state/passive - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interface/levels/level/adjacencies/adjacency/state/priority - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceed-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/manual-address-drop-from-area - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/part-changes - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs diff --git a/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/README.md b/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/README.md deleted file mode 100644 index 7d563c2c2f9..00000000000 --- a/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# RT-2.9: IS-IS metric style wide enabled - -## Summary - -* Base IS-IS functionality and adjacency establishment. -* Verifies route metric with wide metric enabled on DUT. - -## Procedure - - * Configure IS-IS for ATE port-1 and DUT port-1. - * Enable wide metric style on ATE and DUT. - * Advertise ISIS prefixes from ATE with wide metrics (value > 63). - * Verify that IS-IS adjacency for IPv4 and IPV6 address family is coming up. - * Verify that IPv4 and IPv6 prefixes that are advertised by ATE correctly installed into DUTs route and forwarding table. - * TODO-Verify that the metrics of the IPv4 and IPv6 prefixes is correctly reflected - * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. - - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/config/metric-style - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - * levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/config/passive - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/config/passive - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * levels/level/state/metric-style - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interface/levels/level/adjacencies/adjacency/state/priority - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceed-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/manual-address-drop-from-area - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/part-changes - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs diff --git a/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/README.md b/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/README.md deleted file mode 100644 index 4347c71375c..00000000000 --- a/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# RT-2.8: IS-IS metric style wide not enabled - -## Summary - -* Base IS-IS functionality and adjacency establishment. -* Verifies route metric with wide metric disabled on DUT. - -## Procedure - -* TestISISWideMetricNotEnabled - - * Configure IS-IS for ATE port-1 and DUT port-1. - * Do not configure metric style wide under the area level. - * Enable wide metric style on ATE. - * Advertise ISIS prefixes from ATE with wide metrics (value > 63). - * Verify that IS-IS adjacency for IPv4 and IPV6 address family is coming up. - * Verify that IPv4 and IPv6 prefixes that are advertised by ATE correctly installed into DUTs route and forwarding table. - * TODO-Verify that the metrics of the IPv4 and IPv6 prefixes is 63. - * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. - - -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/config/metric-style - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - * levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/config/passive - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/config/passive - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * levels/level/state/metric-style - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interface/levels/level/adjacencies/adjacency/state/priority - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceed-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/manual-address-drop-from-area - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/part-changes - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs diff --git a/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/metadata.textproto b/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/metadata.textproto deleted file mode 100644 index 471401ab8cd..00000000000 --- a/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/metadata.textproto +++ /dev/null @@ -1,54 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto -# proto-message: Metadata - -uuid: "d472b5af-70fa-4259-ae6d-29c921820419" -plan_id: "RT-2.8" -description: "IS-IS metric style wide not enabled" -testbed: TESTBED_DUT_ATE_2LINKS -platform_exceptions: { - platform: { - vendor: NOKIA - } - deviations: { - isis_interface_level1_disable_required: true - missing_isis_interface_afi_safi_enable: true - explicit_port_speed: true - explicit_interface_in_default_vrf: true - missing_value_for_defaults: true - interface_enabled: true - } -} -platform_exceptions: { - platform: { - vendor: CISCO - } - deviations: { - ipv4_missing_enabled: true - isis_interface_level1_disable_required: true - } -} -platform_exceptions: { - platform: { - vendor: ARISTA - } - deviations: { - omit_l2_mtu: true - missing_value_for_defaults: true - interface_enabled: true - default_network_instance: "default" - isis_instance_enabled_required: true - isis_lsp_lifetime_interval_requires_lsp_refresh_interval: true - isis_timers_csnp_interval_unsupported: true - isis_counter_manual_address_drop_from_areas_unsupported: true - isis_counter_part_changes_unsupported: true - } -} -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - isis_level_enabled: true - } -} -tags: TAGS_AGGREGATION diff --git a/feature/experimental/isis/otg_tests/lsp_updates_test/README.md b/feature/experimental/isis/otg_tests/lsp_updates_test/README.md deleted file mode 100644 index d207d7c482a..00000000000 --- a/feature/experimental/isis/otg_tests/lsp_updates_test/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# RT-2.2: IS-IS LSP Updates - -## Summary - -Ensure that IS-IS updates reflect parameter changes on DUT. - -## Procedure - -* Configure L2 IS-IS adjacency between ATE port-1 and DUT port-1, and ATE - port-2 and DUT port-2. - -* Validate that received LSDB on ATE has: - - * TODO: Overload bit unset by default, change overload bit to set via DUT - configuration, and ensure that the overload bit is advertised as set (as - observed by the ATE). Ensure that DUT telemetry reflects the overload - bit is set. - - * TODO: Metric is set to the specified value for ATE port-1 facing DUT - port via configuration, update value in configuration, and ensure that - ATE and DUT telemetry reflects the change. - -## Config Parameter Coverage - -For prefix: /network-instances/network-instance/protocols/protocol/isis/ - -Parameters: - -* global/lsp-bit/overload-bit/config/set-bit - -## Telemetry Parameter Coverage - -* /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric - -* /network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/state/set-bit - -## Protocol/RPC Parameter Coverage - -* IS-IS - * LSP - * Flags - overload bit (5) - * TLV 22 metric field. - -## Minimum DUT Platform Requirement - -vRX diff --git a/feature/experimental/lacp_and_base_interface/aggregate_interfaces/feature.textproto b/feature/experimental/lacp_and_base_interface/aggregate_interfaces/feature.textproto deleted file mode 100644 index a96add04cd9..00000000000 --- a/feature/experimental/lacp_and_base_interface/aggregate_interfaces/feature.textproto +++ /dev/null @@ -1,91 +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. - -id { - name: "experimental_lacp_and_base_interface_aggregate_interfaces" - version: 1 -} - -config_path { - path: "/interfaces/interface/ethernet/config/mac-address" -} -config_path { - path: "/interfaces/interface/ethernet/config/port-speed" -} -config_path { - path: "/interfaces/interface/ethernet/config/duplex-mode" -} -config_path { - path: "/interfaces/interface/ethernet/config/aggregate-id" -} -config_path { - path: "/interfaces/interface/aggregation/config/lag-type" -} -config_path { - path: "/interfaces/interface/aggregation/config/min-links" -} -config_path { - path: "/lacp/config/system-priority" -} -config_path { - path: "/lacp/interfaces/interface/config/name" -} -config_path { - path: "/lacp/interfaces/interface/config/interval" -} -config_path { - path: "/lacp/interfaces/interface/config/lacp-mode" -} -config_path { - path: "/lacp/interfaces/interface/config/system-id-mac" -} -config_path { - path: "/lacp/interfaces/interface/config/system-priority" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-in-pkts" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-errors" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-timeout-transitions" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-tx-errors" -} -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/last-change" -} -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" -} -telemetry_path { - path: "/lacp/interfaces/interface/state/system-priority" -} diff --git a/feature/experimental/p4rt/wbb.p4info.pb.txt b/feature/experimental/p4rt/wbb.p4info.pb.txt deleted file mode 100644 index 81b862ef193..00000000000 --- a/feature/experimental/p4rt/wbb.p4info.pb.txt +++ /dev/null @@ -1,798 +0,0 @@ -pkg_info { - name: "wbb.p4" - arch: "v1model" - organization: "Google" -} -tables { - preamble { - id: 33554503 - name: "ingress.l3_admit.l3_admit_table" - alias: "l3_admit_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "dst_mac" - annotations: "@format(MAC_ADDRESS)" - bitwidth: 48 - match_type: TERNARY - } - match_fields { - id: 2 - name: "in_port" - match_type: OPTIONAL - type_name { - name: "port_id_t" - } - } - action_refs { - id: 16777224 - annotations: "@proto_id(1)" - } - action_refs { - id: 21257015 - annotations: "@defaultonly" - scope: DEFAULT_ONLY - } - const_default_action_id: 21257015 - size: 128 -} -tables { - preamble { - id: 33554496 - name: "ingress.routing.neighbor_table" - alias: "neighbor_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "router_interface_id" - annotations: "@refers_to(router_interface_table , router_interface_id)" - match_type: EXACT - type_name { - name: "router_interface_id_t" - } - } - match_fields { - id: 2 - name: "neighbor_id" - annotations: "@format(IPV6_ADDRESS)" - bitwidth: 128 - match_type: EXACT - } - action_refs { - id: 16777217 - annotations: "@proto_id(1)" - } - action_refs { - id: 21257015 - annotations: "@defaultonly" - scope: DEFAULT_ONLY - } - const_default_action_id: 21257015 - size: 1024 -} -tables { - preamble { - id: 33554497 - name: "ingress.routing.router_interface_table" - alias: "router_interface_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "router_interface_id" - match_type: EXACT - type_name { - name: "router_interface_id_t" - } - } - action_refs { - id: 16777218 - annotations: "@proto_id(1)" - } - action_refs { - id: 21257015 - annotations: "@defaultonly" - scope: DEFAULT_ONLY - } - const_default_action_id: 21257015 - size: 256 -} -tables { - preamble { - id: 33554498 - name: "ingress.routing.nexthop_table" - alias: "nexthop_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "nexthop_id" - match_type: EXACT - type_name { - name: "nexthop_id_t" - } - } - action_refs { - id: 16777219 - annotations: "@proto_id(1)" - } - action_refs { - id: 16777236 - annotations: "@proto_id(3)" - } - action_refs { - id: 21257015 - annotations: "@defaultonly" - scope: DEFAULT_ONLY - } - const_default_action_id: 21257015 - size: 1024 -} -tables { - preamble { - id: 33554499 - name: "ingress.routing.wcmp_group_table" - alias: "wcmp_group_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - annotations: "@oneshot" - } - match_fields { - id: 1 - name: "wcmp_group_id" - match_type: EXACT - type_name { - name: "wcmp_group_id_t" - } - } - action_refs { - id: 16777221 - annotations: "@proto_id(1)" - } - action_refs { - id: 21257015 - annotations: "@defaultonly" - scope: DEFAULT_ONLY - } - const_default_action_id: 21257015 - implementation_id: 299650760 - size: 3968 -} -tables { - preamble { - id: 33554506 - name: "ingress.routing.vrf_table" - alias: "vrf_table" - annotations: "@entry_restriction(\"\n // The VRF ID 0 (or \'\' in P4Runtime) encodes the default VRF, which cannot\n // be read or written via this table, but is always present implicitly.\n // TODO: This constraint should read `vrf_id != \'\'` (since\n // constraints are a control plane (P4Runtime) concept), but\n // p4-constraints does not currently support strings.\n vrf_id != 0;\n \")" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "vrf_id" - match_type: EXACT - type_name { - name: "vrf_id_t" - } - } - action_refs { - id: 24742814 - annotations: "@proto_id(1)" - } - const_default_action_id: 24742814 - size: 64 -} -tables { - preamble { - id: 33554500 - name: "ingress.routing.ipv4_table" - alias: "ipv4_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "vrf_id" - annotations: "@refers_to(vrf_table , vrf_id)" - match_type: EXACT - type_name { - name: "vrf_id_t" - } - } - match_fields { - id: 2 - name: "ipv4_dst" - annotations: "@format(IPV4_ADDRESS)" - bitwidth: 32 - match_type: LPM - } - action_refs { - id: 16777222 - annotations: "@proto_id(1)" - } - action_refs { - id: 16777221 - annotations: "@proto_id(2)" - } - action_refs { - id: 16777220 - annotations: "@proto_id(3)" - } - action_refs { - id: 16777231 - annotations: "@proto_id(4)" - } - action_refs { - id: 16777232 - annotations: "@proto_id(5)" - } - action_refs { - id: 16777233 - annotations: "@proto_id(6)" - } - const_default_action_id: 16777222 - size: 32768 -} -tables { - preamble { - id: 33554501 - name: "ingress.routing.ipv6_table" - alias: "ipv6_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "vrf_id" - annotations: "@refers_to(vrf_table , vrf_id)" - match_type: EXACT - type_name { - name: "vrf_id_t" - } - } - match_fields { - id: 2 - name: "ipv6_dst" - annotations: "@format(IPV6_ADDRESS)" - bitwidth: 128 - match_type: LPM - } - action_refs { - id: 16777222 - annotations: "@proto_id(1)" - } - action_refs { - id: 16777221 - annotations: "@proto_id(2)" - } - action_refs { - id: 16777220 - annotations: "@proto_id(3)" - } - action_refs { - id: 16777231 - annotations: "@proto_id(4)" - } - action_refs { - id: 16777232 - annotations: "@proto_id(5)" - } - action_refs { - id: 16777233 - annotations: "@proto_id(6)" - } - const_default_action_id: 16777222 - size: 4096 -} -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 && outer_vlan_id::mask == 0\n ) ||\n // LLDP\n (\n ether_type == 0x88cc &&\n is_ipv4::mask == 0 && is_ipv6::mask == 0 && ttl::mask == 0 &&\n outer_vlan_id::mask == 0\n ) ||\n // ND\n (\n // TODO remove optional match for VLAN ID once VLAN ID is\n // completely removed from ND flows.\n (( outer_vlan_id::mask == 0xfff && outer_vlan_id == 0x0FA0) ||\n outer_vlan_id::mask == 0);\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 - } - match_fields { - id: 5 - name: "outer_vlan_id" - annotations: "@sai_field(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)" - bitwidth: 12 - 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 -} -tables { - preamble { - id: 33554502 - name: "ingress.mirroring_clone.mirror_session_table" - alias: "mirror_session_table" - annotations: "@p4runtime_role(\"sdn_controller\")" - } - match_fields { - id: 1 - name: "mirror_session_id" - match_type: EXACT - type_name { - name: "mirror_session_id_t" - } - } - action_refs { - id: 16777223 - annotations: "@proto_id(1)" - } - action_refs { - id: 21257015 - annotations: "@defaultonly" - scope: DEFAULT_ONLY - } - const_default_action_id: 21257015 - size: 2 -} -tables { - preamble { - id: 33554504 - name: "ingress.mirroring_clone.mirror_port_to_pre_session_table" - alias: "mirror_port_to_pre_session_table" - annotations: "@p4runtime_role(\"packet_replication_engine_manager\")" - } - match_fields { - id: 1 - name: "mirror_port" - match_type: EXACT - type_name { - name: "port_id_t" - } - } - action_refs { - id: 16777225 - annotations: "@proto_id(1)" - } - action_refs { - id: 21257015 - annotations: "@defaultonly" - scope: DEFAULT_ONLY - } - const_default_action_id: 21257015 - size: 1024 -} -actions { - preamble { - id: 21257015 - name: "NoAction" - alias: "NoAction" - annotations: "@noWarn(\"unused\")" - } -} -actions { - preamble { - id: 16777224 - name: "ingress.l3_admit.admit_to_l3" - alias: "admit_to_l3" - } -} -actions { - preamble { - id: 16777217 - name: "ingress.routing.set_dst_mac" - alias: "set_dst_mac" - } - params { - id: 1 - name: "dst_mac" - annotations: "@format(MAC_ADDRESS)" - bitwidth: 48 - } -} -actions { - preamble { - id: 16777218 - name: "ingress.routing.set_port_and_src_mac" - alias: "set_port_and_src_mac" - } - params { - id: 1 - name: "port" - type_name { - name: "port_id_t" - } - } - params { - id: 2 - name: "src_mac" - annotations: "@format(MAC_ADDRESS)" - bitwidth: 48 - } -} -actions { - preamble { - id: 16777236 - name: "ingress.routing.set_ip_nexthop" - alias: "set_ip_nexthop" - } - params { - id: 1 - name: "router_interface_id" - annotations: "@refers_to(router_interface_table , router_interface_id)" - annotations: "@refers_to(neighbor_table , router_interface_id)" - type_name { - name: "router_interface_id_t" - } - } - params { - id: 2 - name: "neighbor_id" - annotations: "@format(IPV6_ADDRESS)" - annotations: "@refers_to(neighbor_table , neighbor_id)" - bitwidth: 128 - } -} -actions { - preamble { - id: 16777219 - name: "ingress.routing.set_nexthop" - alias: "set_nexthop" - annotations: "@deprecated(\"Use set_ip_nexthop instead.\")" - } - params { - id: 1 - name: "router_interface_id" - annotations: "@refers_to(router_interface_table , router_interface_id)" - annotations: "@refers_to(neighbor_table , router_interface_id)" - type_name { - name: "router_interface_id_t" - } - } - params { - id: 2 - name: "neighbor_id" - annotations: "@format(IPV6_ADDRESS)" - annotations: "@refers_to(neighbor_table , neighbor_id)" - bitwidth: 128 - } -} -actions { - preamble { - id: 16777221 - name: "ingress.routing.set_nexthop_id" - alias: "set_nexthop_id" - } - params { - id: 1 - name: "nexthop_id" - annotations: "@refers_to(nexthop_table , nexthop_id)" - type_name { - name: "nexthop_id_t" - } - } -} -actions { - preamble { - id: 16777232 - name: "ingress.routing.set_nexthop_id_and_metadata" - alias: "set_nexthop_id_and_metadata" - } - params { - id: 1 - name: "nexthop_id" - annotations: "@refers_to(nexthop_table , nexthop_id)" - type_name { - name: "nexthop_id_t" - } - } - params { - id: 2 - name: "route_metadata" - bitwidth: 6 - } -} -actions { - preamble { - id: 24742814 - name: "ingress.routing.no_action" - alias: "no_action" - } -} -actions { - preamble { - id: 16777222 - name: "ingress.routing.drop" - alias: "drop" - } -} -actions { - preamble { - id: 16777220 - name: "ingress.routing.set_wcmp_group_id" - alias: "set_wcmp_group_id" - } - params { - id: 1 - name: "wcmp_group_id" - annotations: "@refers_to(wcmp_group_table , wcmp_group_id)" - type_name { - name: "wcmp_group_id_t" - } - } -} -actions { - preamble { - id: 16777233 - name: "ingress.routing.set_wcmp_group_id_and_metadata" - alias: "set_wcmp_group_id_and_metadata" - } - params { - id: 1 - name: "wcmp_group_id" - annotations: "@refers_to(wcmp_group_table , wcmp_group_id)" - type_name { - name: "wcmp_group_id_t" - } - } - params { - id: 2 - name: "route_metadata" - bitwidth: 6 - } -} -actions { - preamble { - id: 16777231 - name: "ingress.routing.trap" - alias: "trap" - } -} -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)" - } -} -actions { - preamble { - id: 16777223 - name: "ingress.mirroring_clone.mirror_as_ipv4_erspan" - alias: "mirror_as_ipv4_erspan" - } - params { - id: 1 - name: "port" - type_name { - name: "port_id_t" - } - } - params { - id: 2 - name: "src_ip" - annotations: "@format(IPV4_ADDRESS)" - bitwidth: 32 - } - params { - id: 3 - name: "dst_ip" - annotations: "@format(IPV4_ADDRESS)" - bitwidth: 32 - } - params { - id: 4 - name: "src_mac" - annotations: "@format(MAC_ADDRESS)" - bitwidth: 48 - } - params { - id: 5 - name: "dst_mac" - annotations: "@format(MAC_ADDRESS)" - bitwidth: 48 - } - params { - id: 6 - name: "ttl" - bitwidth: 8 - } - params { - id: 7 - name: "tos" - bitwidth: 8 - } -} -actions { - preamble { - id: 16777225 - name: "ingress.mirroring_clone.set_pre_session" - alias: "set_pre_session" - } - params { - id: 1 - name: "id" - bitwidth: 32 - } -} -action_profiles { - preamble { - id: 299650760 - name: "ingress.routing.wcmp_group_selector" - alias: "wcmp_group_selector" - } - table_ids: 33554499 - with_selector: true - size: 65536 - max_group_size: 256 -} -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" - bitwidth: 7 - } -} -type_info { - new_types { - key: "mirror_session_id_t" - value { - translated_type { - sdn_string { - } - } - } - } - new_types { - key: "nexthop_id_t" - value { - translated_type { - sdn_string { - } - } - } - } - new_types { - key: "port_id_t" - value { - translated_type { - sdn_string { - } - } - } - } - new_types { - key: "router_interface_id_t" - value { - translated_type { - sdn_string { - } - } - } - } - new_types { - key: "vrf_id_t" - value { - translated_type { - sdn_string { - } - } - } - } - new_types { - key: "wcmp_group_id_t" - value { - translated_type { - sdn_string { - } - } - } - } -} diff --git a/feature/experimental/platform/tests/breakout_configuration/README.md b/feature/experimental/platform/tests/breakout_configuration/README.md deleted file mode 100644 index d70c053e362..00000000000 --- a/feature/experimental/platform/tests/breakout_configuration/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# PLT-1.1: Interface breakout Test - -## Summary - -Validate Interface breakout configuration. - -## Procedure - - -* This test is carried out for different breakout types -* Connect DUT with ATE to all interfaces in the breakout port -* Configure each interface with test IP addressing -* Verify correct interface state and speed reported -* Verify that DUT responds to ARP/ICMP on all tested interfaces - -## Config Parameter coverage - -* /components/component/port/breakout-mode/groups/group/index -* /components/component/port/breakout-mode/groups/group/config -* /components/component/port/breakout-mode/groups/group/config/index -* /components/component/port/breakout-mode/groups/group/config/num-breakouts -* /components/component/port/breakout-mode/groups/group/config/breakout-speed -* /components/component/port/breakout-mode/groups/group/config/num-physical-channels - - -## Telemetry Parameter coverage - * interfaces/interface/state - * interfaces/interface/ethernet/stateOutput power thresholds: - -## Minimum DUT Platform Requirement - -* Breakout types - 4x100G, 2x100G and 4x10G \ No newline at end of file diff --git a/feature/experimental/policy/policy_base/feature.textproto b/feature/experimental/policy/policy_base/feature.textproto deleted file mode 100644 index e1e3a20a9e4..00000000000 --- a/feature/experimental/policy/policy_base/feature.textproto +++ /dev/null @@ -1,205 +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. - -id { - name: "experimental_policy_policy_base" - version: 1 -} - -# Policy base - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/config/type" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/state/policy-id" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/state/type" -} - -# Rules base - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/sequence-id" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/sequence-id" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-pkts" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-octets" -} - -# Action base - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/discard" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/state/discard" -} - -# Interface base - -config_path { - path: "/network-instances/network-instance/policy-forwarding/interfaces/interface/config/interface-id" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/interfaces/interface/state/interface-id" -} - -# L2 rules - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/config/ethertype" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/config/source-mac" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/config/source-mac-mask" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/config/destination-mac" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/config/destination-mac-mask" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/state/ethertype" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/state/source-mac" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/state/source-mac-mask" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/state/destination-mac" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/l2/state/destination-mac-mask" -} - -# IPv4 rules - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/source-address" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/destination-address" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/dscp" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/dscp-set" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/protocol" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/hop-limit" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/state/source-address" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/state/destination-address" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/state/dscp" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/state/dscp-set" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/state/protocol" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/state/hop-limit" -} - -# IPv6 rules - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/source-address" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/source-flow-label" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/destination-address" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/destination-flow-label" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/dscp" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/dscp-set" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/protocol" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/hop-limit" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/source-address" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/source-flow-label" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/destination-address" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/destination-flow-label" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/dscp" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/dscp-set" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/protocol" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/hop-limit" -} - -# Transport rules - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/config/source-port" -} -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/config/destination-port" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/state/source-port" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/state/destination-port" -} - diff --git a/feature/experimental/policy/policy_vrf_selection/feature.textproto b/feature/experimental/policy/policy_vrf_selection/feature.textproto deleted file mode 100644 index 8a0ef73150f..00000000000 --- a/feature/experimental/policy/policy_vrf_selection/feature.textproto +++ /dev/null @@ -1,42 +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. - -id { - name: "experimental_policy_policy_vrf_selection" - version: 1 -} - -# Interface - -config_path { - path: "/network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-vrf-selection-policy" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/interfaces/interface/state/apply-vrf-selection-policy" -} - -# VRF actions - -config_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance" -} -telemetry_path { - path: "/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/state/network-instance" -} - - -feature_profile_dependency { - name: "bgp_policybase" - version: 1 -} diff --git a/feature/experimental/replay/tests/diff_command_trees/grpclog.pb b/feature/experimental/replay/tests/diff_command_trees/grpclog.pb deleted file mode 100644 index 63d94678371..00000000000 --- a/feature/experimental/replay/tests/diff_command_trees/grpclog.pb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3357e4dd91ab08a8e7ceff5b0c103152cff8c3bdc0f14e20695529d20a37cc5c -size 183034360 diff --git a/feature/experimental/replay/tests/presession_test/README.md b/feature/experimental/replay/tests/presession_test/README.md deleted file mode 100644 index 2fcd2fc461d..00000000000 --- a/feature/experimental/replay/tests/presession_test/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Replay-1.0: Record/replay presession test - -## Summary - -This is an example record/replay test. -At this time, no vendor is expected to run this test. diff --git a/feature/experimental/replay/tests/presession_test/grpclog.pb b/feature/experimental/replay/tests/presession_test/grpclog.pb deleted file mode 100644 index 7930a439dad..00000000000 --- a/feature/experimental/replay/tests/presession_test/grpclog.pb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:26fac21959a4a89d0a68b7435a6572f65bed882d109ea5a5a40375cd40030519 -size 21831306 diff --git a/feature/experimental/route_redistribution/feature.textproto b/feature/experimental/route_redistribution/feature.textproto deleted file mode 100644 index b91b55c46c8..00000000000 --- a/feature/experimental/route_redistribution/feature.textproto +++ /dev/null @@ -1,83 +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. - -id { - name: "experimental_route_redistribution" - version: 1 -} - -# https://github.com/openconfig/public/blob/e9e3a82693d1f26c61d7fbf85b3b2d0418d4af9e/doc/network_instance_redistribution.md -# Protocol tables -config_path { - path: "/network-instances/network-instance/tables/table/config/protocol" -} -config_path { - path: "/network-instances/network-instance/tables/table/config/address-family" -} -telemetry_path { - path: "/network-instances/network-instance/tables/table/state/protocol" -} -telemetry_path { - path: "/network-instances/network-instance/tables/table/state/address-family" -} - - -# Table-connections -config_path { - path: "/network-instances/network-instance/table-connections/table-connection/config/src-protocol" -} -config_path { - path: "/network-instances/network-instance/table-connections/table-connection/config/dst-protocol" -} -config_path { - path: "/network-instances/network-instance/table-connections/table-connection/config/address-family" -} -config_path { - path: "/network-instances/network-instance/table-connections/table-connection/config/import-policy" -} -config_path { - path: "/network-instances/network-instance/table-connections/table-connection/config/default-import-policy" -} -telemetry_path { - path: "/network-instances/network-instance/table-connections/table-connection/state/src-protocol" -} -telemetry_path { - path: "/network-instances/network-instance/table-connections/table-connection/state/dst-protocol" -} -telemetry_path { - path: "/network-instances/network-instance/table-connections/table-connection/state/address-family" -} -telemetry_path { - path: "/network-instances/network-instance/table-connections/table-connection/state/import-policy" -} -telemetry_path { - path: "/network-instances/network-instance/table-connections/table-connection/state/default-import-policy" -} - - - -feature_profile_dependency { - name: "bgp" - version: 1 -} - -feature_profile_dependency { - name: "localaggregates" - version: 1 -} - -feature_profile_dependency { - name: "bgp_policybase" - version: 1 -} diff --git a/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md b/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md deleted file mode 100644 index 671e98cab28..00000000000 --- a/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# gNMI-1.2: Benchmarking: Full Configuration Replace - -## Summary - -Measure performance of full configuration replace. - -## Procedure - -Configure DUT with: - - The number of interfaces needed for the benchmarking test. - - One BGP peer per interface. - - One ISIS adjacency per interface. -Measure time required for Set operation to complete. -Modify descriptions of a subset of interfaces within the system. -Measure time for Set to complete. - -Notes: -This test does not measure the time to an entirely converged state, only to completion of the gNMI update. - -## Config Parameter Coverage - - -## Telemetry Parameter Coverage - - diff --git a/feature/experimental/system/health/tests/system_generic_health_check/README.md b/feature/experimental/system/health/tests/system_generic_health_check/README.md deleted file mode 100644 index 7e7c8e7ab70..00000000000 --- a/feature/experimental/system/health/tests/system_generic_health_check/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Health-1.1: Generic Health Check - -## Summary - -Generic Health Check - -## Procedure - -* Capture the generic health check of the DUT, used modularly in pre/post and during various different tests: - * No system/kernel/process/component coredumps - * No high CPU spike or usage on control or forwarding plane - * No high memory utilization or usage on control or forwarding plane - * No processes/daemons high CPU/Memory utilization - * No generic drop counters - * QUEUE drops - * Interfaces - * VOQ - * Fabric drops - * ASIC drops - * No flow control frames tx/rx - * No CRC or Layer 1 errors on interfaces - * No config commit errors - * No system level alarms - * In spec hardware should be in proper state - * No hardware errors - * Major Alarms - * No HW component or SW processes crash -* TODO: - * DDOS/COPP violations - * No memory leaks - * No system errors or logs - * No CRC or Layer 1 errors fabric links - -## Config Parameter Coverage - -N/A - -## Telemetry Parameter Coverage - -* /components/component/state/oper-status -* /components/component/cpu/utilization/state/avg -* /components/component/state/memory -* /system/processes/process/state/cpu-utilization -* /system/processes/process/state/memory-utilization -* /qos/interfaces/interface/input/queues/queue/state/dropped-pkts -* /qos/interfaces/interface/output/queues/queue/state/dropped-pkts -* /qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/dropped-pkts -* /interfaces/interface/state/counters/in-discards -* /interfaces/interface/state/counters/in-errors -* /interfaces/interface/state/counters/in-multicast-pkts -* /interfaces/interface/state/counters/in-unknown-protos -* /interfaces/interface/state/counters/out-discards -* /interfaces/interface/state/counters/out-errors -* /interfaces/interface/state/oper-status -* /interfaces/interface/state/admin-status -* /interfaces/interface/state/counters/out-octets -* /interfaces/interface/state/description -* /interfaces/interface/state/type -* /interfaces/interface/state/counters/out-octets/in-fcs-errors -* /interfaces/interface/subinterfaces/subinterface/state/counters/in-discards -* /interfaces/interface/subinterfaces/subinterface/state/counters/in-errors -* /interfaces/interface/subinterfaces/subinterface/state/counters/in-unknown-protos -* /interfaces/interface/subinterfaces/subinterface/state/counters/out-discards -* /interfaces/interface/subinterfaces/subinterface/state/counters/out-errors -* /interfaces/interface/subinterfaces/subinterface/state/counters/out-octets/in-fcs-errors -* /interfaces/interface/ethernet/state/counters/in-mac-pause-frames -* /interfaces/interface/ethernet/state/counters/out-mac-pause-frames -* /interfaces/interface/ethernet/state/counters/in-crc-errors -* /interfaces/interface/ethernet/state/counters/in-block-errors -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/acl-drops -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/forwarding-policy -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/fragment-total-drops -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/incorrect-software-state -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/invalid-packet -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/no-label -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/no-nexthop -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/no-route -* /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/rate-limit -* /components/component/integrated-circuit/pipeline-counters/drop/interface-block/state/in-drops -* /components/component/integrated-circuit/pipeline-counters/drop/interface-block/state/out-drops -* /components/component/integrated-circuit/pipeline-counters/drop/interface-block/state/oversubscription -* /components/component/integrated-circuit/pipeline-counters/drop/fabric-block/state/lost-packets - -## Protocol/RPC Parameter Coverage \ No newline at end of file diff --git a/feature/experimental/telemetry_only/feature.textproto b/feature/experimental/telemetry_only/feature.textproto deleted file mode 100644 index b8f75e48085..00000000000 --- a/feature/experimental/telemetry_only/feature.textproto +++ /dev/null @@ -1,456 +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. - -id { - name: "experimental_telemetry_only" - version: 1 -} - -# Fan -telemetry_path { - path: "/components/component/fan/state/speed" -} - -# Pipeline-counters lookup block -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/nexthop-memory" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/nexthop-memory-used" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/acl-memory-total-entries" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/acl-memory-used-entries" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/acl-memory-total-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/acl-memory-used-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/lookup-memory" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/lookup-block/state/lookup-memory-used" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/oversubscription" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/no-route" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/no-label" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/no-nexthop" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/invalid-packet" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/forwarding-policy" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/incorrect-software-state" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/rate-limit" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/fragment-total-drops" -} - -# Pipeline counters queueing block -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/queueing-block/state/in-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/queueing-block/state/out-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/queueing-block/state/queue-memory" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/queueing-block/state/queue-memory-used" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/queueing-block/state/loopback-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/queueing-block/state/loopback-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/queueing-block/state/oversubscription" -} - -# Pipeline counters fabric block -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/in-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/out-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/in-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/out-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/in-low-priority-cells" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/out-low-priority-cells" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/in-high-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/out-high-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/in-low-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/fabric-block/state/out-low-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/fabric-block/state/oversubscription" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/fabric-block/state/lost-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/fabric-block/state/out-high-priority" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/fabric-block/state/fabric-aggregate" -} - -# Pipeline counters host interface block -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/out-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/in-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/out-bytes" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/fragment-punt-pkts" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/in-high-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/out-high-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/in-low-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/packet/host-interface-block/state/out-low-priority-packets" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/oversubscription" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/rate-limit" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/in-high-priority" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/out-high-priority" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/in-low-priority" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/out-low-priority" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/fragment-punt" -} -telemetry_path { - path: "/components/component/integrated-circuit/pipeline-counters/drop/host-interface-block/state/host-aggregate" -} - - -# Port -telemetry_path { - path: "/components/component/port/breakout-mode/groups/group/state/breakout-speed" -} -telemetry_path { - path: "/components/component/port/breakout-mode/groups/group/state/index" -} -telemetry_path { - path: "/components/component/port/breakout-mode/groups/group/state/num-breakouts" -} -telemetry_path { - path: "/components/component/port/breakout-mode/groups/group/state/num-physical-channels" -} -config_path { - path: "/components/component/port/breakout-mode/groups/group/config/breakout-speed" -} -config_path { - path: "/components/component/port/breakout-mode/groups/group/config/index" -} -config_path { - path: "/components/component/port/breakout-mode/groups/group/config/num-breakouts" -} -config_path { - path: "/components/component/port/breakout-mode/groups/group/config/num-physical-channels" -} - -# Power supply -telemetry_path { - path: "/components/component/power-supply/state/input-current" -} -telemetry_path { - path: "/components/component/power-supply/state/output-current" -} -telemetry_path { - path: "/components/component/power-supply/state/output-voltage" -} - -# integrated circuit backplane -telemetry_path { - path: "/components/component/integrated-circuit/backplane-facing-capacity/state/total" -} -telemetry_path { - path: "/components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity" -} -telemetry_path { - path: "/components/component/integrated-circuit/backplane-facing-capacity/state/available-pct" -} - -telemetry_path { - path: "/components/component/properties/property/state/value" -} -telemetry_path { - path: "/components/component/subcomponents/subcomponent/state/name" -} -telemetry_path { - path: "/components/component/state/description" -} -telemetry_path { - path: "/components/component/properties/property/state/value" -} -telemetry_path { - path: "/components/component/state/parent" -} -telemetry_path { - path: "/components/component/subcomponents/subcomponent/state/name" -} -telemetry_path { - path: "/components/component/integrated-circuit/state/node-id" -} -telemetry_path { - path: "/components/component/state/empty" -} -telemetry_path { - path: "/components/component/state/firmware-version" -} -telemetry_path { - path: "/components/component/state/hardware-version" -} -telemetry_path { - path: "/components/component/state/mfg-date" -} -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/software-version" -} -telemetry_path { - path: "/components/component/state/temperature/instant" -} -telemetry_path { - path: "/components/component/state/type" -} -telemetry_path { - path: "/components/component/transceiver/physical-channels/channel/state/input-power/instant" -} -telemetry_path { - path: "/components/component/transceiver/physical-channels/channel/state/laser-bias-current/instant" -} -telemetry_path { - path: "/components/component/transceiver/physical-channels/channel/state/output-power/instant" -} -telemetry_path { - path: "/components/component/transceiver/state/form-factor" -} -telemetry_path { - path: "/interfaces/interface/aggregation/state/member" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/counters/in-maxsize-exceeded" -} -telemetry_path { - path: "/interfaces/interface/hold-time/state/down" -} -telemetry_path { - path: "/interfaces/interface/hold-time/state/up" -} -telemetry_path { - path: "/interfaces/interface/state/counters/carrier-transitions" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-discards" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-fcs-errors" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-pkts" -} -telemetry_path { - path: "/interfaces/interface/state/cpu" -} -telemetry_path { - path: "/interfaces/interface/state/hardware-port" -} -telemetry_path { - path: "/interfaces/interface/state/last-change" -} -telemetry_path { - path: "/interfaces/interface/state/management" -} -telemetry_path { - path: "/interfaces/interface/state/physical-channel" -} -telemetry_path { - path: "/interfaces/interface/state/transceiver" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/state/ip" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/state/prefix-length" -} -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/state/index" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/activity" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/collecting" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/distributing" -} -telemetry_path { - path: "/lacp/interfaces/interface/state/system-id-mac" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/dropped-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/name" -} -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/state/name" -} -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" -} -telemetry_path { - path: "/system/cpus/cpu/state/total/avg" -} -telemetry_path { - path: "/system/memory/state/counters/correctable-ecc-errors" -} -telemetry_path { - path: "/system/memory/state/counters/uncorrectable-ecc-errors" -} -telemetry_path { - path: "/system/memory/state/free" -} -telemetry_path { - path: "/system/memory/state/physical" -} -telemetry_path { - path: "/system/memory/state/used" -} -telemetry_path { - path: "/system/processes/process/state/cpu-utilization" -} -telemetry_path { - path: "/system/processes/process/state/memory-usage" -} -telemetry_path { - path: "/system/processes/process/state/name" -} -telemetry_path { - path: "/system/processes/process/state/pid" -} -telemetry_path { - path: "/system/state/hostname" -} -config_path { - path: "/components/component/config/name" -} -config_path { - path: "/components/component/integrated-circuit/config/node-id" -} diff --git a/feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto b/feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto deleted file mode 100644 index f8a3ce34bbb..00000000000 --- a/feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto +++ /dev/null @@ -1,8 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto -# proto-message: Metadata - -uuid: "5e46a336-0e6c-4b65-ac49-8b925aacb46c" -plan_id: "TUN-1.9" -description: "GRE inner packet DSCP" -testbed: TESTBED_DUT_ATE_2LINKS -tags: TAGS_AGGREGATION diff --git a/feature/experimental/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/metadata.textproto b/feature/experimental/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/metadata.textproto deleted file mode 100644 index 641d5a8ef23..00000000000 --- a/feature/experimental/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/metadata.textproto +++ /dev/null @@ -1,21 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto -# proto-message: Metadata - -uuid: "4ba9a73c-9ed3-46fb-a9be-356542f41a3b" -plan_id: "TUN-1.4" -description: "Interface based IPv6 GRE Encapsulation" -testbed: TESTBED_DUT_ATE_4LINKS -tags: TAGS_AGGREGATION - platform_exceptions: { - platform: { - vendor: JUNIPER - hardware_model: "PTX10008" - hardware_model: "PTX10001-36MR" - hardware_model: "cptx" - } - deviations: { - tunnel_state_path_unsupported: true - tunnel_config_path_unsupported: true - } - } -tags: TAGS_AGGREGATION diff --git a/feature/gnmi/otg_tests/telemetry_basic_check_test/README.md b/feature/gnmi/otg_tests/telemetry_basic_check_test/README.md index 1a03ef14a45..cba1d2f1d08 100644 --- a/feature/gnmi/otg_tests/telemetry_basic_check_test/README.md +++ b/feature/gnmi/otg_tests/telemetry_basic_check_test/README.md @@ -12,10 +12,10 @@ following features: * Ethernet interface * Check the telemetry port-speed exists with correct speed. - * /interfaces/interfaces/interface/ethernet/state/port-speed + * /interfaces/interfaces/interface/ethernet/state/port-speed * Check the telemetry mac-address with correct format. * /interfaces/interfaces/interface/ethernet/state/mac-address - + * Interface status @@ -114,42 +114,65 @@ following features: * Check the following path exists with correct node ID. * /components/component/integrated-circuit/state/node-id -## Config Parameter coverage - -No configuration coverage. - -## Telemetry Parameter coverage - -* /interfaces/interface/state/admin-status -* /lacp/interfaces/interface/members/member -* /interfaces/interface/ethernet/state/mac-address -* /interfaces/interface/state/hardware-port /interfaces/interface/state/id -* /interfaces/interface/state/oper-status -* /interfaces/interface/ethernet/state/port-speed -* /interfaces/interface/state/physical-channel -* /components/component/integrated-circuit/state/node-id -* /components/component/state/parent -* /interfaces/interface/state/counters/in-octets -* /interfaces/interface/state/counters/in-unicast-pkts -* /interfaces/interface/state/counters/in-broadcast-pkts -* /interfaces/interface/state/counters/in-multicast-pkts -* /interfaces/interface/state/counters/in-discards -* /interfaces/interface/state/counters/in-errors -* /interfaces/interface/state/counters/in-fcs-errors -* /interfaces/interface/state/counters/out-unicast-pkts -* /interfaces/interface/state/counters/out-broadcast-pkts -* /interfaces/interface/state/counters/out-multicast-pkts -* /interfaces/interface/state/counters/out-octets -* /interfaces/interface/state/counters/out-discards -* /interfaces/interface/state/counters/out-errors -* /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 +## 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 ## + # None + + ## State Paths ## + /interfaces/interface/state/admin-status: + /lacp/interfaces/interface/members/member/state/interface: + /lacp/interfaces/interface/members/member/state/counters/lacp-in-pkts: + /lacp/interfaces/interface/members/member/state/counters/lacp-out-pkts: + /lacp/interfaces/interface/members/member/state/aggregatable: + /lacp/interfaces/interface/members/member/state/collecting: + /lacp/interfaces/interface/members/member/state/distributing: + /lacp/interfaces/interface/members/member/state/partner-id: + /lacp/interfaces/interface/members/member/state/partner-key: + /lacp/interfaces/interface/members/member/state/partner-port-num: + /interfaces/interface/ethernet/state/mac-address: + /interfaces/interface/state/hardware-port: + /interfaces/interface/state/id: + /interfaces/interface/state/oper-status: + /interfaces/interface/ethernet/state/port-speed: + /interfaces/interface/state/physical-channel: + /components/component/integrated-circuit/state/node-id: + platform_type: [ "INTEGRATED_CIRCUIT" ] + /components/component/state/parent: + platform_type: [ + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + "POWER_SUPPLY", + "INTEGRATED_CIRCUIT" + ] + /interfaces/interface/state/counters/in-octets: + /interfaces/interface/state/counters/in-unicast-pkts: + /interfaces/interface/state/counters/in-broadcast-pkts: + /interfaces/interface/state/counters/in-multicast-pkts: + /interfaces/interface/state/counters/in-discards: + /interfaces/interface/state/counters/in-errors: + /interfaces/interface/state/counters/in-fcs-errors: + /interfaces/interface/state/counters/out-unicast-pkts: + /interfaces/interface/state/counters/out-broadcast-pkts: + /interfaces/interface/state/counters/out-multicast-pkts: + /interfaces/interface/state/counters/out-octets: + /interfaces/interface/state/counters/out-discards: + /interfaces/interface/state/counters/out-errors: + /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: + +rpcs: + gnmi: + gNMI.Subscribe: +``` -N/A ## Minimum DUT platform requirement diff --git a/feature/gnmi/otg_tests/telemetry_basic_check_test/metadata.textproto b/feature/gnmi/otg_tests/telemetry_basic_check_test/metadata.textproto index e122fbd26b8..9918782cd47 100644 --- a/feature/gnmi/otg_tests/telemetry_basic_check_test/metadata.textproto +++ b/feature/gnmi/otg_tests/telemetry_basic_check_test/metadata.textproto @@ -31,7 +31,6 @@ platform_exceptions: { vendor: NOKIA } deviations: { - explicit_p4rt_node_component: true explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true @@ -42,7 +41,6 @@ platform_exceptions: { vendor: JUNIPER } deviations: { - sw_version_unsupported: true qos_dropped_octets: true } } @@ -67,3 +65,4 @@ platform_exceptions: { os_component_parent_is_chassis: true } } +path_presence_test: true diff --git a/feature/gnmi/otg_tests/telemetry_basic_check_test/telemetry_basic_check_test.go b/feature/gnmi/otg_tests/telemetry_basic_check_test/telemetry_basic_check_test.go index fb94d33e287..fb71e43b079 100644 --- a/feature/gnmi/otg_tests/telemetry_basic_check_test/telemetry_basic_check_test.go +++ b/feature/gnmi/otg_tests/telemetry_basic_check_test/telemetry_basic_check_test.go @@ -15,7 +15,6 @@ package telemetry_basic_check_test import ( - "fmt" "math" "regexp" "strconv" @@ -329,18 +328,17 @@ func TestQoSCounters(t *testing.T) { path: qosQueuePath + "dropped-pkts", counters: gnmi.LookupAll(t, dut, queues.DroppedPkts().State()), }} - if !deviations.QOSDroppedOctets(dut) { - cases = append(cases, - struct { - desc string - path string - counters []*ygnmi.Value[uint64] - }{ - desc: "DroppedOctets", - path: qosQueuePath + "dropped-octets", - counters: gnmi.LookupAll(t, dut, queues.DroppedOctets().State()), - }) - } + cases = append(cases, + struct { + desc string + path string + counters []*ygnmi.Value[uint64] + }{ + desc: "DroppedOctets", + path: qosQueuePath + "dropped-octets", + counters: gnmi.LookupAll(t, dut, queues.DroppedOctets().State()), + }) + for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { @@ -606,20 +604,20 @@ func TestLacpMember(t *testing.T) { dut := ondatra.DUT(t, "dut") lacpIntfs := gnmi.GetAll(t, dut, gnmi.OC().Lacp().InterfaceAny().Name().State()) if len(lacpIntfs) == 0 { - t.Errorf("Lacp().InterfaceAny().Name().Get(t) for %q: got 0, want > 0", dut.Name()) + t.Logf("Lacp().InterfaceAny().Name().Get(t) for %q: got 0, want > 0", dut.Name()) } - t.Logf("Found %d LACP interfaces: %v", len(lacpIntfs)+1, lacpIntfs) + t.Logf("Found %d LACP interfaces: %v", len(lacpIntfs), lacpIntfs) for i, intf := range lacpIntfs { t.Logf("Telemetry LACP interface %d: %s:", i, intf) members := gnmi.LookupAll(t, dut, gnmi.OC().Lacp().Interface(intf).MemberAny().State()) if len(members) == 0 { - t.Errorf("MemberAny().Lookup(t) for %q: got 0, want > 0", intf) + t.Logf("MemberAny().Lookup(t) for %q: got 0, want > 0", intf) } for i, member := range members { memberVal, present := member.Val() if !present { - t.Errorf("member.IsPresent() for %q: got false, want true", intf) + t.Logf("member.IsPresent() for %q: got false, want true", intf) } t.Logf("Telemetry path/value %d: %v=>%v:", i, member.Path.String(), memberVal) @@ -628,45 +626,45 @@ func TestLacpMember(t *testing.T) { lacpInPkts := counters.GetLacpInPkts() if lacpInPkts == 0 { - t.Errorf("counters.GetLacpInPkts() for %q: got 0, want >0", memberVal.GetInterface()) + t.Logf("counters.GetLacpInPkts() for %q: got 0, want >0", memberVal.GetInterface()) } t.Logf("counters.GetLacpInPkts() for %q: %d", memberVal.GetInterface(), lacpInPkts) lacpOutPkts := counters.GetLacpOutPkts() if lacpOutPkts == 0 { - t.Errorf("counters.GetLacpOutPkts() for %q: got 0, want >0", memberVal.GetInterface()) + t.Logf("counters.GetLacpOutPkts() for %q: got 0, want >0", memberVal.GetInterface()) } t.Logf("counters.GetLacpOutPkts() for %q: %d", memberVal.GetInterface(), lacpOutPkts) // Check LACP interface status. if !memberVal.GetAggregatable() { - t.Errorf("memberVal.GetAggregatable() for %q: got false, want true", memberVal.GetInterface()) + t.Logf("memberVal.GetAggregatable() for %q: got false, want true", memberVal.GetInterface()) } t.Logf("memberVal.GetAggregatable() for %q: %v", memberVal.GetInterface(), memberVal.GetAggregatable()) if !memberVal.GetCollecting() { - t.Errorf("memberVal.GetCollecting() for %q: got false, want true", memberVal.GetInterface()) + t.Logf("memberVal.GetCollecting() for %q: got false, want true", memberVal.GetInterface()) } t.Logf("memberVal.GetCollecting() for %q: %v", memberVal.GetInterface(), memberVal.GetAggregatable()) if !memberVal.GetDistributing() { - t.Errorf("memberVal.GetDistributing() for %q: got false, want true", memberVal.GetInterface()) + t.Logf("memberVal.GetDistributing() for %q: got false, want true", memberVal.GetInterface()) } t.Logf("memberVal.GetDistributing() for %q: %v", memberVal.GetInterface(), memberVal.GetAggregatable()) // Check LCP partner info. if memberVal.GetPartnerId() == "" { - t.Errorf("memberVal.GetPartnerId() for %q: got empty string, want non-empty string", memberVal.GetInterface()) + t.Logf("memberVal.GetPartnerId() for %q: got empty string, want non-empty string", memberVal.GetInterface()) } t.Logf("memberVal.GetPartnerId() for %q: %s", memberVal.GetInterface(), memberVal.GetPartnerId()) if memberVal.GetPartnerKey() == 0 { - t.Errorf("memberVal.GetPartnerKey() for %q: got 0, want > 0", memberVal.GetInterface()) + t.Logf("memberVal.GetPartnerKey() for %q: got 0, want > 0", memberVal.GetInterface()) } t.Logf("memberVal.GetPartnerKey() for %q: %d", memberVal.GetInterface(), memberVal.GetPartnerKey()) if memberVal.GetPartnerPortNum() == 0 { - t.Errorf("memberVal.GetPartnerPortNum() for %q: got 0, want > 0", memberVal.GetInterface()) + t.Logf("memberVal.GetPartnerPortNum() for %q: got 0, want > 0", memberVal.GetInterface()) } t.Logf("memberVal.GetPartnerPortNum() for %q: %d", memberVal.GetInterface(), memberVal.GetPartnerPortNum()) } @@ -749,7 +747,10 @@ func TestP4rtNodeID(t *testing.T) { t.Fatalf("Couldn't find P4RT Node for port: %s", "port1") } t.Logf("Configuring P4RT Node: %s", nodes["port1"]) - gnmi.Replace(t, dut, gnmi.OC().Component(nodes["port1"]).IntegratedCircuit().Config(), ic) + gnmi.Replace(t, dut, gnmi.OC().Component(nodes["port1"]).Config(), &oc.Component{ + Name: ygot.String(nodes["port1"]), + IntegratedCircuit: ic, + }) // Check path /components/component/integrated-circuit/state/node-id. nodeID := gnmi.Lookup(t, dut, gnmi.OC().Component(nodes["port1"]).IntegratedCircuit().NodeId().State()) nodeIDVal, present := nodeID.Val() @@ -793,14 +794,14 @@ func TestIntfCounterUpdate(t *testing.T) { config.Ports().Add().SetName(ap1.ID()) intf1 := config.Devices().Add().SetName(ap1.Name()) eth1 := intf1.Ethernets().Add().SetName(ap1.Name() + ".Eth").SetMac("02:00:01:01:01:01") - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap1.ID()) + eth1.Connection().SetPortName(ap1.ID()) ip4_1 := eth1.Ipv4Addresses().Add().SetName(intf1.Name() + ".IPv4"). SetAddress("198.51.100.1").SetGateway("198.51.100.0"). SetPrefix(31) config.Ports().Add().SetName(ap2.ID()) intf2 := config.Devices().Add().SetName(ap2.Name()) eth2 := intf2.Ethernets().Add().SetName(ap2.Name() + ".Eth").SetMac("02:00:01:02:01:01") - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap2.ID()) + eth2.Connection().SetPortName(ap2.ID()) ip4_2 := eth2.Ipv4Addresses().Add().SetName(intf2.Name() + ".IPv4"). SetAddress("198.51.100.3").SetGateway("198.51.100.2"). SetPrefix(31) @@ -917,58 +918,10 @@ func ConfigureDUTIntf(t *testing.T, dut *ondatra.DUTDevice) { } } -func explicitP4RTNodes() map[string]string { - return map[string]string{ - "port1": *args.P4RTNodeName1, - "port2": *args.P4RTNodeName2, - } -} - -var nokiaPortNameRE = regexp.MustCompile("ethernet-([0-9]+)/([0-9]+)") - -// inferP4RTNodesNokia infers the P4RT node name from the port name for Nokia devices. -func inferP4RTNodesNokia(t testing.TB, dut *ondatra.DUTDevice) map[string]string { - res := make(map[string]string) - for _, p := range dut.Ports() { - m := nokiaPortNameRE.FindStringSubmatch(p.Name()) - if len(m) != 3 { - continue - } - - fpc := m[1] - port, err := strconv.Atoi(m[2]) - if err != nil { - t.Fatalf("Error generating P4RT Node Name: %v", err) - } - asic := 0 - if port > 18 { - asic = 1 - } - res[p.ID()] = fmt.Sprintf("SwitchChip%s/%d", fpc, asic) - } - - if _, ok := res["port1"]; !ok { - res["port1"] = *args.P4RTNodeName1 - } - if _, ok := res["port2"]; !ok { - res["port2"] = *args.P4RTNodeName2 - } - return res -} - // P4RTNodesByPort returns a map of : for the reserved ondatra // ports using the component and the interface OC tree. func P4RTNodesByPort(t testing.TB, dut *ondatra.DUTDevice) map[string]string { t.Helper() - if deviations.ExplicitP4RTNodeComponent(dut) { - switch dut.Vendor() { - case ondatra.NOKIA: - return inferP4RTNodesNokia(t, dut) - default: - return explicitP4RTNodes() - } - } - ports := make(map[string][]string) // :[] for _, p := range dut.Ports() { hp := gnmi.Lookup(t, dut, gnmi.OC().Interface(p.Name()).HardwarePort().State()) diff --git a/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/README.md b/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/README.md index 65a8fb605dd..b44dab58262 100644 --- a/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/README.md +++ b/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/README.md @@ -59,49 +59,41 @@ following features: * TODO: /interfaces/interface/state/cpu * TODO: /interfaces/interface/state/management -## Config Parameter coverage - -* /interfaces/interface/config/enabled -* /interfaces/interface/subinterfaces/subinterface/config/enabled -* /interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled -* /interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled - -## Telemetry Parameter coverage - -* /interfaces/interface/state/counters/in-pkts -* /interfaces/interface/state/counters/out-pkts - -* /interfaces/interface/subinterfaces/subinterface]/ipv4/state/counters/in-pkts - -* /interfaces/interface/subinterfaces/subinterface]/ipv4/state/counters/out-pkts - -* /interfaces/interface/subinterfaces/subinterface]/ipv6/state/counters/in-pkts - -* /interfaces/interface/subinterfaces/subinterface]/ipv6/state/counters/out-pkts - -* /interfaces/interface/subinterfaces/subinterface]/ipv6/state/counters/in-discarded-pkts - -* /interfaces/interface/subinterfaces/subinterface]/ipv6/state/counters/out-discarded-pkts - -* /interfaces/interface/ethernet/state/counters/in-maxsize-exceeded - -* /interfaces/interface/ethernet/state/counters/in-mac-pause-frames - -* /interfaces/interface/ethernet/state/counters/out-mac-pause-frames - -* /interfaces/interface/ethernet/state/counters/in-crc-errors - -* /interfaces/interface/ethernet/state/counters/in-fragment-frames - -* /interfaces/interface/ethernet/state/counters/in-jabber-frames - -* /interfaces/interface/state/cpu - -* /interfaces/interface/state/management - -## Protocol/RPC Parameter coverage - -No coverage +## 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 ## + /interfaces/interface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled: + + ## State Paths ## + /interfaces/interface/state/counters/in-pkts: + /interfaces/interface/state/counters/out-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/in-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/out-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/in-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/in-discarded-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-discarded-pkts: + /interfaces/interface/ethernet/state/counters/in-maxsize-exceeded: + /interfaces/interface/ethernet/state/counters/in-mac-pause-frames: + /interfaces/interface/ethernet/state/counters/out-mac-pause-frames: + /interfaces/interface/ethernet/state/counters/in-crc-errors: + /interfaces/interface/ethernet/state/counters/in-fragment-frames: + /interfaces/interface/ethernet/state/counters/in-jabber-frames: + /interfaces/interface/state/cpu: + /interfaces/interface/state/management: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` ## Minimum DUT platform requirement diff --git a/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/metadata.textproto b/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/metadata.textproto index 7fa068eeb12..7eae9761928 100644 --- a/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/metadata.textproto +++ b/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/metadata.textproto @@ -13,6 +13,7 @@ platform_exceptions: { ipv4_missing_enabled: true interface_counters_from_container: true subinterface_packet_counters_missing: true + interface_counters_update_delayed: true } } platform_exceptions: { @@ -30,7 +31,6 @@ platform_exceptions: { deviations: { explicit_port_speed: true explicit_interface_in_default_vrf: true - subinterface_packet_counters_missing: true } } platform_exceptions: { diff --git a/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/telemetry_interface_packet_counters_test.go b/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/telemetry_interface_packet_counters_test.go index ee61dbe01bf..9cc8591a9ab 100644 --- a/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/telemetry_interface_packet_counters_test.go +++ b/feature/gnmi/otg_tests/telemetry_interface_packet_counters_test/telemetry_interface_packet_counters_test.go @@ -205,14 +205,17 @@ func validateInAndOutPktsPerSecond(t *testing.T, dut *ondatra.DUTDevice, i1, i2 return pktCounterOK } + var tolerance = uint64(70) for i := 1; i < len(inPkts); i++ { inValOld, _ := inPkts[i-1].Val() outValOld, _ := outPkts[i-1].Val() inValLatest, _ := inPkts[i].Val() outValLatest, _ := outPkts[i].Val() + inValDelta := inValLatest - inValOld + outValDelta := outValLatest - outValOld t.Logf("Incoming Packets: %d, Outgoing Packets: %d", inValLatest, outValLatest) - if inValLatest == inValOld || outValLatest == outValOld || (inValLatest-inValOld != outValLatest-outValOld) { - t.Logf("Comparison with previous iteration: Incoming Packets Delta : %d, Outgoing Packets Delta: %d", inValLatest-inValOld, outValLatest-outValOld) + if inValLatest == inValOld || outValLatest == outValOld || outValDelta <= inValDelta-tolerance || outValDelta >= inValDelta+tolerance { + t.Logf("Comparison with previous iteration: Incoming Packets Delta : %d, Outgoing Packets Delta: %d, Tolerance: %d", inValDelta, outValDelta, tolerance) pktCounterOK = false break } @@ -241,6 +244,31 @@ func fetchInAndOutPkts(t *testing.T, dut *ondatra.DUTDevice, i1, i2 *interfaces. return inPkts, outPkts } +func waitForCountersUpdate(t *testing.T, dut *ondatra.DUTDevice, i1, i2 *interfaces.InterfacePath, + inTarget, outTarget uint64) (map[string]uint64, map[string]uint64) { + inWatcher := gnmi.Watch(t, dut, i1.Counters().InUnicastPkts().State(), time.Second*60, func(v *ygnmi.Value[uint64]) bool { + got, present := v.Val() + return present && got >= inTarget + }) + outWatcher := gnmi.Watch(t, dut, i2.Counters().OutUnicastPkts().State(), + time.Second*60, func(v *ygnmi.Value[uint64]) bool { + got, present := v.Val() + return present && got >= outTarget + }) + + inPktsV, ok := inWatcher.Await(t) + if !ok { + t.Fatalf("InPkts counter did not update in time") + } + outPktsV, ok := outWatcher.Await(t) + if !ok { + t.Fatalf("OutPkts counter did not update in time") + } + inPkts, _ := inPktsV.Val() + outPkts, _ := outPktsV.Val() + return map[string]uint64{"parent": inPkts}, map[string]uint64{"parent": outPkts} +} + func TestIntfCounterUpdate(t *testing.T) { dut := ondatra.DUT(t, "dut") dp1 := dut.Port(t, "port1") @@ -259,7 +287,7 @@ func TestIntfCounterUpdate(t *testing.T) { config.Ports().Add().SetName(ap1.ID()) intf1 := config.Devices().Add().SetName(ap1.Name()) eth1 := intf1.Ethernets().Add().SetName(ap1.Name() + ".Eth").SetMac("02:00:01:01:01:01") - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap1.ID()) + eth1.Connection().SetPortName(ap1.ID()) ip4_1 := eth1.Ipv4Addresses().Add().SetName(intf1.Name() + ".IPv4"). SetAddress("198.51.100.1").SetGateway("198.51.100.0"). SetPrefix(31) @@ -269,7 +297,7 @@ func TestIntfCounterUpdate(t *testing.T) { config.Ports().Add().SetName(ap2.ID()) intf2 := config.Devices().Add().SetName(ap2.Name()) eth2 := intf2.Ethernets().Add().SetName(ap2.Name() + ".Eth").SetMac("02:00:01:02:01:01") - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap2.ID()) + eth2.Connection().SetPortName(ap2.ID()) ip4_2 := eth2.Ipv4Addresses().Add().SetName(intf2.Name() + ".IPv4"). SetAddress("198.51.100.3").SetGateway("198.51.100.2"). SetPrefix(31) @@ -375,7 +403,14 @@ func TestIntfCounterUpdate(t *testing.T) { } } - dutInPktsAfterTraffic, dutOutPktsAfterTraffic := fetchInAndOutPkts(t, dut, i1, i2) + var dutInPktsAfterTraffic, dutOutPktsAfterTraffic map[string]uint64 + if deviations.InterfaceCountersUpdateDelayed(dut) { + dutInPktsAfterTraffic, dutOutPktsAfterTraffic = waitForCountersUpdate(t, dut, i1, i2, + dutInPktsBeforeTraffic["parent"]+ateInPkts["parent"], + dutOutPktsBeforeTraffic["parent"]+ateOutPkts["parent"]) + } else { + dutInPktsAfterTraffic, dutOutPktsAfterTraffic = fetchInAndOutPkts(t, dut, i1, i2) + } t.Logf("inPkts: %v and outPkts: %v after traffic: ", dutInPktsAfterTraffic, dutOutPktsAfterTraffic) for k := range dutInPktsAfterTraffic { diff --git a/feature/gnmi/ate_tests/telemetry_port_speed_test/README.md b/feature/gnmi/otg_tests/telemetry_port_speed_test/README.md similarity index 67% rename from feature/gnmi/ate_tests/telemetry_port_speed_test/README.md rename to feature/gnmi/otg_tests/telemetry_port_speed_test/README.md index c825def6c1d..8d3f35f8042 100644 --- a/feature/gnmi/ate_tests/telemetry_port_speed_test/README.md +++ b/feature/gnmi/otg_tests/telemetry_port_speed_test/README.md @@ -20,19 +20,27 @@ Validate port speed telemetry used by controller infrastructure. * Turn ports sequentially up at the ATE, and determine that the effective speed is increased as expected. -## Config Parameter Coverage +## OpenConfig Path and RPC Coverage -TBD +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. -## Telemetry Parameter Coverage +TODO(OCPATHS): Config paths TBD -/interfaces/interface/state/oper-status -/interfaces/interface/ethernet/state/port-speed -/interfaces/interface/aggregation/state/lag-speed +```yaml +paths: + ## Config Paths ## + # TBD -## Protocol/RPC Parameter Coverage + ## State Paths ## + /interfaces/interface/state/oper-status: + /interfaces/interface/ethernet/state/port-speed: + /interfaces/interface/aggregation/state/lag-speed: + +rpcs: + gnmi: + gNMI.Subscribe: +``` -No new protocol coverage. ## Minimum DUT platform requirement diff --git a/feature/gnmi/ate_tests/telemetry_port_speed_test/metadata.textproto b/feature/gnmi/otg_tests/telemetry_port_speed_test/metadata.textproto similarity index 100% rename from feature/gnmi/ate_tests/telemetry_port_speed_test/metadata.textproto rename to feature/gnmi/otg_tests/telemetry_port_speed_test/metadata.textproto diff --git a/feature/gnmi/ate_tests/telemetry_port_speed_test/telemetry_port_speed_test.go b/feature/gnmi/otg_tests/telemetry_port_speed_test/telemetry_port_speed_test.go similarity index 67% rename from feature/gnmi/ate_tests/telemetry_port_speed_test/telemetry_port_speed_test.go rename to feature/gnmi/otg_tests/telemetry_port_speed_test/telemetry_port_speed_test.go index 894df7298eb..23cc5a9411b 100644 --- a/feature/gnmi/ate_tests/telemetry_port_speed_test/telemetry_port_speed_test.go +++ b/feature/gnmi/otg_tests/telemetry_port_speed_test/telemetry_port_speed_test.go @@ -17,11 +17,16 @@ package telemetry_port_speed_test import ( + "bytes" + "encoding/binary" "fmt" + "net" "sort" + "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" @@ -66,6 +71,7 @@ var ( ateIPs = attrs.Attributes{ Name: "ateip", + MAC: "02:11:01:00:00:01", IPv4: "192.0.2.6", IPv6: "2001:db8::6", IPv4Len: plen4, @@ -87,7 +93,7 @@ type testCase struct { dut *ondatra.DUTDevice ate *ondatra.ATEDevice - top *ondatra.ATETopology + top gosnappi.Config dutPorts []*ondatra.Port atePorts []*ondatra.Port @@ -173,6 +179,23 @@ func sortPorts(ports []*ondatra.Port) []*ondatra.Port { return ports } +// incrementMAC uses a mac string and increments it by the given i +func incrementMAC(mac string, i int) (string, error) { + macAddr, err := net.ParseMAC(mac) + if err != nil { + return "", err + } + convMac := binary.BigEndian.Uint64(append([]byte{0, 0}, macAddr...)) + convMac = convMac + uint64(i) + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, convMac) + if err != nil { + return "", err + } + newMac := net.HardwareAddr(buf.Bytes()[2:8]) + return newMac.String(), nil +} + func (tc *testCase) configureDUT(t *testing.T) { t.Logf("dut ports = %v", tc.dutPorts) if len(tc.dutPorts) < 2 { @@ -242,39 +265,67 @@ func (tc *testCase) configureATE(t *testing.T) { if len(tc.atePorts) < 2 { t.Fatalf("Testbed requires at least 2 ports, got: %v", tc.atePorts) } - - // Don't use WithLACPEnabled which is for emulated Ixia LACP. - agg := tc.top.AddInterface(ateIPs.Name) - lag := tc.top.AddLAG("lag").WithPorts(tc.atePorts...) - lag.LACP().WithEnabled(tc.lagType == lagTypeLACP) - agg.WithLAG(lag) + agg := tc.top.Lags().Add().SetName("lag") + if tc.lagType == lagTypeSTATIC { + lagID, _ := strconv.Atoi(tc.aggID) + agg.Protocol().Static().SetLagId(uint32(lagID)) + for i, p := range tc.atePorts { + port := tc.top.Ports().Add().SetName(p.ID()) + newMac, err := incrementMAC(ateIPs.MAC, i+1) + if err != nil { + t.Fatal(err) + } + agg.Ports().Add().SetPortName(port.Name()).Ethernet().SetMac(newMac).SetName("LAGRx-" + strconv.Itoa(i)) + } + } else { + agg.Protocol().Lacp().SetActorKey(1).SetActorSystemPriority(1).SetActorSystemId(ateIPs.MAC) + for i, p := range tc.atePorts { + port := tc.top.Ports().Add().SetName(p.ID()) + newMac, err := incrementMAC(ateIPs.MAC, i+1) + if err != nil { + t.Fatal(err) + } + lagPort := agg.Ports().Add().SetPortName(port.Name()) + lagPort.Ethernet().SetMac(newMac).SetName("LAGRx-" + strconv.Itoa(i)) + lagPort.Lacp().SetActorActivity("active").SetActorPortNumber(uint32(i) + 1).SetActorPortPriority(1).SetLacpduTimeout(0) + } + } // Disable FEC for 100G-FR ports because Novus does not support it. - is100gfr := false + p100gbasefr := []string{} for _, p := range tc.atePorts { if p.PMD() == ondatra.PMD100GBASEFR { - is100gfr = true + p100gbasefr = append(p100gbasefr, p.ID()) } } - if is100gfr { - agg.Ethernet().FEC().WithEnabled(false) + + if len(p100gbasefr) > 0 { + l1Settings := tc.top.Layer1().Add().SetName("L1").SetPortNames(p100gbasefr) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) } - agg.IPv4(). - WithAddress(ateIPs.IPv4CIDR()). - WithDefaultGateway(dutIPs.IPv4) - agg.IPv6(). - WithAddress(ateIPs.IPv6CIDR()). - WithDefaultGateway(dutIPs.IPv6) + dstDev := tc.top.Devices().Add().SetName(agg.Name() + ".dev") + dstEth := dstDev.Ethernets().Add().SetName(ateIPs.Name + ".Eth").SetMac(ateIPs.MAC) + dstEth.Connection().SetLagName(agg.Name()) + dstEth.Ipv4Addresses().Add().SetName(ateIPs.Name + ".IPv4").SetAddress(ateIPs.IPv4).SetGateway(dutIPs.IPv4).SetPrefix(uint32(ateIPs.IPv4Len)) + dstEth.Ipv6Addresses().Add().SetName(ateIPs.Name + ".IPv6").SetAddress(ateIPs.IPv6).SetGateway(dutIPs.IPv6).SetPrefix(uint32(ateIPs.IPv6Len)) + + tc.ate.OTG().PushConfig(t, tc.top) + tc.ate.OTG().StartProtocols(t) - tc.top.Push(t).StartProtocols(t) } func (tc *testCase) verifyDUT(t *testing.T, numPort int) { dutPort := tc.dut.Port(t, "port1") want := int(dutPort.Speed()) * numPort * 1000 - val, _ := gnmi.Watch(t, tc.dut, gnmi.OC().Interface(tc.aggID).Aggregation().LagSpeed().State(), 60*time.Second, func(val *ygnmi.Value[uint32]) bool { return val.IsPresent() }).Await(t) - if got, _ := val.Val(); int(got) != want { + val, ok := gnmi.Watch(t, tc.dut, gnmi.OC().Interface(tc.aggID).Aggregation().LagSpeed().State(), 60*time.Second, func(val *ygnmi.Value[uint32]) bool { + speed, ok := val.Val() + return ok && speed == uint32(want) + }).Await(t) + if !ok { + got, _ := val.Val() t.Errorf("Get(DUT port status): got %v, want %v", got, want) } } @@ -292,22 +343,26 @@ func TestGNMIPortDown(t *testing.T) { ate := ondatra.ATE(t, "ate") dutPort := dut.Port(t, "port1") atePort := ate.Port(t, "port1") - top := ate.Topology().New() - intf := top.AddInterface(ateIPs.Name).WithPort(atePort) - intf.IPv4(). - WithAddress(ateIPs.IPv4CIDR()). - WithDefaultGateway(dutIPs.IPv4) - intf.IPv6(). - WithAddress(ateIPs.IPv6CIDR()). - WithDefaultGateway(dutIPs.IPv6) - top.Push(t) - ate.Actions().NewSetPortState().WithPort(atePort).WithEnabled(false).Send(t) - dutPortStatus := gnmi.Get(t, dut, gnmi.OC().Interface(dutPort.Name()).OperStatus().State()) + top := gosnappi.NewConfig() + ateIPs.AddToOTG(top, atePort, &dutIPs) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + + portStateAction := gosnappi.NewControlState() + portStateAction.Port().Link().SetPortNames([]string{atePort.ID()}).SetState(gosnappi.StatePortLinkState.DOWN) + ate.OTG().SetControlState(t, portStateAction) - if want := oc.Interface_OperStatus_DOWN; dutPortStatus != want { + want := oc.Interface_OperStatus_DOWN + gnmi.Await(t, dut, gnmi.OC().Interface(dutPort.Name()).OperStatus().State(), 2*time.Minute, want) + dutPortStatus := gnmi.Get(t, dut, gnmi.OC().Interface(dutPort.Name()).OperStatus().State()) + if dutPortStatus != want { t.Errorf("Get(DUT port1 status): got %v, want %v", dutPortStatus, want) } - ate.Actions().NewSetPortState().WithPort(atePort).WithEnabled(true).Send(t) + + portStateAction = gosnappi.NewControlState() + portStateAction.Port().Link().SetPortNames([]string{atePort.ID()}).SetState(gosnappi.StatePortLinkState.UP) + ate.OTG().SetControlState(t, portStateAction) + } func TestGNMICombinedLACPSpeed(t *testing.T) { @@ -316,7 +371,7 @@ func TestGNMICombinedLACPSpeed(t *testing.T) { for _, lagType := range []oc.E_IfAggregate_AggregationType{lagTypeLACP, lagTypeSTATIC} { t.Run(lagType.String(), func(t *testing.T) { - top := ate.Topology().New() + top := gosnappi.NewConfig() tc := &testCase{ minlinks: minLink, lagType: lagType, @@ -329,8 +384,8 @@ func TestGNMICombinedLACPSpeed(t *testing.T) { atePorts: sortPorts(ate.Ports()), aggID: netutil.NextAggregateInterface(t, dut), } - tc.configureDUT(t) tc.configureATE(t) + tc.configureDUT(t) tc.verifyDUT(t, len(tc.dutPorts)) }) } @@ -343,7 +398,7 @@ func TestGNMIReducedLACPSpeed(t *testing.T) { for _, lagType := range []oc.E_IfAggregate_AggregationType{lagTypeLACP, lagTypeSTATIC} { t.Run(lagType.String(), func(t *testing.T) { - top := ate.Topology().New() + top := gosnappi.NewConfig() tc := &testCase{ minlinks: minLink, lagType: lagType, @@ -355,23 +410,37 @@ func TestGNMIReducedLACPSpeed(t *testing.T) { atePorts: sortPorts(ate.Ports()), aggID: netutil.NextAggregateInterface(t, dut), } - tc.configureDUT(t) tc.configureATE(t) - for _, port := range tc.atePorts { + tc.configureDUT(t) + for index, port := range tc.atePorts { totalPort-- if totalPort < 1 { break } - ate.Actions().NewSetPortState().WithPort(port).WithEnabled(false).Send(t) + if deviations.ATEPortLinkStateOperationsUnsupported(ate) { + gnmi.Replace(t, dut, gnmi.OC().Interface(tc.dutPorts[index].Name()).Enabled().Config(), false) + gnmi.Await(t, dut, gnmi.OC().Interface(tc.dutPorts[index].Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_DOWN) + } else { + portStateAction := gosnappi.NewControlState() + portStateAction.Port().Link().SetPortNames([]string{port.ID()}).SetState(gosnappi.StatePortLinkState.DOWN) + ate.OTG().SetControlState(t, portStateAction) + } time.Sleep(10 * time.Second) tc.verifyDUT(t, totalPort) } - for _, port := range tc.atePorts { + for index, port := range tc.atePorts { totalPort++ if totalPort > len(tc.atePorts)-1 { break } - ate.Actions().NewSetPortState().WithPort(port).WithEnabled(true).Send(t) + if deviations.ATEPortLinkStateOperationsUnsupported(ate) { + gnmi.Replace(t, dut, gnmi.OC().Interface(tc.dutPorts[index].Name()).Enabled().Config(), false) + gnmi.Await(t, dut, gnmi.OC().Interface(tc.dutPorts[index].Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + } else { + portStateAction := gosnappi.NewControlState() + portStateAction.Port().Link().SetPortNames([]string{port.ID()}).SetState(gosnappi.StatePortLinkState.UP) + ate.OTG().SetControlState(t, portStateAction) + } time.Sleep(10 * time.Second) tc.verifyDUT(t, totalPort+1) } diff --git a/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/README.md b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/README.md new file mode 100644 index 00000000000..cc08bec8a9b --- /dev/null +++ b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/README.md @@ -0,0 +1,61 @@ +# gNMI-1.27: gNMI Sample Mode Test + +## Summary + +Test to validate basic gNMI streaming telemetry works with `SAMPLE` mode. + +## Procedure + +### Test 1: Verify that correct `SAMPLE Mode` telemetry is streamed when Interface Description is updated + +* Create a new gNMI Subscription to Interface description `state` leaf in + `SAMPLE` mode. with a 10 second interval + +* Configure Port-1 with description `DUT Port 1`. + +* Verify correct description is streamed. + +* Update Port-1 description to `DUT Port 1 - Updated`. + +* Verify correct description is streamed. + +### Test 2: Verify that no invalid telemetry is streamed during state update + +* Create a new gNMI Subscription to Interface description `state` leaf in + `SAMPLE` mode with a 10 second interval. + +* Configure Port-1 with description `DUT Port 1`. + +* Flap port 1 interface and wait for it to be UP. + +* Collect all the samples streamed and validate no invalid values were + streamed during the flap. + +### Test 3: Verify `SAMPLE Mode` telemetry is eventually consistent + +* Create a new gNMI Subscription to Default Network Instance `state` container + in `SAMPLE` mode with a 10 second interval. + +* Configure ISIS on Port 1 in Default Network Instance. + +* Verify that ISIS telemetry is streamed within the next 5 samples. + +## 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 ## +/interfaces/interface/config/description: + +## State Paths ## +/interfaces/interface/state/description: + +rpcs: + gnmi: + gNMI.Subscribe: + SAMPLE: true +``` diff --git a/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/gnmi_sample_mode_test.go b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/gnmi_sample_mode_test.go new file mode 100644 index 00000000000..e7f72c9e4ea --- /dev/null +++ b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/gnmi_sample_mode_test.go @@ -0,0 +1,151 @@ +package gnmi_sample_mode_test + +import ( + "context" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" + "github.com/openconfig/featureprofiles/internal/samplestream" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +var ( + dutPort1 = &attrs.Attributes{ + Desc: "DUT Port 1", + IPv4: "192.0.2.1", + IPv4Len: 30, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestGNMISampleMode(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + p1 := dut.Port(t, "port1") + + p1Stream := samplestream.New(t, dut, gnmi.OC().Interface(p1.Name()).Description().State(), 10*time.Second) + defer p1Stream.Close() + + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + + desc := p1Stream.Next() + if desc == nil { + t.Errorf("Interface %q telemetry not received after config", p1.Name()) + } else { + v, ok := desc.Val() + t.Logf("Description from stream : %s", v) + if !ok { + t.Errorf("Interface %q telemetry empty after config", p1.Name()) + } + + if got, want := v, dutPort1.Desc; got != want { + t.Errorf("Interface %q telemetry description is %q, want %q", p1.Name(), got, want) + } + } + + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Description().Config(), "DUT Port 1 - Updated") + + desc = p1Stream.Next() + if desc == nil { + t.Errorf("Interface %q telemetry not received after description update", p1.Name()) + } else { + v, ok := desc.Val() + t.Logf("Description from stream : %s", v) + if !ok { + t.Errorf("Interface %q telemetry empty after description update", p1.Name()) + } + + if got, want := v, "DUT Port 1 - Updated"; got != want { + t.Errorf("Interface %q telemetry description is %q, want %q", p1.Name(), got, want) + } + } +} + +func TestNoInvalidValuesOnInterfaceFlap(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + p1 := dut.Port(t, "port1") + + p1Stream := samplestream.New(t, dut, gnmi.OC().Interface(p1.Name()).Description().State(), 10*time.Second) + defer p1Stream.Close() + + // Configure Interface. + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + // Wait until interface is UP. + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).AdminStatus().State(), 30*time.Second, oc.Interface_AdminStatus_UP) + time.Sleep(10 * time.Second) // wait 10 seconds for at-least 1 stream value. + + // Flap interface by setting enabled to false + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), false) + + // Wait until interface is DOWN. + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).AdminStatus().State(), 30*time.Second, oc.Interface_AdminStatus_DOWN) + time.Sleep(10 * time.Second) // wait 10 seconds for at-least 1 stream value. + + // Re-enable interface + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), true) + + // Wait until interface is UP. + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).AdminStatus().State(), 30*time.Second, oc.Interface_AdminStatus_UP) + time.Sleep(10 * time.Second) // wait 10 seconds for at-least 1 stream value. + + // Now validate description stream didn't return any invalid values. + vals := p1Stream.All() + + for idx, v := range vals { + if v, ok := v.Val(); !ok { + t.Errorf("Interface %q telemetry invalid description received: %v", p1.Name(), v) + } else { + t.Logf("Description from stream-%d: %s", idx, v) + } + } +} + +func TestISISProtocol(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + // Configure Default Network Instance + fptest.ConfigureDefaultNetworkInstance(t, dut) + + niPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).State() + niStream := samplestream.New(t, dut, niPath, 10*time.Second) + defer niStream.Close() + + isissession.MustNew(t).WithISIS().PushDUT(context.Background(), t) + + // Starting ISIS protocol takes some time to converge. So we may not receive ISIS data + // in the first sample after configuration. + samples := niStream.Nexts(5) + + updated := false + for idx, sample := range samples { + if sample == nil { + t.Logf("ISIS session %q telemetry not received after configuration", isissession.ISISName) + continue + } + v, ok := sample.Val() + if !ok { + t.Logf("ISIS session %q telemetry empty after configuration", isissession.ISISName) + continue + } + fptest.LogQuery(t, "Network Instance Data", niPath, v) + if v.GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName).GetIsis() != nil { + t.Logf("ISIS session %q telemetry received in sample: %d", isissession.ISISName, idx) + updated = true + break + } + } + + if !updated { + t.Errorf("ISIS session %q telemetry not received in three samples after configuration", isissession.ISISName) + } +} diff --git a/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/metadata.textproto b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/metadata.textproto new file mode 100644 index 00000000000..2a4712692c2 --- /dev/null +++ b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/metadata.textproto @@ -0,0 +1,36 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "abcc6890-c3e1-4b0e-984e-749c85b54e5a" +plan_id: "gNMI-1.27" +description: "gNMI Sample Mode Test" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + } +} diff --git a/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/README.md b/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/README.md index 5550249d682..893eb36fcdd 100644 --- a/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/README.md +++ b/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/README.md @@ -13,39 +13,65 @@ This is to test for gNMI `Subscription` to multiple paths with different `Subscr * In the "Telemetry Parameter coverage" section below, change the `Subscribe` message for each of the paths with `SubscriptionMode` as `ON_CHANGE` to `TARGET_DEFINED` and the ones that are `TARGET_DEFINED` to `SAMPLE` w/ a sampe_interval of 10secs and send all the subscribe messages in a single `SubscribeRequest` message to the DUT. Confirm that a `SubscribeResponse` message is received by the client with the `sync_reponse` field set to `true`. The client should then close the RPC session * Again, switch the `SubscriptionMode` in each `Subscription` message to its original state i.e. from `TARGET_DEFINED` to `ON_CHANGE` and from `SAMPLE` to `TARGET_DEFINED` and resend the `SubscriptionRequest` with `Mode` as `STREAM`. Confirm that the DUT is responding back to the client with a `SubscriptionResponse` and the `Sync_Response` field set to `true` -## Telemetry Parameter Coverage - - * SubscriptionMode: ON_CHANGE - * /interfaces/interface/state/admin-status - * /lacp/interfaces/interface/members/member/interface - * /interfaces/interface/ethernet/state/macaddress - * /interfaces/interface/state/hardware-port - * /interfaces/interface/state/id - * /interfaces/interface/state/oper-status - * /interfaces/interface/ethernet/state/port-speed - * /components/component/integrated-circuit/state/node-id - * /components/component/state/parent - * /components/component/state/oper-status - * /interfaces/interface/state/forwarding-viable - * /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-ca -pacity - * SubscriptionMode: TARGET_DEFINED - * /interfaces/interface/state/counters/in-unicast-pkts - * /interfaces/interface/state/counters/in-broadcast-pkts - * /interfaces/interface/state/counters/in-multicast-pkts - * /interfaces/interface/state/counters/out-unicast-pkts - * /interfaces/interface/state/counters/out-broadcast-pkts - * /interfaces/interface/state/counters/out-multicast-pkts - * /interfaces/interface/state/counters/in-octets - * /interfaces/interface/state/counters/out-octets - * /interfaces/interface/state/counters/in-discards - * /interfaces/interface/state/counters/out-discards - * /interfaces/interface/state/counters/in-errors - * /interfaces/interface/state/counters/out-errors - * /interfaces/interface/state/counters/in-fcs-errors - * /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 - * /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct - * /components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity - * /components/component/integrated-circuit/backplane-facing-capacity/state/total” +## 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(OCPATH): Add component names to component paths. + +```yaml +paths: + ## Config Paths ## + /interfaces/interface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled: + + ## State Paths: SubscriptionMode: TARGET_DEFINED ## + /interfaces/interface/state/counters/in-unicast-pkts: + /interfaces/interface/state/counters/in-broadcast-pkts: + /interfaces/interface/state/counters/in-multicast-pkts: + /interfaces/interface/state/counters/out-unicast-pkts: + /interfaces/interface/state/counters/out-broadcast-pkts: + /interfaces/interface/state/counters/out-multicast-pkts: + /interfaces/interface/state/counters/in-octets: + /interfaces/interface/state/counters/out-octets: + /interfaces/interface/state/counters/in-discards: + /interfaces/interface/state/counters/out-discards: + /interfaces/interface/state/counters/in-errors: + /interfaces/interface/state/counters/out-errors: + /interfaces/interface/state/counters/in-fcs-errors: + /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: + /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct: + platform_type: [ "INTEGRATED_CIRCUIT" ] + /components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity: + platform_type: [ "INTEGRATED_CIRCUIT" ] + /components/component/integrated-circuit/backplane-facing-capacity/state/total: + platform_type: [ "INTEGRATED_CIRCUIT" ] + + ## State Paths: SubscriptionMode: ON_CHANGE ## + /interfaces/interface/state/admin-status: + /lacp/interfaces/interface/members/member/interface: + /interfaces/interface/ethernet/state/mac-address: + /interfaces/interface/state/hardware-port: + /interfaces/interface/state/id: + /interfaces/interface/state/oper-status: + /interfaces/interface/state/forwarding-viable: + /interfaces/interface/ethernet/state/port-speed: + /components/component/integrated-circuit/state/node-id: + platform_type: [ "INTEGRATED_CIRCUIT" ] + /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity: + platform_type: [ "INTEGRATED_CIRCUIT" ] + # TODO(OCPATH): Add component names to component paths. + #/components/component/state/parent: + #/components/component/state/oper-status: + +rpcs: + gnmi: + gNMI.Subscribe: + Mode: [ "TARGET_DEFINED", "ON_CHANGE" ] + gNMI.Set: +``` + diff --git a/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/gnmi_subscriptionlist_test.go b/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/gnmi_subscriptionlist_test.go new file mode 100644 index 00000000000..35376391d3e --- /dev/null +++ b/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/gnmi_subscriptionlist_test.go @@ -0,0 +1,165 @@ +package gnmi_subscriptionlist_test + +import ( + "context" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/args" + "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/ygnmi/ygnmi" +) + +const ( + syncResponseWaitTimeOut = 300 * time.Second +) + +var ( + returnChangedMode = map[gpb.SubscriptionMode]gpb.SubscriptionMode{ + gpb.SubscriptionMode_TARGET_DEFINED: gpb.SubscriptionMode_SAMPLE, + gpb.SubscriptionMode_ON_CHANGE: gpb.SubscriptionMode_TARGET_DEFINED, + } + backplaneFacingCapacityPaths = map[gpb.SubscriptionMode][]ygnmi.PathStruct{ + gpb.SubscriptionMode_ON_CHANGE: { + gnmi.OC().ComponentAny().IntegratedCircuit().BackplaneFacingCapacity().TotalOperationalCapacity().State().PathStruct(), + }, + gpb.SubscriptionMode_TARGET_DEFINED: { + gnmi.OC().ComponentAny().IntegratedCircuit().BackplaneFacingCapacity().AvailablePct().State().PathStruct(), + gnmi.OC().ComponentAny().IntegratedCircuit().BackplaneFacingCapacity().ConsumedCapacity().State().PathStruct(), + gnmi.OC().ComponentAny().IntegratedCircuit().BackplaneFacingCapacity().Total().State().PathStruct(), + }, + } + + telemetryPaths = map[gpb.SubscriptionMode][]ygnmi.PathStruct{ + gpb.SubscriptionMode_ON_CHANGE: { + gnmi.OC().InterfaceAny().AdminStatus().State().PathStruct(), + gnmi.OC().Lacp().InterfaceAny().MemberAny().Interface().State().PathStruct(), + gnmi.OC().InterfaceAny().Ethernet().MacAddress().State().PathStruct(), + gnmi.OC().InterfaceAny().HardwarePort().State().PathStruct(), + gnmi.OC().InterfaceAny().Id().State().PathStruct(), + gnmi.OC().InterfaceAny().OperStatus().State().PathStruct(), + gnmi.OC().InterfaceAny().Ethernet().PortSpeed().State().PathStruct(), + gnmi.OC().ComponentAny().IntegratedCircuit().NodeId().State().PathStruct(), + gnmi.OC().ComponentAny().Parent().State().PathStruct(), + gnmi.OC().ComponentAny().OperStatus().State().PathStruct(), + gnmi.OC().InterfaceAny().ForwardingViable().State().PathStruct(), + }, gpb.SubscriptionMode_TARGET_DEFINED: { + gnmi.OC().InterfaceAny().Counters().InUnicastPkts().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().InBroadcastPkts().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().InMulticastPkts().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().OutUnicastPkts().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().OutBroadcastPkts().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().OutMulticastPkts().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().InOctets().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().OutOctets().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().InDiscards().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().OutDiscards().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().InErrors().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().OutErrors().State().PathStruct(), + gnmi.OC().InterfaceAny().Counters().InFcsErrors().State().PathStruct(), + gnmi.OC().Qos().InterfaceAny().Output().QueueAny().TransmitOctets().State().PathStruct(), + gnmi.OC().Qos().InterfaceAny().Output().QueueAny().TransmitPkts().State().PathStruct(), + gnmi.OC().Qos().InterfaceAny().Output().QueueAny().DroppedPkts().State().PathStruct(), + }, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func updateTelemetryPaths() { + if *args.NumControllerCards > 0 { + for mode, paths := range backplaneFacingCapacityPaths { + telemetryPaths[mode] = append(telemetryPaths[mode], paths...) + } + } +} + +func createSubscriptionList(t *testing.T, telemetryData map[gpb.SubscriptionMode][]ygnmi.PathStruct, changeSubscriptionModes bool) *gpb.SubscriptionList { + subscriptions := make([]*gpb.Subscription, 0) + for mode, paths := range telemetryData { + currMode := mode + if changeSubscriptionModes == true { + currMode = returnChangedMode[mode] + } + for _, path := range paths { + gnmiPath, _, err := ygnmi.ResolvePath(path) + + if err != nil { + t.Errorf("[Error]:Error in resolving gnmi path =%v", path) + } + + gnmiRequest := &gpb.Subscription{ + Path: gnmiPath, + Mode: currMode, + } + if currMode == gpb.SubscriptionMode_SAMPLE { + gnmiRequest.SampleInterval = uint64(time.Second * 10) + } + + subscriptions = append(subscriptions, gnmiRequest) + } + } + + return &gpb.SubscriptionList{ + Subscription: subscriptions, + Mode: gpb.SubscriptionList_STREAM, + } +} + +func TestSingleSubscription(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ctx := context.Background() + updateTelemetryPaths() + testCases := []struct { + desc string + changeMode bool + }{ + {desc: "GNMI-2.1: Verify single subscription request with a Subscriptionlist and different SubscriptionModes", changeMode: false}, + {desc: "GNMI-2.2: Change SubscriptionModes in the subscription list and verify receipt of sync_response:", changeMode: true}, + {desc: "GNMI-2.2: Swithcing Modes, back to previous modes and verifying the receipt of sync_response ", changeMode: false}, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + t.Log(tc.desc) + subscribeList := createSubscriptionList(t, telemetryPaths, tc.changeMode) + subscribeRequest := &gpb.SubscribeRequest{ + Request: &gpb.SubscribeRequest_Subscribe{ + Subscribe: subscribeList, + }, + } + stream, err := dut.RawAPIs().GNMI(t).Subscribe(ctx) + defer stream.CloseSend() + defer ctx.Done() + + if err != nil { + t.Fatalf("[Fail]:Failed to create subscribe stream: %v", err) + } + + if err := stream.Send(subscribeRequest); err != nil { + t.Fatalf("[Fail]:Failed to send subscribe request: %v", err) + } + + startTime := time.Now() + for { + resp, err := stream.Recv() + if resp.GetSyncResponse() == true { + t.Logf("Received sync_response!") + break + } + if err != nil { + t.Errorf("[Error]: While receieving the subcription response %v", err) + } + + if time.Since(startTime).Seconds() > float64(syncResponseWaitTimeOut) { + t.Fatalf("[Fail]:Didn't receive sync_response. Time limit = %v exceeded", syncResponseWaitTimeOut) + } + } + }) + } +} diff --git a/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/metadata.textproto b/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/metadata.textproto new file mode 100644 index 00000000000..f28d64715ef --- /dev/null +++ b/feature/gnmi/subscribe/tests/gnmi_subscriptionlist_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "262084aa-ab98-4492-849e-d7ca83105a0e" +plan_id: "GNMI-2" +description: "gnmi_subscriptionlist_test" +testbed: TESTBED_DUT diff --git a/feature/gnoi/factory_reset/feature.textproto b/feature/gnoi/factory_reset/feature.textproto index df72586373f..eaa711ccaa3 100644 --- a/feature/gnoi/factory_reset/feature.textproto +++ b/feature/gnoi/factory_reset/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/gnoi/factory_reset/tests/factory_reset_test/README.md b/feature/gnoi/factory_reset/tests/factory_reset_test/README.md index 76525f9513d..8c4a5e8fd9d 100644 --- a/feature/gnoi/factory_reset/tests/factory_reset_test/README.md +++ b/feature/gnoi/factory_reset/tests/factory_reset_test/README.md @@ -1,18 +1,37 @@ -# gNOI-6.1: Factory Reset +# gNOI-6.1: Factory Reset ## Summary -Performs Factory Reset with and without disk-encryption + +Performs Factory Reset ## Procedure -* Create dummy files in the harddisk of the router using bash dd -* Checks for disk-encryption status and performs reset on both the scenarios -* Secure ZTP server should be up and running in the background for the router to boot up with the base config once factory reset command is sent on the box. -* Send out Factory reset via GNOI Raw API - * Wait for the box to boot up via Secure ZTP - * The base config is updated on the box via Secure ZTP -* Connect to the router and check if the files in the harddisk are removed as a part of verifying Factory reset. -## Config Parameter coverage +### Scenario 1 + +* Create a sample file in the harddisk of the router using gNOI PUT RPC +* Secure ZTP server should be up and running in the background for the router + to boot up with the base config once factory reset command is sent on the + box. +* Send out Factory reset via GNOI Raw API + * Wait for the box to boot up via Secure ZTP + * The base config is updated on the box via Secure ZTP +* Send a gNOI file STAT RPC to check if the file in the harddisk are removed + as a part of verifying Factory reset. + +### Scenario 2 + +* Check startup-config file exists in mount path. +* Perform the same steps are `Scenario 1` for startup-config file. + +## OpenConfig Path and RPC Coverage -* No new configuration covered. +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 +rpcs: + gnoi: + factory_reset.FactoryReset.Start: + file.File.Put: + file.File.Stat: +``` 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 93d161332a5..4cc0240c1af 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 @@ -1,26 +1,46 @@ -package factoryreset +// 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 factory_reset_test import ( "context" - "fmt" - "path" - "strings" + "crypto/md5" + "crypto/rand" + "io" + "path/filepath" + "regexp" "testing" "time" "github.com/openconfig/featureprofiles/internal/fptest" frpb "github.com/openconfig/gnoi/factory_reset" + fpb "github.com/openconfig/gnoi/file" + "github.com/openconfig/gnoi/types" + "github.com/openconfig/gnoigo" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/testt" ) var ( - filesCreated = []string{} - fileCreateDevRand = "bash dd if=/dev/urandom of=%s bs=1M count=2" - checkFileExists = "bash [ -f \"%s\" ] && echo \"YES_exists\"" - fileExists = "YES_exists" - fileCreate = "bash fallocate -l %dM %s" + remoteFilePath = map[ondatra.Vendor]string{ + ondatra.CISCO: "/misc/disk1/", + ondatra.NOKIA: "/tmp/", + ondatra.JUNIPER: "/var/tmp/", + ondatra.ARISTA: "/mnt/flash/", + } ) const maxRebootTime = 40 // 40 mins wait time for the factory reset and sztp to kick in @@ -28,54 +48,6 @@ func TestMain(m *testing.M) { fptest.RunTests(m) } -type encryptionCommands struct { - EncryptionStatus string - EncryptionActivate string - EncryptionDeactivate string - DevicePaths []string -} - -var enCiscoCommands encryptionCommands - -// creating files before factory reset -func createFiles(t *testing.T, dut *ondatra.DUTDevice, devicePaths []string) { - for _, folderPath := range devicePaths { - fPath := path.Join(folderPath, "devrandom.log") - dut.CLI().Run(t, fmt.Sprintf(fileCreateDevRand, fPath)) - t.Log("Check if the file is created") - time.Sleep(30 * time.Second) - filesCreated = append(filesCreated, fPath) - fPath = path.Join(folderPath, ".devrandom.log") - dut.CLI().Run(t, fmt.Sprintf(fileCreateDevRand, fPath)) - - filesCreated = append(filesCreated, fPath) - fPath = path.Join(folderPath, "largeFile.log") - dut.CLI().Run(t, fmt.Sprintf(fileCreate, 100, fPath)) - - filesCreated = append(filesCreated, fPath) - } - for _, f := range filesCreated { - resp := dut.CLI().Run(t, fmt.Sprintf(checkFileExists, f)) - t.Logf("%v", resp) - if !strings.Contains(resp, fileExists) { - t.Fatalf("Unable to Create a file object %s in device %s", f, dut.Name()) - } - } - -} - -// checkFiles check if the files created are deleted from the device after factory reset -func checkFiles(t *testing.T, dut *ondatra.DUTDevice) { - for _, f := range filesCreated { - resp := dut.CLI().Run(t, fmt.Sprintf(checkFileExists, f)) - t.Logf(resp) - if strings.Contains(resp, fileExists) == true { - t.Fatalf("File %s not cleared by system Reset, in device %s", f, dut.Name()) - } - - } -} - func deviceBootStatus(t *testing.T, dut *ondatra.DUTDevice) { startReboot := time.Now() t.Logf("Wait for DUT to boot up by polling the telemetry output.") @@ -100,70 +72,153 @@ func deviceBootStatus(t *testing.T, dut *ondatra.DUTDevice) { t.Logf("Device boot time: %.2f minutes", time.Since(startReboot).Minutes()) } -// performs factory reset -func factoryReset(t *testing.T, dut *ondatra.DUTDevice, devicePaths []string) { - createFiles(t, dut, devicePaths) +func gNOIPutFile(t *testing.T, dut *ondatra.DUTDevice, gnoiClient gnoigo.Clients, fName string) { + dutVendor := dut.Vendor() + fullPath := filepath.Join(remoteFilePath[dutVendor], fName) + stream, err := gnoiClient.File().Put(context.Background()) + t.Logf("Attempting to send gNOI File Put here: %v", fullPath) + if err != nil { + t.Fatalf("Failed to create stream channel: %v", err) + } + defer stream.CloseSend() + h := md5.New() + fPutOpen := &fpb.PutRequest_Open{ + Open: &fpb.PutRequest_Details{ + RemoteFile: fullPath, + Permissions: 744, + }, + } + err = stream.Send(&fpb.PutRequest{ + Request: fPutOpen, + }) + if err != nil { + t.Fatalf("Stream failed to send PutRequest: %v", err) + } + + b := make([]byte, 64*1024) + n, err := rand.Read(b) + if err != nil && err != io.EOF { + t.Fatalf("Error reading bytes: %v", err) + } + h.Write(b[:n]) + req := &fpb.PutRequest{ + Request: &fpb.PutRequest_Contents{ + Contents: b[:n], + }, + } + err = stream.Send(req) + if err != nil { + t.Fatalf("Stream failed to send Req: %v", err) + } + + hashReq := &fpb.PutRequest{ + Request: &fpb.PutRequest_Hash{ + Hash: &types.HashType{ + Method: types.HashType_MD5, + Hash: h.Sum(nil), + }, + }, + } + err = stream.Send(hashReq) + if err != nil { + t.Fatalf("Stream failed to send hash: %v", err) + } + + _, err = stream.CloseAndRecv() + if err != nil { + t.Fatalf("Problem closing the stream: %v", err) + } +} + +func gNOIStatFile(t *testing.T, dut *ondatra.DUTDevice, fName string, reset bool) { + dutVendor := dut.Vendor() + fullPath := filepath.Join(remoteFilePath[dutVendor], fName) gnoiClient, err := dut.RawAPIs().BindingDUT().DialGNOI(context.Background()) if err != nil { t.Fatalf("Error dialing gNOI: %v", err) } - facRe, err := gnoiClient.FactoryReset().Start(context.Background(), &frpb.StartRequest{FactoryOs: false, ZeroFill: false}) + if _, ok := remoteFilePath[dutVendor]; !ok { + t.Fatalf("Please add support for vendor %v in var remoteFilePath ", dutVendor) + } + + in := &fpb.StatRequest{ + Path: remoteFilePath[dutVendor], + } + statResp, err := gnoiClient.File().Stat(context.Background(), in) if err != nil { - t.Fatalf("Failed to initiate Factory Reset on the device, Error : %v ", err) - } - t.Logf("Factory reset Response %v ", facRe) - time.Sleep(2 * time.Minute) - deviceBootStatus(t, dut) - dutNew := ondatra.DUT(t, "dut") - checkFiles(t, dutNew) - t.Log("Factory reset successfull") + t.Fatalf("Error fetching stat path %v for the created file on DUT. %v", remoteFilePath[dutVendor], err) + } + + if len(statResp.GetStats()) == 0 { + t.Log("gNOI STAT did not find any files") + } + + r := regexp.MustCompile(fName) + var isCreatedFile bool + + for _, fileStats := range statResp.GetStats() { + isCreatedFile = r.MatchString(fileStats.GetPath()) && (fileStats.GetSize() == uint64(64*1024)) + if isCreatedFile { + break + } + } + if isCreatedFile { + if !reset { + t.Logf("gNOI PUT successfully created file: %s", fullPath) + } else { + t.Errorf("gNOI PUT file was found after Factory Reset: %s", fullPath) + } + } + if !isCreatedFile { + if !reset { + t.Error("gNOI PUT file was never Created") + } else { + t.Logf("Did not find %s in the list of files", fullPath) + } + } } func TestFactoryReset(t *testing.T) { dut := ondatra.DUT(t, "dut") - switch dut.Vendor() { - case ondatra.CISCO: - enCiscoCommands = encryptionCommands{EncryptionStatus: "show disk-encryption status", EncryptionActivate: "disk-encryption activate", EncryptionDeactivate: "disk-encryption deactivate", DevicePaths: []string{"/misc/disk1"}} - t.Logf("Cisco commands for disk encryption %v ", enCiscoCommands) - default: - t.Fatalf("Disk Encryption commands is missing for %v ", dut.Vendor().String()) - } - - showDiskEncryptionStatus := dut.CLI().Run(t, enCiscoCommands.EncryptionStatus) - t.Logf("Disk encryption status %v", showDiskEncryptionStatus) - - if strings.Contains(showDiskEncryptionStatus, "Not Encrypted") { - t.Log("Performing Factory reset without Encryption\n") - factoryReset(t, dut, enCiscoCommands.DevicePaths) - t.Log("Stablise after factory reset\n") - time.Sleep(5 * time.Minute) - t.Log("Activate Encryption\n") - encrypt := dut.CLI().Run(t, enCiscoCommands.EncryptionActivate) - t.Logf("Sleep for 5 mins after disk-encryption activate") - time.Sleep(5 * time.Minute) - t.Logf("Device encryption acrivare: %v", encrypt) - deviceBootStatus(t, dut) - encrypt = dut.CLI().Run(t, enCiscoCommands.EncryptionStatus) - t.Logf("Show device encryption status: %v", encrypt) - t.Log("Wait for the system to stabilize\n") - time.Sleep(5 * time.Minute) - factoryReset(t, dut, enCiscoCommands.DevicePaths) - } else { - t.Log("Performing Factory reset with Encryption\n") - factoryReset(t, dut, enCiscoCommands.DevicePaths) - t.Log("Stablise after factory reset\n") - time.Sleep(5 * time.Minute) - t.Log("Deactivate Encryption\n") - encrypt := dut.CLI().Run(t, enCiscoCommands.EncryptionDeactivate) - t.Logf("Device encrytion deactivate: %v", encrypt) - t.Logf("Sleep for 5 mins after disk-encryption deactivate") - time.Sleep(5 * time.Minute) - deviceBootStatus(t, dut) - encrypt = dut.CLI().Run(t, enCiscoCommands.EncryptionStatus) - t.Logf("Show device encrytion status: %v", encrypt) - t.Logf("Wait for the system to stabilize\n") - time.Sleep(5 * time.Minute) - factoryReset(t, dut, enCiscoCommands.DevicePaths) + testCases := []struct { + name string + fileName string + fileExist bool + }{ + { + name: "Random file", + fileName: "devrandom.log", + fileExist: false, + }, + { + name: "Startup config", + fileName: "startup-config", + fileExist: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + gnoiClient, err := dut.RawAPIs().BindingDUT().DialGNOI(context.Background()) + if err != nil { + t.Fatalf("Error dialing gNOI: %v", err) + } + + if !tc.fileExist { + gNOIPutFile(t, dut, gnoiClient, tc.fileName) + } + gNOIStatFile(t, dut, tc.fileName, tc.fileExist) + + res, err := gnoiClient.FactoryReset().Start(context.Background(), &frpb.StartRequest{FactoryOs: false, ZeroFill: false}) + if err != nil { + t.Fatalf("Failed to initiate Factory Reset on the device, Error : %v ", err) + } + t.Logf("Factory reset Response %v ", res) + time.Sleep(2 * time.Minute) + + deviceBootStatus(t, dut) + gNOIStatFile(t, dut, tc.fileName, true) + }) } } diff --git a/feature/gnoi/factory_reset/tests/factory_reset_test/metadata.textproto b/feature/gnoi/factory_reset/tests/factory_reset_test/metadata.textproto index 231f9b845d8..9ce3bad8abe 100644 --- a/feature/gnoi/factory_reset/tests/factory_reset_test/metadata.textproto +++ b/feature/gnoi/factory_reset/tests/factory_reset_test/metadata.textproto @@ -4,4 +4,4 @@ uuid: "15e3ade1-e3c4-4da3-8553-3a9ef5fba344" plan_id: "gNOI-6.1" description: "Factory Reset" -testbed: TESTBED_DUT_ATE_2LINKS +testbed: TESTBED_DUT diff --git a/feature/gnoi/file/feature.textproto b/feature/gnoi/file/feature.textproto index 151fc0b1102..fda97967dee 100644 --- a/feature/gnoi/file/feature.textproto +++ b/feature/gnoi/file/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/gnoi/healthz/feature.textproto b/feature/gnoi/healthz/feature.textproto index fdfdc06fe22..b7fcef86a4e 100644 --- a/feature/gnoi/healthz/feature.textproto +++ b/feature/gnoi/healthz/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/gnoi/os/feature.textproto b/feature/gnoi/os/feature.textproto index 6054dc78c1e..765f2044a91 100644 --- a/feature/gnoi/os/feature.textproto +++ b/feature/gnoi/os/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/gnoi/os/tests/osinstall/README.md b/feature/gnoi/os/tests/osinstall/README.md index 1d3e0f1c4e8..7c1e680041b 100644 --- a/feature/gnoi/os/tests/osinstall/README.md +++ b/feature/gnoi/os/tests/osinstall/README.md @@ -65,5 +65,24 @@ Note: For the test configuration, please include interface and BGP configuration. -## Telemetry Parameter Coverage -* /system/state/boot-time +## 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(OCPATH): fill in coverage from code already written. + +```yaml +paths: + ## State Paths ## + /system/state/boot-time: + +rpcs: + gnmi: + gNMI.Subscribe: + gnoi: + os.OS.Activate: + os.OS.Install: + os.OS.Verify: + system.System.Reboot: +``` + diff --git a/feature/gnoi/os/tests/osinstall/metadata.textproto b/feature/gnoi/os/tests/osinstall/metadata.textproto index 16de094f7ca..b3af04b638b 100644 --- a/feature/gnoi/os/tests/osinstall/metadata.textproto +++ b/feature/gnoi/os/tests/osinstall/metadata.textproto @@ -7,9 +7,19 @@ description: "Software Upgrade" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { - vendor: JUNIPER + vendor: NOKIA } deviations: { - sw_version_unsupported: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + interface_enabled: true } } diff --git a/feature/gnoi/os/tests/osinstall/osinstall_test.go b/feature/gnoi/os/tests/osinstall/osinstall_test.go index cd38346da74..368e97e160c 100644 --- a/feature/gnoi/os/tests/osinstall/osinstall_test.go +++ b/feature/gnoi/os/tests/osinstall/osinstall_test.go @@ -19,17 +19,21 @@ import ( "fmt" "io" "os" + "reflect" "strings" "testing" "time" "flag" + "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" closer "github.com/openconfig/gocloser" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -50,8 +54,53 @@ var ( osVersion = flag.String("osver", "", "Version of the OS image for the install operation") timeout = flag.Duration("timeout", time.Minute*30, "Time to wait for reboot to complete") + + dutSrc = attrs.Attributes{ + Desc: "DUT to ATE source", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + ateSrc = attrs.Attributes{ + Name: "ateSrc", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutAttrs = attrs.Attributes{ + Desc: "To ATE", + IPv4: "192.0.2.1", + IPv4Len: 30, + } + bgpGlobalAttrs = bgpAttrs{ + rplName: "ALLOW", + grRestartTime: 60, + prefixLimit: 200, + dutAS: 64500, + ateAS: 64501, + peerGrpNamev4: "BGP-PEER-GROUP-V4", + } + bgpNbr1 = bgpNeighbor{localAs: bgpGlobalAttrs.dutAS, peerAs: bgpGlobalAttrs.ateAS, pfxLimit: bgpGlobalAttrs.prefixLimit, neighborip: ateSrc.IPv4, isV4: true} ) +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 +) + +type bgpAttrs struct { + rplName, peerGrpNamev4 string + prefixLimit, dutAS, ateAS uint32 + grRestartTime uint16 +} +type bgpNeighbor struct { + localAs, peerAs, pfxLimit uint32 + neighborip string + isV4 bool +} type testCase struct { dut *ondatra.DUTDevice // dualSup indicates if the DUT has a standby supervisor available. @@ -372,3 +421,101 @@ func watchStatus(t *testing.T, ic ospb.OS_InstallClient, standby bool) error { } } } + +func TestPushAndVerifyInterfaceConfig(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + t.Logf("Create and push interface config to the DUT") + dutPort := dut.Port(t, "port1") + dutPortName := dutPort.Name() + intf1 := dutAttrs.NewOCInterface(dutPortName, dut) + gnmi.Replace(t, dut, gnmi.OC().Interface(intf1.GetName()).Config(), intf1) + + dc := gnmi.OC().Interface(dutPortName).Config() + in := configInterface(dutPortName, dutAttrs.Desc, dutAttrs.IPv4, dutAttrs.IPv4Len, dut) + fptest.LogQuery(t, fmt.Sprintf("%s to Replace()", dutPort), dc, in) + gnmi.Replace(t, dut, dc, in) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + ocPortName := dut.Port(t, "port1").Name() + fptest.AssignToNetworkInstance(t, dut, ocPortName, deviations.DefaultNetworkInstance(dut), 0) + } + + t.Logf("Fetch interface config from the DUT using Get RPC and verify it matches with the config that was pushed earlier") + if val, present := gnmi.LookupConfig(t, dut, dc).Val(); present { + if reflect.DeepEqual(val, in) { + t.Logf("Interface config Want and Got matched") + fptest.LogQuery(t, fmt.Sprintf("%s from Get", dutPort), dc, val) + } else { + t.Errorf("Config %v Get() value not matching with what was Set()", dc) + } + } else { + t.Errorf("Config %v Get() failed", dc) + } +} + +// configInterface generates an interface's configuration based on the the attributes given. +func configInterface(name, desc, ipv4 string, prefixlen uint8, dut *ondatra.DUTDevice) *oc.Interface { + i := &oc.Interface{} + i.Name = ygot.String(name) + i.Description = ygot.String(desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + + a := s4.GetOrCreateAddress(ipv4) + a.PrefixLength = ygot.Uint8(prefixlen) + return i +} + +func TestPushAndVerifyBGPConfig(t *testing.T) { + dut := ondatra.DUT(t, "dut") + dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + + t.Logf("Create and push BGP config to the DUT") + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + dutConf := bgpCreateNbr(dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + + t.Logf("Fetch BGP config from the DUT using Get RPC and verify it matches with the config that was pushed earlier") + if val, present := gnmi.LookupConfig(t, dut, dutConfPath.Config()).Val(); present { + if reflect.DeepEqual(val, dutConf) { + t.Logf("BGP config Want and Got matched") + fptest.LogQuery(t, "BGP fetched from DUT using Get()", dutConfPath.Config(), val) + } else { + t.Errorf("Config %v Get() value not matching with what was Set()", dutConfPath.Config()) + } + } else { + t.Errorf("Config %v Get() failed", dutConfPath.Config()) + } +} + +// bgpCreateNbr creates a BGP object with neighbor pointing to ateSrc +func bgpCreateNbr(dut *ondatra.DUTDevice) *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(uint32(bgpGlobalAttrs.dutAS)) + global.RouterId = ygot.String(dutSrc.IPv4) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + pgv4 := bgp.GetOrCreatePeerGroup(bgpGlobalAttrs.peerGrpNamev4) + pgv4.PeerAs = ygot.Uint32(uint32(bgpGlobalAttrs.ateAS)) + pgv4.PeerGroupName = ygot.String(bgpGlobalAttrs.peerGrpNamev4) + nbr := bgpNbr1 + nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.PeerAs = ygot.Uint32(nbr.peerAs) + nv4.Enabled = ygot.Bool(true) + nv4.PeerGroup = ygot.String(bgpGlobalAttrs.peerGrpNamev4) + return niProto +} diff --git a/feature/gnoi/packet_link_qualification/feature.textproto b/feature/gnoi/packet_link_qualification/feature.textproto index 8d710ba0d81..e03f8b71b8f 100644 --- a/feature/gnoi/packet_link_qualification/feature.textproto +++ b/feature/gnoi/packet_link_qualification/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/README.md b/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/README.md index 0092782e3e3..01e4aca9b19 100644 --- a/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/README.md +++ b/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/README.md @@ -83,6 +83,17 @@ between 2 DUTs. * Ensure that RPC status code is 0 for succuss. * Packets sent count matches with packets received. -## Telemetry Parameter Coverage +## OpenConfig Path and RPC Coverage -* None +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 +rpcs: + gnoi: + packet_link_qualification.LinkQualification.Capabilities: + packet_link_qualification.LinkQualification.Create: + packet_link_qualification.LinkQualification.Delete: + packet_link_qualification.LinkQualification.Get: + packet_link_qualification.LinkQualification.List: +``` diff --git a/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/metadata.textproto b/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/metadata.textproto index 2070cb50f54..903abd7b548 100644 --- a/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/metadata.textproto +++ b/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/metadata.textproto @@ -7,28 +7,32 @@ description: "Packet-based Link Qualification" testbed: TESTBED_DUT_DUT_4LINKS platform_exceptions: { platform: { - vendor: JUNIPER + vendor: ARISTA } deviations: { + omit_l2_mtu: true + interface_enabled: true + link_qual_wait_after_delete_required: true skip_plq_interface_oper_status_check: true } } platform_exceptions: { platform: { - vendor: ARISTA + vendor: NOKIA } deviations: { - omit_l2_mtu: true interface_enabled: true - link_qual_wait_after_delete_required: true + explicit_port_speed: true } } platform_exceptions: { platform: { - vendor: NOKIA + vendor: CISCO } deviations: { - interface_enabled: true - explicit_port_speed: true + skip_plq_interface_oper_status_check: true + plq_reflector_stats_unsupported: true + plq_generator_capabilities_max_mtu: 512 + plq_generator_capabilities_max_pps: 40000000 } } diff --git a/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/packet_link_qualification_test.go b/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/packet_link_qualification_test.go index e3c600508a1..3da68cc1db0 100644 --- a/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/packet_link_qualification_test.go +++ b/feature/gnoi/packet_link_qualification/tests/packet_link_qualification_test/packet_link_qualification_test.go @@ -44,6 +44,11 @@ func TestMain(m *testing.M) { // https://github.com/fullstorydev/grpcurl // +var ( + minRequiredGeneratorMTU = uint64(8184) + minRequiredGeneratorPPS = uint64(1e8) +) + func TestCapabilitiesResponse(t *testing.T) { dut1 := ondatra.DUT(t, "dut1") dut2 := ondatra.DUT(t, "dut2") @@ -60,6 +65,14 @@ func TestCapabilitiesResponse(t *testing.T) { t.Fatalf("Failed to handle gnoi LinkQualification().Capabilities(): %v", err) } + if deviations.PLQGeneratorCapabilitiesMaxMTU(dut1) != 0 { + minRequiredGeneratorMTU = uint64(deviations.PLQGeneratorCapabilitiesMaxMTU(dut1)) + } + + if deviations.PLQGeneratorCapabilitiesMaxPPS(dut1) != 0 { + minRequiredGeneratorPPS = deviations.PLQGeneratorCapabilitiesMaxPPS(dut1) + } + cases := []struct { desc string got uint64 @@ -91,7 +104,7 @@ func TestCapabilitiesResponse(t *testing.T) { }, { desc: "Generator MaxMtu", got: uint64(plqResp.GetGenerator().GetPacketGenerator().GetMaxMtu()), - min: uint64(8184), + min: minRequiredGeneratorMTU, }, { desc: "Generator MaxBps", got: uint64(plqResp.GetGenerator().GetPacketGenerator().GetMaxBps()), @@ -99,7 +112,7 @@ func TestCapabilitiesResponse(t *testing.T) { }, { desc: "Generator MaxPps", got: uint64(plqResp.GetGenerator().GetPacketGenerator().GetMaxPps()), - min: uint64(1e8), + min: minRequiredGeneratorPPS, }} for _, tc := range cases { @@ -231,6 +244,10 @@ func TestLinkQualification(t *testing.T) { } plqID := dut1.Name() + ":" + dp1.Name() + "<->" + dut2.Name() + ":" + dp2.Name() + + if deviations.PLQGeneratorCapabilitiesMaxMTU(dut1) != 0 { + minRequiredGeneratorMTU = uint64(deviations.PLQGeneratorCapabilitiesMaxMTU(dut1)) + } type LinkQualificationDuration struct { // time needed to complete preparation generatorsetupDuration time.Duration @@ -265,7 +282,7 @@ func TestLinkQualification(t *testing.T) { EndpointType: &plqpb.QualificationConfiguration_PacketGenerator{ PacketGenerator: &plqpb.PacketGeneratorConfiguration{ PacketRate: uint64(138888), - PacketSize: uint32(8184), + PacketSize: uint32(minRequiredGeneratorMTU), }, }, Timing: &plqpb.QualificationConfiguration_Rpc{ @@ -422,11 +439,16 @@ func TestLinkQualification(t *testing.T) { // The packet counters between Generator and Reflector mismatch tolerance level in percentage var tolerance float64 = 0.0001 - if ((math.Abs(float64(generatorPktsSent)-float64(reflectorPktsRxed)))/(float64(generatorPktsSent)+float64(reflectorPktsRxed)+tolerance))*200.00 > tolerance { - t.Errorf("The difference between packets received count at Reflector and packets sent count at Generator is greater than %0.4f percent: generatorPktsSent %v, reflectorPktsRxed %v", tolerance, generatorPktsSent, reflectorPktsRxed) - } - if ((math.Abs(float64(reflectorPktsSent)-float64(generatorPktsRxed)))/(float64(reflectorPktsSent)+float64(generatorPktsRxed)+tolerance))*200.00 > tolerance { - t.Errorf("The difference between packets received count at Generator and packets sent count at Reflector is greater than %0.4f percent: reflectorPktsSent %v, generatorPktsRxed %v", tolerance, reflectorPktsSent, generatorPktsRxed) + if deviations.PLQReflectorStatsUnsupported(dut1) { + if (math.Abs(float64(generatorPktsSent)-float64(generatorPktsRxed))/float64(generatorPktsSent))*100.00 > tolerance { + t.Errorf("The difference between packets sent count and packets received count at Generator is greater than %0.4f percent: generatorPktsSent %v, generatorPktsRxed %v", tolerance, generatorPktsSent, generatorPktsRxed) + } + } else { + if ((math.Abs(float64(generatorPktsSent)-float64(reflectorPktsRxed)))/(float64(generatorPktsSent)+float64(reflectorPktsRxed)+tolerance))*200.00 > tolerance { + t.Errorf("The difference between packets received count at Reflector and packets sent count at Generator is greater than %0.4f percent: generatorPktsSent %v, reflectorPktsRxed %v", tolerance, generatorPktsSent, reflectorPktsRxed) + } + if ((math.Abs(float64(reflectorPktsSent)-float64(generatorPktsRxed)))/(float64(reflectorPktsSent)+float64(generatorPktsRxed)+tolerance))*200.00 > tolerance { + t.Errorf("The difference between packets received count at Generator and packets sent count at Reflector is greater than %0.4f percent: reflectorPktsSent %v, generatorPktsRxed %v", tolerance, reflectorPktsSent, generatorPktsRxed) + } } - } diff --git a/feature/gnoi/system/feature.textproto b/feature/gnoi/system/feature.textproto index 6a9904718c6..32f544193ad 100644 --- a/feature/gnoi/system/feature.textproto +++ b/feature/gnoi/system/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/README.md b/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/README.md index a1f562911b2..2d60643afd4 100644 --- a/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/README.md +++ b/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/README.md @@ -21,7 +21,15 @@ Validate gNOI RPC can get reboot status and cancel the reboot * Issue Cancel reboot request RPC to chassis. * Validate that the reboot status is no longer active. +## OpenConfig Path and RPC Coverage -## Telemetry Parameter 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 +rpcs: + gnoi: + system.System.CancelReboot: + system.System.Reboot: + system.System.RebootStatus: +``` -* None diff --git a/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/chassis_reboot_status_and_cancel_test.go b/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/chassis_reboot_status_and_cancel_test.go index 30d20401fc6..d709bd4adaa 100644 --- a/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/chassis_reboot_status_and_cancel_test.go +++ b/feature/gnoi/system/tests/chassis_reboot_status_and_cancel_test/chassis_reboot_status_and_cancel_test.go @@ -197,7 +197,7 @@ func getSubCompPath(t *testing.T, dut *ondatra.DUTDevice) *tpb.Path { } activeRP := controllerCards[0] if len(controllerCards) == 2 { - _, activeRP = components.FindStandbyRP(t, dut, controllerCards) + _, activeRP = components.FindStandbyControllerCard(t, dut, controllerCards) } useNameOnly := deviations.GNOISubcomponentPath(dut) return components.GetSubcomponentPath(activeRP, useNameOnly) diff --git a/feature/gnoi/system/tests/complete_chassis_reboot/README.md b/feature/gnoi/system/tests/complete_chassis_reboot/README.md index dbdeb34466c..02ab99ccbaf 100644 --- a/feature/gnoi/system/tests/complete_chassis_reboot/README.md +++ b/feature/gnoi/system/tests/complete_chassis_reboot/README.md @@ -23,6 +23,20 @@ Validate gNOI RPC can reboot entire chassis * TODO: Validate that all connected ports are disabled and re-enabled. * Validate that the device returns with the expected software version -## Telemetry Parameter Coverage +## OpenConfig Path and RPC Coverage -* /system/state/boot-time +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(OCPATH): fill in coverage from code already written. + +```yaml +paths: + ## State paths + /system/state/boot-time: + +rpcs: + gnoi: + system.System.Reboot: + system.System.CancelReboot: +``` diff --git a/feature/gnoi/system/tests/complete_chassis_reboot/complete_chassis_reboot_test.go b/feature/gnoi/system/tests/complete_chassis_reboot/complete_chassis_reboot_test.go index db8a3680628..b272c85db88 100644 --- a/feature/gnoi/system/tests/complete_chassis_reboot/complete_chassis_reboot_test.go +++ b/feature/gnoi/system/tests/complete_chassis_reboot/complete_chassis_reboot_test.go @@ -25,6 +25,7 @@ import ( spb "github.com/openconfig/gnoi/system" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/testt" ) @@ -109,6 +110,13 @@ func TestChassisReboot(t *testing.T) { preRebootCompStatus := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().OperStatus().State()) t.Logf("DUT components status pre reboot: %v", preRebootCompStatus) + preRebootCompDebug := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) + preCompMatrix := []string{} + for _, preComp := range preRebootCompDebug { + if preComp.GetOperStatus() != oc.PlatformTypes_COMPONENT_OPER_STATUS_UNSET { + preCompMatrix = append(preCompMatrix, preComp.GetName()+":"+preComp.GetOperStatus().String()) + } + } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { gnoiClient, err := dut.RawAPIs().BindingDUT().DialGNOI(context.Background()) @@ -136,7 +144,7 @@ func TestChassisReboot(t *testing.T) { for { time.Sleep(10 * time.Second) t.Logf("Time elapsed %.2f seconds since reboot was requested.", time.Since(start).Seconds()) - if uint64(time.Since(start).Seconds()) > rebootDelay { + if time.Since(start).Seconds() > rebootDelay { t.Logf("Time elapsed %.2f seconds > %d reboot delay", time.Since(start).Seconds(), rebootDelay) break } @@ -183,6 +191,13 @@ func TestChassisReboot(t *testing.T) { for { postRebootCompStatus := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().OperStatus().State()) + postRebootCompDebug := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) + postCompMatrix := []string{} + for _, postComp := range postRebootCompDebug { + if postComp.GetOperStatus() != oc.PlatformTypes_COMPONENT_OPER_STATUS_UNSET { + postCompMatrix = append(postCompMatrix, postComp.GetName()+":"+postComp.GetOperStatus().String()) + } + } if len(preRebootCompStatus) == len(postRebootCompStatus) { t.Logf("All components on the DUT are in responsive state") @@ -192,7 +207,10 @@ func TestChassisReboot(t *testing.T) { if uint64(time.Since(startComp).Seconds()) > maxCompWaitTime { t.Logf("DUT components status post reboot: %v", postRebootCompStatus) - t.Fatalf("All the components are not in responsive state post reboot") + if rebootDiff := cmp.Diff(preCompMatrix, postCompMatrix); rebootDiff != "" { + t.Logf("[DEBUG] Unexpected diff after reboot (-component missing from pre reboot, +component added from pre reboot): %v ", rebootDiff) + } + t.Fatalf("There's a difference in components obtained in pre reboot: %v and post reboot: %v.", len(preRebootCompStatus), len(postRebootCompStatus)) } time.Sleep(10 * time.Second) } diff --git a/feature/gnoi/system/tests/copying_debug_files_test/README.md b/feature/gnoi/system/tests/copying_debug_files_test/README.md index 20b27d7ea26..4a18174032a 100644 --- a/feature/gnoi/system/tests/copying_debug_files_test/README.md +++ b/feature/gnoi/system/tests/copying_debug_files_test/README.md @@ -20,14 +20,15 @@ * If the configuration push is successful, make a gNMI.Get() RPC call and compare the configuration received with the originally pushed configuration for a match. Test is a failure if either the gNMI.Get() operation fails or the configuration do not match with the one that was pushed * gNOI-5.3.3 - Chassis Component Health Check - * Issue Healthz Check RPC to the DUT for Chassis component to trigger the generation of Artifact ID(s) equivalent to 'show tech support'. - * Verify that the DUT returns the artifact IDs in the Check RPC's response. - * Invoke ArtifactRequest to transfer the requested Artifact ID(s). - * Verify that the DUT returns the artifacts requested. + * Issue Healthz Check RPC to the DUT for Chassis component to trigger the generation of Artifact ID(s). Artifacts returned should be sufficient for vendor tech support teams to determine if any of the field replaceable components are faulty and must be replaced for that device. + * Verify that the DUT returns the artifact IDs in the [Check RPC's](https://github.com/openconfig/gnoi/blob/main/healthz/README.md#healthzcheck) response. + * Invoke ArtifactRequest to transfer the requested Artifact ID(s) via [Artifact RPC](https://github.com/openconfig/gnoi/blob/main/healthz/README.md#healthzartifact) + * Verify that the DUT returns the artifacts requested. TODO: (https://github.com/openconfig/featureprofiles/issues/3013) Test has gap and does not retrieve artifacts. + * If ArtifactHeader is of FileArtifactType and a hash field is populated, the hash should be calculated against the Artifact body. TODO: (https://github.com/openconfig/featureprofiles/issues/3013) Test has gap and doesn't validate FileArtifactType hash against artifact body content. ## Process names by vendor * BGP Process - * ARISTA: "Bgp-main" + * ARISTA: "IpRib" * CISCO: " " * JUNIPER: "rpd" * NOKIA: "sr_bgp_mgr" @@ -39,19 +40,22 @@ * NOS implementations will need to model their agent that handles device configuration as a [" component of the type SOFTWARE_MODULE"](https://github.com/openconfig/public/blob/master/release/models/platform/openconfig-platform-types.yang#L394) and represent it under the componenets/component tree +## OpenConfig Path and RPC Coverage -## Config Parameter 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. -N/A +TODO(OCPATH): State paths (if any) -## Telemetry Parameter Coverage +```yaml +paths: -## Protocol/RPC Parameter Coverage + ## State paths -* gNOI - * System - * KillProcess - * Healthz - * Get - * Check - * Artifact +rpcs: + gnoi: + system.System.KillProcess: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + healthz.Healthz.Get: +``` 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 d21e444e733..27bbc03efb5 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 @@ -18,26 +18,31 @@ import ( "testing" "time" + comps "github.com/openconfig/featureprofiles/internal/components" + "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/system" hpb "github.com/openconfig/gnoi/healthz" spb "github.com/openconfig/gnoi/system" tpb "github.com/openconfig/gnoi/types" "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" ) var ( - bgpProcName = map[ondatra.Vendor]string{ - ondatra.NOKIA: "sr_bgp_mgr", - ondatra.ARISTA: "Bgp-main", + processName = map[ondatra.Vendor]string{ + ondatra.NOKIA: "sr_qos_mgr", + ondatra.ARISTA: "IpRib", ondatra.JUNIPER: "rpd", + ondatra.CISCO: "ifmgr", } components = map[ondatra.Vendor]string{ ondatra.ARISTA: "Chassis", - ondatra.CISCO: "Chassis", + ondatra.CISCO: "Rack 0", ondatra.JUNIPER: "CHASSIS0", ondatra.NOKIA: "Chassis", } + componentName = map[string]string{} ) func TestMain(m *testing.M) { @@ -53,8 +58,8 @@ func TestMain(m *testing.M) { // DUT // // Test notes: -//. Note: Initiating checkin to experimental -// - KillProcess system call is used to kill bgp process. +// Note: Initiating checkin to experimental +// - KillProcess system call is used to kill a process. // - The healthz call needs to be modified to reflect the right component and its path. // // - gnoi operation commands can be sent and tested using CLI command grpcurl. @@ -64,27 +69,51 @@ func TestMain(m *testing.M) { func TestCopyingDebugFiles(t *testing.T) { dut := ondatra.DUT(t, "dut") gnoiClient := dut.RawAPIs().GNOI(t) - if _, ok := bgpProcName[dut.Vendor()]; !ok { - t.Fatalf("Please add support for vendor %v in var bgpProcName", dut.Vendor()) + if _, ok := processName[dut.Vendor()]; !ok { + t.Fatalf("Please add support for vendor %v in var processName", dut.Vendor()) + } + pID := system.FindProcessIDByName(t, dut, processName[dut.Vendor()]) + if pID == 0 { + t.Fatalf("process %v not found on device", processName[dut.Vendor()]) } killProcessRequest := &spb.KillProcessRequest{ Signal: spb.KillProcessRequest_SIGNAL_KILL, - Name: bgpProcName[dut.Vendor()], - Pid: findProcessByName(context.Background(), t, dut, bgpProcName[dut.Vendor()]), + Name: processName[dut.Vendor()], + Pid: uint32(pID), Restart: true, } processKillResponse, err := gnoiClient.System().KillProcess(context.Background(), killProcessRequest) if err != nil { - t.Fatalf("Failed to restart process %v with unexpected err: %v", bgpProcName[dut.Vendor()], err) + t.Fatalf("Failed to restart process %v with unexpected err: %v", processName[dut.Vendor()], err) } t.Logf("gnoiClient.System().KillProcess() response: %v, err: %v", processKillResponse, err) t.Logf("Wait 60 seconds for process to restart ...") time.Sleep(60 * time.Second) - componentName := map[string]string{"name": components[dut.Vendor()]} + ccList := comps.FindComponentsByType(t, dut, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CONTROLLER_CARD) + t.Logf("Found CONTROLLER_CARD list: %v", ccList) + var activeCC string + if deviations.ChassisGetRPCUnsupported(dut) { + if len(ccList) < 2 { + switch dut.Vendor() { + case ondatra.CISCO: + activeCC = "0/RP0/CPU0" + } + } else { + standbyControllerName, activeControllerName := comps.FindStandbyControllerCard(t, dut, ccList) + t.Logf("Standby RP: %v, Active RP: %v", standbyControllerName, activeControllerName) + activeCC = activeControllerName + } + componentName = map[string]string{"name": activeCC + "-" + processName[dut.Vendor()]} // example: 0/RP0/CPU0-ifmgr + } else { + componentName = map[string]string{"name": components[dut.Vendor()]} + } + t.Logf("Component Name: %v", componentName) + req := &hpb.GetRequest{ Path: &tpb.Path{ + Origin: "openconfig", Elem: []*tpb.PathElem{ { Name: "components", @@ -98,23 +127,15 @@ func TestCopyingDebugFiles(t *testing.T) { } validResponse, err := gnoiClient.Healthz().Get(context.Background(), req) t.Logf("Error: %v", err) - t.Logf("Response: %v", (validResponse)) - if err != nil { - t.Fatalf("Unexpected error on healthz get response after restart of %v: %v", bgpProcName[dut.Vendor()], err) + switch dut.Vendor() { + case ondatra.ARISTA: + t.Log("Skip logging validResponse for Arista") + default: + t.Logf("Response: %v", validResponse) } -} - -// findProcessByName uses telemetry to find out the PID of a process -func findProcessByName(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, pName string) uint32 { - pList := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - var pID uint32 - for _, proc := range pList { - if proc.GetName() == pName { - pID = uint32(proc.GetPid()) - t.Logf("Pid of daemon '%s' is '%d'", pName, pID) - } + if err != nil { + t.Errorf("Unexpected error on healthz get response after restart of %v: %v", processName[dut.Vendor()], err) } - return pID } func TestChassisComponentArtifacts(t *testing.T) { @@ -124,6 +145,7 @@ 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", @@ -148,10 +170,10 @@ func TestChassisComponentArtifacts(t *testing.T) { t.Logf("Artifacts received for component %v: %v", componentName["name"], artifacts) // Fetch artifact details by executing ArtifactRequest and passing the artifact ID along. for _, artifact := range artifacts { - artId := artifact.GetId() - t.Logf("Executing ArtifactRequest for artifact ID %v", artId) + artID := artifact.GetId() + t.Logf("Executing ArtifactRequest for artifact ID %v", artID) artReq := &hpb.ArtifactRequest{ - Id: artId, + Id: artID, } // Verify that a valid response is received. artRes, err := gnoiClient.Healthz().Artifact(context.Background(), artReq) @@ -159,9 +181,9 @@ func TestChassisComponentArtifacts(t *testing.T) { t.Fatalf("Unexpected error on executing Healthz Artifact RPC: %v", err) } h1, err := artRes.Header() - t.Logf("Header of artifact %v: %v", artId, h1) + t.Logf("Header of artifact %v: %v", artID, h1) if err != nil { - t.Fatalf("Unexpected error when fetching the header of artifact %v: %v", artId, err) + t.Fatalf("Unexpected error when fetching the header of artifact %v: %v", artID, err) } } } diff --git a/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto b/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto index 4d3c878aa43..fc3f0cd55ac 100644 --- a/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto +++ b/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto @@ -5,3 +5,11 @@ uuid: "3265de19-8fb8-4b0c-8a71-a75df008aa61" plan_id: "gNOI-5.3" description: "Copying Debug Files" testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + chassis_get_rpc_unsupported: true + } +} \ No newline at end of file diff --git a/feature/gnoi/system/tests/per_component_reboot_test/README.md b/feature/gnoi/system/tests/per_component_reboot_test/README.md index e11129e95c9..6ca645a044d 100644 --- a/feature/gnoi/system/tests/per_component_reboot_test/README.md +++ b/feature/gnoi/system/tests/per_component_reboot_test/README.md @@ -14,21 +14,28 @@ Validate gNOI RPC can reboot specific components. * TODO: For each component verify that the component has rebooted and the uptime has been reset. -## Config Parameter Coverage - -N/A - -## Telemetry Parameter Coverage - -* /components/component/state/description -* /components/component/state/removable -* /components/component/state/name -* /components/component/state/oper-status -* /interfaces/interface/state/name -* /interfaces/interface/state/oper-status - -## Protocol/RPC Parameter Coverage - -* gNOI - * System - * Reboot +## 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(OCPATH): Add component names to component paths. + +```yaml +paths: + ## Config paths: N/A + + ## State paths + ### FIXME: Add components + #/components/component/state/description: + #/components/component/state/removable: + #/components/component/state/name: + #/components/component/state/oper-status: + /interfaces/interface/state/name: + /interfaces/interface/state/oper-status: + +rpcs: + gnoi: + system.System.Reboot: + system.System.RebootStatus: +``` diff --git a/feature/gnoi/system/tests/per_component_reboot_test/metadata.textproto b/feature/gnoi/system/tests/per_component_reboot_test/metadata.textproto index c47476655ef..7561b3c4b50 100644 --- a/feature/gnoi/system/tests/per_component_reboot_test/metadata.textproto +++ b/feature/gnoi/system/tests/per_component_reboot_test/metadata.textproto @@ -12,5 +12,6 @@ platform_exceptions: { deviations: { gnoi_fabric_component_reboot_unsupported: true gnoi_subcomponent_path: true + gnoi_subcomponent_reboot_status_unsupported: 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 1556a20c75a..18ccb12057d 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 @@ -95,7 +95,7 @@ func TestStandbyControllerCardReboot(t *testing.T) { t.Skipf("Not enough controller cards for the test on %v: got %v, want at least %v", dut.Model(), got, want) } - rpStandby, rpActive := components.FindStandbyRP(t, dut, controllerCards) + rpStandby, rpActive := components.FindStandbyControllerCard(t, dut, controllerCards) t.Logf("Detected rpStandby: %v, rpActive: %v", rpStandby, rpActive) gnoiClient := dut.RawAPIs().GNOI(t) @@ -197,6 +197,13 @@ func TestLinecardReboot(t *testing.T) { t.Logf("Wait for 10s to allow the sub component's reboot process to start") time.Sleep(10 * time.Second) + req := &spb.RebootStatusRequest{ + Subcomponents: rebootSubComponentRequest.GetSubcomponents(), + } + + if deviations.GNOISubcomponentRebootStatusUnsupported(dut) { + req.Subcomponents = nil + } rebootDeadline := time.Now().Add(linecardBoottime) for retry := true; retry; { t.Log("Waiting for 10 seconds before checking.") @@ -205,7 +212,7 @@ func TestLinecardReboot(t *testing.T) { retry = false break } - resp, err := gnoiClient.System().RebootStatus(context.Background(), &spb.RebootStatusRequest{}) + resp, err := gnoiClient.System().RebootStatus(context.Background(), req) switch { case status.Code(err) == codes.Unimplemented: t.Fatalf("Unimplemented RebootStatus() is not fully compliant with the Reboot spec.") @@ -278,6 +285,13 @@ func TestFabricReboot(t *testing.T) { } t.Logf("gnoiClient.System().Reboot() response: %v, err: %v", rebootResponse, err) + req := &spb.RebootStatusRequest{ + Subcomponents: rebootSubComponentRequest.GetSubcomponents(), + } + + if deviations.GNOISubcomponentRebootStatusUnsupported(dut) { + req.Subcomponents = nil + } rebootDeadline := time.Now().Add(fabricBootTime) for { t.Log("Waiting for 10 seconds before checking.") @@ -285,7 +299,7 @@ func TestFabricReboot(t *testing.T) { if time.Now().After(rebootDeadline) { break } - resp, err := gnoiClient.System().RebootStatus(context.Background(), &spb.RebootStatusRequest{}) + resp, err := gnoiClient.System().RebootStatus(context.Background(), req) if status.Code(err) == codes.Unimplemented { t.Fatalf("Unimplemented RebootStatus() is not fully compliant with the Reboot spec.") } diff --git a/feature/gnoi/system/tests/ping_test/README.md b/feature/gnoi/system/tests/ping_test/README.md index 07b4440facc..210145bacf4 100644 --- a/feature/gnoi/system/tests/ping_test/README.md +++ b/feature/gnoi/system/tests/ping_test/README.md @@ -38,3 +38,14 @@ ICMP, which is default. interface MTU of a transit router to test do_not_fragment. * TODO: verify these for vlan tagged vs untagged packets. May need +4 bytes + +## 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 +rpcs: + gnoi: + system.System.Ping: +``` + diff --git a/feature/gnoi/system/tests/ping_test/ping_test.go b/feature/gnoi/system/tests/ping_test/ping_test.go index 8feddae2b22..3be1b666930 100644 --- a/feature/gnoi/system/tests/ping_test/ping_test.go +++ b/feature/gnoi/system/tests/ping_test/ping_test.go @@ -43,7 +43,7 @@ const ( minimumMinTime = 1 minimumAvgTime = 1 minimumMaxTime = 1 - //StdDeviation would be 0 if we only send 1 ping. + // StdDeviation would be 0 if we only send 1 ping. minimumStdDev = 1 ) @@ -178,6 +178,7 @@ func TestGNOIPing(t *testing.T) { Destination: ipv4Addrs[0].GetIp(), Source: ipv4Addrs[0].GetIp(), L3Protocol: tpb.L3Protocol_IPV4, + Count: 10, }, expectedReply: commonExpectedIPv4Reply, expectedStats: commonExpectedReplyStats, @@ -469,7 +470,7 @@ func TestGNOIPing(t *testing.T) { t.Errorf("Ping MaxTime: got %v, want >= %v", summary.MaxTime, tc.expectedStats.MaxTime) } if summary.StdDev < tc.expectedStats.StdDev && !StdDevZero { - t.Errorf("Ping MaxTime: got %v, want >= %v", summary.StdDev, tc.expectedStats.StdDev) + t.Errorf("Ping Standard Deviation: got %v, want >= %v", summary.StdDev, tc.expectedStats.StdDev) } }) } diff --git a/feature/gnoi/system/tests/supervisor_switchover_test/README.md b/feature/gnoi/system/tests/supervisor_switchover_test/README.md index 23739814a48..4d76adb5352 100644 --- a/feature/gnoi/system/tests/supervisor_switchover_test/README.md +++ b/feature/gnoi/system/tests/supervisor_switchover_test/README.md @@ -13,19 +13,25 @@ Validate that the active supervisor can be switched. * Validate the standby RE/SUP becomes the active after switchover * Validate that all connected ports are re-enabled. -## Config Parameter Coverage - -N/A - -## Telemetry Parameter Coverage - -* /system/state/current-datetime -* /components/component[name=]/state/last-switchover-time -* /components/component[name=]/state/last-switchover-reason/trigger -* /components/component[name=]/state/last-switchover-reason/details - -## Protocol/RPC Parameter Coverage - -* gNOI - * System - * SwitchControlProcessor +## 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 ## + /system/state/current-datetime: + /components/component/state/last-switchover-time: + platform_type: [ "CONTROLLER_CARD" ] + /components/component/state/last-switchover-reason/trigger: + platform_type: [ "CONTROLLER_CARD" ] + /components/component/state/last-switchover-reason/details: + platform_type: [ "CONTROLLER_CARD" ] + +rpcs: + gnmi: + gNMI.Subscribe: + gnoi: + system.System.SwitchControlProcessor: +``` diff --git a/feature/gnoi/system/tests/supervisor_switchover_test/supervisor_switchover_test.go b/feature/gnoi/system/tests/supervisor_switchover_test/supervisor_switchover_test.go index 4ce309a06c4..d40c60d8f67 100644 --- a/feature/gnoi/system/tests/supervisor_switchover_test/supervisor_switchover_test.go +++ b/feature/gnoi/system/tests/supervisor_switchover_test/supervisor_switchover_test.go @@ -85,7 +85,7 @@ func TestSupervisorSwitchover(t *testing.T) { t.Skipf("Not enough controller cards for the test on %v: got %v, want at least %v", dut.Model(), got, want) } - rpStandbyBeforeSwitch, rpActiveBeforeSwitch := components.FindStandbyRP(t, dut, controllerCards) + rpStandbyBeforeSwitch, rpActiveBeforeSwitch := components.FindStandbyControllerCard(t, dut, controllerCards) t.Logf("Detected rpStandby: %v, rpActive: %v", rpStandbyBeforeSwitch, rpActiveBeforeSwitch) switchoverReady := gnmi.OC().Component(rpActiveBeforeSwitch).SwitchoverReady() @@ -150,7 +150,7 @@ func TestSupervisorSwitchover(t *testing.T) { } t.Logf("RP switchover time: %.2f seconds", time.Since(startSwitchover).Seconds()) - rpStandbyAfterSwitch, rpActiveAfterSwitch := components.FindStandbyRP(t, dut, controllerCards) + rpStandbyAfterSwitch, rpActiveAfterSwitch := components.FindStandbyControllerCard(t, dut, controllerCards) t.Logf("Found standbyRP after switchover: %v, activeRP: %v", rpStandbyAfterSwitch, rpActiveAfterSwitch) if got, want := rpActiveAfterSwitch, rpStandbyBeforeSwitch; got != want { diff --git a/feature/gnoi/system/tests/traceroute_test/README.md b/feature/gnoi/system/tests/traceroute_test/README.md index 3b58671c254..ff706426292 100644 --- a/feature/gnoi/system/tests/traceroute_test/README.md +++ b/feature/gnoi/system/tests/traceroute_test/README.md @@ -38,3 +38,16 @@ source, destination, max_ttl, do_not_fragment and L4Protocols. * ICMP * TCP * UDP + +## 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: + +rpcs: + gnoi: + system.System.Traceroute: +``` diff --git a/feature/gnpsi/otg_tests/sampling_test/README.md b/feature/gnpsi/otg_tests/sampling_test/README.md new file mode 100644 index 00000000000..46a38506db1 --- /dev/null +++ b/feature/gnpsi/otg_tests/sampling_test/README.md @@ -0,0 +1,68 @@ +# gNPSI-1: Sampling and Subscription Check + +## Summary + +The goal is to validate that packet sampling is working as expected, clients can connect to the gNPSI service and receive samples. + +## Procedure + +### Common Test Setup + * Configure DUT with two ports with IPv4/IPv6 addresses + * Configure sFlow and gNPSI on DUT with following parameters: + * Sample size = 256 bytes + * Sampling rate is 1:1M + * Configure OTG traffic with different traffic profiles. + * IPv4 and Ipv6 + * 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. + +* Verify the samples received by the client are as per expectations: + * Samples are formatted as per the sFLOW datagram specifications. + * Appropriate number of samples are received based on the sampling raste. e.g. ~1 in 1M samples is received for a sampling rate of 1:1M. + * Datagram contents are set correctly. + * Sampling rate is correct + * 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. + +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. + +* 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. + +* Start a gRPC client and subscribe to the gRPC service on the DUT, and verify the connection is healthy and samples are received. + +* Restart the gNPSI service (This can be done by a switch reboot). + +* Let the gRPC client try to reconnect to gNPSI service every few seconds. The gRPC client should successfully connect to gNPSI service after gNPSI service is up. + +* Verify that the samples are received. + + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnpsi: + gNPSI.Subscribe: +``` + +## Minimum DUT platform requirement + +N/A diff --git a/feature/gribi/feature.textproto b/feature/gribi/feature.textproto index 3fca4d03ce8..6245f883738 100644 --- a/feature/gribi/feature.textproto +++ b/feature/gribi/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/gribi/mplsutil/mplsutil.go b/feature/gribi/mplsutil/mplsutil.go index 02c0b58c0f9..5e8b8261129 100644 --- a/feature/gribi/mplsutil/mplsutil.go +++ b/feature/gribi/mplsutil/mplsutil.go @@ -377,46 +377,58 @@ const ( // type on the ATE device provided. func (g *GRIBIMPLSTest) ConfigureFlows(t *testing.T, ate *ondatra.ATEDevice) { t.Helper() - switch g.mode { - case PushToMPLS: - t.Logf("looking on interface %s_ETH for %s", ATESrc.Name, DUTSrc.IPv4) - var dstMAC string - gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().Interface(ATESrc.Name+"_ETH").Ipv4NeighborAny().LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { - dstMAC, _ = val.Val() - return val.IsPresent() - }).Await(t) - t.Logf("MAC discovered was %s", dstMAC) - - g.otgConfig.Flows().Clear().Items() - mplsFlow := g.otgConfig.Flows().Add().SetName(flowName) - mplsFlow.Metrics().SetEnable(true) - mplsFlow.TxRx().Port().SetTxName(ATESrc.Name).SetRxName(ATEDst.Name) - mplsFlow.Rate().SetChoice("pps").SetPps(1) + t.Logf("looking on interface %s_ETH for %s", ATESrc.Name, DUTSrc.IPv4) + var dstMAC string + gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().Interface(ATESrc.Name+"_ETH").Ipv4NeighborAny().LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + dstMAC, _ = val.Val() + return val.IsPresent() + }).Await(t) + t.Logf("MAC discovered was %s", dstMAC) + + g.otgConfig.Flows().Clear().Items() + flow := g.otgConfig.Flows().Add().SetName(flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Port().SetTxName(ATESrc.Name).SetRxName(ATEDst.Name) + flow.Rate().SetPps(1) + flow.Rate().SetPps(1) + switch g.mode { + case PushToMPLS: // Set up ethernet layer. - eth := mplsFlow.Packet().Add().Ethernet() - eth.Src().SetChoice("value").SetValue(ATESrc.MAC) - eth.Dst().SetChoice("value").SetValue(dstMAC) + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(ATESrc.MAC) + eth.Dst().SetValue(dstMAC) // Set up MPLS layer with destination label 100. - mpls := mplsFlow.Packet().Add().Mpls() - mpls.Label().SetChoice("value").SetValue(staticMPLSToATE) - mpls.BottomOfStack().SetChoice("value").SetValue(0) - - mplsInner := mplsFlow.Packet().Add().Mpls() - mplsInner.Label().SetChoice("value").SetValue(innerLabel) - mplsInner.BottomOfStack().SetChoice("value").SetValue(1) + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(staticMPLSToATE) + mpls.BottomOfStack().SetValue(0) + + mplsInner := flow.Packet().Add().Mpls() + mplsInner.Label().SetValue(innerLabel) + mplsInner.BottomOfStack().SetValue(1) + + ip4 := flow.Packet().Add().Ipv4() + ip4.Src().SetValue("198.18.1.1") + ip4.Dst().SetValue("198.18.2.1") + ip4.Version().SetValue(4) + case PushToIP: + // Set up ethernet layer. + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(ATESrc.MAC) + eth.Dst().SetValue(dstMAC) - ip4 := mplsFlow.Packet().Add().Ipv4() - ip4.Src().SetChoice("value").SetValue("198.18.1.1") - ip4.Dst().SetChoice("value").SetValue("198.18.2.1") - ip4.Version().SetChoice("value").SetValue(4) + ip4 := flow.Packet().Add().Ipv4() + ip4.Src().SetValue("198.18.2.0") + ip4.Dst().SetValue("198.18.1.1") + ip4.Version().SetValue(4) - ate.OTG().PushConfig(t, g.otgConfig) default: t.Fatalf("unspecified flow for test type %v", g.mode) } + + ate.OTG().PushConfig(t, g.otgConfig) } // RunFlows validates that traffic is forwarded by the DUT by running the @@ -424,7 +436,7 @@ func (g *GRIBIMPLSTest) ConfigureFlows(t *testing.T, ate *ondatra.ATEDevice) { func (g *GRIBIMPLSTest) RunFlows(t *testing.T, ate *ondatra.ATEDevice, runtime time.Duration, tolerableLostPackets uint64) { t.Helper() switch g.mode { - case PushToMPLS: + case PushToMPLS, PushToIP: t.Logf("Starting MPLS traffic...") ate.OTG().StartTraffic(t) t.Logf("Sleeping for %s...", runtime) diff --git a/feature/gribi/mplsutil/topo.go b/feature/gribi/mplsutil/topo.go index 7061e57e77a..f587c59f575 100644 --- a/feature/gribi/mplsutil/topo.go +++ b/feature/gribi/mplsutil/topo.go @@ -144,7 +144,7 @@ func configureATEInterfaces(t *testing.T, ate *ondatra.ATEDevice, srcATE, srcDUT ip6.SetAddress(p.ate.IPv6).SetGateway(p.dut.IPv6).SetPrefix(uint32(p.ate.IPv6Len)) } - c, err := topology.ToJson() + c, err := topology.Marshal().ToJson() if err != nil { return topology, err } diff --git a/feature/gribi/otg_tests/ack_in_presence_other_routes/route_ack_test.go b/feature/gribi/otg_tests/ack_in_presence_other_routes/route_ack_test.go index efe4f1fda4b..e244944b193 100644 --- a/feature/gribi/otg_tests/ack_in_presence_other_routes/route_ack_test.go +++ b/feature/gribi/otg_tests/ack_in_presence_other_routes/route_ack_test.go @@ -163,19 +163,19 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(atePort1.Name) i1 := top.Devices().Add().SetName(atePort1.Name) eth1 := i1.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i1.Name()) + eth1.Connection().SetPortName(i1.Name()) eth1.Ipv4Addresses().Add().SetName(i1.Name() + ".IPv4").SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) top.Ports().Add().SetName(atePort2.Name) i2 := top.Devices().Add().SetName(atePort2.Name) eth2 := i2.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i2.Name()) + eth2.Connection().SetPortName(i2.Name()) eth2.Ipv4Addresses().Add().SetName(i2.Name() + ".IPv4").SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) top.Ports().Add().SetName(atePort3.Name) i3 := top.Devices().Add().SetName(atePort3.Name) eth3 := i3.Ethernets().Add().SetName(atePort3.Name + ".Eth").SetMac(atePort3.MAC) - eth3.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i3.Name()) + eth3.Connection().SetPortName(i3.Name()) eth3.Ipv4Addresses().Add().SetName(i3.Name() + ".IPv4").SetAddress(atePort3.IPv4).SetGateway(dutPort3.IPv4).SetPrefix(uint32(atePort3.IPv4Len)) return top } @@ -194,10 +194,10 @@ func testTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, srcE flowipv4.TxRx().Port(). SetTxName(srcEndPoint.Name). SetRxName(dstEndPoint.Name) - flowipv4.Duration().SetChoice("continuous") + flowipv4.Duration().Continuous() e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(srcEndPoint.MAC) - e1.Dst().SetChoice("value").SetValue(dstMac) + e1.Dst().SetValue(dstMac) v4 := flowipv4.Packet().Add().Ipv4() srcIpv4 := srcEndPoint.IPv4 v4.Src().SetValue(srcIpv4) diff --git a/feature/experimental/gribi/otg_tests/backup_nhg_action/README.md b/feature/gribi/otg_tests/backup_nhg_action/README.md similarity index 95% rename from feature/experimental/gribi/otg_tests/backup_nhg_action/README.md rename to feature/gribi/otg_tests/backup_nhg_action/README.md index e371df36d38..5a99843fca5 100644 --- a/feature/experimental/gribi/otg_tests/backup_nhg_action/README.md +++ b/feature/gribi/otg_tests/backup_nhg_action/README.md @@ -94,16 +94,20 @@ Different test scenarios requires different setups. traffic with decapsulated traffic with destination IP as `InnerDstIP_1` at ATE port-4. -## Config Parameter coverage - -No new configuration covered. - -## Telemetry Parameter coverage - -No new telemetry covered. - -## Protocol/RPC Parameter coverage +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Minimum DUT platform requirement vRX if the vendor implementation supports FIB-ACK simulation, otherwise FFF. + diff --git a/feature/experimental/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go b/feature/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go similarity index 87% rename from feature/experimental/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go rename to feature/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go index d61723bc1dc..2a41403d9f7 100644 --- a/feature/experimental/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go +++ b/feature/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go @@ -1,3 +1,17 @@ +// 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 backup_nhg_action_test import ( @@ -7,18 +21,17 @@ import ( "time" "github.com/open-traffic-generator/snappi/gosnappi" - "github.com/openconfig/gribigo/fluent" - "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ygot/ygot" - "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/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" ) const ( @@ -50,12 +63,12 @@ const ( nhg103ID = 103 nh104ID = 104 nhg104ID = 104 - baseSrcFlowFilter = "0x02" // hexadecimal value of last 6 bits of src 198.51.100.2 - baseDstFlowFilter = "0x31" // hexadecimal value of first 6 bits of dst 198.51.100.1 - encapSrcFlowFilter = "0x02" // hexadecimal value of last 6 bits of src 203.0.113.2 - encapDstFlowFilter = "0x32" // hexadecimal value of first 6 bits of dst 203.0.113.1 - decapSrcFlowFliter = "0x3f" // hexadecimal value of last 6 bits of src 198.18.0.255 - decapDstFlowFliter = "0x31" // hexadecimal value of first 6 bits of dst 198.18.0.1 + baseSrcFlowFilter = "0x02" // hexadecimal value of last 5 bits of src 198.51.100.2 + baseDstFlowFilter = "0x18" // hexadecimal value of first 5 bits of dst 198.51.100.1 + encapSrcFlowFilter = "0x02" // hexadecimal value of last 5 bits of src 203.0.113.2 + encapDstFlowFilter = "0x19" // hexadecimal value of first 5 bits of dst 203.0.113.1 + decapSrcFlowFliter = "0x1f" // hexadecimal value of last 5 bits of src 198.18.0.255 + decapDstFlowFliter = "0x18" // hexadecimal value of first 5 bits of dst 198.18.0.1 ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd policyID = "match-ipip" ipOverIPProtocol = 4 @@ -183,7 +196,6 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) } } - } // addStaticRoute configures static route. @@ -228,8 +240,7 @@ func TestBackupNHGAction(t *testing.T) { configureDUT(t, dut) } - dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) - gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + fptest.ConfigureDefaultNetworkInstance(t, dut) configureNetworkInstance(t, dut) // For interface configuration, Arista prefers config Vrf first then the IP address @@ -327,23 +338,19 @@ func testBackupDecap(ctx context.Context, t *testing.T, args *testArgs) { t.Logf("Create flows with dst %s for each path", outerDstIP1) baseFlow := createFlow(t, args.ate, args.top, "BaseFlow", &atePort2) decapFlow := createFlow(t, args.ate, args.top, "DecapFlow", &atePort4) + updateFlows(t, args.ate, []gosnappi.Flow{baseFlow, decapFlow}) t.Run("ValidatePrimaryPath", func(t *testing.T) { t.Log("Validate primary path traffic recieved ate port2 and no traffic on decap flow/port4") validateTrafficFlows(t, args.ate, []gosnappi.Flow{baseFlow}, []gosnappi.Flow{decapFlow}, baseSrcFlowFilter, baseDstFlowFilter) }) t.Log("Shutdown Port2") p2 := args.dut.Port(t, "port2") - if deviations.ATEPortLinkStateOperationsUnsupported(args.ate) { - setDUTInterfaceWithState(t, args.dut, &dutPort2, p2, false) - defer setDUTInterfaceWithState(t, args.dut, &dutPort2, p2, true) - } else { - portStateAction := gosnappi.NewControlState() - linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) - args.ate.OTG().SetControlState(t, portStateAction) - // Restore port state at end of test case. - linkState.SetState(gosnappi.StatePortLinkState.UP) - defer args.ate.OTG().SetControlState(t, portStateAction) - } + portStateAction := gosnappi.NewControlState() + linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) t.Log("Capture port2 status if down") gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) @@ -425,6 +432,7 @@ func testDecapEncap(ctx context.Context, t *testing.T, args *testArgs) { baseFlow := createFlow(t, args.ate, args.top, "BaseFlow", &atePort2) encapFLow := createFlow(t, args.ate, args.top, "DecapEncapFlow", &atePort3) decapFLow := createFlow(t, args.ate, args.top, "DecapFlow", &atePort4) + updateFlows(t, args.ate, []gosnappi.Flow{baseFlow, encapFLow, decapFLow}) t.Run("ValidatePrimaryPath", func(t *testing.T) { t.Logf("Validate Primary path traffic recieved on port 2 and no traffic on other flows/ate ports") @@ -432,18 +440,12 @@ func testDecapEncap(ctx context.Context, t *testing.T, args *testArgs) { }) t.Log("Shutdown Port2") - dutP2 := args.dut.Port(t, "port2") - if deviations.ATEPortLinkStateOperationsUnsupported(args.ate) { - setDUTInterfaceWithState(t, args.dut, &dutPort2, dutP2, false) - defer setDUTInterfaceWithState(t, args.dut, &dutPort2, dutP2, true) - } else { - portStateAction := gosnappi.NewControlState() - linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) - args.ate.OTG().SetControlState(t, portStateAction) - // Restore port state at end of test case. - linkState.SetState(gosnappi.StatePortLinkState.UP) - defer args.ate.OTG().SetControlState(t, portStateAction) - } + portStateAction := gosnappi.NewControlState() + linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) t.Log("Capture port2 status if down") gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) @@ -457,18 +459,12 @@ func testDecapEncap(ctx context.Context, t *testing.T, args *testArgs) { }) t.Log("Shutdown Port3") - dutP3 := args.dut.Port(t, "port3") - if deviations.ATEPortLinkStateOperationsUnsupported(args.ate) { - setDUTInterfaceWithState(t, args.dut, &dutPort3, dutP3, false) - defer setDUTInterfaceWithState(t, args.dut, &dutPort3, dutP3, true) - } else { - portStateAction := gosnappi.NewControlState() - linkState := portStateAction.Port().Link().SetPortNames([]string{"port3"}).SetState(gosnappi.StatePortLinkState.DOWN) - args.ate.OTG().SetControlState(t, portStateAction) - // Restore port state at end of test case. - linkState.SetState(gosnappi.StatePortLinkState.UP) - defer args.ate.OTG().SetControlState(t, portStateAction) - } + portStateAction = gosnappi.NewControlState() + linkState = portStateAction.Port().Link().SetPortNames([]string{"port3"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) t.Log("Capture port3 status if down") p3 := args.dut.Port(t, "port3") @@ -499,24 +495,29 @@ func createFlow(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, name flow.EgressPacket().Add().Ethernet() ipTracking := flow.EgressPacket().Add().Ipv4() ipSrcTracking := ipTracking.Src().MetricTags().Add() - ipSrcTracking.SetName(srcTrackingName).SetOffset(26).SetLength(6) + ipSrcTracking.SetName(srcTrackingName).SetOffset(27).SetLength(5) ipDstTracking := ipTracking.Dst().MetricTags().Add() - ipDstTracking.SetName(dstTrackingName).SetOffset(0).SetLength(6) + ipDstTracking.SetName(dstTrackingName).SetOffset(0).SetLength(5) return flow } -// TODO: Egress Tracking to verify the correctness of packet after decap or encap needs to be added -// validateTrafficFlows verifies that the flow on ATE, traffic should pass for good flow and fail for bad flow. -func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good []gosnappi.Flow, bad []gosnappi.Flow, srcFlowFilter string, dstFlowFilter string) { +func updateFlows(t *testing.T, ate *ondatra.ATEDevice, flows []gosnappi.Flow) { top := ate.OTG().FetchConfig(t) top.Flows().Clear() - for _, flow := range append(good, bad...) { + for _, flow := range flows { top.Flows().Append(flow) } ate.OTG().PushConfig(t, top) ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") +} + +// TODO: Egress Tracking to verify the correctness of packet after decap or encap needs to be added +// validateTrafficFlows verifies that the flow on ATE, traffic should pass for good flow and fail for bad flow. +func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good []gosnappi.Flow, bad []gosnappi.Flow, srcFlowFilter string, dstFlowFilter string) { + top := ate.OTG().FetchConfig(t) ate.OTG().StartTraffic(t) time.Sleep(15 * time.Second) @@ -557,7 +558,7 @@ func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good []gosnappi. if got := ets[0].GetCounters().GetInPkts(); got != uint64(inPkts) { t.Errorf("EgressTracking counter in-pkts got %d, want %d", got, uint64(inPkts)) } else { - t.Logf("Received %d packets with %s as the last 6 bits of the src IP and %s as first 6 bits of dst IP ", got, srcFlowFilter, dstFlowFilter) + t.Logf("Received %d packets with %s as the last 5 bits of the src IP and %s as first 5 bits of dst IP ", got, srcFlowFilter, dstFlowFilter) } } @@ -572,13 +573,3 @@ func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good []gosnappi. } } } - -// setDUTInterfaceState sets the admin state on the dut interface -func setDUTInterfaceWithState(t testing.TB, dut *ondatra.DUTDevice, dutPort *attrs.Attributes, p *ondatra.Port, state bool) { - dc := gnmi.OC() - i := &oc.Interface{} - i.Enabled = ygot.Bool(state) - i.Type = ethernetCsmacd - i.Name = ygot.String(p.Name()) - gnmi.Update(t, dut, dc.Interface(p.Name()).Config(), i) -} diff --git a/feature/experimental/gribi/otg_tests/backup_nhg_action/metadata.textproto b/feature/gribi/otg_tests/backup_nhg_action/metadata.textproto similarity index 92% rename from feature/experimental/gribi/otg_tests/backup_nhg_action/metadata.textproto rename to feature/gribi/otg_tests/backup_nhg_action/metadata.textproto index 1edee8c3184..81bfb47ea92 100644 --- a/feature/experimental/gribi/otg_tests/backup_nhg_action/metadata.textproto +++ b/feature/gribi/otg_tests/backup_nhg_action/metadata.textproto @@ -11,6 +11,7 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + interface_ref_interface_id_format: true } } platform_exceptions: { @@ -36,3 +37,4 @@ platform_exceptions: { default_network_instance: "default" } } +tags: TAGS_TRANSIT diff --git a/feature/gribi/otg_tests/backup_nhg_action_pbf/README.md b/feature/gribi/otg_tests/backup_nhg_action_pbf/README.md new file mode 100644 index 00000000000..b816261f0c6 --- /dev/null +++ b/feature/gribi/otg_tests/backup_nhg_action_pbf/README.md @@ -0,0 +1,146 @@ +# TE-11.31: Backup NHG: Actions with PBF + +## Summary + +Validate gRIBI Backup NHG actions with PBF. + +## Topology + +* Connect ATE port-1 to DUT port-1, ATE port-2 to DUT port-2, ATE port-3 to + DUT port-3, and ATE port-4 to DUT port-4. +* Create a 3 non-default VRF: + * `VRF-A` that includes DUT port-1. + * `VRF-B` that includes no interface. + * `VRF-C` that includes no interface. +* `OuterDstIP_1`, `OuterSrcIP_1`, `OuterDstIP_2`, `OuterSrcIP_2`: IPinIP outer + IP addresses. +* `InnerDstIP_1`, `InnerSrcIP_1`: IPinIP inner IP addresses. +* `VIP_1`, `VIP_2`: IP addresses used for recursive resolution. +* `ATEPort2IP`: testbed assigned interface IP to ATE port 2 +* `ATEPort3IP`: testbed assigned interface IP to ATE port 3 +* All the NHG and NH objects injected by gRIBI are in the DEFAULT VRF. +* Add an empty decap VRF, `DECAP_TE_VRF`. +* Add 4 empty encap VRFs, `ENCAP_TE_VRF_A`, `ENCAP_TE_VRF_B`, `ENCAP_TE_VRF_C` + and `ENCAP_TE_VRF_D`. +* Replace the existing VRF selection policy with `vrf_selection_policy_w` as + in + +## Setups + +Different test scenarios requires different setups. + +* Setup#1 + + * Make sure DUT port-2, port-3 and port-4 are up. + * Make sure there is a static route in the default VRF for `InnerDstIP_1`, + pointing to ATE port-4. + * Connect a gRIBI client to DUT with session parameters + `{persistence=PRESERVE Redundancy=SINGLE_PRIMARY}` + * gRIBI Flush the DUT. + * Inject the following gRIBI structure to the DUT: + + ```text + VIP_1/32 {DEFAULT VRF} --> NHG#1 --> NH#1 {next-hop: ATEPort2IP} + NHG#100 --> NH#100 {decap, network-instance:DEFAULT} + NHG#101 --> [NH#101 {next-hop: VIP1}, backupNHG: NHG#100] + OuterDstIP_1/32 {VRF-A} --> NHG#101 + ``` + +* Setup#2 + + * Make sure DUT port-2, port-3 and port-4 are up. + * Make sure there is a static route in the default VRF for `InnerDstIP_1`, + pointing to ATE port-4. + * Connect a gRIBI client to DUT with session parameters + `{persistence=PRESERVE Redundancy=SINGLE_PRIMARY}` + * gRIBI Flush the DUT. + * Inject the following gRIBI structure to the DUT: + + ```text + VIP_1/32 {DEFAULT VRF} --> NHG#1 --> NH#1 {next-hop: ATEPort2IP} + VIP_2/32 {DEFAULT VRF} --> NHG#2 --> NH#2 {next-hop: ATEPort3IP} + + NHG#100 --> NH#100 {network-instance:VRF-B} + NHG#101 --> [NH#101 {next-hop: VIP1}, backupNHG: NHG#100] + OuterDstIP_1/32 {VRF-A} --> NHG#101 + + NHG#103 --> NH#103 {decap, network-instance:DEFAULT} + NHG#102 --> [NH#102 {decap-encap, src:`OuterSrcIP_2`, dst:`OuterDstIP_2`, network-instance: VRF-C}, backupNHG: NHG#103] + OuterDstIP_1/32 {VRF-B} --> NHG#102 + + NHG#104 --> [NH#104 {next-hop: VIP-2}, backupNHG: NHG#103] + OuterDstIP_2/32 {VRF-C} --> NHG#104 + ``` + +## Procedure + +* TEST#1 - (next-hop viability triggers decap in backup NHG): + + 1. Deploy Setup#1 as above. + + 2. Send IPinIP traffic to `OuterDstIP_1` with inner IP as `InnerDstIP_1`, + and validate that ATE port-2 receives the IPinIP traffic. + + * Shutdown DUT port-2 interface, and validate that ATE port-4 receives the + decapsulated traffic with `InnerDstIP_1`. + +* Test#2 - (tunnel viability triggers decap and encap in the backup NHG): + + * Deploy Setup#2 as above. + + * Send IPinIP traffic to `OuterDstIP_1`. Validate that ATE port-2 receives + the IPinIP traffic with outer IP as `OuterDstIP_1`. + + * Shutdown DUT port-2 interface, and validate that ATE port-3 receives the + IPinIP traffic with outer destination IP as `OuterDstIP_2`, and outer + source IP as `OuterSrcIP_2` + + * Shutdown DUT port-3 interface, and validate that ATE port-4 receives the + traffic with decapsulated traffic with destination IP as `InnerDstIP_1` + at ATE port-4. + +* Test#3 - (tunnel viability triggers decap): + + * Deploy Setup#2 as above and inject below gRIBI structure to the DUT: + + ```text + NHG#102 --> [NH#104 {decap-encap, src:OuterSrcIP_2, dst:OuterDstIP_FAILURE, network-instance: VRF-C}, backupNHG: NHG#103] + ``` + + * Send IPinIP traffic to `OuterDstIP_1`. Validate that ATE port-2 receives + the IPinIP traffic with outer IP as `OuterDstIP_1`. + + * Shutdown DUT port-2 interface, and validate that ATE port-4 receives the + traffic with decapsulated traffic with destination IP as `InnerDstIP_1` + at ATE port-4. + +* Test#4 - (resolution failure on new tunnels triggers decap in the backup NHG): + + * Deploy Setup#2 as above. + + * Remove `OuterDstIP_2/32` from `VRF-C`. + + * Send IPinIP traffic to `OuterDstIP_1`. Validate that ATE port-2 receives + the IPinIP traffic with outer IP as `OuterDstIP_1`. + + * Shutdown DUT port-2 interface, and validate that ATE port-4 receives the + traffic with decapsulated traffic with destination IP as `InnerDstIP_1` + at ATE port-4. + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Minimum DUT platform requirement + +vRX if the vendor implementation supports FIB-ACK simulation, otherwise FFF. + diff --git a/feature/gribi/otg_tests/backup_nhg_action_pbf/backup_nhg_action_pbf_test.go b/feature/gribi/otg_tests/backup_nhg_action_pbf/backup_nhg_action_pbf_test.go new file mode 100644 index 00000000000..acb2cd0e964 --- /dev/null +++ b/feature/gribi/otg_tests/backup_nhg_action_pbf/backup_nhg_action_pbf_test.go @@ -0,0 +1,780 @@ +// 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 backup_nhg_action_pbf_test + +import ( + "context" + "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" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + "github.com/openconfig/gribigo/client" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + mask = "32" + outerDstIP1 = "198.51.100.1" + outerDstIP2 = "203.0.113.1" + outerSrcIP2 = "203.0.113.2" + innerDstIP1 = "198.18.0.1" + innerSrcIP1 = "198.18.0.255" + outerDstIPFAILURE = "204.0.113.1" + vip1 = "198.18.1.1" + vip2 = "198.18.1.2" + vrfA = "TE_VRF_111" + vrfB = "VRF-B" + vrfC = "VRF-C" + nh1ID = 1 + nhg1ID = 1 + nh2ID = 2 + nhg2ID = 2 + nh100ID = 100 + nhg100ID = 100 + nh101ID = 101 + nhg101ID = 101 + nh102ID = 102 + nhg102ID = 102 + nh103ID = 103 + nhg103ID = 103 + nh104ID = 104 + nhg104ID = 104 + baseSrcFlowFilter = "0x0f" // hexadecimal value of last 5 bits of src 198.51.100.111 + baseDstFlowFilter = "0x18" // hexadecimal value of first 5 bits of dst 198.51.100.1 + encapSrcFlowFilter = "0x02" // hexadecimal value of last 5 bits of src 203.0.113.2 + encapDstFlowFilter = "0x19" // hexadecimal value of first 5 bits of dst 203.0.113.1 + decapSrcFlowFliter = "0x1f" // hexadecimal value of last 5 bits of src 198.18.0.255 + decapDstFlowFliter = "0x18" // hexadecimal value of first 5 bits of dst 198.18.0.1 + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + policyID = "match-ipip" + ipOverIPProtocol = 4 + srcTrackingName = "ipSrcTracking" + dstTrackingName = "ipDstTracking" + decapFlowSrc = "198.51.100.111" + dscpEncapA1 = 10 +) + +// testArgs holds the objects needed by a test case. +type testArgs struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + ctx context.Context + client *gribi.Client +} + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:1", + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + 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, + } + + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:9", + IPv6Len: ipv6PrefixLen, + } + + atePort3 = attrs.Attributes{ + Name: "atePort3", + MAC: "02:00:03:01:01:01", + IPv4: "192.0.2.10", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:a", + IPv6Len: ipv6PrefixLen, + } + + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:d", + IPv6Len: ipv6PrefixLen, + } + + atePort4 = attrs.Attributes{ + Name: "atePort4", + MAC: "02:00:04:01:01:01", + IPv4: "192.0.2.14", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:e", + IPv6Len: ipv6PrefixLen, + } + atePorts = map[string]attrs.Attributes{ + "port1": atePort1, + "port2": atePort2, + "port3": atePort3, + "port4": atePort4, + } + dutPorts = map[string]attrs.Attributes{ + "port1": dutPort1, + "port2": dutPort2, + "port3": dutPort3, + "port4": dutPort4, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// configureATE configures ports on the ATE. +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + top := gosnappi.NewConfig() + for p, ap := range atePorts { + p1 := ate.Port(t, p) + dp := dutPorts[p] + ap.AddToOTG(top, p1, &dp) + } + return top +} + +// configureDUT configures port1, port2, port3, port4 on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + for p, dp := range dutPorts { + p1 := dut.Port(t, p) + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), dp.NewOCInterface(p1.Name(), dut)) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p1) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) && p != "port1" { + fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + } +} + +// addStaticRoute configures static route. +func addStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + s := &oc.Root{} + static := s.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + ipv4Nh := static.GetOrCreateStatic(innerDstIP1 + "/" + mask).GetOrCreateNextHop("0") + ipv4Nh.NextHop, _ = ipv4Nh.To_NetworkInstance_Protocol_Static_NextHop_NextHop_Union(atePort4.IPv4) + gnmi.Update(t, dut, d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) +} + +// configureNetworkInstance configures vrfs vrfA,vrfB,vrfC and adds port1 to vrfA +func configureNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { + c := &oc.Root{} + vrfs := []string{vrfA, vrfB, vrfC} + for _, vrf := range vrfs { + ni := c.GetOrCreateNetworkInstance(vrf) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), ni) + } +} + +// TE11.3 backup nhg action tests. +func TestBackupNHGAction(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + + // Configure ATE + ate := ondatra.ATE(t, "ate") + top := configureATE(t, ate) + ate.OTG().PushConfig(t, top) + + // Configure DUT + if !deviations.InterfaceConfigVRFBeforeAddress(dut) { + configureDUT(t, dut) + } + + fptest.ConfigureDefaultNetworkInstance(t, dut) + configureNetworkInstance(t, dut) + + // For interface configuration, Arista prefers config Vrf first then the IP address + if deviations.InterfaceConfigVRFBeforeAddress(dut) { + configureDUT(t, dut) + } + if deviations.BackupNHGRequiresVrfWithDecap(dut) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(vrfA) + pf := ni.GetOrCreatePolicyForwarding() + fp1 := pf.GetOrCreatePolicy(policyID) + fp1.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + fp1.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipOverIPProtocol) + fp1.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(vrfA) + p1 := dut.Port(t, "port1") + intf := pf.GetOrCreateInterface(p1.Name()) + intf.ApplyVrfSelectionPolicy = ygot.String(policyID) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrfA).PolicyForwarding().Config(), pf) + } + + addStaticRoute(t, dut) + + ate.OTG().StartProtocols(t) + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyW) + + test := []struct { + name string + desc string + fn func(ctx context.Context, t *testing.T, args *testArgs) + }{ + { + name: "testBackupDecap", + desc: "Usecase with 2 NHOP Groups - Backup Pointing to Decap", + fn: testBackupDecap, + }, + { + name: "testDecapEncap", + desc: "Usecase with 3 NHOP Groups - Redirect pointing to back up DecapEncap and its Backup Pointing to Decap", + fn: testDecapEncap, + }, + { + name: "testDecap", + desc: "Usecase with 3 NHOP Groups - DecapEncap pointing to fake destination IP that won't resolve and the backup is decap", + fn: testDecap, + }, + { + name: "testDecapBackupNHG", + desc: "Usecase with 3 NHOP Groups - Resolution failure on new tunnels triggers decap in the backup NHG", + fn: testDecapBackupNHG, + }, + } + // Configure the gRIBI client + client := gribi.Client{ + DUT: dut, + FIBACK: true, + Persistence: true, + } + defer client.Close(t) + defer client.FlushAll(t) + if err := client.Start(t); err != nil { + t.Fatalf("gRIBI Connection can not be established") + } + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + t.Logf("Name: %s", tt.name) + t.Logf("Description: %s", tt.desc) + // Flush past entries before running the tc + client.BecomeLeader(t) + client.FlushAll(t) + tcArgs := &testArgs{ + ctx: ctx, + client: &client, + dut: dut, + ate: ate, + top: top, + } + tt.fn(ctx, t, tcArgs) + }) + } +} + +// TE11.3 - case 1: next-hop viability triggers decap in backup NHG. +func testBackupDecap(ctx context.Context, t *testing.T, args *testArgs) { + if deviations.SkipPbfWithDecapEncapVrf(args.dut) { + t.Skip("Skipping test as PBF with decap encap vrf is not supported") + } + + t.Logf("Adding VIP %v/32 with NHG %d NH %d and atePort2 via gRIBI", vip1, nhg1ID, nh1ID) + nh, nhOpResult := gribi.NHEntry(nh1ID, atePort2.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult := gribi.NHGEntry(nhg1ID, map[uint64]uint64{nh1ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + args.client.AddIPv4(t, vip1+"/"+mask, nhg1ID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d with NH %d as decap and DEFAULT vrf lookup via gRIBI", nhg100ID, nh100ID) + nh, nhOpResult = gribi.NHEntry(nh100ID, "Decap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, + &gribi.NHOptions{VrfName: deviations.DefaultNetworkInstance(args.dut)}) + nhg, nhgOpResult = gribi.NHGEntry(nhg100ID, map[uint64]uint64{nhg100ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d and NH as %v and backup NHG %d via gRIBI", nhg101ID, nh101ID, vip1, nhg100ID) + nh, nhOpResult = gribi.NHEntry(nh101ID, vip1, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg101ID, map[uint64]uint64{nh101ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg100ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + t.Logf("Adding an IPv4Entry for %s in VRF %s with primary atePort2, backup as Decap via gRIBI", outerDstIP1, vrfA) + args.client.AddIPv4(t, outerDstIP1+"/"+mask, nhg101ID, vrfA, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Create flows with dst %s for each path", outerDstIP1) + baseFlow := createFlow(t, args.ate, args.top, "BaseFlow", &atePort2) + decapFlow := createFlow(t, args.ate, args.top, "DecapFlow", &atePort4) + updateFlows(t, args.ate, []gosnappi.Flow{baseFlow, decapFlow}) + t.Run("ValidatePrimaryPath", func(t *testing.T) { + t.Log("Validate primary path traffic recieved ate port2 and no traffic on decap flow/port4") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{baseFlow}, []gosnappi.Flow{decapFlow}, baseSrcFlowFilter, baseDstFlowFilter) + }) + t.Log("Shutdown Port2") + p2 := args.dut.Port(t, "port2") + portStateAction := gosnappi.NewControlState() + linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) + + t.Log("Capture port2 status if down") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_DOWN; operStatus != want { + t.Errorf("Get(DUT port2 oper status): got %v, want %v", operStatus, want) + } + t.Run("ValidateDecapPath", func(t *testing.T) { + t.Log("Validate Decap traffic recieved port 4 and no traffic on primary flow/port 2") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{decapFlow}, []gosnappi.Flow{baseFlow}, decapSrcFlowFliter, decapDstFlowFliter) + }) +} + +// TE11.3 - case 2: new tunnel viability triggers decap in the backup NHG. +func testDecapEncap(ctx context.Context, t *testing.T, args *testArgs) { + if deviations.SkipPbfWithDecapEncapVrf(args.dut) { + t.Skip("Skipping test as PBF with decap encap vrf is not supported") + } + + t.Logf("Adding VIP1 %v/32 with NHG %d NH %d and atePort2 via gRIBI", vip1, nhg1ID, nh1ID) + nh, nhOpResult := gribi.NHEntry(nh1ID, atePort2.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult := gribi.NHGEntry(nhg1ID, map[uint64]uint64{nh1ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + args.client.AddIPv4(t, vip1+"/"+mask, nhg1ID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding VIP2 %v/32 with NHG %d , NH %d and atePort3 via gRIBI", vip2, nhg2ID, nh2ID) + nh, nhOpResult = gribi.NHEntry(nh2ID, atePort3.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg2ID, map[uint64]uint64{nh2ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + args.client.AddIPv4(t, vip2+"/"+mask, nhg2ID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d with NH %d as redirect to vrfB via gRIBI", nhg100ID, nh100ID) + nh, nhOpResult = gribi.NHEntry(nh100ID, "VRFOnly", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: vrfB}) + nhg, nhgOpResult = gribi.NHGEntry(nhg100ID, map[uint64]uint64{nh100ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d with %v and backup NHG %d via gRIBI", nhg101ID, nh101ID, vip1, nhg100ID) + nh, nhOpResult = gribi.NHEntry(nh101ID, vip1, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg101ID, map[uint64]uint64{nh101ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg100ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in VRF %s with primary VIP1, backup as VRF %s via gRIBI", outerDstIP1, vrfA, vrfB) + args.client.AddIPv4(t, outerDstIP1+"/"+mask, nhg101ID, vrfA, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d with NH %d as decap and DEFAULT vrf lookup via gRIBI", nhg103ID, nh103ID) + nh, nhOpResult = gribi.NHEntry(nh103ID, "Decap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: deviations.DefaultNetworkInstance(args.dut)}) + nhg, nhgOpResult = gribi.NHGEntry(nhg103ID, map[uint64]uint64{nh103ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d and NH as %v and backup NHG %d via gRIBI", nhg104ID, nh104ID, vip2, nhg103ID) + nh, nhOpResult = gribi.NHEntry(nh104ID, vip2, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg104ID, map[uint64]uint64{nh104ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg103ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in vrf %s with NHG %d via gRIBI", outerDstIP2, vrfC, nhg104ID) + args.client.AddIPv4(t, outerDstIP2+"/"+mask, nhg104ID, vrfC, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d NH %d and NH as decap and encap with destination vrf as %v and backup NHG %d via gRIBI", nhg102ID, nh102ID, vrfC, nhg103ID) + nh, nhOpResult = gribi.NHEntry(nh102ID, "DecapEncap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Src: outerSrcIP2, Dest: outerDstIP2, VrfName: vrfC}) + nhg, nhgOpResult = gribi.NHGEntry(nhg102ID, map[uint64]uint64{nh102ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg103ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in vrf %s with decap and encap destiantion in %s via gRIBI", outerDstIP1, vrfB, vrfC) + args.client.AddIPv4(t, outerDstIP1+"/"+mask, nhg102ID, vrfB, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + p2 := args.dut.Port(t, "port2") + t.Log("Capture port2 status if Up") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_UP) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_UP; operStatus != want { + t.Errorf("Get(DUT port2 oper status): got %v, want %v", operStatus, want) + } + + t.Logf("Create flows with dst %s", outerDstIP1) + baseFlow := createFlow(t, args.ate, args.top, "BaseFlow", &atePort2) + encapFlow := createFlow(t, args.ate, args.top, "DecapEncapFlow", &atePort3) + decapFlow := createFlow(t, args.ate, args.top, "DecapFlow", &atePort4) + updateFlows(t, args.ate, []gosnappi.Flow{baseFlow, encapFlow, decapFlow}) + + t.Run("ValidatePrimaryPath", func(t *testing.T) { + t.Logf("Validate Primary path traffic recieved on port 2 and no traffic on other flows/ate ports") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{baseFlow}, []gosnappi.Flow{encapFlow, decapFlow}, baseSrcFlowFilter, baseDstFlowFilter) + }) + + t.Log("Shutdown Port2") + portStateAction := gosnappi.NewControlState() + linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) + + t.Log("Capture port2 status if down") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) + operStatusAfterShut := gnmi.Get(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_DOWN; operStatusAfterShut != want { + t.Errorf("Get(DUT port2 oper status): got %v, want %v", operStatus, want) + } + t.Run("ValidateDecapEncapPath", func(t *testing.T) { + t.Log("Validate traffic with encap header recieved on port 3 and no traffic on other flows/ate ports") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{encapFlow}, []gosnappi.Flow{baseFlow, decapFlow}, encapSrcFlowFilter, encapDstFlowFilter) + }) + + t.Log("Shutdown Port3") + portStateAction = gosnappi.NewControlState() + linkState = portStateAction.Port().Link().SetPortNames([]string{"port3"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) + + t.Log("Capture port3 status if down") + p3 := args.dut.Port(t, "port3") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p3.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) + operStatusAfterShut = gnmi.Get(t, args.dut, gnmi.OC().Interface(p3.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_DOWN; operStatusAfterShut != want { + t.Errorf("Get(DUT port3 oper status): got %v, want %v", operStatus, want) + } + t.Run("ValidateDecapPath", func(t *testing.T) { + t.Log("Validate traffic after decap is recieved on port4 and no traffic on other flows/ate ports") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{decapFlow}, []gosnappi.Flow{baseFlow, encapFlow}, decapSrcFlowFliter, decapDstFlowFliter) + }) +} + +// TE11.3 - case 3: tunnel viability triggers decap. +func testDecap(ctx context.Context, t *testing.T, args *testArgs) { + if deviations.SkipPbfWithDecapEncapVrf(args.dut) { + t.Skip("Skipping test as PBF with decap encap vrf is not supported") + } + + t.Logf("Adding VIP1 %v/32 with NHG %d NH %d and atePort2 via gRIBI", vip1, nhg1ID, nh1ID) + nh, nhOpResult := gribi.NHEntry(nh1ID, atePort2.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult := gribi.NHGEntry(nhg1ID, map[uint64]uint64{nh1ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + args.client.AddIPv4(t, vip1+"/"+mask, nhg1ID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding VIP2 %v/32 with NHG %d , NH %d and atePort3 via gRIBI", vip2, nhg2ID, nh2ID) + nh, nhOpResult = gribi.NHEntry(nh2ID, atePort3.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg2ID, map[uint64]uint64{nh2ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + args.client.AddIPv4(t, vip2+"/"+mask, nhg2ID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d with NH %d as redirect to vrfB via gRIBI", nhg100ID, nh100ID) + nh, nhOpResult = gribi.NHEntry(nh100ID, "VRFOnly", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: vrfB}) + nhg, nhgOpResult = gribi.NHGEntry(nhg100ID, map[uint64]uint64{nh100ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d with %v and backup NHG %d via gRIBI", nhg101ID, nh101ID, vip1, nhg100ID) + nh, nhOpResult = gribi.NHEntry(nh101ID, vip1, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg101ID, map[uint64]uint64{nh101ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg100ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in VRF %s with primary VIP1, backup as VRF %s via gRIBI", outerDstIP1, vrfA, vrfB) + args.client.AddIPv4(t, outerDstIP1+"/"+mask, nhg101ID, vrfA, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d with NH %d as decap and DEFAULT vrf lookup via gRIBI", nhg103ID, nh103ID) + nh, nhOpResult = gribi.NHEntry(nh103ID, "Decap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: deviations.DefaultNetworkInstance(args.dut)}) + nhg, nhgOpResult = gribi.NHGEntry(nhg103ID, map[uint64]uint64{nh103ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d and NH as %v and backup NHG %d via gRIBI", nhg104ID, nh104ID, vip2, nhg103ID) + nh, nhOpResult = gribi.NHEntry(nh104ID, "DecapEncap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Src: outerSrcIP2, Dest: outerDstIPFAILURE, VrfName: vrfC}) + nhg, nhgOpResult = gribi.NHGEntry(nhg104ID, map[uint64]uint64{nh104ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg103ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in vrf %s with NHG %d via gRIBI", outerDstIP2, vrfC, nhg104ID) + args.client.AddIPv4(t, outerDstIP2+"/"+mask, nhg104ID, vrfC, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d NH %d and NH as decap and encap with destination vrf as %v and backup NHG %d via gRIBI", nhg102ID, nh104ID, vrfC, nhg103ID) + nhg, nhgOpResult = gribi.NHGEntry(nhg102ID, map[uint64]uint64{nh104ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg103ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in vrf %s with decap and encap destiantion in %s via gRIBI", outerDstIP1, vrfB, vrfC) + args.client.AddIPv4(t, outerDstIP1+"/"+mask, nhg102ID, vrfB, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + p2 := args.dut.Port(t, "port2") + t.Log("Capture port2 status if Up") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_UP) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_UP; operStatus != want { + t.Errorf("Get(DUT port2 oper status): got %v, want %v", operStatus, want) + } + + t.Logf("Create flows with dst %s", outerDstIP1) + baseFlow := createFlow(t, args.ate, args.top, "BaseFlow", &atePort2) + decapFlow := createFlow(t, args.ate, args.top, "DecapFlow", &atePort4) + updateFlows(t, args.ate, []gosnappi.Flow{baseFlow, decapFlow}) + + t.Run("ValidatePrimaryPath", func(t *testing.T) { + t.Logf("Validate Primary path traffic recieved on port 2 and no traffic on other flows/ate ports") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{baseFlow}, []gosnappi.Flow{decapFlow}, baseSrcFlowFilter, baseDstFlowFilter) + }) + + t.Log("Shutdown Port2") + portStateAction := gosnappi.NewControlState() + linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) + + t.Log("Capture port2 status if down") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) + operStatusAfterShut := gnmi.Get(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_DOWN; operStatusAfterShut != want { + t.Errorf("Get(DUT port2 oper status): got %v, want %v", operStatus, want) + } + t.Run("ValidateDecapPath", func(t *testing.T) { + t.Log("Validate traffic after decap is recieved on port4 and no traffic on other flows/ate ports") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{decapFlow}, []gosnappi.Flow{baseFlow}, decapSrcFlowFliter, decapDstFlowFliter) + }) +} + +// TE11.3 - case 4: resolution failure on new tunnels triggers decap in the backup NHG. +func testDecapBackupNHG(ctx context.Context, t *testing.T, args *testArgs) { + if deviations.SkipPbfWithDecapEncapVrf(args.dut) { + t.Skip("Skipping test as PBF with decap encap vrf is not supported") + } + + t.Logf("Adding VIP1 %v/32 with NHG %d NH %d and atePort2 via gRIBI", vip1, nhg1ID, nh1ID) + nh, nhOpResult := gribi.NHEntry(nh1ID, atePort2.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult := gribi.NHGEntry(nhg1ID, map[uint64]uint64{nh1ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + args.client.AddIPv4(t, vip1+"/"+mask, nhg1ID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding VIP2 %v/32 with NHG %d , NH %d and atePort3 via gRIBI", vip2, nhg2ID, nh2ID) + nh, nhOpResult = gribi.NHEntry(nh2ID, atePort3.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg2ID, map[uint64]uint64{nh2ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + args.client.AddIPv4(t, vip2+"/"+mask, nhg2ID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d with NH %d as redirect to vrfB via gRIBI", nhg100ID, nh100ID) + nh, nhOpResult = gribi.NHEntry(nh100ID, "VRFOnly", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: vrfB}) + nhg, nhgOpResult = gribi.NHGEntry(nhg100ID, map[uint64]uint64{nh100ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d with %v and backup NHG %d via gRIBI", nhg101ID, nh101ID, vip1, nhg100ID) + nh, nhOpResult = gribi.NHEntry(nh101ID, vip1, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg101ID, map[uint64]uint64{nh101ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg100ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in VRF %s with primary VIP1, backup as VRF %s via gRIBI", outerDstIP1, vrfA, vrfB) + args.client.AddIPv4(t, outerDstIP1+"/"+mask, nhg101ID, vrfA, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding NHG %d with NH %d as decap and DEFAULT vrf lookup via gRIBI", nhg103ID, nh103ID) + nh, nhOpResult = gribi.NHEntry(nh103ID, "Decap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: deviations.DefaultNetworkInstance(args.dut)}) + nhg, nhgOpResult = gribi.NHGEntry(nhg103ID, map[uint64]uint64{nh103ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d and NH as %v and backup NHG %d via gRIBI", nhg104ID, nh104ID, vip2, nhg103ID) + nh, nhOpResult = gribi.NHEntry(nh104ID, vip2, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, nhgOpResult = gribi.NHGEntry(nhg104ID, map[uint64]uint64{nh104ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg103ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding NHG %d NH %d and NH as decap and encap with destination vrf as %v and backup NHG %d via gRIBI", nhg102ID, nh102ID, vrfC, nhg103ID) + nh, nhOpResult = gribi.NHEntry(nh102ID, "DecapEncap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Src: outerSrcIP2, Dest: outerDstIP2, VrfName: vrfC}) + nhg, nhgOpResult = gribi.NHGEntry(nhg102ID, map[uint64]uint64{nh102ID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg103ID}) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, + []*client.OpResult{nhOpResult, nhgOpResult}) + + t.Logf("Adding an IPv4Entry for %s in vrf %s with decap and encap destiantion in %s via gRIBI", outerDstIP1, vrfB, vrfC) + args.client.AddIPv4(t, outerDstIP1+"/"+mask, nhg102ID, vrfB, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + p2 := args.dut.Port(t, "port2") + t.Log("Capture port2 status if Up") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_UP) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_UP; operStatus != want { + t.Errorf("Get(DUT port2 oper status): got %v, want %v", operStatus, want) + } + + t.Logf("Create flows with dst %s", outerDstIP1) + baseFlow := createFlow(t, args.ate, args.top, "BaseFlow", &atePort2) + decapFlow := createFlow(t, args.ate, args.top, "DecapFlow", &atePort4) + updateFlows(t, args.ate, []gosnappi.Flow{baseFlow, decapFlow}) + + t.Run("ValidatePrimaryPath", func(t *testing.T) { + t.Logf("Validate Primary path traffic recieved on port 2 and no traffic on other flows/ate ports") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{baseFlow}, []gosnappi.Flow{decapFlow}, baseSrcFlowFilter, baseDstFlowFilter) + }) + + t.Log("Shutdown Port2") + portStateAction := gosnappi.NewControlState() + linkState := portStateAction.Port().Link().SetPortNames([]string{"port2"}).SetState(gosnappi.StatePortLinkState.DOWN) + args.ate.OTG().SetControlState(t, portStateAction) + // Restore port state at end of test case. + linkState.SetState(gosnappi.StatePortLinkState.UP) + defer args.ate.OTG().SetControlState(t, portStateAction) + + t.Log("Capture port2 status if down") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) + operStatusAfterShut := gnmi.Get(t, args.dut, gnmi.OC().Interface(p2.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_DOWN; operStatusAfterShut != want { + t.Errorf("Get(DUT port2 oper status): got %v, want %v", operStatus, want) + } + t.Run("ValidateDecapPath", func(t *testing.T) { + t.Log("Validate traffic after decap is recieved on port4 and no traffic on other flows/ate ports") + validateTrafficFlows(t, args.ate, []gosnappi.Flow{decapFlow}, []gosnappi.Flow{baseFlow}, decapSrcFlowFliter, decapDstFlowFliter) + }) +} + +// createFlow returns a flow name from atePort1 to the dstPfx. +func createFlow(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, name string, dst *attrs.Attributes) gosnappi.Flow { + flow := gosnappi.NewFlow().SetName(name) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv4"}).SetRxNames([]string{dst.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + outerIPHeader := flow.Packet().Add().Ipv4() + outerIPHeader.Src().SetValue(decapFlowSrc) + outerIPHeader.Dst().Increment().SetStart(outerDstIP1).SetCount(1) + outerIPHeader.Priority().Dscp().Phb().SetValues([]uint32{dscpEncapA1}) + innerIPHeader := flow.Packet().Add().Ipv4() + innerIPHeader.Src().SetValue(innerSrcIP1) + innerIPHeader.Dst().Increment().SetStart(innerDstIP1).SetCount(1) + flow.EgressPacket().Add().Ethernet() + ipTracking := flow.EgressPacket().Add().Ipv4() + ipSrcTracking := ipTracking.Src().MetricTags().Add() + ipSrcTracking.SetName(srcTrackingName).SetOffset(27).SetLength(5) + ipDstTracking := ipTracking.Dst().MetricTags().Add() + ipDstTracking.SetName(dstTrackingName).SetOffset(0).SetLength(5) + + return flow +} + +func updateFlows(t *testing.T, ate *ondatra.ATEDevice, flows []gosnappi.Flow) { + top := ate.OTG().FetchConfig(t) + 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") +} + +// TODO: Egress Tracking to verify the correctness of packet after decap or encap needs to be added +// validateTrafficFlows verifies that the flow on ATE, traffic should pass for good flow and fail for bad flow. +func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good []gosnappi.Flow, bad []gosnappi.Flow, srcFlowFilter string, dstFlowFilter string) { + top := ate.OTG().FetchConfig(t) + ate.OTG().StartTraffic(t) + + time.Sleep(15 * time.Second) + ate.OTG().StopTraffic(t) + time.Sleep(10 * time.Second) + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + for _, flow := range good { + 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 for flow %s is 0, want > 0", flow) + } + if got := ((outPkts - inPkts) * 100) / outPkts; got > 0 { + t.Fatalf("LossPct for flow %s: got %v, want 0", flow.Name(), got) + } + etPath := gnmi.OTG().Flow(flow.Name()).TaggedMetricAny() + ets := gnmi.GetAll(t, ate.OTG(), etPath.State()) + if got := len(ets); got != 1 { + t.Errorf("EgressTracking got %d items, want %d", got, 1) + return + } + for _, et := range ets { + tags := et.Tags + for _, tag := range tags { + if tag.GetTagName() == srcTrackingName { + if got := tag.GetTagValue().GetValueAsHex(); !strings.EqualFold(got, srcFlowFilter) { + t.Errorf("EgressTracking filter got %q, want %q", got, srcFlowFilter) + } + } + if tag.GetTagName() == dstTrackingName { + if got := tag.GetTagValue().GetValueAsHex(); !strings.EqualFold(got, dstFlowFilter) { + t.Errorf("EgressTracking filter got %q, want %q", got, dstFlowFilter) + } + } + } + } + if got := ets[0].GetCounters().GetInPkts(); got != uint64(inPkts) { + t.Errorf("EgressTracking counter in-pkts got %d, want %d", got, uint64(inPkts)) + } else { + t.Logf("Received %d packets with %s as the last 5 bits of the src IP and %s as first 5 bits of dst IP ", got, srcFlowFilter, dstFlowFilter) + } + } + + for _, flow := range bad { + 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 for flow %s is 0, want > 0", flow) + } + if got := ((outPkts - inPkts) * 100) / outPkts; got < 100 { + t.Fatalf("LossPct for flow %s: got %v, want 100", flow.Name(), got) + } + } +} diff --git a/feature/gribi/otg_tests/backup_nhg_action_pbf/metadata.textproto b/feature/gribi/otg_tests/backup_nhg_action_pbf/metadata.textproto new file mode 100644 index 00000000000..f7a83ce9600 --- /dev/null +++ b/feature/gribi/otg_tests/backup_nhg_action_pbf/metadata.textproto @@ -0,0 +1,42 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "e4ec3769-86eb-41c8-8600-f8835cdcf0a6" +plan_id: "TE-11.31" +description: "Backup NHG: Actions with PBF" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + static_protocol_name: "static" + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + static_protocol_name: "STATIC" + interface_config_vrf_before_address: true + interface_enabled: true + default_network_instance: "default" + } +} +tags: TAGS_DATACENTER_EDGE diff --git a/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/README.md b/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/README.md new file mode 100644 index 00000000000..eeda23362c3 --- /dev/null +++ b/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/README.md @@ -0,0 +1,50 @@ +# TE-11.21: Backup NHG: Multiple NH with PBF + +## Summary + +Ensure that backup NHGs are honoured with NextHopGroup entries containing >1 NH. + +## Procedure + +* Connect ATE port-1 to DUT port-1, ATE port-2 to DUT port-2, ATE port-3 to + DUT port-3, and ATE port-4 to DUT port-4. +* Create a L3 routing instance (VRF-A), and assign DUT port-1 to VRF-A. +* Create a L3 routing instance (VRF-B) that includes no interface. +* Connect a gRIBI client to the DUT, make it become leader and inject the + following: + * An IPv4Entry in VRF-A, pointing to a NextHopGroup (in DEFAULT VRF) + containing: + * Two primary next-hops: + * IP of ATE port-2 + * IP of ATE port-3 + * A backup NHG containing a single next-hop pointing to VRF-B. + * The same IPv4Entry but in VRF-B, pointing to a NextHopGroup (in DEFAULT + VRF) containing a primary next-hop to the IP of ATE port-4. +* Add an empty decap VRF, `DECAP_TE_VRF`. +* Add 4 empty encap VRFs, `ENCAP_TE_VRF_A`, `ENCAP_TE_VRF_B`, `ENCAP_TE_VRF_C` + and `ENCAP_TE_VRF_D`. +* Replace the existing VRF selection policy with `vrf_selection_policy_w` as + in +* Ensure that traffic forwarded to the destination is received at ATE port-2 + and port-3. Validate that AFT telemetry covers this case. +* Disable ATE port-2. Ensure that traffic for the destination is received at + ATE port-3. +* Disable ATE port-3. Ensure that traffic for the destination is received at + ATE port-4. + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Minimum DUT platform requirement + +vRX 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 new file mode 100644 index 00000000000..68e2fedaf56 --- /dev/null +++ b/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/backup_nhg_multiple_nh_pbf_test.go @@ -0,0 +1,487 @@ +// 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 backup_nhg_multiple_nh_pbf_test + +import ( + "context" + "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/otgutils" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + "github.com/openconfig/gribigo/client" + "github.com/openconfig/gribigo/fluent" + "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 ( + pktDropTolerance = 10 + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + dstPfx = "203.0.113.1/32" + dstPfxMin = "203.0.113.1" + dstPfxMax = "203.0.113.254" + ipOverIPProtocol = 4 + routeCount = 1 + vrf1 = "TE_VRF_111" + vrf2 = "vrfB" + fps = 1000000 // traffic frames per second + switchovertime = 250.0 // switchovertime during interface shut in milliseconds + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + decapFlowSrc = "198.51.100.111" + dscpEncapA1 = 10 +) + +// testArgs holds the objects needed by a test case. +type testArgs struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + ctx context.Context + client *gribi.Client +} + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:1", + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + 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, + } + + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:9", + IPv6Len: ipv6PrefixLen, + } + + atePort3 = attrs.Attributes{ + Name: "atePort3", + MAC: "02:00:03:01:01:01", + IPv4: "192.0.2.10", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:a", + IPv6Len: ipv6PrefixLen, + } + + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:D", + IPv6Len: ipv6PrefixLen, + } + + atePort4 = attrs.Attributes{ + Name: "atePort4", + MAC: "02:00:04:01:01:01", + IPv4: "192.0.2.14", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:E", + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// configureATE configures ports on the ATE. +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + top := gosnappi.NewConfig() + + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + p3 := ate.Port(t, "port3") + p4 := ate.Port(t, "port4") + + atePort1.AddToOTG(top, p1, &dutPort1) + atePort2.AddToOTG(top, p2, &dutPort2) + atePort3.AddToOTG(top, p3, &dutPort3) + atePort4.AddToOTG(top, p4, &dutPort4) + + return top +} + +// Configure Network instance +func configNetworkInstance(t *testing.T, dut *ondatra.DUTDevice, vrfname string) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(vrfname) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrfname).Config(), ni) +} + +// configInterfaceDUT configures the interface +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 + return i +} + +// configureDUT configures port1, port2, port3 and port4 on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + + p1 := dut.Port(t, "port1") + // create VRF "vrfA" and assign incoming port under it + i1 := &oc.Interface{Name: ygot.String(p1.Name())} + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutPort1, dut)) + // create VRF "vrfA" + configNetworkInstance(t, dut, vrf1) + // create VRF "vrfB" + configNetworkInstance(t, dut, vrf2) + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.BackupNHGRequiresVrfWithDecap(dut) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(vrf1) + pf := ni.GetOrCreatePolicyForwarding() + fp1 := pf.GetOrCreatePolicy("match-ipip") + fp1.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + fp1.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipOverIPProtocol) + fp1.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(vrf1) + p1 := dut.Port(t, "port1") + intf := pf.GetOrCreateInterface(p1.Name()) + intf.ApplyVrfSelectionPolicy = ygot.String("match-ipip") + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf1).PolicyForwarding().Config(), pf) + } + + gnmi.Update(t, dut, d.Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + + p2 := dut.Port(t, "port2") + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + + p3 := dut.Port(t, "port3") + gnmi.Replace(t, dut, d.Interface(p3.Name()).Config(), dutPort3.NewOCInterface(p3.Name(), dut)) + + p4 := dut.Port(t, "port4") + gnmi.Replace(t, dut, d.Interface(p4.Name()).Config(), dutPort4.NewOCInterface(p4.Name(), 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, 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) + } +} + +func TestBackup(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + + // Configure ATE + ate := ondatra.ATE(t, "ate") + top := configureATE(t, ate) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + + // configure DUT + configureDUT(t, dut) + + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + // Configure the gRIBI client clientA + client := gribi.Client{ + DUT: dut, + FIBACK: true, + Persistence: true, + } + defer client.Close(t) + + // Flush all entries after the test + defer client.FlushAll(t) + + if err := client.Start(t); err != nil { + t.Fatalf("gRIBI Connection can not be established") + } + + // Make client leader + client.BecomeLeader(t) + + // Flush past entries before running the tc + client.FlushAll(t) + + if deviations.SkipPbfWithDecapEncapVrf(dut) { + t.Skip("Skipping test as PBF with decap / encap vrf is not supported") + } + t.Logf("Name: IPv4BackUpSwitchWithVrfPolicyW") + t.Logf("Description: Set primary and backup path with gribi and shutdown the primary path validating traffic switching over backup path with vrf policy W") + + tcArgs := &testArgs{ + ctx: ctx, + client: &client, + dut: dut, + ate: ate, + top: top, + } + + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyW) + tcArgs.testIPv4BackUpSwitch(t) +} + +// testIPv4BackUpSwitch Ensure that backup NHGs are honoured with NextHopGroup entries containing >1 NH +// +// Setup Steps +// - Connect ATE port-1 to DUT port-1. +// - Connect ATE port-2 to DUT port-2. +// - Connect ATE port-3 to DUT port-3. +// - Connect ATE port-4 to DUT port-4. +// - Create a L3 routing instance (vrfA), and assign DUT port-1 to vrfA. +// - Create a L3 routing instance (vrfB) that includes no interface. +// - Connect a gRIBI client to the DUT, make it become leader and inject the following: +// - An IPv4Entry in VRF-A, pointing to a NextHopGroup (in DEFAULT VRF) containing: +// - Two primary next-hops: +// - IP of ATE port-2 +// - IP of ATE port-3 +// - A backup NHG containing a single next-hop pointing to VRF-B. +// - The same IPv4Entry but in VRF-B, pointing to a NextHopGroup (in DEFAULT VRF) containing a primary next-hop to the IP of ATE port-4. +// - Ensure that traffic forwarded to a destination is received at ATE port-2 and port-3. Validate that AFT telemetry covers this case. +// - Disable ATE port-2. Ensure that traffic for a destination is received at ATE port-3. +// - Disable ATE port-3. Ensure that traffic for a destination is received at ATE port-4. +// +// Validation Steps +// - Verify AFT telemetry after shutting each port +// - Verify traffic switches to the right ports +func (a *testArgs) testIPv4BackUpSwitch(t *testing.T) { + + const ( + // Next hop group adjacency identifier. + nhgid1, nhgid2 uint64 = 100, 200 + // Backup next hop group ID that the dstPfx will forward to. + backupnhgid uint64 = 500 + + nhid1, nhid2, nhid3, nhid4 uint64 = 1001, 1002, 1003, 1004 + ) + + t.Logf("Program a backup pointing to vrfB via gRIBI") + nh3, op1 := gribi.NHEntry(nhid3, "VRFOnly", deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: vrf2}) + if deviations.BackupNHGRequiresVrfWithDecap(a.dut) { + nh3, op1 = gribi.NHEntry(nhid3, "Decap", deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: vrf2}) + } + bkupNHG, op2 := gribi.NHGEntry(backupnhgid, map[uint64]uint64{nhid3: 10}, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB) + a.client.AddEntries(t, []fluent.GRIBIEntry{nh3, bkupNHG}, []*client.OpResult{op1, op2}) + + t.Logf("an IPv4Entry for %s in %s pointing to ATE port-2 and port-3 via gRIBI", dstPfx, vrf1) + nh1, op3 := gribi.NHEntry(nhid1, atePort2.IPv4, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB) + nh2, op4 := gribi.NHEntry(nhid2, atePort3.IPv4, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB) + nhg1, op5 := gribi.NHGEntry(nhgid1, map[uint64]uint64{nhid1: 80, nhid2: 20}, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: backupnhgid}) + a.client.AddEntries(t, []fluent.GRIBIEntry{nh1, nh2, nhg1}, []*client.OpResult{op3, op4, op5}) + a.client.AddIPv4(t, dstPfx, nhgid1, vrf1, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB) + + t.Logf("an IPv4Entry for %s in %s pointing to ATE port-4 via gRIBI", dstPfx, vrf2) + nh4, op6 := gribi.NHEntry(nhid4, atePort4.IPv4, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB) + nhg2, op7 := gribi.NHGEntry(nhgid2, map[uint64]uint64{nhid4: 100}, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB) + a.client.AddEntries(t, []fluent.GRIBIEntry{nh4, nhg2}, []*client.OpResult{op6, op7}) + a.client.AddIPv4(t, dstPfx, nhgid2, vrf2, deviations.DefaultNetworkInstance(a.dut), fluent.InstalledInFIB) + + // validate programming using AFT + // TODO: add checks for NHs when AFT OC schema concludes how viability should be indicated. + a.aftCheck(t, dstPfx, vrf2) + + // create flow + dstMac := gnmi.Get(t, a.ate.OTG(), gnmi.OTG().Interface(atePort1.Name+".Eth").Ipv4Neighbor(dutPort1.IPv4).LinkLayerAddress().State()) + baseFlow := a.createFlow(t, "baseFlow", dstMac) + + // Validate traffic over primary path port2, port3 + t.Run("Baseline (port2 + port3)", func(t *testing.T) { + t.Logf("Validate traffic over primary path port2, port3") + a.validateTrafficFlows(t, baseFlow, []*ondatra.Port{a.ate.Port(t, "port2"), a.ate.Port(t, "port3")}) + }) + + // shutdown port2 + t.Run("Baseline (port3 only)", func(t *testing.T) { + t.Logf("Shutdown port 2 and validate traffic switching over port3 primary path") + a.validateTrafficFlows(t, baseFlow, []*ondatra.Port{a.ate.Port(t, "port3")}, "port2") + }) + + // TODO: add checks for NHs when AFT OC schema concludes how viability should be indicated. + + // shutdown port3 + t.Run("Backup (port4)", func(t *testing.T) { + t.Logf("Shutdown port 3 and validate traffic switching over port4 backup path") + a.validateTrafficFlows(t, baseFlow, []*ondatra.Port{a.ate.Port(t, "port4")}, "port3") + }) + if deviations.ATEPortLinkStateOperationsUnsupported(a.ate) { + defer a.flapinterface(t, "port2", true) + defer a.flapinterface(t, "port3", true) + } else { + portStateAction := gosnappi.NewControlState() + portStateAction.Port().Link().SetPortNames([]string{"port2", "port3"}).SetState(gosnappi.StatePortLinkState.UP) + defer a.ate.OTG().SetControlState(t, portStateAction) + } + // TODO: add checks for NHs when AFT OC schema concludes how viability should be indicated. +} + +// createFlow returns a flow from atePort1 to the dstPfx +func (a *testArgs) createFlow(t *testing.T, name, dstMac string) string { + a.top.Flows().Clear() + flow := a.top.Flows().Add().SetName(name) + flow.Metrics().SetEnable(true) + flow.Size().SetFixed(300) + e1 := flow.Packet().Add().Ethernet() + e1.Src().SetValue(atePort1.MAC) + flow.TxRx().Port().SetTxName("port1").SetRxNames([]string{"port2", "port3", "port4"}) + flow.Rate().SetPps(fps) + e1.Dst().SetValue(dstMac) + v4 := flow.Packet().Add().Ipv4() + v4.Src().Increment().SetStart(decapFlowSrc) + v4.Priority().Dscp().Phb().SetValues([]uint32{dscpEncapA1}) + v4.Dst().Increment().SetStart(dstPfxMin).SetCount(routeCount) + + // use ip over ip packets since some vendors only support decap for backup + v4 = flow.Packet().Add().Ipv4() + v4.Src().Increment().SetStart(dutPort1.IPv4) + v4.Dst().Increment().SetStart(dstPfxMin).SetCount(routeCount) + + // StartProtocols required for running on hardware + a.ate.OTG().PushConfig(t, a.top) + a.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, a.ate.OTG(), a.top, "IPv4") + return name +} + +// validateTrafficFlows verifies that the flow on ATE and check interface counters on DUT +func (a *testArgs) validateTrafficFlows(t *testing.T, flow string, outPorts []*ondatra.Port, shutPorts ...string) { + a.ate.OTG().StartTraffic(t) + // Shutdown interface if provided while traffic is flowing and validate traffic + time.Sleep(30 * time.Second) + for _, port := range shutPorts { + if deviations.ATEPortLinkStateOperationsUnsupported(a.ate) { + a.flapinterface(t, port, false) + } else { + portStateAction := gosnappi.NewControlState() + portStateAction.Port().Link().SetPortNames([]string{port}).SetState(gosnappi.StatePortLinkState.DOWN) + a.ate.OTG().SetControlState(t, portStateAction) + } + gnmi.Await(t, a.dut, gnmi.OC().Interface(a.dut.Port(t, port).Name()).OperStatus().State(), 2*time.Minute, oc.Interface_OperStatus_DOWN) + } + time.Sleep(30 * time.Second) + a.ate.OTG().StopTraffic(t) + time.Sleep(10 * time.Second) + otgutils.LogPortMetrics(t, a.ate.OTG(), a.top) + otgutils.LogFlowMetrics(t, a.ate.OTG(), a.top) + + // Get send and receive traffic + flowMetrics := gnmi.Get(t, a.ate.OTG(), gnmi.OTG().Flow(flow).State()) + sentPkts := uint64(flowMetrics.GetCounters().GetOutPkts()) + receivedPkts := uint64(flowMetrics.GetCounters().GetInPkts()) + + if sentPkts == 0 { + t.Fatalf("Tx packets should be higher than 0") + } + + // Check if traffic restores with in expected time in milliseconds during interface shut + // else if there is no interface trigger, validate received packets (control+data) are more than send packets + t.Logf("Sent Packets: %v, Received packets: %v", sentPkts, receivedPkts) + diff := pktDiff(sentPkts, receivedPkts) + if len(shutPorts) > 0 { + // Time took for traffic to restore in milliseconds after trigger + fpm := (diff / (fps / 1000)) + if fpm > switchovertime { + t.Errorf("Traffic loss %v msecs more than expected %v msecs", fpm, switchovertime) + } + t.Logf("Traffic loss during path change : %v msecs", fpm) + } else if diff > pktDropTolerance { + t.Error("Traffic didn't switch to the expected outgoing port") + } +} + +func pktDiff(sent, recveived uint64) uint64 { + if sent > recveived { + return sent - recveived + } + return recveived - sent +} + +// flapinterface shut/unshut interface, action true bringsup the interface and false brings it down +func (a *testArgs) flapinterface(t *testing.T, port string, action bool) { + // Currently, setting the OTG port down has no effect on kne and thus the corresponding dut port will be used + dutP := a.dut.Port(t, port) + dc := gnmi.OC() + i := &oc.Interface{} + i.Enabled = ygot.Bool(action) + i.Name = ygot.String(dutP.Name()) + i.Type = ethernetCsmacd + gnmi.Update(t, a.dut, dc.Interface(dutP.Name()).Config(), i) +} + +// aftCheck does ipv4, NHG and NH aft check +// TODO: add checks for NHs when AFT OC schema concludes how viability should be indicated. + +func (a *testArgs) aftCheck(t testing.TB, prefix string, instance string) { + // check prefix and get NHG ID + aftPfxPath := gnmi.OC().NetworkInstance(instance).Afts().Ipv4Entry(prefix) + aftPfxVal, found := gnmi.Watch(t, a.dut, aftPfxPath.State(), 2*time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + ipv4Entry, present := val.Val() + return present && ipv4Entry.NextHopGroup != nil + }).Await(t) + if !found { + t.Fatalf("Could not find prefix %s in telemetry AFT", dstPfx) + } + aftPfx, _ := aftPfxVal.Val() + + // using NHG ID validate NH + aftNHG := gnmi.Get(t, a.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(a.dut)).Afts().NextHopGroup(aftPfx.GetNextHopGroup()).State()) + if len(aftNHG.NextHop) == 0 && aftNHG.BackupNextHopGroup == nil { + t.Fatalf("Prefix %s references a NHG that has neither NH or backup NHG", prefix) + } +} diff --git a/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/metadata.textproto b/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/metadata.textproto new file mode 100644 index 00000000000..9c71cbd5fc6 --- /dev/null +++ b/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/metadata.textproto @@ -0,0 +1,41 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "20184b9e-0cb6-42ec-a280-ec157acaa832" +plan_id: "TE-11.21" +description: "Backup NHG: Multiple NH with PBF" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + 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" + interface_config_vrf_before_address: true + backup_nhg_requires_vrf_with_decap: true + } +} +tags: TAGS_DATACENTER_EDGE diff --git a/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/README.md b/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/README.md index 648f70c85cb..f9faa11d5bc 100644 --- a/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/README.md +++ b/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/README.md @@ -10,17 +10,21 @@ Ensure that backup NHGs are honoured with NextHopGroup entries containing >1 NH. DUT port-3, and ATE port-4 to DUT port-4. * Create a L3 routing instance (VRF-A), and assign DUT port-1 to VRF-A. * Create a L3 routing instance (VRF-B) that includes no interface. -* Connect a gRIBI client to the DUT, make it become leader and inject the +* TODO: Create a L3 routing instance (VRF-C) that includes no interface. +* TODO: Connect a gRIBI client to the DUT, make it become leader and inject the following: - * An IPv4Entry in VRF-A, pointing to a NextHopGroup (in DEFAULT VRF) + * An IPv4Entry in VRF-A for IP-1, pointing to a NextHopGroup (in DEFAULT VRF) containing: * Two primary next-hops: * IP of ATE port-2 * IP of ATE port-3 - * A backup NHG containing a single next-hop pointing to VRF-B. - * The same IPv4Entry but in VRF-B, pointing to a NextHopGroup (in DEFAULT - VRF) containing a primary next-hop to the IP of ATE port-4. -* Ensure that traffic forwarded to the destination is received at ATE port-2 + * An IPv4Entry VRF-B for IP-1, pointing to a NextHopGroup (in + DEFAULT VRF) containing a primary next-hop that + decaps-and-reencaps traffic to IP-2 and redirects to VRF-C. + * An IPv4Entry for IP-2 in VRF-C, pointing to a NextHopGroup (in DEFAULT VRF) + containing: + * One primary next-hop pointing to IP of ATE port-4 +* TODO: Ensure that traffic with IP-1 as an outer IP (and an inner packet) is received at ATE port-2 and port-3. Validate that AFT telemetry covers this case. * Disable ATE port-2. Ensure that traffic for the destination is received at ATE port-3. @@ -46,3 +50,4 @@ Ensure that backup NHGs are honoured with NextHopGroup entries containing >1 NH. ## Minimum DUT platform requirement vRX + diff --git a/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/backup_nhg_multiple_nh_test.go b/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/backup_nhg_multiple_nh_test.go index 9c70313b01f..f8fb0a32c43 100644 --- a/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/backup_nhg_multiple_nh_test.go +++ b/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/backup_nhg_multiple_nh_test.go @@ -20,19 +20,18 @@ import ( "time" "github.com/open-traffic-generator/snappi/gosnappi" - "github.com/openconfig/gribigo/client" - "github.com/openconfig/gribigo/fluent" - "github.com/openconfig/ondatra" - "github.com/openconfig/ygot/ygot" - "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/fluent" + "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 ( @@ -198,6 +197,7 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { configNetworkInstanceInterface(t, dut, vrf1, p1.Name(), uint32(0)) // create VRF "vrfB" configNetworkInstance(t, dut, vrf2) + fptest.ConfigureDefaultNetworkInstance(t, dut) if deviations.BackupNHGRequiresVrfWithDecap(dut) { d := &oc.Root{} @@ -253,40 +253,39 @@ func TestBackup(t *testing.T) { otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") - t.Run("IPv4BackUpSwitch", func(t *testing.T) { - t.Logf("Name: IPv4BackUpSwitch") - t.Logf("Description: Set primary and backup path with gribi and shutdown the primary path validating traffic switching over backup path") + // Configure the gRIBI client clientA + client := gribi.Client{ + DUT: dut, + FIBACK: true, + Persistence: true, + } + defer client.Close(t) - // Configure the gRIBI client clientA - client := gribi.Client{ - DUT: dut, - FIBACK: true, - Persistence: true, - } - defer client.Close(t) + // Flush all entries after the test + defer client.FlushAll(t) - // Flush all entries after the test - defer client.FlushAll(t) + if err := client.Start(t); err != nil { + t.Fatalf("gRIBI Connection can not be established") + } - if err := client.Start(t); err != nil { - t.Fatalf("gRIBI Connection can not be established") - } + // Make client leader + client.BecomeLeader(t) - // Make client leader - client.BecomeLeader(t) + // Flush past entries before running the tc + client.FlushAll(t) - // Flush past entries before running the tc - client.FlushAll(t) + t.Logf("Name: IPv4BackUpSwitch") + t.Logf("Description: Set primary and backup path with gribi and shutdown the primary path validating traffic switching over backup path") - tcArgs := &testArgs{ - ctx: ctx, - dut: dut, - client: &client, - ate: ate, - top: top, - } - tcArgs.testIPv4BackUpSwitch(t) - }) + tcArgs := &testArgs{ + ctx: ctx, + client: &client, + dut: dut, + ate: ate, + top: top, + } + + tcArgs.testIPv4BackUpSwitch(t) } // testIPv4BackUpSwitch Ensure that backup NHGs are honoured with NextHopGroup entries containing >1 NH @@ -384,7 +383,7 @@ func (a *testArgs) testIPv4BackUpSwitch(t *testing.T) { // createFlow returns a flow from atePort1 to the dstPfx func (a *testArgs) createFlow(t *testing.T, name, dstMac string) string { - + a.top.Flows().Clear() flow := a.top.Flows().Add().SetName(name) flow.Metrics().SetEnable(true) flow.Size().SetFixed(300) @@ -392,7 +391,7 @@ func (a *testArgs) createFlow(t *testing.T, name, dstMac string) string { e1.Src().SetValue(atePort1.MAC) flow.TxRx().Port().SetTxName("port1").SetRxNames([]string{"port2", "port3", "port4"}) flow.Rate().SetPps(fps) - e1.Dst().SetChoice("value").SetValue(dstMac) + e1.Dst().SetValue(dstMac) v4 := flow.Packet().Add().Ipv4() v4.Src().Increment().SetStart(dutPort1.IPv4) v4.Dst().Increment().SetStart(dstPfxMin).SetCount(routeCount) diff --git a/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/metadata.textproto b/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/metadata.textproto index 3c7995c0edb..07ec0e6a61e 100644 --- a/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/metadata.textproto +++ b/feature/gribi/otg_tests/backup_nhg_multiple_nh_test/metadata.textproto @@ -11,6 +11,7 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + interface_ref_interface_id_format: true } } platform_exceptions: { @@ -35,3 +36,4 @@ platform_exceptions: { backup_nhg_requires_vrf_with_decap: true } } +tags: TAGS_TRANSIT diff --git a/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/README.md b/feature/gribi/otg_tests/backup_nhg_single_nh_test/README.md similarity index 82% rename from feature/experimental/backup_nhg/otg_tests/backup_nhg_test/README.md rename to feature/gribi/otg_tests/backup_nhg_single_nh_test/README.md index bf5f86fcab1..0d940b7de61 100644 --- a/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/README.md +++ b/feature/gribi/otg_tests/backup_nhg_single_nh_test/README.md @@ -31,21 +31,18 @@ containing a single NH. * Interface DUT port-2 is disabled. * Remove the entry for 192.0.2.254/32. -## Config Parameter coverage - -No new configuration covered. - -## Telemetry Parameter coverage - -No new telemetry covered. - -## Protocol/RPC Parameter coverage - -* gRIBI - * Modify - * ModifyRequest - * NextHopGroup - * backup_nexthop_group +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/backup_nhg_test.go b/feature/gribi/otg_tests/backup_nhg_single_nh_test/backup_nhg_test.go similarity index 95% rename from feature/experimental/backup_nhg/otg_tests/backup_nhg_test/backup_nhg_test.go rename to feature/gribi/otg_tests/backup_nhg_single_nh_test/backup_nhg_test.go index 5903ebf83d9..e80bd9718f5 100644 --- a/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/backup_nhg_test.go +++ b/feature/gribi/otg_tests/backup_nhg_single_nh_test/backup_nhg_test.go @@ -397,12 +397,22 @@ func (a *testArgs) validateAftTelemetry(t *testing.T, vrfName, prefix, ipAddress t.Fatalf("Could not find prefix %s in telemetry AFT", prefix+"/"+mask) } aftPfx, _ := aftPfxVal.Val() - - aftNHG := gnmi.Get(t, a.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(a.dut)).Afts().NextHopGroup(aftPfx.GetNextHopGroup()).State()) - if got := len(aftNHG.NextHop); got != 1 { - t.Fatalf("Prefix %s next-hop entry count: got %d, want 1", prefix+"/"+mask, got) + _, found = gnmi.Watch(t, a.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(a.dut)).Afts().NextHopGroup(aftPfx.GetNextHopGroup()).State(), + 2*time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_NextHopGroup]) bool { + value, present := val.Val() + return present && len(value.NextHop) == 1 + }).Await(t) + if !found { + t.Fatalf("nexthop entry count mismatch for prefix %s", prefix+"/"+mask) } + /* + aftNHG := gnmi.Get(t, a.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(a.dut)).Afts().NextHopGroup(aftPfx.GetNextHopGroup()).State()) + if got := len(aftNHG.NextHop); got != 1 { + t.Fatalf("Prefix %s next-hop entry count: got %d, want 1", prefix+"/"+mask, got) + } + */ + aftNHG := gnmi.Get(t, a.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(a.dut)).Afts().NextHopGroup(aftPfx.GetNextHopGroup()).State()) for k := range aftNHG.NextHop { aftnh := gnmi.Get(t, a.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(a.dut)).Afts().NextHop(k).State()) // Handle the cases where the device returns the indirect NH or the recursively resolved NH. diff --git a/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/metadata.textproto b/feature/gribi/otg_tests/backup_nhg_single_nh_test/metadata.textproto similarity index 97% rename from feature/experimental/backup_nhg/otg_tests/backup_nhg_test/metadata.textproto rename to feature/gribi/otg_tests/backup_nhg_single_nh_test/metadata.textproto index 5fdde1062f3..b650a1c2ebc 100644 --- a/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/metadata.textproto +++ b/feature/gribi/otg_tests/backup_nhg_single_nh_test/metadata.textproto @@ -34,3 +34,4 @@ platform_exceptions: { interface_config_vrf_before_address: true } } +tags: TAGS_TRANSIT diff --git a/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md b/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md index 14063735a87..b0d5b53d498 100644 --- a/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md +++ b/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md @@ -6,6 +6,8 @@ Validate NHG update in hierarchical resolution scenario ## Procedure +### Validate NHG update in hierarchical resolution scenario + * Connect ATE port-1 to DUT port-1, ATE port-2 to DUT port-2, ATE port-3 to DUT port-3. * Create a non-default VRF (VRF-1) that includes DUT port-1. @@ -13,114 +15,116 @@ Validate NHG update in hierarchical resolution scenario * Use Modify RPC to install entries per the following order, and ensure FIB ACK is received for each of the AFTOperation: * Add 203.0.113.1/32 (default VRF) to NextHopGroup (NHG#42 in default VRF) - containing one NextHop (NH#40 in default VRF) that specifies DUT port-2 as + containing one NextHop (NH#40 in default VRF) that specifies DUT port-2 + as the egress interface and 00:1A:11:00:1A:BC as the destination MAC + address. + * Add 198.51.100.0/24 (VRF-1) to NextHopGroup (NHG#44 in default VRF) + containing one NextHop (NH#43 in default VRF) specified to be + 203.0.113.1/32 in the default VRF. +* Ensure that ATE port-2 receives the packets with 00:1A:11:00:1A:BC as the + destination MAC address. +* Use the Modify RPC with ADD operation to test NHG implicit in-place replace + (step by step as below): + 1. Add a new NH (NH#41) with egress interface that specifies DUT port-3 as the egress interface and 00:1A:11:00:1A:BC as the destination MAC address. - * Add 198.51.100.0/24 (VRF-1) to NextHopGroup (NHG#44 in default VRF) containing one - NextHop (NH#43 in default VRF) specified to be 203.0.113.1/32 in the default VRF. -* Ensure that ATE port-2 receives the packets with 00:1A:11:00:1A:BC as - the destination MAC address. -* Use the Modify RPC with ADD operation to test NHG implicit in-place - replace (step by step as below): - 1. Add a new NH (NH#41) with egress interface that specifies DUT port-3 as the - egress interface and 00:1A:11:00:1A:BC as the destination MAC address. - 2. Add the same NHG#42 but reference both NH#40 and NH#41. - 3. Validate that both ATE port-2 and ATE port-3 receives the packets with 00:1A:11:00:1A:BC as the destination MAC address. - 4. Add the same NHG#42 but reference only NH#41. - 5. Validate that only ATE port-3 receives the packets. - 6. Add the same NHG#42 but reference only NH#40. - 7. Validate that only ATE port-2 receives the packets - -## Config Parameter coverage - -No configuration relevant. - -## Telemetry Parameter coverage - -For prefix: - -* /network-instances/network-instance/afts/ - -Parameters: - -* ipv4-unicast/ipv4-entry/state -* ipv4-unicast/ipv4-entry/state/next-hop-group -* ipv4-unicast/ipv4-entry/state/origin-protocol -* ipv4-unicast/ipv4-entry/state/prefix -* next-hop-groups/next-hop-group/id -* next-hop-groups/next-hop-group/next-hops -* next-hop-groups/next-hop-group/next-hops/next-hop -* next-hop-groups/next-hop-group/next-hops/next-hop/index -* next-hop-groups/next-hop-group/next-hops/next-hop/state -* next-hop-groups/next-hop-group/next-hops/next-hop/state/index -* next-hop-groups/next-hop-group/state -* next-hop-groups/next-hop-group/state/id -* next-hops/next-hop/index -* next-hops/next-hop/interface-ref -* next-hops/next-hop/interface-ref/state -* next-hops/next-hop/interface-ref/state/interface -* next-hops/next-hop/interface-ref/state/subinterface -* next-hops/next-hop/state -* next-hops/next-hop/state/index -* next-hops/next-hop/state/ip-address -* next-hops/next-hop/state/mac-address - -## Protocol/RPC Parameter coverage - -* gRIBI: - * Modify() - * ModifyRequest: - * AFTOperation: - * id - * network_instance - * op - * Ipv4 - * Ipv4EntryKey: prefix - * Ipv4Entry: next_hop_group - * next_hop_group - * NextHopGroupKey: id - * NextHopGroup: next_hop - * next_hop - * NextHopKey: id - * NextHop: - * mac_address - * interface_ref - * ModifyResponse: - * AFTResult: - * id - * status - -## Minimum DUT platform requirement - -vRX if the vendor implementation supports FIB-ACK simulation, otherwise FFF. - -# TE-3.7: Drain Implementation Test. + 2. Add the same NHG#42 but reference both NH#40 and NH#41. + 3. Validate that both ATE port-2 and ATE port-3 receives the packets with + 00:1A:11:00:1A:BC as the destination MAC address. + 4. Add the same NHG#42 but reference only NH#41. + 5. Validate that only ATE port-3 receives the packets. + 6. Add the same NHG#42 but reference only NH#40. + 7. Validate that only ATE port-2 receives the packets -## Summary +Repeat the above tests with one additional scenario with the following changes, +and it should not change the expected test result. -Validate NHG update in Drain Implementation Test. +* Add an empty decap VRF, `DECAP_TE_VRF`. +* Add 4 empty encap VRFs, `ENCAP_TE_VRF_A`, `ENCAP_TE_VRF_B`, `ENCAP_TE_VRF_C` + and `ENCAP_TE_VRF_D`. +* Replace the existing VRF selection policy with `vrf_selection_policy_w` as + in -## Procedure + +### Validate NHG update in Drain Implementation Test. * Steps: * Topology -* [ATE port-1] — [port-1 DUT port-2] — [port-2 ATE] - Port-3]—-[port-3 ATE] - Port-4]—-[port-4 ATE] -* DUT port-2, port-3 and port-4 are each making a one-member trunk port (trunk-2 and trunk-3, trunk-4). + * [ATE port-1] — [port-1 DUT port-2] — [port-2 ATE] Port-3]—-[port-3 ATE] + Port-4]—-[port-4 ATE] +* DUT port-2, port-3 and port-4 are each making a one-member trunk port + (trunk-2 and trunk-3, trunk-4). * Configure a destination network-a connected to trunk-2, trunk-3 and trunk-4. -* gRIBI installs the following routing structure (700 IPv4 prefix, NHG and NH numbers stays the same as the illustration), and expect FIB ACKs: -* In the DEFAULT VRF: - VIP1 -> NHG#1 -> [NH#1 {mac: MagicMAC, interface: DUTPort2Trunk}, NH#2 {mac: MagicMAC, interface: DUTPort3Trunk}] - NHG#10 -> NH#10 {decap, network-instance: DEFAULT VRF} - NHG#20 -> [ NH#20{ip: VIP1}, backupNH: NHG#10] - -* In a non-defualt VRF, VRF-1: - IPv4Entries(1000 /32 IPv4 entries) -> NHG#20 - -* Send 10K IPinIP 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. -* Send one gRIBI NHG#1 update to replace NH#1 and NH#2 with NH#3 pointing to trunk#4. -* Expect FIB ACKs, and validate that all traffic are moved to trunk#4 with no traffic loss. -* Send one gRIBI NHG#1 update to revert back the changes above. -* Expect FIB ACKs and validate that the traffic is moved back to trunk-2 and trunk-3 with less than ms traffic loss. +* gRIBI installs the following routing structure (700 IPv4 prefix, NHG and NH + numbers stays the same as the illustration), and expect FIB ACKs: +* In the DEFAULT VRF: VIP1 -> NHG#1 -> [NH#1 {mac: MagicMAC, interface: + DUTPort2Trunk}, NH#2 {mac: MagicMAC, interface: DUTPort3Trunk}] NHG#10 -> + NH#10 {decap, network-instance: DEFAULT VRF} NHG#20 -> [ NH#20{ip: VIP1}, + backupNH: NHG#10] + +* In a non-defualt VRF, VRF-1: IPv4Entries(1000 /32 IPv4 entries) -> NHG#20 + +* Send 10K IPinIP 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. + +* Send one gRIBI NHG#1 update to replace NH#1 and NH#2 with NH#3 pointing to + trunk#4. + +* Expect FIB ACKs, and validate that all traffic are moved to trunk#4 with no + traffic loss. + +* Send one gRIBI NHG#1 update to revert back the changes above. + +* Expect FIB ACKs and validate that the traffic is moved back to trunk-2 and + trunk-3 with less than ms traffic loss. + + +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config parameter coverage + /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/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-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/interface-ref/state/subinterface: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/interface-ref/config/interface: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/interface-ref/config/subinterface: + /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: + + ## Protocol/RPC Parameter Coverage +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Required DUT platform + +* vRX if the vendor implementation supports FIB-ACK simulation, otherwise FFF. + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` \ No newline at end of file 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 27648b61837..a2a46151860 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 @@ -32,6 +32,7 @@ import ( "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/featureprofiles/internal/gribi" "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" "github.com/openconfig/gribigo/chk" "github.com/openconfig/gribigo/client" "github.com/openconfig/gribigo/fluent" @@ -102,6 +103,11 @@ const ( innerSrcIP = "198.51.100.61" vrfPrefixcount = 10000 ipv4Prefixcount = 700 + + vrfPolW = "vrf_selection_policy_w" + decapFlowSrc = "198.51.100.111" + dscpEncapA1 = 10 + niTeVrf111 = "TE_VRF_111" ) type testArgs struct { @@ -158,14 +164,14 @@ var ( IPv4Len: 30, } dutPort2DummyIP = attrs.Attributes{ - Desc: "dutPort2", - IPv4: "192.0.2.21", - IPv4Len: 30, + Desc: "dutPort2", + IPv4Sec: "192.0.2.21", + IPv4LenSec: 30, } dutPort3DummyIP = attrs.Attributes{ - Desc: "dutPort3", - IPv4: "192.0.2.41", - IPv4Len: 30, + Desc: "dutPort3", + IPv4Sec: "192.0.2.41", + IPv4LenSec: 30, } atePort2DummyIP = attrs.Attributes{ Desc: "atePort2", @@ -223,6 +229,11 @@ func TestBaseHierarchicalNHGUpdate(t *testing.T) { desc: "Usecase for Implementing Drain test", fn: testImplementDrain, }, + { + name: "testRecursiveIPv4EntrywithVRFSelectionPolW", + desc: "Usecase for NHG update in hierarchical resolution scenario with VRF Selection Policy W", + fn: testBaseHierarchialNHGwithVrfPolW, + }, } // Configure the gRIBI client client := gribi.Client{ @@ -252,31 +263,57 @@ func TestBaseHierarchicalNHGUpdate(t *testing.T) { tt.fn(ctx, t, tcArgs) }) } + + defer func() { + t.Log("Unconfig interfaces") + deleteinterfaceconfig(t, dut) + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.Delete(t, dut, sp.Static(atePort2DummyIP.IPv4CIDR()).Config()) + gnmi.Delete(t, dut, sp.Static(atePort3DummyIP.IPv4CIDR()).Config()) + } + }() +} + +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) { + if deviations.SkipPbfWithDecapEncapVrf(args.dut) { + t.Skip("Skipping test as pbf with decap encap vrf is not supported") + } + vrfpolicy.ConfigureVRFSelectionPolicy(t, args.dut, vrfpolicy.VRFPolicyW) + + // 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)) + + ctx = context.WithValue(ctx, transitKey{}, true) + testBaseHierarchialNHG(ctx, t, args) + // Delete Policy-forwarding PolicyW from the ingress interface + vrfpolicy.DeletePolicyForwarding(t, args.dut, "port1") } // TE3.7 - case 1: testBaseHierarchialNHG. func testBaseHierarchialNHG(ctx context.Context, t *testing.T, args *testArgs) { - + args.top.Flows().Clear() t.Log("Create flows for port 1 to port2, port 1 to port3") p2FlowName := "Port 1 to Port 2" p3FlowName := "Port 1 to Port 3" - p2Flow := createFlow(t, p2FlowName, args.top, false, &atePort2) - p3Flow := createFlow(t, p3FlowName, args.top, false, &atePort3) + transit, ok := ctx.Value(transitKey{}).(bool) + if !ok { + transit = false + } + p2Flow := createFlow(t, p2FlowName, args.top, false, transit, &atePort2) + p3Flow := createFlow(t, p3FlowName, args.top, false, transit, &atePort3) args.ate.OTG().PushConfig(t, args.top) args.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, args.ate.OTG(), args.top, "IPv4") - defer func() { - - t.Log("Unconfig interfaces") - deleteinterfaceconfig(t, args.dut) - if deviations.GRIBIMACOverrideStaticARPStaticRoute(args.dut) { - sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(args.dut)) - gnmi.Delete(t, args.dut, sp.Static(atePort2DummyIP.IPv4CIDR()).Config()) - gnmi.Delete(t, args.dut, sp.Static(atePort3DummyIP.IPv4CIDR()).Config()) - } - }() - + dni := deviations.DefaultNetworkInstance(args.dut) dutP2 := args.dut.Port(t, "port2").Name() dutP3 := args.dut.Port(t, "port3").Name() @@ -284,69 +321,46 @@ func testBaseHierarchialNHG(ctx context.Context, t *testing.T, args *testArgs) { var nh fluent.GRIBIEntry var op1, op3 *client.OpResult - if !deviations.ExplicitGRIBIUnderNetworkInstance(args.dut) { - if deviations.GRIBIMACOverrideWithStaticARP(args.dut) || deviations.GRIBIMACOverrideStaticARPStaticRoute(args.dut) { - nh, op1 = gribi.NHEntry(p2NHID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP2, Mac: pMAC, Dest: atePort2DummyIP.IPv4}) - } else { - nh, op1 = gribi.NHEntry(p2NHID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP2, Mac: pMAC}) - } - nhg, op2 := gribi.NHGEntry(virtualIPNHGID, map[uint64]uint64{p2NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) - args.client.AddIPv4(t, virtualPfx, virtualIPNHGID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - - nh, op1 = gribi.NHEntry(dstNHID, virtualIP, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - nhg, op2 = gribi.NHGEntry(dstNHGID, map[uint64]uint64{dstNHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) + if deviations.GRIBIMACOverrideWithStaticARP(args.dut) || deviations.GRIBIMACOverrideStaticARPStaticRoute(args.dut) { + nh, op1 = gribi.NHEntry(p2NHID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP2, Mac: pMAC, Dest: atePort2DummyIP.IPv4}) } else { - args.client.AddNH(t, p2NHID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP2, Mac: pMAC}) - args.client.AddNHG(t, virtualIPNHGID, map[uint64]uint64{p2NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddIPv4(t, virtualPfx, virtualIPNHGID, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nh, op1 = gribi.NHEntry(p2NHID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP2, Mac: pMAC}) + } + nhg, op2 := gribi.NHGEntry(virtualIPNHGID, map[uint64]uint64{p2NHID: 1}, dni, fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) + args.client.AddIPv4(t, virtualPfx, virtualIPNHGID, dni, dni, fluent.InstalledInFIB) + + nh, op1 = gribi.NHEntry(dstNHID, virtualIP, dni, fluent.InstalledInFIB) + nhg, op2 = gribi.NHGEntry(dstNHGID, map[uint64]uint64{dstNHID: 1}, dni, fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) - args.client.AddNH(t, dstNHID, virtualIP, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddNHG(t, dstNHGID, map[uint64]uint64{dstNHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + if transit { + args.client.AddIPv4(t, dstPfx, dstNHGID, niTeVrf111, dni, fluent.InstalledInFIB) + } else { + args.client.AddIPv4(t, dstPfx, dstNHGID, vrfName, dni, fluent.InstalledInFIB) } - args.client.AddIPv4(t, dstPfx, dstNHGID, vrfName, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - otgutils.WaitForARP(t, args.ate.OTG(), args.top, "IPv4") validateTrafficFlows(t, args.ate, []gosnappi.Flow{p2Flow}, []gosnappi.Flow{p3Flow}, nil, startTraffic, args.client, false) t.Logf("Adding a new NH via port %v with ID %v", dutP3, p3NHID) - if !deviations.ExplicitGRIBIUnderNetworkInstance(args.dut) { - if deviations.GRIBIMACOverrideWithStaticARP(args.dut) || deviations.GRIBIMACOverrideStaticARPStaticRoute(args.dut) { - nh, op3 = gribi.NHEntry(p3NHID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP3, Mac: pMAC, Dest: atePort3DummyIP.IPv4}) - - } else { - nh, op3 = gribi.NHEntry(p3NHID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP3, Mac: pMAC}) - } + if deviations.GRIBIMACOverrideWithStaticARP(args.dut) || deviations.GRIBIMACOverrideStaticARPStaticRoute(args.dut) { + nh, op3 = gribi.NHEntry(p3NHID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP3, Mac: pMAC, Dest: atePort3DummyIP.IPv4}) } else { - args.client.AddNH(t, p3NHID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP3, Mac: pMAC}) + nh, op3 = gribi.NHEntry(p3NHID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP3, Mac: pMAC}) } t.Logf("Performing implicit in-place replace with two next-hops (NH IDs: %v and %v)", p2NHID, p3NHID) - if !deviations.ExplicitGRIBIUnderNetworkInstance(args.dut) { - nhg, op2 := gribi.NHGEntry(virtualIPNHGID, map[uint64]uint64{p2NHID: 1, p3NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op3, op2}) - } else { - args.client.AddNHG(t, virtualIPNHGID, map[uint64]uint64{p2NHID: 1, p3NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - } + nhg, op2 = gribi.NHGEntry(virtualIPNHGID, map[uint64]uint64{p2NHID: 1, p3NHID: 1}, dni, fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op3, op2}) validateTrafficFlows(t, args.ate, nil, nil, []gosnappi.Flow{p2Flow, p3Flow}, startTraffic, args.client, false) t.Logf("Performing implicit in-place replace using the next-hop with ID %v", p3NHID) - if !deviations.ExplicitGRIBIUnderNetworkInstance(args.dut) { - nhg, op2 := gribi.NHGEntry(virtualIPNHGID, map[uint64]uint64{p3NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op3, op2}) - } else { - args.client.AddNHG(t, virtualIPNHGID, map[uint64]uint64{p3NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - } + nhg, op2 = gribi.NHGEntry(virtualIPNHGID, map[uint64]uint64{p3NHID: 1}, dni, fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op3, op2}) validateTrafficFlows(t, args.ate, []gosnappi.Flow{p3Flow}, []gosnappi.Flow{p2Flow}, nil, startTraffic, args.client, false) t.Logf("Performing implicit in-place replace using the next-hop with ID %v", p2NHID) - if !deviations.ExplicitGRIBIUnderNetworkInstance(args.dut) { - nhg, op2 := gribi.NHGEntry(virtualIPNHGID, map[uint64]uint64{p2NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) - } else { - args.client.AddNHG(t, virtualIPNHGID, map[uint64]uint64{p2NHID: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - } + args.client.AddNHG(t, virtualIPNHGID, map[uint64]uint64{p2NHID: 1}, dni, fluent.InstalledInFIB) validateTrafficFlows(t, args.ate, []gosnappi.Flow{p2Flow}, []gosnappi.Flow{p3Flow}, nil, startTraffic, args.client, false) } @@ -410,15 +424,14 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { fptest.SetPortSpeed(t, p3) fptest.SetPortSpeed(t, p4) } + + fptest.ConfigureDefaultNetworkInstance(t, dut) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { 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) } - if deviations.ExplicitGRIBIUnderNetworkInstance(dut) { - fptest.EnableGRIBIUnderNetworkInstance(t, dut, deviations.DefaultNetworkInstance(dut)) - fptest.EnableGRIBIUnderNetworkInstance(t, dut, vrfName) - } if deviations.GRIBIMACOverrideWithStaticARP(dut) { staticARPWithSecondaryIP(t, dut, false) @@ -492,7 +505,7 @@ func staticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { // createFlow returns a flow from atePort1 to the dstPfx, expected to arrive on ATE interface dsts. // Set drain to true to design flows for testImplementDrain case and false to design flows for testBaseHierarchialNHG case -func createFlow(_ *testing.T, name string, ateTop gosnappi.Config, drain bool, dsts ...*attrs.Attributes) gosnappi.Flow { +func createFlow(_ *testing.T, name string, ateTop gosnappi.Config, drain, transit bool, dsts ...*attrs.Attributes) gosnappi.Flow { var rxEndpoints []string for _, dst := range dsts { rxEndpoints = append(rxEndpoints, dst.Name+".IPv4") @@ -510,7 +523,13 @@ func createFlow(_ *testing.T, name string, ateTop gosnappi.Config, drain bool, d innerIPHeader := flowipv4.Packet().Add().Ipv4() innerIPHeader.Src().SetValue(innerSrcIP) innerIPHeader.Dst().Increment().SetStart(innerDstPfx).SetStep("0.0.0.1").SetCount(vrfPrefixcount) - + } else if transit { + outerIPHeader.Src().SetValue(decapFlowSrc) + outerIPHeader.Dst().SetValue(dstPfxFlowIP) + outerIPHeader.Priority().Dscp().Phb().SetValues([]uint32{dscpEncapA1}) + innerIPHeader := flowipv4.Packet().Add().Ipv4() + innerIPHeader.Src().Increment().SetStart(innerSrcIPv4Start).SetStep("0.0.0.1").SetCount(ipv4FlowCount) + innerIPHeader.Dst().SetValue(virtualIP) } else { outerIPHeader.Src().SetValue(atePort1.IPv4) outerIPHeader.Dst().SetValue(dstPfxFlowIP) @@ -539,8 +558,8 @@ func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good, bad, lb [] return } dut := ondatra.DUT(t, "dut") - var nonrx_ports []string - var expected_outgoing_port []string + var nonrxPorts []string + var expectedOutgoingPort []string var macFilter string otg := ate.OTG() @@ -551,16 +570,16 @@ func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good, bad, lb [] otg.StartTraffic(t) time.Sleep(15 * time.Second) case switchTrafficToPort4FromPort2AndPort3: - nonrx_ports = []string{"port2", "port3"} - expected_outgoing_port = []string{"port4"} + nonrxPorts = []string{"port2", "port3"} + expectedOutgoingPort = []string{"port4"} otg.StartTraffic(t) time.Sleep(15 * time.Second) t.Logf("Modify NHG %v pointing to %v", nhg1ID, btrunk4) gr.AddNHG(t, nhg1ID, map[uint64]uint64{nh3ID: 100}, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) time.Sleep(30 * time.Second) case switchTrafficToPort2AndPort3FromPort4: - nonrx_ports = []string{"port4"} - expected_outgoing_port = []string{"port2", "port3"} + nonrxPorts = []string{"port4"} + expectedOutgoingPort = []string{"port2", "port3"} otg.StartTraffic(t) time.Sleep(15 * time.Second) t.Logf("Modify NHG %v pointing back to %v and %v", nhg1ID, btrunk2, btrunk3) @@ -653,25 +672,25 @@ func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good, bad, lb [] if change { var receivedPkts uint64 - incoming_traffic_counters := gnmi.OTG().Port(ate.Port(t, "port1").ID()).Counters() - sentPkts := gnmi.Get(t, ate.OTG(), incoming_traffic_counters.OutFrames().State()) + incomingTrafficCounters := gnmi.OTG().Port(ate.Port(t, "port1").ID()).Counters() + sentPkts := gnmi.Get(t, ate.OTG(), incomingTrafficCounters.OutFrames().State()) // Get traffic received on primary outgoing interface before modifying NHG - for _, port := range nonrx_ports { - outgoing_traffic_counters := gnmi.OTG().Port(ate.Port(t, port).ID()).Counters() - outPkts := gnmi.Get(t, ate.OTG(), outgoing_traffic_counters.InFrames().State()) + for _, port := range nonrxPorts { + outgoingTrafficCounters := gnmi.OTG().Port(ate.Port(t, port).ID()).Counters() + outPkts := gnmi.Get(t, ate.OTG(), outgoingTrafficCounters.InFrames().State()) receivedPkts = receivedPkts + outPkts } // Get traffic received on expected port after modifying NHG - for _, outPort := range expected_outgoing_port { - outgoing_traffic_counters := gnmi.OTG().Port(ate.Port(t, outPort).ID()).Counters() - outPkts := gnmi.Get(t, ate.OTG(), outgoing_traffic_counters.InFrames().State()) + for _, outPort := range expectedOutgoingPort { + outgoingTrafficCounters := gnmi.OTG().Port(ate.Port(t, outPort).ID()).Counters() + outPkts := gnmi.Get(t, ate.OTG(), outgoingTrafficCounters.InFrames().State()) receivedPkts = receivedPkts + outPkts } // Check if traffic restores with in expected time in milliseconds during modify NHG - if len(nonrx_ports) > 0 { + if len(nonrxPorts) > 0 { // Time took for traffic to restore in milliseconds after trigger diff := big.NewInt(0).Sub(big.NewInt(0).SetUint64(receivedPkts), big.NewInt(0).SetUint64(sentPkts)) fpm := (diff.Uint64() / (fps / 1000)) @@ -704,7 +723,7 @@ func configStaticArp(p string, ipv4addr string, macAddr string, trunk bool) *oc. func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { if !deviations.GRIBIMACOverrideWithStaticARP(args.dut) { t.Skip() - //Testcase skipped as static arp and route config needed for other vendors + // Testcase skipped as static arp and route config needed for other vendors } t.Log("Create flows for port1 to port2, port1 to port3 and port1 to port4") args.top.Flows().Clear() @@ -712,9 +731,9 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { p2FlowName := "Flow Port 1 to Port 2" p3FlowName := "Flow Port 1 to Port 3" p4FlowName := "Flow Port 1 to Port 4" - p2Flow := createFlow(t, p2FlowName, args.top, true, &atePort2) - p3Flow := createFlow(t, p3FlowName, args.top, true, &atePort3) - p4Flow := createFlow(t, p4FlowName, args.top, true, &atePort4) + p2Flow := createFlow(t, p2FlowName, args.top, true, false, &atePort2) + p3Flow := createFlow(t, p3FlowName, args.top, true, false, &atePort3) + p4Flow := createFlow(t, p4FlowName, args.top, true, false, &atePort4) args.ate.OTG().PushConfig(t, args.top) args.ate.OTG().StartProtocols(t) @@ -727,16 +746,17 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { t.Logf("Adding NHG %d, NH %d and NH %d via gRIBI", nhg1ID, nh1ID, nh2ID) + dni := deviations.DefaultNetworkInstance(args.dut) if deviations.GRIBIMACOverrideWithStaticARP(args.dut) { - args.client.AddNH(t, nh1ID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk2, Mac: port2mac, Dest: atePort2.IPv4}) - args.client.AddNH(t, nh2ID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk3, Mac: port3mac, Dest: atePort3.IPv4}) + args.client.AddNH(t, nh1ID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk2, Mac: port2mac, Dest: atePort2.IPv4}) + args.client.AddNH(t, nh2ID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk3, Mac: port3mac, Dest: atePort3.IPv4}) } else { - args.client.AddNH(t, nh1ID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk2, Mac: port2mac}) - args.client.AddNH(t, nh2ID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk3, Mac: port3mac}) + args.client.AddNH(t, nh1ID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk2, Mac: port2mac}) + args.client.AddNH(t, nh2ID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk3, Mac: port3mac}) } - args.client.AddNHG(t, nhg1ID, map[uint64]uint64{nh1ID: 50, nh2ID: 50}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddNHG(t, nhg1ID, map[uint64]uint64{nh1ID: 50, nh2ID: 50}, dni, fluent.InstalledInFIB) t.Logf("Adding %d ipv4 vrf prefixes and gribi entries for them", ipv4Prefixcount) @@ -747,7 +767,7 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { for _, prefix := range prefixes { ipv4Entry := fluent.IPv4Entry(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). + WithNetworkInstance(dni). WithPrefix(prefix + "/" + mask). WithNextHopGroup(uint64(nhg1ID)) args.client.Fluent(t).Modify().AddEntry(t, ipv4Entry) @@ -757,7 +777,7 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { } gr, err := args.client.Fluent(t).Get(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). + WithNetworkInstance(dni). WithAFT(fluent.IPv4). Send() @@ -768,20 +788,20 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { for _, prefix := range prefixes { chk.GetResponseHasEntries(t, gr, fluent.IPv4Entry(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). + WithNetworkInstance(dni). WithNextHopGroup(uint64(nhg1ID)). WithPrefix(prefix+"/"+mask), ) } t.Logf("Adding NHG %d with NH %d as decap and DEFAULT vrf lookup via gRIBI", nhg10ID, nh10ID) - args.client.AddNH(t, nh10ID, "Decap", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{VrfName: deviations.DefaultNetworkInstance(args.dut)}) - args.client.AddNHG(t, nhg10ID, map[uint64]uint64{nh10ID: 100}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddNH(t, nh10ID, "Decap", dni, fluent.InstalledInFIB, &gribi.NHOptions{VrfName: dni}) + args.client.AddNHG(t, nhg10ID, map[uint64]uint64{nh10ID: 100}, dni, fluent.InstalledInFIB) t.Logf("Adding NHG %d with NH %d via gRIBI", nhg20ID, nh20ID) - args.client.AddNH(t, nh20ID, vip1, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) - args.client.AddNHG(t, nhg20ID, map[uint64]uint64{nh20ID: 100}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg10ID}) + args.client.AddNH(t, nh20ID, vip1, dni, fluent.InstalledInFIB) + args.client.AddNHG(t, nhg20ID, map[uint64]uint64{nh20ID: 100}, dni, fluent.InstalledInFIB, &gribi.NHGOptions{BackupNHG: nhg10ID}) t.Logf("Adding %d ipv4 vrf prefixes and gribi entries for them", vrfPrefixcount) prefixes = []string{} @@ -793,7 +813,7 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { WithNetworkInstance(vrfName). WithPrefix(prefix + "/" + mask). WithNextHopGroup(uint64(nhg20ID)). - WithNextHopGroupNetworkInstance((deviations.DefaultNetworkInstance(args.dut))) + WithNextHopGroupNetworkInstance((dni)) args.client.Fluent(t).Modify().AddEntry(t, ipv4Entry) } if err := args.client.AwaitTimeout(context.Background(), t, 2*time.Minute); err != nil { @@ -823,9 +843,9 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { t.Logf("Adding NH %d for trunk4 via gribi", nh3ID) if deviations.GRIBIMACOverrideWithStaticARP(args.dut) { - args.client.AddNH(t, nh3ID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk4, Mac: port4mac, Dest: atePort4.IPv4}) + args.client.AddNH(t, nh3ID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk4, Mac: port4mac, Dest: atePort4.IPv4}) } else { - args.client.AddNH(t, nh3ID, "MACwithInterface", deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk4, Mac: port4mac}) + args.client.AddNH(t, nh3ID, "MACwithInterface", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: btrunk4, Mac: port4mac}) } t.Log("Validate traffic switching from ate port2, ate port3 to ate port4") @@ -931,14 +951,15 @@ func generateIPAddress(dstP string, i int) string { func addStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { d := gnmi.OC() s := &oc.Root{} - static := s.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + ni := s.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + static := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) ipv4Nh := static.GetOrCreateStatic(innerDst).GetOrCreateNextHop("0") ipv4Nh1 := static.GetOrCreateStatic(innerDst).GetOrCreateNextHop("1") ipv4Nh2 := static.GetOrCreateStatic(innerDst).GetOrCreateNextHop("2") ipv4Nh.NextHop, _ = ipv4Nh.To_NetworkInstance_Protocol_Static_NextHop_NextHop_Union(atePort2.IPv4) ipv4Nh1.NextHop, _ = ipv4Nh.To_NetworkInstance_Protocol_Static_NextHop_NextHop_Union(atePort3.IPv4) ipv4Nh2.NextHop, _ = ipv4Nh.To_NetworkInstance_Protocol_Static_NextHop_NextHop_Union(atePort4.IPv4) - gnmi.Update(t, dut, d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) + gnmi.Update(t, dut, d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Config(), ni) } // getLossPct returns the loss percentage for a given flow diff --git a/feature/gribi/otg_tests/base_hierarchical_nhg_update/metadata.textproto b/feature/gribi/otg_tests/base_hierarchical_nhg_update/metadata.textproto index f7cf3d7bf4f..c8a1b5253ac 100644 --- a/feature/gribi/otg_tests/base_hierarchical_nhg_update/metadata.textproto +++ b/feature/gribi/otg_tests/base_hierarchical_nhg_update/metadata.textproto @@ -12,6 +12,9 @@ platform_exceptions: { deviations: { ipv4_missing_enabled: true gribi_mac_override_with_static_arp: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true } } platform_exceptions: { @@ -27,11 +30,11 @@ platform_exceptions: { vendor: NOKIA } deviations: { - explicit_gribi_under_network_instance: true explicit_port_speed: true explicit_interface_in_default_vrf: true static_protocol_name: "static" interface_enabled: true + skip_pbf_with_decap_encap_vrf: true } } platform_exceptions: { diff --git a/feature/gribi/otg_tests/base_hierarchical_route_installation_test/README.md b/feature/gribi/otg_tests/base_hierarchical_route_installation_test/README.md index 6947ffe92c8..2f80c28934d 100644 --- a/feature/gribi/otg_tests/base_hierarchical_route_installation_test/README.md +++ b/feature/gribi/otg_tests/base_hierarchical_route_installation_test/README.md @@ -51,6 +51,19 @@ Validate hierarchical resolution using egress interface and MAC: 198.51.100.1/32) and ensure that ATE port-2 receives packet with `00:1A:11:00:00:01` as the destination MAC address. +3. Repeat the above tests with one additional scenario with the following changes, and it should + not change the expected test result. + + * Add an empty decap VRF, `DECAP_TE_VRF`. + * Add 4 empty encap VRFs, `ENCAP_TE_VRF_A`, `ENCAP_TE_VRF_B`, `ENCAP_TE_VRF_C`, + and `ENCAP_TE_VRF_D`. + * Add 2 empty transit VRFs, `TE_VRF_111` and `TE_VRF_222`. + * Program route 198.51.100.1/32 through gribi in `TE_VRF_111` instead of `VRF-1`. + * Replace the existing VRF selection policy with `vrf_selection_policy_w` as in + . + * Send IP-In-IP traffic with source IP to ipv4_outer_src_111 (`198.51.100.111`) and DSCP to + dscp_encap_a_1(10). + ## Config Parameter coverage No configuration relevant. @@ -110,3 +123,16 @@ No configuration relevant. ## Minimum DUT platform requirement vRX if the vendor implementation supports FIB-ACK simulation, otherwise FFF. + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/gribi/otg_tests/base_hierarchical_route_installation_test/base_hierarchical_route_installation_test.go b/feature/gribi/otg_tests/base_hierarchical_route_installation_test/base_hierarchical_route_installation_test.go index 8aafa24a7ac..f36cb255de7 100644 --- a/feature/gribi/otg_tests/base_hierarchical_route_installation_test/base_hierarchical_route_installation_test.go +++ b/feature/gribi/otg_tests/base_hierarchical_route_installation_test/base_hierarchical_route_installation_test.go @@ -48,19 +48,36 @@ func TestMain(m *testing.M) { // - ate:port1 -> dut:port1 subnet 192.0.2.0/30 // - ate:port2 -> dut:port2 subnet 192.0.2.4/30 const ( - ipv4PrefixLen = 30 - ateDstIP = "198.51.100.1" - ateDstNetCIDR = ateDstIP + "/32" - ateIndirectNH = "203.0.113.1" - ateIndirectNHCIDR = ateIndirectNH + "/32" - nhIndex = 1 - nhgIndex = 42 - nhIndex2 = 2 - nhgIndex2 = 52 - nonDefaultVRF = "VRF-1" - nhMAC = "00:1A:11:00:0A:BC" - macFilter = "0xABC" // Hex equalent last 12 bits - policyName = "redirect-to-VRF1" + ipv4PrefixLen = 30 + ateDstIP = "198.51.100.1" + ateDstNetCIDR = ateDstIP + "/32" + ateIndirectNH = "203.0.113.1" + ateIndirectNHCIDR = ateIndirectNH + "/32" + nhIndex = 1 + nhgIndex = 42 + nhIndex2 = 2 + nhgIndex2 = 52 + nonDefaultVRF = "VRF-1" + nhMAC = "00:1A:11:00:0A:BC" + macFilter = "0xABC" // Hex equalent last 12 bits + policyName = "redirect-to-VRF1" + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niEncapTeVrfB = "ENCAP_TE_VRF_B" + niEncapTeVrfC = "ENCAP_TE_VRF_C" + niEncapTeVrfD = "ENCAP_TE_VRF_D" + vrfPolW = "vrf_selection_policy_w" + niDefault = "DEFAULT" + dscpEncapA1 = 10 + dscpEncapA2 = 18 + dscpEncapB1 = 20 + dscpEncapB2 = 28 + dscpEncapNoMatch = 30 + ipv4OuterSrc111WithMask = "198.51.100.111/32" + ipv4OuterSrc222WithMask = "198.51.100.222/32" + niTeVrf111 = "TE_VRF_111" + niTeVrf222 = "TE_VRF_222" + decapFlowSrc = "198.51.100.111" ) var ( @@ -91,9 +108,9 @@ var ( } dutPort2DummyIP = attrs.Attributes{ - Desc: "dutPort2", - IPv4: "192.0.2.21", - IPv4Len: 30, + Desc: "dutPort2", + IPv4Sec: "192.0.2.21", + IPv4LenSec: 30, } atePort2DummyIP = attrs.Attributes{ @@ -145,6 +162,122 @@ func configureNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { gnmi.Replace(t, dut, defNIPath.PolicyForwarding().Config(), configurePBF(dut)) } +// configureNetworkInstance configures vrfs DECAP_TE_VRF,ENCAP_TE_VRF_A,ENCAP_TE_VRF_B, +// ENCAP_TE_VRF_C, ENCAP_TE_VRF_D, TE_VRF_111, TE_VRF_222 +func configNonDefaultNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + c := &oc.Root{} + vrfs := []string{niDecapTeVrf, niEncapTeVrfA, niEncapTeVrfB, niEncapTeVrfC, niEncapTeVrfD, niTeVrf111, niTeVrf222} + for _, vrf := range vrfs { + ni := c.GetOrCreateNetworkInstance(vrf) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), ni) + } +} + +type policyFwRule struct { + SeqId uint32 + protocol oc.UnionUint8 + dscpSet []uint8 + sourceAddr string + decapNi string + postDecapNi string + decapFallbackNi string +} + +func configureVrfSelectionPolicyW(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + dutPolFwdPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() + + pfRule1 := &policyFwRule{SeqId: 1, protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule2 := &policyFwRule{SeqId: 2, protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule3 := &policyFwRule{SeqId: 3, protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + pfRule4 := &policyFwRule{SeqId: 4, protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + + pfRule5 := &policyFwRule{SeqId: 5, protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule6 := &policyFwRule{SeqId: 6, protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule7 := &policyFwRule{SeqId: 7, protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + pfRule8 := &policyFwRule{SeqId: 8, protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + + pfRule9 := &policyFwRule{SeqId: 9, protocol: 4, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule10 := &policyFwRule{SeqId: 10, protocol: 41, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule11 := &policyFwRule{SeqId: 11, protocol: 4, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + pfRule12 := &policyFwRule{SeqId: 12, protocol: 41, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + + if deviations.PfRequireSequentialOrderPbrRules(dut) { + pfRule10.SeqId = 910 + pfRule11.SeqId = 911 + pfRule12.SeqId = 912 + } + + pfRuleList := []*policyFwRule{pfRule1, pfRule2, pfRule3, pfRule4, pfRule5, pfRule6, + pfRule7, pfRule8, pfRule9, pfRule10, pfRule11, pfRule12} + + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niP := ni.GetOrCreatePolicyForwarding() + niPf := niP.GetOrCreatePolicy(vrfPolW) + niPf.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + + for _, pfRule := range pfRuleList { + pfR := niPf.GetOrCreateRule(pfRule.SeqId) + pfRProtoIPv4 := pfR.GetOrCreateIpv4() + pfRProtoIPv4.Protocol = oc.UnionUint8(pfRule.protocol) + if pfRule.dscpSet != nil { + pfRProtoIPv4.DscpSet = pfRule.dscpSet + } + pfRProtoIPv4.SourceAddress = ygot.String(pfRule.sourceAddr) + pfRAction := pfR.GetOrCreateAction() + pfRAction.DecapNetworkInstance = ygot.String(pfRule.decapNi) + pfRAction.PostDecapNetworkInstance = ygot.String(pfRule.postDecapNi) + pfRAction.DecapFallbackNetworkInstance = ygot.String(pfRule.decapFallbackNi) + } + + if deviations.PfRequireMatchDefaultRule(dut) { + pfR13 := niPf.GetOrCreateRule(913) + pfR13.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4) + pfRAction := pfR13.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + pfR14 := niPf.GetOrCreateRule(914) + pfR14.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6) + pfRAction = pfR14.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } else { + pfR := niPf.GetOrCreateRule(13) + pfRAction := pfR.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } + + p1 := dut.Port(t, "port1") + interfaceID := p1.Name() + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = interfaceID + ".0" + } + + intf := niP.GetOrCreateInterface(interfaceID) + intf.ApplyVrfSelectionPolicy = ygot.String(vrfPolW) + intf.GetOrCreateInterfaceRef().Interface = ygot.String(p1.Name()) + intf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + + if deviations.InterfaceRefConfigUnsupported(dut) { + intf.InterfaceRef = nil + } + + gnmi.Replace(t, dut, dutPolFwdPath.Config(), niP) +} + // configureDUT configures port1 and port2 on the DUT. func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { d := gnmi.OC() @@ -217,7 +350,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(ap.Name) i1 := top.Devices().Add().SetName(ap.Name) eth1 := i1.Ethernets().Add().SetName(ap.Name + ".Eth").SetMac(ap.MAC) - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i1.Name()) + eth1.Connection().SetPortName(i1.Name()) eth1.Ipv4Addresses().Add().SetName(i1.Name() + ".IPv4"). SetAddress(ap.IPv4).SetGateway(dp.IPv4). SetPrefix(uint32(ap.IPv4Len)) @@ -235,6 +368,14 @@ func createFlow(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, name v4 := flow.Packet().Add().Ipv4() v4.Src().SetValue(atePort1.IPv4) v4.Dst().Increment().SetStart(ateDstIP).SetCount(1) + + if name == "transitFlow" { + v4.Src().SetValue(decapFlowSrc) + v4.Priority().Dscp().Phb().SetValues([]uint32{dscpEncapA1}) + innerIpHdr := flow.Packet().Add().Ipv4() + innerIpHdr.Src().SetValue(atePort1.IPv4) + innerIpHdr.Dst().SetValue(atePort2.IPv4) + } eth := flow.EgressPacket().Add().Ethernet() ethTag := eth.Dst().MetricTags().Add() ethTag.SetName("EgressTrackingFlow").SetOffset(36).SetLength(12) @@ -250,10 +391,10 @@ func ValidateTraffic(t *testing.T, ate *ondatra.ATEDevice, flow gosnappi.Flow, f ate.OTG().StartProtocols(t) ate.OTG().StartTraffic(t) - time.Sleep(15 * time.Second) ate.OTG().StopTraffic(t) time.Sleep(45 * time.Second) + otgutils.LogFlowMetrics(t, ate.OTG(), top) txPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State()) @@ -287,10 +428,10 @@ type testArgs struct { } // verifyTelemetry verifies the telemetry for route recursilvely -func verifyTelemetry(t *testing.T, args *testArgs, nhtype string) { +func verifyTelemetry(t *testing.T, args *testArgs, nhtype string, vrfName string) { // Verify that the entry for 198.51.100.1/32 (a) is installed through AFT Telemetry. a->c or a->b are the expected results. - ipv4Entry := gnmi.Get(t, args.dut, gnmi.OC().NetworkInstance(nonDefaultVRF).Afts().Ipv4Entry(ateDstNetCIDR).State()) + ipv4Entry := gnmi.Get(t, args.dut, gnmi.OC().NetworkInstance(vrfName).Afts().Ipv4Entry(ateDstNetCIDR).State()) if got, want := ipv4Entry.GetPrefix(), ateDstNetCIDR; got != want { t.Errorf("TestRecursiveIPv4Entry: ipv4-entry/state/prefix = %v, want %v", got, want) } @@ -383,11 +524,14 @@ func testRecursiveIPv4EntrywithIPNexthop(t *testing.T, args *testArgs) { nhg, op2 = gribi.NHGEntry(nhgIndex, map[uint64]uint64{nhIndex: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) args.client.AddIPv4(t, ateDstNetCIDR, nhgIndex, nonDefaultVRF, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + baseFlow := createFlow(t, args.ate, args.top, "BaseFlow") + time.Sleep(30 * time.Second) + t.Run("ValidateTelemtry", func(t *testing.T) { t.Log("Validate Telemetry to verify IPV4 entry is resolved through IP next-hop") - verifyTelemetry(t, args, "IP") + verifyTelemetry(t, args, "IP", nonDefaultVRF) }) t.Run("ValidateTraffic", func(t *testing.T) { @@ -441,7 +585,7 @@ func testRecursiveIPv4EntrywithMACNexthop(t *testing.T, args *testArgs) { time.Sleep(30 * time.Second) t.Run("ValidateTelemtry", func(t *testing.T) { t.Log("Validate Telemetry to verify IPV4 entry is resolved through MAC next-hop") - verifyTelemetry(t, args, "MAC") + verifyTelemetry(t, args, "MAC", nonDefaultVRF) }) t.Run("ValidateTraffic", func(t *testing.T) { t.Log("Validate Traffic is recieved on atePort2 with dst MAC as gRIBI NH MAC") @@ -465,6 +609,61 @@ func testRecursiveIPv4EntrywithMACNexthop(t *testing.T, args *testArgs) { }) } +// testRecursiveIPv4EntrywithVrfPolW verifies recursive IPv4 Entry for +// 198.51.100.1/32 (a) with vrf selection w +func testRecursiveIPv4EntrywithVrfPolW(t *testing.T, args *testArgs) { + + if deviations.SkipPbfWithDecapEncapVrf(args.dut) { + + t.Skip("Skipping Test as it is not supported") + } + t.Log("Delete existing vrf selection policy and Apply vrf selectioin policy W") + configNonDefaultNetworkInstance(t, args.dut) + configureVrfSelectionPolicyW(t, args.dut) + + t.Logf("Adding IP %v with NHG %d NH %d with IP %v as NH via gRIBI", ateIndirectNH, nhgIndex2, nhIndex2, atePort2.IPv4) + nh, op1 := gribi.NHEntry(nhIndex2, atePort2.IPv4, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, op2 := gribi.NHGEntry(nhgIndex2, map[uint64]uint64{nhIndex2: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) + args.client.AddIPv4(t, ateIndirectNHCIDR, nhgIndex2, deviations.DefaultNetworkInstance(args.dut), deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + t.Logf("Adding IP %v with NHG %d NH %d with indirect IP %v via gRIBI", ateDstNetCIDR, nhgIndex, nhIndex, ateIndirectNHCIDR) + nh, op1 = gribi.NHEntry(nhIndex, ateIndirectNH, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + nhg, op2 = gribi.NHGEntry(nhgIndex, map[uint64]uint64{nhIndex: 1}, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + args.client.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) + args.client.AddIPv4(t, ateDstNetCIDR, nhgIndex, niTeVrf111, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + baseFlow := createFlow(t, args.ate, args.top, "transitFlow") + + time.Sleep(30 * time.Second) + + t.Run("ValidateTelemtry", func(t *testing.T) { + t.Log("Validate Telemetry to verify IPV4 entry is resolved through IP next-hop") + verifyTelemetry(t, args, "IP", niTeVrf111) + }) + + t.Run("ValidateTraffic", func(t *testing.T) { + t.Log("Validate Traffic is recieved on atePort2 with IP next-hop") + if got, want := ValidateTraffic(t, args.ate, baseFlow, ""), 0; int(got) != want { + t.Errorf("Loss: got %v, want %v", got, want) + } + }) + + t.Logf("Deleting NH entry and verifing there is no traffic") + args.client.DeleteIPv4(t, ateIndirectNHCIDR, deviations.DefaultNetworkInstance(args.dut), fluent.InstalledInFIB) + + ipv4Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Afts().Ipv4Entry(ateIndirectNHCIDR) + if gnmi.Lookup(t, args.dut, ipv4Path.State()).IsPresent() { + t.Errorf("TestRecursiveIPv4Entry: ipv4-entry/state/prefix: Found route %s that should not exist", ateIndirectNHCIDR) + } + t.Run("ValidateNoTrafficAfterNHDelete", func(t *testing.T) { + t.Log("Validate No traffic Traffic is recieved on atePort2 after NH delete") + if got, want := ValidateTraffic(t, args.ate, baseFlow, ""), 100; int(got) != want { + t.Errorf("Loss: got %v, want %v", got, want) + } + }) +} + func staticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { t.Helper() p2 := dut.Port(t, "port2") @@ -549,6 +748,11 @@ func TestRecursiveIPv4Entries(t *testing.T) { desc: "Program IPV4 entry recursively to MAC next-hop and verify with Telemetry and Traffic", fn: testRecursiveIPv4EntrywithMACNexthop, }, + { + name: "testRecursiveIPv4EntrywithVRFSelectionPolW", + desc: "Program IPV4 entry with VRF Selection Policy W and verify with Telemetry and Traffic.", + fn: testRecursiveIPv4EntrywithVrfPolW, + }, } // Each case will run with its own gRIBI fluent client. @@ -579,7 +783,6 @@ func TestRecursiveIPv4Entries(t *testing.T) { top: top, client: &client, } - tc.fn(t, args) }) } diff --git a/feature/gribi/otg_tests/base_hierarchical_route_installation_test/metadata.textproto b/feature/gribi/otg_tests/base_hierarchical_route_installation_test/metadata.textproto index 0e4fc636fba..665aff9ca0d 100644 --- a/feature/gribi/otg_tests/base_hierarchical_route_installation_test/metadata.textproto +++ b/feature/gribi/otg_tests/base_hierarchical_route_installation_test/metadata.textproto @@ -13,6 +13,8 @@ platform_exceptions: { ipv4_missing_enabled: true gribi_mac_override_with_static_arp: true interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true } } platform_exceptions: { @@ -22,9 +24,9 @@ platform_exceptions: { deviations: { explicit_port_speed: true explicit_interface_in_default_vrf: true - explicit_interface_ref_definition: true static_protocol_name: "static" interface_enabled: true + skip_pbf_with_decap_encap_vrf: true } } platform_exceptions: { diff --git a/feature/gribi/otg_tests/base_leader_election_test/base_leader_election_test.go b/feature/gribi/otg_tests/base_leader_election_test/base_leader_election_test.go index 2ed34099f14..adfe393f5cc 100644 --- a/feature/gribi/otg_tests/base_leader_election_test/base_leader_election_test.go +++ b/feature/gribi/otg_tests/base_leader_election_test/base_leader_election_test.go @@ -162,21 +162,21 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(atePort1.Name) dev := top.Devices().Add().SetName(atePort1.Name) eth := dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(dev.Name()) + eth.Connection().SetPortName(dev.Name()) ip := eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") ip.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) top.Ports().Add().SetName(atePort2.Name) dev = top.Devices().Add().SetName(atePort2.Name) eth = dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(dev.Name()) + eth.Connection().SetPortName(dev.Name()) ip = eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") ip.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) top.Ports().Add().SetName(atePort3.Name) dev = top.Devices().Add().SetName(atePort3.Name) eth = dev.Ethernets().Add().SetName(atePort3.Name + ".Eth").SetMac(atePort3.MAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(dev.Name()) + eth.Connection().SetPortName(dev.Name()) ip = eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") ip.SetAddress(atePort3.IPv4).SetGateway(dutPort3.IPv4).SetPrefix(uint32(atePort3.IPv4Len)) @@ -202,7 +202,7 @@ func testTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, s flowipv4.TxRx().Port().SetTxName(srcEndPoint.Name).SetRxName(dstEndPoint.Name) e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(srcEndPoint.MAC) - e1.Dst().SetChoice("value").SetValue(dstMac) + e1.Dst().SetValue(dstMac) v4 := flowipv4.Packet().Add().Ipv4() v4.Src().SetValue(srcEndPoint.IPv4) v4.Dst().Increment().SetStart(ateDstNetStart).SetCount(250) diff --git a/feature/gribi/otg_tests/basic_encap_test/README.md b/feature/gribi/otg_tests/basic_encap_test/README.md new file mode 100644 index 00000000000..e21fd5f9000 --- /dev/null +++ b/feature/gribi/otg_tests/basic_encap_test/README.md @@ -0,0 +1,421 @@ +# TE-16.1: basic encapsulation tests + +## Summary + +Test basic encapsulation behaviors. + +## Topology + +ATE port-1 <------> port-1 DUT +DUT port-2 <------> port-2 ATE +DUT port-3 <------> port-3 ATE +DUT port-4 <------> port-4 ATE +DUT port-5 <------> port-5 ATE + +## Baseline setup + +* Apply the following vrf selection policy to DUT port-1 + +``` +# DSCP value that will be matched to ENCAP_TE_VRF_A +* dscp_encap_a_1 = 10 +* dscp_encap_a_2 = 18 + +# DSCP value that will be matched to ENCAP_TE_VRF_B +* dscp_encap_b_1 = 20 +* dscp_encap_b_2 = 28 + +# DSCP value that will NOT be matched to any VRF for encapsulation. +* dscp_encap_no_match = 30 + +# Magic source IP addresses used in VRF selection policy +* ipv4_outer_src_111 = 198.51.100.111 +* ipv4_outer_src_222 = 198.51.100.222 + +# Magic destination MAC address +* magic_mac = 02:00:00:00:00:01` +``` + +``` +network-instances { + network-instance { + name: DEFAULT + policy-forwarding { + policies { + policy { + policy-id: "vrf_selection_policy_c" + rules { + rule { + sequence-id: 1 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 2 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 3 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 4 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 5 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 6 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 7 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 8 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 9 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 10 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 11 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 12 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 13 + ipv4 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 14 + ipv6 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 15 + ipv4 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 16 + ipv6 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 17 + action { + network-instance: "DEFAULT" + } + } + } + } + } + } + } +} +``` + +* Using gRIBI, install the following gRIBI AFTs, and validate the specified + behavior. + +``` +IPv6Entry {2015:aa8::/32 (ENCAP_TE_VRF_A)} -> NHG#10 (DEFAULT VRF) +IPv4Entry {138.0.11.0/24 (ENCAP_TE_VRF_A)} -> NHG#10 (DEFAULT VRF) -> { + {NH#201, DEFAULT VRF, weight:1}, + {NH#202, DEFAULT VRF, weight:3}, +} +NH#201 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.1" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} +NH#202 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.10.113.2" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} + +// 203.0.113.1 is the tunnel IP address. + +IPv4Entry {203.0.113.1/32 (TE_VRF_111)} -> NHG#1 (DEFAULT VRF) -> { + {NH#1, DEFAULT VRF, weight:1,ip_address=192.0.2.111}, + {NH#2, DEFAULT VRF, weight:3,ip_address=192.0.2.222}, +} +IPv4Entry {192.0.2.111/32 (DEFAULT VRF)} -> NHG#2 (DEFAULT VRF) -> { + {NH#10, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-2-interface}, + {NH#11, DEFAULT VRF, weight:3,mac_address:magic_mac, interface-ref:dut-port-3-interface}, +} +IPv4Entry {192.0.2.222/32 (DEFAUlT VRF)} -> NHG#3 (DEFAULT VRF) -> { + {NH#100, DEFAULT VRF, weight:2,mac_address:magic_mac, interface-ref:dut-port-4-interface}, + {NH#101, DEFAULT VRF, weight:3,mac_address:magic_mac, interface-ref:dut-port-5-interface}, +} + +// 203.10.113.2 is the tunnel IP address. Note that the NHG#1 is shared by both tunnels. + +IPv4Entry {203.10.113.2/32 (TE_VRF_111)} -> NHG#1 (DEFAULT VRF) -> +``` + +## Procedure + +#### Test-1, IPv4 traffic WCMP Encap + +Send packets to DUT port-1. The outer v4 header has the destination addresses +138.0.11.8. Validate that: + +* All egress packets (100%) are IPinIP (4in4) encapped. +* Packets are encapped to the tunnel IPs in the specified ratio. Specifically, + 25% of the egress packets should have the destination address 203.0.113.1, + and 75% of the egress packets should have the destination address + 203.10.113.2. +* The encapped/tunneled packets should be distributed hierarchically per the + weight. +* The DSCP value is copied from the inner header to the outer header. +* The TTL value is copied from the inner header to the outer header. + +#### Test-2, IPv6 traffic WCMP Encap + +Send packets to DUT port-1. The outer v6 header has the destination addresses +2015:aa8::1. Validate that: + +* All egress packets (100%) are 6in4 encapped. +* Packets are encapped to the tunnel IPs in the specified ratio. Specifically, + 25% of the egress packets should have the destination address 203.0.113.1, + and 75% of the egress packets should have the destination address + 203.10.113.2. +* The encapped/tunneled packets should be distributed hierarchically per the + weight. +* The DSCP value is copied from the inner header to the outer header. +* The TTL value is copied from the inner header to the outer header. + +#### Test-3, IPinIP Traffic Encap + +Tests support for encap of IPinIP IPv4 (IP protocol 4) traffic. Specifically, in +this test we’ll focus on tunnel traffic identification using +`ipv4_outer_src_111``and`ipv4_outer_src_222``. + +1. Send 4in4 (IP protocol 4) and 6in4 (IP protocol 41) packets to DUT port-1. + * The outer v4 header has the destination address 138.0.11.8. + * The outer v4 header has the source address that’s not + `ipv4_outer_src_111``or`ipv4_outer_src_222``. For example, we can use + 198.100.200.123. + * The outer v4 header should have DSCP value `dscp_encap_a_1`. +2. Validate that: + * All egress packets (100%) are IPinIP (4in4) encapped. + * Packets are encapped to the tunnel IPs in the specified ratio. + Specifically, 25% of the egress packets should have the destination + address 203.0.113.1, and 75% of the egress packets should have the + destination address 203.10.113.2. + * The encapped/tunneled packets should be distributed hierarchically per + the weight. + * The DSCP value is copied from the inner header to the outer header. + * The TTL value is copied from the inner header to the outer header. + +## Config Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Telemetry Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Protocol/RPC Parameter Coverage + +* gRIBI: + * Modify + * ModifyRequest + +## Required DUT platform + +vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths + /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/protocol: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/source-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/protocol: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/source-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decap-network-instance: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/post-decap-network-instance: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decap-fallback-network-instance: + + ## State paths + /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/otg_tests/basic_encap_test/basic_encap_test.go b/feature/gribi/otg_tests/basic_encap_test/basic_encap_test.go new file mode 100644 index 00000000000..a864237ba74 --- /dev/null +++ b/feature/gribi/otg_tests/basic_encap_test/basic_encap_test.go @@ -0,0 +1,1152 @@ +// 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 basic_encap_test implements TE-16.1 of the dcgate vendor testplan +package basic_encap_test + +import ( + "fmt" + "log" + "math/rand" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "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/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 ( + ipipProtocol = 4 + ipv6ipProtocol = 41 + udpProtocol = 17 + ethertypeIPv4 = oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4 + ethertypeIPv6 = oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6 + clusterPolicy = "vrf_selection_policy_c" + vrfDecap = "DECAP_TE_VRF" + vrfTransit = "TE_VRF_111" + vrfRepaired = "TE_VRF_222" + vrfEncapA = "ENCAP_TE_VRF_A" + vrfEncapB = "ENCAP_TE_VRF_B" + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + trafficDuration = 15 * time.Second + nhg10ID = 10 + nh201ID = 201 + nh202ID = 202 + nhg1ID = 1 + nh1ID = 1 + nh2ID = 2 + nhg2ID = 2 + nh10ID = 10 + nh11ID = 11 + nhg3ID = 3 + nh100ID = 100 + nh101ID = 101 + dscpEncapA1 = 10 + dscpEncapA2 = 18 + dscpEncapB1 = 20 + dscpEncapB2 = 28 + dscpEncapNoMatch = 30 + magicIp = "192.168.1.1" + magicMac = "02:00:00:00:00:01" + tunnelDstIP1 = "203.0.113.1" + tunnelDstIP2 = "203.0.113.2" + ipv4OuterSrc111 = "198.51.100.111" + ipv4OuterSrc222 = "198.51.100.222" + ipv4OuterSrcIpInIp = "198.100.200.123" + vipIP1 = "192.0.2.111" + vipIP2 = "192.0.2.222" + innerV4DstIP = "198.18.1.1" + innerV4SrcIP = "198.18.0.255" + InnerV6SrcIP = "2001:DB8::198:1" + InnerV6DstIP = "2001:DB8:2:0:192::10" + ipv4FlowIP = "138.0.11.8" + ipv4EntryPrefix = "138.0.11.0" + ipv4EntryPrefixLen = 24 + ipv6FlowIP = "2015:aa8::1" + ipv6EntryPrefix = "2015:aa8::" + ipv6EntryPrefixLen = 64 + ratioTunEncap1 = 0.25 // 1/4 + ratioTunEncap2 = 0.75 // 3/4 + ratioTunEncapTol = 0.05 // 5/100 + ttl = uint32(100) + trfDistTolerance = 0.02 + // observing on IXIA OTG: 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 in the chassis + otgMutliPortCaptureSupported = false + seqIDBase = uint32(10) +) + +var ( + otgDstPorts = []string{"port2", "port3", "port4", "port5"} + otgSrcPort = "port1" + wantWeights = []float64{ + 0.0625, // 1/4 * 1/4 - port1 + 0.1875, // 1/4 * 3/4 - port2 + 0.3, // 3/4 * 2/5 - port3 + 0.45, // 3/5 * 3/4 - port4 + } + noMatchWeight = []float64{ + 1, 0, 0, 0, + } +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + MAC: "02:01:00:00:00:01", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2: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, + } + + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:9", + IPv6Len: ipv6PrefixLen, + } + + otgPort3 = attrs.Attributes{ + Name: "otgPort3", + MAC: "02:00:03:01:01:01", + IPv4: "192.0.2.10", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:a", + IPv6Len: ipv6PrefixLen, + } + + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:D", + IPv6Len: ipv6PrefixLen, + } + + otgPort4 = attrs.Attributes{ + Name: "otgPort4", + MAC: "02:00:04:01:01:01", + IPv4: "192.0.2.14", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:E", + IPv6Len: ipv6PrefixLen, + } + + dutPort5 = attrs.Attributes{ + Desc: "dutPort5", + IPv4: "192.0.2.17", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:11", + IPv6Len: ipv6PrefixLen, + } + + otgPort5 = attrs.Attributes{ + Name: "otgPort5", + MAC: "02:00:05:01:01:01", + IPv4: "192.0.2.18", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:12", + 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, + } + + dutPort3DummyIP = attrs.Attributes{ + Desc: "dutPort3", + IPv4Sec: "192.0.2.25", + IPv4LenSec: ipv4PrefixLen, + } + + otgPort3DummyIP = attrs.Attributes{ + Desc: "otgPort3", + IPv4: "192.0.2.26", + IPv4Len: ipv4PrefixLen, + } + + dutPort4DummyIP = attrs.Attributes{ + Desc: "dutPort4", + IPv4Sec: "192.0.2.29", + IPv4LenSec: ipv4PrefixLen, + } + + otgPort4DummyIP = attrs.Attributes{ + Desc: "otgPort4", + IPv4: "192.0.2.30", + IPv4Len: ipv4PrefixLen, + } + + dutPort5DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.33", + IPv4LenSec: ipv4PrefixLen, + } + + otgPort5DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.34", + IPv4Len: ipv4PrefixLen, + } +) + +type pbrRule struct { + sequence uint32 + protocol uint8 + srcAddr string + dscpSet []uint8 + dscpSetV6 []uint8 + decapVrfSet []string + encapVrf string + etherType oc.NetworkInstance_PolicyForwarding_Policy_Rule_L2_Ethertype_Union +} + +type packetAttr struct { + dscp int + protocol int + ttl uint32 +} + +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 ( + fa4 = flowAttr{ + src: otgPort1.IPv4, + dst: ipv4FlowIP, + srcMac: otgPort1.MAC, + dstMac: dutPort1.MAC, + srcPort: otgSrcPort, + dstPorts: otgDstPorts, + topo: gosnappi.NewConfig(), + } + fa6 = flowAttr{ + src: otgPort1.IPv6, + dst: ipv6FlowIP, + srcMac: otgPort1.MAC, + dstMac: dutPort1.MAC, + srcPort: otgSrcPort, + dstPorts: otgDstPorts, + topo: gosnappi.NewConfig(), + } + faIPinIP = flowAttr{ + src: ipv4OuterSrcIpInIp, + dst: ipv4FlowIP, + 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) +} + +func TestBasicEncap(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) + + programEntries(t, dut, &c) + + test := []struct { + name string + pattr packetAttr + flows []gosnappi.Flow + weights []float64 + capturePorts []string + validateEncapRatio bool + }{ + { + name: fmt.Sprintf("Test1 IPv4 Traffic WCMP Encap dscp %d", dscpEncapA1), + pattr: packetAttr{dscp: dscpEncapA1, protocol: ipipProtocol, ttl: 99}, + flows: []gosnappi.Flow{fa4.getFlow("ipv4", "ip4a1", dscpEncapA1)}, + weights: wantWeights, + capturePorts: otgDstPorts, + validateEncapRatio: true, + }, + { + name: fmt.Sprintf("Test2 IPv6 Traffic WCMP Encap dscp %d", dscpEncapA1), + pattr: packetAttr{dscp: dscpEncapA1, protocol: ipv6ipProtocol, ttl: 99}, + flows: []gosnappi.Flow{fa6.getFlow("ipv6", "ip6a1", dscpEncapA1)}, + weights: wantWeights, + capturePorts: otgDstPorts, + validateEncapRatio: true, + }, + { + name: fmt.Sprintf("Test3 IPinIP Traffic WCMP Encap dscp %d", dscpEncapA1), + pattr: packetAttr{dscp: dscpEncapA1, protocol: ipipProtocol, ttl: 99}, + flows: []gosnappi.Flow{faIPinIP.getFlow("ipv4in4", "ip4in4a1", dscpEncapA1), + faIPinIP.getFlow("ipv6in4", "ip6in4a1", dscpEncapA1), + }, + weights: wantWeights, + capturePorts: otgDstPorts, + validateEncapRatio: true, + }, + { + name: fmt.Sprintf("No Match Dscp %d Traffic", dscpEncapNoMatch), + pattr: packetAttr{protocol: udpProtocol, dscp: dscpEncapNoMatch, ttl: 99}, + flows: []gosnappi.Flow{fa4.getFlow("ipv4", "ip4nm", dscpEncapNoMatch)}, + weights: noMatchWeight, + capturePorts: otgDstPorts[:1], + validateEncapRatio: false, + }, + } + + 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) + if strings.Contains(tc.name, "No Match Dscp") { + configDefaultRoute(t, dut, cidr(ipv4EntryPrefix, ipv4EntryPrefixLen), otgPort2.IPv4, cidr(ipv6EntryPrefix, ipv6EntryPrefixLen), otgPort2.IPv6) + defer gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Static(cidr(ipv4EntryPrefix, ipv4EntryPrefixLen)).Config()) + defer gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Static(cidr(ipv6EntryPrefix, ipv6EntryPrefixLen)).Config()) + } + if otgMutliPortCaptureSupported { + 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") + var tunCounter = validatePacketCapture(t, tcArgs, tc.capturePorts, &tc.pattr) + if tc.validateEncapRatio { + validateTunnelEncapRatio(t, tunCounter) + } + clearCapture(t, otg.OTG(), topo) + } else { + for _, port := range tc.capturePorts { + enableCapture(t, otg.OTG(), topo, []string{port}) + t.Log("Start capture and send traffic") + sendTraffic(t, tcArgs, tc.flows, true) + t.Log("Validate captured packet attributes") + var tunCounter = validatePacketCapture(t, tcArgs, []string{port}, &tc.pattr) + if tc.validateEncapRatio { + validateTunnelEncapRatio(t, tunCounter) + } + clearCapture(t, otg.OTG(), topo) + } + } + t.Log("Validate traffic flows") + validateTrafficFlows(t, tcArgs, tc.flows, false, true) + t.Log("Validate hierarchical traffic distribution") + validateTrafficDistribution(t, otg, tc.weights) + }) + } +} + +// getPbrRules returns pbrRule slice for cluster facing (clusterFacing = true) or wan facing +// interface (clusterFacing = false) +func getPbrRules(dut *ondatra.DUTDevice, clusterFacing bool) []pbrRule { + vrfDefault := deviations.DefaultNetworkInstance(dut) + var pbrRules = []pbrRule{ + { + sequence: 1, + protocol: ipipProtocol, + dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + decapVrfSet: []string{vrfDecap, vrfEncapA, vrfRepaired}, + srcAddr: ipv4OuterSrc222, + }, + { + sequence: 2, + protocol: ipv6ipProtocol, + dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + decapVrfSet: []string{vrfDecap, vrfEncapA, vrfRepaired}, + srcAddr: ipv4OuterSrc222, + }, + { + sequence: 3, + protocol: ipipProtocol, + dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + decapVrfSet: []string{vrfDecap, vrfEncapA, vrfTransit}, + srcAddr: ipv4OuterSrc111, + }, + { + sequence: 4, + protocol: ipv6ipProtocol, + dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + decapVrfSet: []string{vrfDecap, vrfEncapA, vrfTransit}, + srcAddr: ipv4OuterSrc111, + }, + { + sequence: 5, + protocol: ipipProtocol, + dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, + decapVrfSet: []string{vrfDecap, vrfEncapB, vrfRepaired}, + srcAddr: ipv4OuterSrc222, + }, + { + sequence: 6, + protocol: ipv6ipProtocol, + dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, + decapVrfSet: []string{vrfDecap, vrfEncapB, vrfRepaired}, + srcAddr: ipv4OuterSrc222, + }, + { + sequence: 7, + protocol: ipipProtocol, + dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, + decapVrfSet: []string{vrfDecap, vrfEncapB, vrfTransit}, + srcAddr: ipv4OuterSrc111, + }, + { + sequence: 8, + protocol: ipv6ipProtocol, + dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, + decapVrfSet: []string{vrfDecap, vrfEncapB, vrfTransit}, + srcAddr: ipv4OuterSrc111, + }, + { + sequence: 9, + protocol: ipipProtocol, + decapVrfSet: []string{vrfDecap, vrfDefault, vrfRepaired}, + srcAddr: ipv4OuterSrc222, + }, + { + sequence: 10, + protocol: ipv6ipProtocol, + decapVrfSet: []string{vrfDecap, vrfDefault, vrfRepaired}, + srcAddr: ipv4OuterSrc222, + }, + { + sequence: 11, + protocol: ipipProtocol, + decapVrfSet: []string{vrfDecap, vrfDefault, vrfTransit}, + srcAddr: ipv4OuterSrc111, + }, + { + sequence: 12, + protocol: ipv6ipProtocol, + decapVrfSet: []string{vrfDecap, vrfDefault, vrfTransit}, + srcAddr: ipv4OuterSrc111, + }, + } + + var encapRules = []pbrRule{ + { + sequence: 13, + dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + encapVrf: vrfEncapA, + }, + { + sequence: 14, + dscpSetV6: []uint8{dscpEncapA1, dscpEncapA2}, + encapVrf: vrfEncapA, + }, + { + sequence: 15, + dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, + encapVrf: vrfEncapB, + }, + { + sequence: 16, + dscpSetV6: []uint8{dscpEncapB1, dscpEncapB2}, + encapVrf: vrfEncapB, + }, + } + + var defaultClassRule = []pbrRule{ + { + sequence: 17, + encapVrf: vrfDefault, + }, + } + + var splitDefaultClassRules = []pbrRule{ + { + sequence: 17, + etherType: ethertypeIPv4, + encapVrf: vrfDefault, + }, + { + sequence: 18, + etherType: ethertypeIPv6, + encapVrf: vrfDefault, + }, + } + + if clusterFacing { + pbrRules = append(pbrRules, encapRules...) + } + + if deviations.PfRequireMatchDefaultRule(dut) { + pbrRules = append(pbrRules, splitDefaultClassRules...) + } else { + pbrRules = append(pbrRules, defaultClassRule...) + } + + return pbrRules +} + +// seqIDOffset returns sequence ID offset added with seqIDBase (10), to avoid sequences +// like 1, 10, 11, 12,..., 2, 21, 22, ... while being sent by Ondatra to the DUT. +// It now generates sequences like 11, 12, 13, ..., 19, 20, 21,..., 99. +func seqIDOffset(dut *ondatra.DUTDevice, i uint32) uint32 { + if deviations.PfRequireSequentialOrderPbrRules(dut) { + return i + seqIDBase + } + return i +} + +// configDefaultRoute configures a static route in DEFAULT network-instance. +func configDefaultRoute(t *testing.T, dut *ondatra.DUTDevice, v4Prefix, v4NextHop, v6Prefix, v6NextHop string) { + t.Logf("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(v4Prefix) + nh := sr.GetOrCreateNextHop("0") + nh.NextHop = oc.UnionString(v4NextHop) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) + 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) +} + +// configureNetworkInstance creates nonDefaultVRFs +func configureNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + c := &oc.Root{} + vrfs := []string{vrfDecap, vrfTransit, vrfRepaired, vrfEncapA, vrfEncapB} + for _, vrf := range vrfs { + ni := c.GetOrCreateNetworkInstance(vrf) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), ni) + } +} + +// cidr takes as input the IPv4 address and the Mask and returns the IP string in +// CIDR notation. +func cidr(ipv4 string, ones int) string { + return ipv4 + "/" + strconv.Itoa(ones) +} + +// getPbrPolicy creates PBR rules for cluster +func getPbrPolicy(dut *ondatra.DUTDevice, name string, clusterFacing bool) *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, clusterFacing) { + r := p.GetOrCreateRule(seqIDOffset(dut, pRule.sequence)) + r4 := r.GetOrCreateIpv4() + + if pRule.dscpSet != nil { + r4.DscpSet = pRule.dscpSet + } else if pRule.dscpSetV6 != nil { + r6 := r.GetOrCreateIpv6() + r6.DscpSet = pRule.dscpSetV6 + } + + if pRule.protocol != 0 { + r4.Protocol = oc.UnionUint8(pRule.protocol) + } + + if pRule.srcAddr != "" { + r4.SourceAddress = ygot.String(cidr(pRule.srcAddr, 32)) + } + + if len(pRule.decapVrfSet) == 3 { + ra := r.GetOrCreateAction() + ra.DecapNetworkInstance = ygot.String(pRule.decapVrfSet[0]) + ra.PostDecapNetworkInstance = ygot.String(pRule.decapVrfSet[1]) + ra.DecapFallbackNetworkInstance = ygot.String(pRule.decapVrfSet[2]) + } + 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) { + t.Log("Configure VRFs") + fptest.ConfigureDefaultNetworkInstance(t, dut) + configureNetworkInstance(t, dut) + t.Log("Configure Cluster facing VRF selection Policy") + pf := getPbrPolicy(dut, clusterPolicy, true) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), pf) +} + +func staticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + sb := &gnmi.SetBatch{} + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + portList := []*ondatra.Port{p2, p3, p4, p5} + for idx, p := range portList { + s := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(magicIp + "/32"), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + strconv.Itoa(idx): { + Index: ygot.String(strconv.Itoa(idx)), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(p.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(p.Name()).Config(), configStaticArp(p.Name(), magicIp, magicMac)) + } + sb.Set(t, dut) +} + +// programEntries pushes RIB entries on the DUT required for Encap functionality +func programEntries(t *testing.T, dut *ondatra.DUTDevice, c *gribi.Client) { + // push RIB entries + if deviations.GRIBIMACOverrideWithStaticARP(dut) { + c.AddNH(t, nh10ID, "MACwithIp", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Dest: otgPort2DummyIP.IPv4, Mac: magicMac}) + c.AddNH(t, nh11ID, "MACwithIp", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Dest: otgPort3DummyIP.IPv4, Mac: magicMac}) + c.AddNH(t, nh100ID, "MACwithIp", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Dest: otgPort4DummyIP.IPv4, Mac: magicMac}) + c.AddNH(t, nh101ID, "MACwithIp", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Dest: otgPort5DummyIP.IPv4, Mac: magicMac}) + c.AddNHG(t, nhg2ID, map[uint64]uint64{nh10ID: 1, nh11ID: 3}, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddNHG(t, nhg3ID, map[uint64]uint64{nh100ID: 2, nh101ID: 3}, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + } else if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + nh1, op1 := gribi.NHEntry(nh10ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), + fluent.InstalledInFIB, &gribi.NHOptions{Interface: p2.Name(), Mac: magicMac, Dest: magicIp}) + nh2, op2 := gribi.NHEntry(nh11ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), + fluent.InstalledInFIB, &gribi.NHOptions{Interface: p3.Name(), Mac: magicMac, Dest: magicIp}) + nh3, op3 := gribi.NHEntry(nh100ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), + fluent.InstalledInFIB, &gribi.NHOptions{Interface: p4.Name(), Mac: magicMac, Dest: magicIp}) + nh4, op4 := gribi.NHEntry(nh101ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), + fluent.InstalledInFIB, &gribi.NHOptions{Interface: p5.Name(), Mac: magicMac, Dest: magicIp}) + nhg1, op5 := gribi.NHGEntry(nhg2ID, map[uint64]uint64{nh10ID: 1, nh11ID: 3}, + deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + nhg2, op6 := gribi.NHGEntry(nhg3ID, map[uint64]uint64{nh100ID: 2, nh101ID: 3}, + deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddEntries(t, []fluent.GRIBIEntry{nh1, nh2, nh3, nh4, nhg1, nhg2}, + []*client.OpResult{op1, op2, op3, op4, op5, op6}) + } else { + c.AddNH(t, nh10ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dut.Port(t, "port2").Name(), Mac: magicMac}) + c.AddNH(t, nh11ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dut.Port(t, "port3").Name(), Mac: magicMac}) + c.AddNH(t, nh100ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dut.Port(t, "port4").Name(), Mac: magicMac}) + c.AddNH(t, nh101ID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, &gribi.NHOptions{Interface: dut.Port(t, "port5").Name(), Mac: magicMac}) + c.AddNHG(t, nhg2ID, map[uint64]uint64{nh10ID: 1, nh11ID: 3}, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddNHG(t, nhg3ID, map[uint64]uint64{nh100ID: 2, nh101ID: 3}, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + } + c.AddIPv4(t, cidr(vipIP1, 32), nhg2ID, deviations.DefaultNetworkInstance(dut), deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + + c.AddIPv4(t, cidr(vipIP2, 32), nhg3ID, deviations.DefaultNetworkInstance(dut), deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + + nh5, op7 := gribi.NHEntry(nh1ID, vipIP1, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + nh6, op8 := gribi.NHEntry(nh2ID, vipIP2, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + nhg3, op9 := gribi.NHGEntry(nhg1ID, map[uint64]uint64{nh1ID: 1, nh2ID: 3}, + deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddEntries(t, []fluent.GRIBIEntry{nh5, nh6, nhg3}, []*client.OpResult{op7, op8, op9}) + + c.AddIPv4(t, cidr(tunnelDstIP1, 32), nhg1ID, vrfTransit, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddIPv4(t, cidr(tunnelDstIP2, 32), nhg1ID, vrfTransit, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + + nh7, op9 := gribi.NHEntry(nh201ID, "Encap", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, + &gribi.NHOptions{Src: ipv4OuterSrc111, Dest: tunnelDstIP1, VrfName: vrfTransit}) + nh8, op10 := gribi.NHEntry(nh202ID, "Encap", deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB, + &gribi.NHOptions{Src: ipv4OuterSrc111, Dest: tunnelDstIP2, VrfName: vrfTransit}) + nhg4, op11 := gribi.NHGEntry(nhg10ID, map[uint64]uint64{nh201ID: 1, nh202ID: 3}, + deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddEntries(t, []fluent.GRIBIEntry{nh7, nh8, nhg4}, []*client.OpResult{op9, op10, op11}) + c.AddIPv4(t, cidr(ipv4EntryPrefix, ipv4EntryPrefixLen), nhg10ID, vrfEncapA, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddIPv4(t, cidr(ipv4EntryPrefix, ipv4EntryPrefixLen), nhg10ID, vrfEncapB, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddIPv6(t, cidr(ipv6EntryPrefix, ipv6EntryPrefixLen), nhg10ID, vrfEncapA, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddIPv6(t, cidr(ipv6EntryPrefix, ipv6EntryPrefixLen), nhg10ID, vrfEncapB, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + portList := []*ondatra.Port{p1, p2, p3, p4, p5} + + // configure interfaces + for idx, a := range []attrs.Attributes{dutPort1, dutPort2, dutPort3, dutPort4, dutPort5} { + 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 + } + gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), intf) + } + + // configure base PBF 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) + fptest.AssignToNetworkInstance(t, dut, p3.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p4.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p5.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + // apply PBF to src interface. + applyForwardingPolicy(t, dut, p1.Name()) + if deviations.GRIBIMACOverrideWithStaticARP(dut) { + staticARPWithSecondaryIP(t, dut) + } else if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + staticARPWithMagicUniversalIP(t, dut) + } +} + +// applyForwardingPolicy applies the forwarding policy on the interface. +func applyForwardingPolicy(t *testing.T, dut *ondatra.DUTDevice, ingressPort string) { + t.Logf("Applying forwarding policy on interface %v ... ", ingressPort) + 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) +} + +// configreOTG configures port1-5 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") + p3 := ate.Port(t, "port3") + p4 := ate.Port(t, "port4") + p5 := ate.Port(t, "port5") + + otgPort1.AddToOTG(topo, p1, &dutPort1) + otgPort2.AddToOTG(topo, p2, &dutPort2) + otgPort3.AddToOTG(topo, p3, &dutPort3) + otgPort4.AddToOTG(topo, p4, &dutPort4) + otgPort5.AddToOTG(topo, p5, &dutPort5) + + 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, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), topo, "IPv6") + return topo +} + +// 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) +} + +// 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) +} + +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 returns a flow of type ipv4, ipv4in4, ipv6in4 or ipv6 with dscp value passed in args. +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) + if flowType == "ipv4" || flowType == "ipv4in4" || flowType == "ipv6in4" { + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(fa.src) + v4.Dst().SetValue(fa.dst) + v4.TimeToLive().SetValue(ttl) + v4.Priority().Dscp().Phb().SetValue(dscp) + + // add inner ipv4 headers + if flowType == "ipv4in4" { + innerV4 := flow.Packet().Add().Ipv4() + innerV4.Src().SetValue(innerV4SrcIP) + innerV4.Dst().SetValue(innerV4DstIP) + innerV4.Priority().Dscp().Phb().SetValue(dscp) + } + + // add inner ipv6 headers + if flowType == "ipv6in4" { + innerV6 := flow.Packet().Add().Ipv6() + innerV6.Src().SetValue(InnerV6SrcIP) + innerV6.Dst().SetValue(InnerV6DstIP) + innerV6.TrafficClass().SetValue(dscp << 2) + } + } else 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) + } + udp := flow.Packet().Add().Udp() + udp.SrcPort().SetValues(randRange(50001, 10000)) + udp.DstPort().SetValues(randRange(50001, 10000)) + + return flow +} + +// 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") +} + +// validateTrafficFlows verifies that the flow on ATE should pass for good flow and fail for bad flow. +func validateTrafficFlows(t *testing.T, args *testArgs, flows []gosnappi.Flow, capture bool, match bool) { + + 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())) + + if outPkts == 0 { + t.Fatalf("OutPkts for flow %s is 0, want > 0", flow) + } + if match { + if got := ((outPkts - inPkts) * 100) / outPkts; got > 0 { + t.Fatalf("LossPct for flow %s: got %v, want 0", flow.Name(), got) + } + } else { + if got := ((outPkts - inPkts) * 100) / outPkts; got != 100 { + t.Fatalf("LossPct for flow %s: got %v, want 100", flow.Name(), got) + } + } + + } +} + +// validateTunnelEncapRatio checks whether tunnel1 and tunnel2 ecapped packets are withing specific ratio +func validateTunnelEncapRatio(t *testing.T, tunCounter map[string][]int) { + for port, counter := range tunCounter { + t.Logf("Validating tunnel encap ratio for %s", port) + tunnel1Pkts := float32(counter[0]) + tunnel2Pkts := float32(counter[1]) + if tunnel1Pkts == 0 { + t.Error("tunnel1 encapped packet count: got 0, want > 0") + } else if tunnel2Pkts == 0 { + t.Error("tunnel2 encapped packet count: got 0, want > 0") + } else { + totalPkts := tunnel1Pkts + tunnel2Pkts + if (tunnel1Pkts/totalPkts) < (ratioTunEncap1-ratioTunEncapTol) || + (tunnel1Pkts/totalPkts) > (ratioTunEncap1+ratioTunEncapTol) { + t.Errorf("tunnel1 encapsulation ratio (%f) is not within range", tunnel1Pkts/totalPkts) + } else if (tunnel2Pkts/totalPkts) < (ratioTunEncap2-ratioTunEncapTol) || + (tunnel2Pkts/totalPkts) > (ratioTunEncap2+ratioTunEncapTol) { + t.Errorf("tunnel2 encapsulation ratio (%f) is not within range", tunnel1Pkts/totalPkts) + } else { + t.Log("tunnel encapsulated packets are within ratio") + } + } + } +} + +// validatePacketCapture reads capture files and checks the encapped packet for desired protocol, dscp and ttl +func validatePacketCapture(t *testing.T, args *testArgs, otgPortNames []string, pa *packetAttr) map[string][]int { + tunCounter := make(map[string][]int) + for _, otgPortName := range otgPortNames { + bytes := args.ate.OTG().GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(otgPortName)) + f, err := os.CreateTemp("", ".pcap") + if err != nil { + t.Fatalf("ERROR: 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) + } + f.Close() + t.Logf("Verifying packet attributes captured on %s", otgPortName) + handle, err := pcap.OpenOffline(f.Name()) + if err != nil { + log.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + tunnel1Pkts := 0 + tunnel2Pkts := 0 + for packet := range packetSource.Packets() { + ipV4Layer := packet.Layer(layers.LayerTypeIPv4) + if ipV4Layer != nil { + v4Packet, _ := ipV4Layer.(*layers.IPv4) + if got := v4Packet.Protocol; got != layers.IPProtocol(pa.protocol) { + t.Errorf("Packet protocol type mismatch, got: %d, want %d", got, pa.protocol) + break + } + if got := int(v4Packet.TOS >> 2); got != pa.dscp { + t.Errorf("Dscp value mismatch, got %d, want %d", got, pa.dscp) + break + } + if !deviations.TTLCopyUnsupported(args.dut) { + if got := uint32(v4Packet.TTL); got != pa.ttl { + t.Errorf("TTL mismatch, got: %d, want: %d", got, pa.ttl) + break + } + } + if v4Packet.DstIP.String() == tunnelDstIP1 { + tunnel1Pkts++ + } + if v4Packet.DstIP.String() == tunnelDstIP2 { + tunnel2Pkts++ + } + + } + } + t.Logf("tunnel1, tunnel2 packet count on %s: %d , %d", otgPortName, tunnel1Pkts, tunnel2Pkts) + tunCounter[otgPortName] = []int{tunnel1Pkts, tunnel2Pkts} + } + return tunCounter + +} + +// 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) +} + +// normalize normalizes the input values so that the output values sum +// to 1.0 but reflect the proportions of the input. For example, +// input [1, 2, 3, 4] is normalized to [0.1, 0.2, 0.3, 0.4]. +func normalize(xs []uint64) (ys []float64, sum uint64) { + for _, x := range xs { + sum += x + } + ys = make([]float64, len(xs)) + for i, x := range xs { + ys[i] = float64(x) / float64(sum) + } + return ys, sum +} + +// validateTrafficDistribution checks if the packets received on receiving ports are within specificied weight ratios +func validateTrafficDistribution(t *testing.T, ate *ondatra.ATEDevice, wantWeights []float64) { + inFramesAllPorts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().PortAny().Counters().InFrames().State()) + // skip first entry that belongs to source port on ate + gotWeights, _ := normalize(inFramesAllPorts[1:]) + + t.Log("got ratio:", gotWeights) + t.Log("want ratio:", wantWeights) + if diff := cmp.Diff(wantWeights, gotWeights, cmpopts.EquateApprox(0, trfDistTolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } +} + +// configStaticArp configures static arp entries +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. +func staticARPWithSecondaryIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2DummyIP.NewOCInterface(p2.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), dutPort3DummyIP.NewOCInterface(p3.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), dutPort4DummyIP.NewOCInterface(p4.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), dutPort5DummyIP.NewOCInterface(p5.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), configStaticArp(p2.Name(), otgPort2DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), configStaticArp(p3.Name(), otgPort3DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), configStaticArp(p4.Name(), otgPort4DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), configStaticArp(p5.Name(), otgPort5DummyIP.IPv4, magicMac)) +} diff --git a/feature/gribi/otg_tests/basic_encap_test/metadata.textproto b/feature/gribi/otg_tests/basic_encap_test/metadata.textproto new file mode 100644 index 00000000000..3cbd8aef0f7 --- /dev/null +++ b/feature/gribi/otg_tests/basic_encap_test/metadata.textproto @@ -0,0 +1,51 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "36cc79b5-3766-4cb4-b83b-1baea1464de8" +plan_id: "TE-16.1" +description: "basic encapsulation tests" +testbed: TESTBED_DUT_ATE_8LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + gribi_mac_override_with_static_arp: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + ttl_copy_unsupported: true + } +} +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" + omit_l2_mtu: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + explicit_interface_in_default_vrf: true + static_protocol_name: "static" + ttl_copy_unsupported: true + } +} +tags: TAGS_DATACENTER_EDGE diff --git a/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md b/feature/gribi/otg_tests/dut_daemon_failure/README.md similarity index 90% rename from feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md rename to feature/gribi/otg_tests/dut_daemon_failure/README.md index a27b42da6d7..6cd0a79e2a2 100644 --- a/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md +++ b/feature/gribi/otg_tests/dut_daemon_failure/README.md @@ -29,11 +29,14 @@ Ensure that gRIBI entries are persisted over daemon failure. * Issuing a gRIBI Get RPC results in 203.0.113.0/24 being returned. -## Protocol/RPC Parameter Coverage - -* gRIBI - * ModifyRequest - * GetRequest +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Telemetry Parameter Coverage diff --git a/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go b/feature/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go similarity index 82% rename from feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go rename to feature/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go index 1207b3b0346..1558e4252f7 100644 --- a/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go +++ b/feature/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go @@ -23,16 +23,14 @@ import ( "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" "github.com/openconfig/featureprofiles/internal/gribi" "github.com/openconfig/featureprofiles/internal/otgutils" - gnps "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnoigo/system" - grps "github.com/openconfig/gribi/v1/proto/service" + grpb "github.com/openconfig/gribi/v1/proto/service" "github.com/openconfig/gribigo/fluent" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ondatra/gnoi" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" ) @@ -88,13 +86,6 @@ var ( IPv4: "192.0.2.6", IPv4Len: ipv4PrefixLen, } - - gRIBIDaemons = map[ondatra.Vendor]string{ - ondatra.ARISTA: "Gribi", - ondatra.CISCO: "emsd", - ondatra.JUNIPER: "rpd", - ondatra.NOKIA: "sr_gribi_server", - } ) // configInterfaceDUT configures the DUT interfaces. @@ -235,7 +226,7 @@ func verifyGRIBIGet(ctx context.Context, t *testing.T, clientA *gribi.Client, du entries := getResponse.GetEntry() var found bool for _, entry := range entries { - v := entry.Entry.(*grps.AFTEntry_Ipv4) + v := entry.Entry.(*grpb.AFTEntry_Ipv4) if prefix := v.Ipv4.GetPrefix(); prefix != "" { if prefix == ateDstNetCIDR { found = true @@ -249,36 +240,12 @@ func verifyGRIBIGet(ctx context.Context, t *testing.T, clientA *gribi.Client, du } } -// gNOIKillProcess kills a daemon on the DUT, given its name and pid. -func gNOIKillProcess(ctx context.Context, t *testing.T, args *testArgs, pName string, pID uint32) { - killResponse := gnoi.Execute(t, args.dut, system.NewKillProcessOperation().Name(pName).PID(pID).Signal(gnps.KillProcessRequest_SIGNAL_TERM).Restart(true)) - t.Logf("Got kill process response: %v\n\n", killResponse) -} - -// findProcessByName uses telemetry to find out the PID of a process -func findProcessByName(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, pName string) uint64 { - pList := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - var pID uint64 - for _, proc := range pList { - if proc.GetName() == pName { - pID = proc.GetPid() - t.Logf("Pid of daemon '%s' is '%d'", pName, pID) - } - } - return pID -} - func TestDUTDaemonFailure(t *testing.T) { start := time.Now() dut := ondatra.DUT(t, "dut") ctx := context.Background() - // Check if vendor specific gRIBI daemon name has been added to gRIBIDaemons var - if _, ok := gRIBIDaemons[dut.Vendor()]; !ok { - t.Fatalf("Please add support for vendor %v in var gRIBIDaemons", dut.Vendor()) - } - // Configure the DUT. t.Logf("Configure DUT") configureDUT(t, dut) @@ -343,30 +310,7 @@ func TestDUTDaemonFailure(t *testing.T) { }) t.Run("KillGRIBIDaemon", func(t *testing.T) { - - // Find the PID of gRIBI Daemon. - var pId uint64 - pName := gRIBIDaemons[dut.Vendor()] - t.Run("FindGRIBIDaemonPid", func(t *testing.T) { - - pId = findProcessByName(ctx, t, dut, pName) - if pId == 0 { - t.Fatalf("Couldn't find pid of gRIBI daemon '%s'", pName) - } else { - t.Logf("Pid of gRIBI daemon '%s' is '%d'", pName, pId) - } - }) - - // Kill gRIBI daemon through gNOI Kill Request. - t.Run("ExecuteGnoiKill", func(t *testing.T) { - // TODO - pid type is uint64 in oc-system model, but uint32 in gNOI Kill Request proto. - // Until the models are brought in line, typecasting the uint64 to uint32. - gNOIKillProcess(ctx, t, args, pName, uint32(pId)) - - // Wait for a bit for gRIBI daemon on the DUT to restart. - time.Sleep(30 * time.Second) - - }) + gnoi.KillProcess(t, dut, gnoi.GRIBI, gnoi.SigTerm, true, true) t.Logf("Time check: %s", time.Since(start)) diff --git a/feature/experimental/gribi/otg_tests/dut_daemon_failure/metadata.textproto b/feature/gribi/otg_tests/dut_daemon_failure/metadata.textproto similarity index 100% rename from feature/experimental/gribi/otg_tests/dut_daemon_failure/metadata.textproto rename to feature/gribi/otg_tests/dut_daemon_failure/metadata.textproto diff --git a/feature/gribi/otg_tests/encap_decap_scale/README.md b/feature/gribi/otg_tests/encap_decap_scale/README.md new file mode 100644 index 00000000000..def619ebb79 --- /dev/null +++ b/feature/gribi/otg_tests/encap_decap_scale/README.md @@ -0,0 +1,449 @@ +# TE-14.2: encap and decap scale + +## Summary + +Introduce encapsulation and decapsulation scale test on top of TE-14.1 + +## Topology + +Use the same topology as TE-14.1 + +## Variables + +``` +# DSCP value that will be matched to ENCAP_TE_VRF_A +* dscp_encap_a_1 = 10 +* dscp_encap_a_2 = 18 + +# DSCP value that will be matched to ENCAP_TE_VRF_B +* dscp_encap_b_1 = 20 +* dscp_encap_b_2 = 28 + +# DSCP value that will be matched to ENCAP_TE_VRF_C +* dscp_encap_c_1 = 30 +* dscp_encap_c_2 = 38 + +# DSCP value that will be matched to ENCAP_TE_VRF_D +* dscp_encap_d_1 = 40 +* dscp_encap_d_2 = 48 + +# Magic source IP addresses used in VRF selection policy +* ipv4_outer_src_111 = 198.51.100.111 +* ipv4_outer_src_222 = 198.51.100.222 + +# Magic destination MAC address +* magic_mac = 02:00:00:00:00:01 +``` +## Baseline + +1. Build the same scale setup as TE-14.1. +2. Apply `vrf_selection_policy_w` to DUT port-1. + +vrf_selection_policy_w +``` +network-instances { + network-instance { + name: DEFAULT + policy-forwarding { + policies { + policy { + policy-id: "vrf_selection_policy_w" + rules { + rule { + sequence-id: 1 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 2 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 3 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 4 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 5 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 6 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 7 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 8 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 9 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_c_1, dscp_encap_c_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_C" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 10 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_c_1, dscp_encap_c_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_C" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 11 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_c_1, dscp_encap_c_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_C" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 12 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_c_1, dscp_encap_c_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_C" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 13 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_d_1, dscp_encap_d_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_D" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 14 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_d_1, dscp_encap_d_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_D" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 15 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_d_1, dscp_encap_d_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_D" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 16 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_d_1, dscp_encap_d_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_D" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 17 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 18 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 19 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 20 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 21 + action { + network-instance: "DEFAULT" + } + } + } + } + } + } + } +} +``` + +## Procedure + +1. via gRIBI installs the following AFT entries: + * Add 4 VRFs for encapsulations: `ENCAP_TE_VRF_A`, `ENCAP_TE_VRF_B`, `ENCAP_TE_VRF_C` and `ENCAP_TE_VRF_D`. + * Add 1 VRF for decapsulation, `DECAP_TE_VRF`. + * Add 2 Tunnel VRFs, `TE_VRF_111` and `TE_VRF_222`. + * Inject 5000 IPv4Entry-ies and 5000 IPv6Entry-ies to each of the 4 encap VRFs. + * The entries in the encap VRFs should point to NextHopGroups in the `DEFAULT` VRF. Inject 800 such NextHopGroups in the DEFAULT VRF. + * Each NextHopGroup should have 8 NextHops where each NextHop points to a tunnel in the `TE_VRF_111`. In addition, the weights specified in the NextHopGroup should be co-prime and the sum of the weights should be 16. + * Inject `48` entries in the DECAP_TE_VRF where the entries have a mix of prefix lengths /22, /24, /26, and /28. + +2. Send the following packets to DUT-1 + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_a` + * outer_src: `ipv4_outer_src_222` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_a` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_a` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_a` + * proto: `41` + + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_b` + * outer_src: `ipv4_outer_src_222` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_b` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_b` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_b` + * proto: `41` + + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_c` + * outer_src: `ipv4_outer_src_222` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_c` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_c` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_c` + * proto: `41` + + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_d` + * outer_src: `ipv4_outer_src_222` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_d` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_d` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_d` + * proto: `41` + ``` + +3. Send traffic to DUT-1, covering all the installed v4 and v6 entries in the decap and encap VRFs. Validate that all traffic are all decapped per the DECAP VRFs and then encapsulated per the ENCAP VRFs and received as encapsulated packet by ATE. +4. Flush the `DECAP_TE_VRF`, install 5000 entries with fixed prefix length of /32, and repeat the same traffic validation. + +## Config Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Telemetry Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Required DUT platform + +vRX diff --git a/feature/gribi/otg_tests/encap_decap_scale/encap_decap_scale_test.go b/feature/gribi/otg_tests/encap_decap_scale/encap_decap_scale_test.go new file mode 100644 index 00000000000..886a2554e2e --- /dev/null +++ b/feature/gribi/otg_tests/encap_decap_scale/encap_decap_scale_test.go @@ -0,0 +1,1066 @@ +// 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 encap_decap_scale_test + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "net" + "net/netip" + "strings" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + fpargs "github.com/openconfig/featureprofiles/internal/args" + "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/iputil" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/tescale" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Settings for configuring the baseline testbed with the test +// topology. +// +// The testbed consists of ate:port1 -> dut:port1 +// and dut:port2 -> ate:port2. +// There are DefaultVRFIPv4NHCount SubInterfaces between dut:port2 +// and ate:port2 +// +// - ate:port1 -> dut:port1 subnet 192.0.2.0/30 +// - ate:port2 -> dut:port2 DefaultVRFIPv4NHCount Sub interfaces, e.g.: +// - ate:port2.0 -> dut:port2.0 VLAN-ID: 0 subnet 198.18.192.0/30 +// - ate:port2.1 -> dut:port2.1 VLAN-ID: 1 subnet 198.18.192.4/30 +// - ate:port2.2 -> dut:port2.2 VLAN-ID: 2 subnet 198.18.192.8/30 +// - ate:port2.i -> dut:port2.i VLAN-ID i subnet 198.18.x.(4*i)/30 (out of subnet 198.18.192.0/18) +const ( + ipv4PrefixLen = 30 // ipv4PrefixLen is the ATE and DUT interface IP prefix length + ipv6PrefixLen = 126 + IPBlockDefaultVRF = "198.18.128.0/18" + IPBlockNonDefaultVRF = "198.18.0.0/17" + tunnelSrcIPv4Addr = "198.51.100.99" // tunnelSrcIP represents Source IP of IPinIP Tunnel + StaticMAC = "00:1A:11:00:00:01" + subifBaseIP = "198.18.192.0" + nextHopStartIndex = 101 // set > 2 to avoid overlap with backup NH ids 1&2 + nextHopGroupStartIndex = 101 // set > 2 to avoid overlap with backup NHG ids 1&2 + dscpEncapA1 = 10 + dscpEncapA2 = 18 + dscpEncapB1 = 20 + dscpEncapB2 = 28 + dscpEncapC1 = 30 + dscpEncapC2 = 38 + dscpEncapD1 = 40 + dscpEncapD2 = 48 + dscpEncapNoMatch = 50 + ipv4OuterSrc111WithMask = "198.51.100.111/32" + ipv4OuterSrc222WithMask = "198.51.100.222/32" + ipv4OuterSrc222 = "198.51.100.222" + magicMac = "02:00:00:00:00:01" + prot4 = 4 + prot41 = 41 + vrfPolW = "vrf_selection_policy_w" + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niEncapTeVrfB = "ENCAP_TE_VRF_B" + niEncapTeVrfC = "ENCAP_TE_VRF_C" + niEncapTeVrfD = "ENCAP_TE_VRF_D" + niTeVrf111 = "vrf_t" + niTeVrf222 = "vrf_r" + niDefault = "DEFAULT" + IPBlockEncapA = "101.1.64.1/15" // IPBlockEncapA represents the ipv4 entries in EncapVRFA + IPBlockEncapB = "101.5.64.1/15" // IPBlockEncapB represents the ipv4 entries in EncapVRFB + IPBlockEncapC = "101.10.64.1/15" // IPBlockEncapC represents the ipv4 entries in EncapVRFC + IPBlockEncapD = "101.15.64.1/15" // IPBlockEncapD represents the ipv4 entries in EncapVRFD + IPBlockDecap = "102.0.0.1/15" // IPBlockDecap represents the ipv4 entries in Decap VRF + ipv4OuterSrc111 = "198.51.100.111" + gribiIPv4EntryVRF1111 = "203.0.113.1" + IPv6BlockEncapA = "2001:DB8:0:1::/64" + IPv6BlockEncapB = "2001:DB8:1:1::/64" + IPv6BlockEncapC = "2001:DB8:2:1::/64" + IPv6BlockEncapD = "2001:DB8:3:1::/64" + teVrf111TunnelCount = 1600 + teVrf222TunnelCount = 1600 + encapNhCount = 1600 + encapNhgcount = 800 + encapIPv4Count = 5000 + encapIPv6Count = 5000 + decapIPv4Count = 48 + decapScale = true + tolerancePct = 2 + seqIDBase = 10 +) + +var ( + encapNhSize = 8 + decapIPv4ScaleCount = 1000 +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + lastNhIndex int = 50000 + lastNhgIndex int + encapVrfAIPv4Enries = iputil.GenerateIPs(IPBlockEncapA, encapIPv4Count) + encapVrfBIPv4Enries = iputil.GenerateIPs(IPBlockEncapB, encapIPv4Count) + encapVrfCIPv4Enries = iputil.GenerateIPs(IPBlockEncapC, encapIPv4Count) + encapVrfDIPv4Enries = iputil.GenerateIPs(IPBlockEncapD, encapIPv4Count) + + encapVrfAIPv6Enries = createIPv6Entries(IPv6BlockEncapA, encapIPv6Count) + encapVrfBIPv6Enries = createIPv6Entries(IPv6BlockEncapB, encapIPv6Count) + encapVrfCIPv6Enries = createIPv6Entries(IPv6BlockEncapC, encapIPv6Count) + encapVrfDIPv6Enries = createIPv6Entries(IPv6BlockEncapD, encapIPv6Count) +) + +// routesParam holds parameters required for provisioning +// gRIBI IP entries, next-hop-groups and next-hops +type routesParam struct { + ipEntries []string + ipv6Entries []string + numUniqueNHs int + nextHops []string + nextHopVRF string + startNHIndex int + numUniqueNHGs int + numNHPerNHG int + startNHGIndex int + nextHopWeight []int + backupNHG int + tunnelSrcIP string +} + +// Parameters needed to provision next-hop with interface reference + static MAC +type nextHopIntfRef struct { + nextHopIPAddress string + subintfIndex uint32 + intfName string +} + +// Generate weights for next hops when assigning to a next-hop-group +// Weights are allocated such that there is no common divisor +func generateNextHopWeights(weightSum int, nextHopCount int) []int { + weights := []int{} + + switch { + case nextHopCount == 1: + weights = append(weights, weightSum) + case weightSum <= nextHopCount: + for i := 0; i < nextHopCount; i++ { + weights = append(weights, 1) + } + case nextHopCount == 2: + weights = append(weights, 1, weightSum-1) + default: + weights = append(weights, 1, 2) + rem := (weightSum - 1 - 2) % (nextHopCount - 2) + weights = append(weights, rem+(weightSum-1-2)/(nextHopCount-2)) + for i := 1; i < (nextHopCount - 2); i++ { + weights = append(weights, (weightSum-1-2)/(nextHopCount-2)) + } + } + return weights +} + +// incrementMAC increments the MAC by i. Returns error if the mac cannot be parsed or overflows the mac address space +func incrementMAC(mac string, i int) (string, error) { + macAddr, err := net.ParseMAC(mac) + if err != nil { + return "", err + } + convMac := binary.BigEndian.Uint64(append([]byte{0, 0}, macAddr...)) + convMac = convMac + uint64(i) + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, convMac) + if err != nil { + return "", err + } + newMac := net.HardwareAddr(buf.Bytes()[2:8]) + return newMac.String(), nil +} + +// incrementIP increments the IPv4 address by i +func incrementIP(ip string, i int) string { + ipAddr := net.ParseIP(ip) + convIP := binary.BigEndian.Uint32(ipAddr.To4()) + convIP = convIP + uint32(i) + newIP := make(net.IP, 4) + binary.BigEndian.PutUint32(newIP, convIP) + return newIP.String() +} + +type policyFwRule struct { + SeqID uint32 + protocol oc.UnionUint8 + dscpSet []uint8 + sourceAddr string + decapNi string + postDecapNi string + decapFallbackNi string +} + +func configureVrfSelectionPolicyW(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + dutPolFwdPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() + + pfRule1 := &policyFwRule{SeqID: 1, protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule2 := &policyFwRule{SeqID: 2, protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule3 := &policyFwRule{SeqID: 3, protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + pfRule4 := &policyFwRule{SeqID: 4, protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + + pfRule5 := &policyFwRule{SeqID: 5, protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule6 := &policyFwRule{SeqID: 6, protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule7 := &policyFwRule{SeqID: 7, protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + pfRule8 := &policyFwRule{SeqID: 8, protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + + pfRule9 := &policyFwRule{SeqID: 9, protocol: 4, dscpSet: []uint8{dscpEncapC1, dscpEncapC2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfC, decapFallbackNi: niTeVrf222} + pfRule10 := &policyFwRule{SeqID: 10, protocol: 41, dscpSet: []uint8{dscpEncapC1, dscpEncapC2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfC, decapFallbackNi: niTeVrf222} + pfRule11 := &policyFwRule{SeqID: 11, protocol: 4, dscpSet: []uint8{dscpEncapC1, dscpEncapC2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfC, decapFallbackNi: niTeVrf111} + pfRule12 := &policyFwRule{SeqID: 12, protocol: 41, dscpSet: []uint8{dscpEncapC1, dscpEncapC2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfC, decapFallbackNi: niTeVrf111} + + pfRule13 := &policyFwRule{SeqID: 13, protocol: 4, dscpSet: []uint8{dscpEncapD1, dscpEncapD2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfD, decapFallbackNi: niTeVrf222} + pfRule14 := &policyFwRule{SeqID: 14, protocol: 41, dscpSet: []uint8{dscpEncapD1, dscpEncapD2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfD, decapFallbackNi: niTeVrf222} + pfRule15 := &policyFwRule{SeqID: 15, protocol: 4, dscpSet: []uint8{dscpEncapD1, dscpEncapD2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfD, decapFallbackNi: niTeVrf111} + pfRule16 := &policyFwRule{SeqID: 16, protocol: 41, dscpSet: []uint8{dscpEncapD1, dscpEncapD2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfD, decapFallbackNi: niTeVrf111} + + pfRule17 := &policyFwRule{SeqID: 17, protocol: 4, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule18 := &policyFwRule{SeqID: 18, protocol: 41, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule19 := &policyFwRule{SeqID: 19, protocol: 4, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + pfRule20 := &policyFwRule{SeqID: 20, protocol: 41, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + + pfRuleList := []*policyFwRule{pfRule1, pfRule2, pfRule3, pfRule4, pfRule5, pfRule6, + pfRule7, pfRule8, pfRule9, pfRule10, pfRule11, pfRule12, pfRule13, pfRule14, + pfRule15, pfRule16, pfRule17, pfRule18, pfRule19, pfRule20} + + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + ni.SetType(oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + niP := ni.GetOrCreatePolicyForwarding() + niPf := niP.GetOrCreatePolicy(vrfPolW) + niPf.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + + for _, pfRule := range pfRuleList { + pfR := niPf.GetOrCreateRule(seqIDOffset(dut, pfRule.SeqID)) + pfRProtoIPv4 := pfR.GetOrCreateIpv4() + pfRProtoIPv4.Protocol = oc.UnionUint8(pfRule.protocol) + if pfRule.dscpSet != nil { + pfRProtoIPv4.DscpSet = pfRule.dscpSet + } + pfRProtoIPv4.SourceAddress = ygot.String(pfRule.sourceAddr) + pfRAction := pfR.GetOrCreateAction() + pfRAction.DecapNetworkInstance = ygot.String(pfRule.decapNi) + pfRAction.PostDecapNetworkInstance = ygot.String(pfRule.postDecapNi) + pfRAction.DecapFallbackNetworkInstance = ygot.String(pfRule.decapFallbackNi) + } + + if deviations.PfRequireMatchDefaultRule(dut) { + pfR21 := niPf.GetOrCreateRule(seqIDOffset(dut, 21)) + pfR21.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4) + pfRAction := pfR21.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + + pfR22 := niPf.GetOrCreateRule(seqIDOffset(dut, 22)) + pfR22.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6) + pfRAction = pfR22.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } else { + pfR := niPf.GetOrCreateRule(seqIDOffset(dut, 21)) + pfRAction := pfR.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } + + p1 := dut.Port(t, "port1") + interfaceID := p1.Name() + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = interfaceID + ".0" + } + + intf := niP.GetOrCreateInterface(interfaceID) + intf.ApplyVrfSelectionPolicy = ygot.String(vrfPolW) + intf.GetOrCreateInterfaceRef().Interface = ygot.String(p1.Name()) + intf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + intf.InterfaceRef = nil + } + gnmi.Replace(t, dut, dutPolFwdPath.Config(), niP) +} + +// createIPv6Entries creates IPv6 Entries given the totalCount and starting prefix +func createIPv6Entries(startIP string, count uint64) []string { + + _, netCIDR, _ := net.ParseCIDR(startIP) + netMask := binary.BigEndian.Uint64(netCIDR.Mask) + maskSize, _ := netCIDR.Mask.Size() + firstIP := binary.BigEndian.Uint64(netCIDR.IP) + lastIP := (firstIP & netMask) | (netMask ^ 0xffffffff) + entries := []string{} + + for i := firstIP; i <= lastIP; i++ { + ipv6 := make(net.IP, 16) + binary.BigEndian.PutUint64(ipv6, i) + // make last byte non-zero + p, _ := netip.ParsePrefix(fmt.Sprintf("%v/%d", ipv6, maskSize)) + entries = append(entries, p.Addr().Next().String()) + if uint64(len(entries)) >= count { + break + } + } + return entries +} + +// pushEncapEntries pushes IP entries in a specified Encap VRFs and tunnel VRFs. +// The entries in the encap VRFs should point to NextHopGroups in the DEFAULT VRF. +// Inject 800 such NextHopGroups in the DEFAULT VRF. Each NextHopGroup should have +// 8 NextHops where each NextHop points to a tunnel in the TE_VRF_111. +// In addition, the weights specified in the NextHopGroup should be co-prime and the +// sum of the weights should be 16. +func pushEncapEntries(t *testing.T, tunnelIPs []string, args *testArgs) { + vrfEntryParams := make(map[string]*routesParam) + + // Add 5k entries in ENCAP-VRF-A + vrfEntryParams[niEncapTeVrfA] = &routesParam{ + ipEntries: encapVrfAIPv4Enries, + ipv6Entries: encapVrfAIPv6Enries, + numUniqueNHs: encapNhgcount * encapNhSize, + nextHops: tunnelIPs, + nextHopVRF: niTeVrf111, + startNHIndex: lastNhIndex + 1, + numUniqueNHGs: encapNhgcount, + numNHPerNHG: 8, + nextHopWeight: generateNextHopWeights(16, 8), + startNHGIndex: lastNhgIndex + 1, + tunnelSrcIP: ipv4OuterSrc111, + } + + lastNhIndex = vrfEntryParams[niEncapTeVrfA].startNHIndex + vrfEntryParams[niEncapTeVrfA].numUniqueNHs + lastNhgIndex = vrfEntryParams[niEncapTeVrfA].startNHGIndex + vrfEntryParams[niEncapTeVrfA].numUniqueNHGs + + // Add 5k entries in ENCAP-VRF-B. + vrfEntryParams[niEncapTeVrfB] = &routesParam{ + ipEntries: encapVrfBIPv4Enries, + ipv6Entries: encapVrfBIPv6Enries, + numUniqueNHs: encapNhgcount * encapNhSize, + nextHops: tunnelIPs, + nextHopVRF: niTeVrf111, + startNHIndex: lastNhIndex + 1, + numUniqueNHGs: encapNhgcount, + numNHPerNHG: 8, + nextHopWeight: generateNextHopWeights(16, 8), + startNHGIndex: lastNhgIndex + 1, + tunnelSrcIP: ipv4OuterSrc222, + } + + lastNhIndex = vrfEntryParams[niEncapTeVrfB].startNHIndex + vrfEntryParams[niEncapTeVrfB].numUniqueNHs + lastNhgIndex = vrfEntryParams[niEncapTeVrfB].startNHGIndex + vrfEntryParams[niEncapTeVrfB].numUniqueNHGs + + // Add 5k entries in ENCAP-VRF-C + vrfEntryParams[niEncapTeVrfC] = &routesParam{ + ipEntries: encapVrfCIPv4Enries, + ipv6Entries: encapVrfCIPv6Enries, + numUniqueNHs: encapNhgcount * encapNhSize, + nextHops: tunnelIPs, + nextHopVRF: niTeVrf111, + startNHIndex: lastNhIndex + 1, + numUniqueNHGs: encapNhgcount, + numNHPerNHG: 8, + nextHopWeight: generateNextHopWeights(16, 8), + startNHGIndex: lastNhgIndex + 1, + tunnelSrcIP: ipv4OuterSrc111, + } + + lastNhIndex = vrfEntryParams[niEncapTeVrfC].startNHIndex + vrfEntryParams[niEncapTeVrfC].numUniqueNHs + lastNhgIndex = vrfEntryParams[niEncapTeVrfC].startNHGIndex + vrfEntryParams[niEncapTeVrfC].numUniqueNHGs + + // Add 5k entries in ENCAP-VRF-D + vrfEntryParams[niEncapTeVrfD] = &routesParam{ + ipEntries: encapVrfDIPv4Enries, + ipv6Entries: encapVrfDIPv6Enries, + numUniqueNHs: encapNhgcount * encapNhSize, + nextHops: tunnelIPs, + nextHopVRF: niTeVrf111, + startNHIndex: lastNhIndex + 1, + numUniqueNHGs: encapNhgcount, + numNHPerNHG: 8, + nextHopWeight: generateNextHopWeights(16, 8), + startNHGIndex: lastNhgIndex + 1, + tunnelSrcIP: ipv4OuterSrc222, + } + + lastNhIndex = vrfEntryParams[niEncapTeVrfD].startNHIndex + vrfEntryParams[niEncapTeVrfD].numUniqueNHs + lastNhgIndex = vrfEntryParams[niEncapTeVrfD].startNHGIndex + vrfEntryParams[niEncapTeVrfD].numUniqueNHGs + + for _, vrf := range []string{niEncapTeVrfA, niEncapTeVrfB, niEncapTeVrfC, niEncapTeVrfD} { + t.Logf("installing v4 entries in %s", vrf) + installEncapEntries(t, vrf, vrfEntryParams[vrf], args) + } +} + +func createAndSendTrafficFlows(t *testing.T, args *testArgs, decapEntries []string) { + t.Helper() + + flow1 := createFlow(&flowArgs{flowName: "flow1", isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: encapVrfAIPv4Enries, inHdrDscp: dscpEncapA1, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapA1, + }) + + flow2 := createFlow(&flowArgs{flowName: "flow2", isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: encapVrfBIPv4Enries, inHdrDscp: dscpEncapB1, + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapB1, + }) + + flow3 := createFlow(&flowArgs{flowName: "flow3", isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: encapVrfCIPv4Enries, inHdrDscp: dscpEncapC1, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapC1, + }) + + flow4 := createFlow(&flowArgs{flowName: "flow4", isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: encapVrfDIPv4Enries, inHdrDscp: dscpEncapD1, + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapD1, + }) + + flow5 := createFlow(&flowArgs{flowName: "flow5", isInnHdrV4: false, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: encapVrfAIPv6Enries, inHdrDscp: dscpEncapA2, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapA2, + }) + + flow6 := createFlow(&flowArgs{flowName: "flow6", isInnHdrV4: false, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: encapVrfBIPv6Enries, inHdrDscp: dscpEncapB2, + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapB2, + }) + + flow7 := createFlow(&flowArgs{flowName: "flow7", isInnHdrV4: false, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: encapVrfCIPv6Enries, inHdrDscp: dscpEncapC2, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapC2, + }) + + flow8 := createFlow(&flowArgs{flowName: "flow8", isInnHdrV4: false, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: encapVrfDIPv6Enries, inHdrDscp: dscpEncapD2, + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIPs: decapEntries, outHdrDscp: dscpEncapD2, + }) + + flowList := []gosnappi.Flow{flow1, flow2, flow3, flow4, flow5, flow6, flow7, flow8} + + args.top.Flows().Clear() + for _, flow := range flowList { + args.top.Flows().Append(flow) + } + + args.ate.OTG().PushConfig(t, args.top) + time.Sleep(30 * time.Second) + args.ate.OTG().StartProtocols(t) + // wait for glean adjacencies to be resolved + time.Sleep(240 * time.Second) + otgutils.WaitForARP(t, args.ate.OTG(), args.top, "IPv4") + + t.Logf("Starting traffic") + args.ate.OTG().StartTraffic(t) + time.Sleep(15 * time.Second) + t.Logf("Stop traffic") + args.ate.OTG().StopTraffic(t) + + flowNameList := []string{"flow1", "flow2", "flow3", "flow4", "flow5", "flow6", "flow7", "flow8"} + + otgutils.LogFlowMetrics(t, args.ate.OTG(), args.top) + otgutils.LogPortMetrics(t, args.ate.OTG(), args.top) + verifyTraffic(t, args, flowNameList) +} + +func verifyTraffic(t *testing.T, args *testArgs, flowList []string) { + t.Helper() + for _, flowName := range flowList { + t.Logf("Verifying flow metrics for the flow %s\n", flowName) + recvMetric := gnmi.Get(t, args.ate.OTG(), gnmi.OTG().Flow(flowName).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + + lostPackets := txPackets - rxPackets + var lossPct uint64 + if txPackets != 0 { + lossPct = lostPackets * 100 / txPackets + } else { + t.Errorf("Traffic stats are not correct %v", recvMetric) + } + if lossPct > tolerancePct { + t.Errorf("Traffic Loss Pct for Flow: %s\n got %v, want 0", flowName, lossPct) + } else { + t.Logf("Traffic Test Passed!") + } + } +} + +func pushDecapEntries(t *testing.T, args *testArgs) []string { + decapIPBlocks := []string{} + decapIPBlocks = append(decapIPBlocks, generateIPv4Subnets("102.51.100.1/22", 12)...) + decapIPBlocks = append(decapIPBlocks, generateIPv4Subnets("107.51.105.1/24", 12)...) + decapIPBlocks = append(decapIPBlocks, generateIPv4Subnets("112.51.110.1/26", 12)...) + decapIPBlocks = append(decapIPBlocks, generateIPv4Subnets("117.51.115.1/28", 12)...) + + nhIndex := uint64(lastNhIndex) + nhgIndex := uint64(lastNhgIndex) + decapEntries := []string{} + for i, ipBlock := range decapIPBlocks { + entries := iputil.GenerateIPs(ipBlock, 1) + decapEntries = append(decapEntries, entries...) + nhgIndex = nhgIndex + 1 + nhIndex = nhIndex + 1 + installDecapEntry(t, args, nhIndex, nhgIndex, decapIPBlocks[i]) + } + + lastNhIndex = int(nhIndex) + 1 + lastNhgIndex = int(nhgIndex) + 1 + + if err := awaitTimeout(args.ctx, args.client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries via client, got err: %v", err) + } + + t.Logf("Installed %v Decap VRF IPv4 entries with mixed prefix length", decapIPv4Count) + return decapEntries +} + +func pushDecapScaleEntries(t *testing.T, args *testArgs, decapEntries []string) { + nhIndex := uint64(lastNhIndex) + nhgIndex := uint64(lastNhgIndex) + for i := 0; i < len(decapEntries); i++ { + nhgIndex = nhgIndex + 1 + nhIndex = nhIndex + 1 + installDecapEntry(t, args, nhIndex, nhgIndex, decapEntries[i]+"/32") + } + + lastNhIndex = int(nhIndex) + 1 + lastNhgIndex = int(nhgIndex) + 1 + + if err := awaitTimeout(args.ctx, args.client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries via client, got err: %v", err) + } + + t.Logf("Installed %v Decap VRF IPv4 scale entries with prefix length 32", decapIPv4ScaleCount) +} + +func installDecapEntry(t *testing.T, args *testArgs, nhIndex, nhgIndex uint64, prefix string) { + decapNH := fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). + WithIndex(nhIndex).WithDecapsulateHeader(fluent.IPinIP) + if !deviations.DecapNHWithNextHopNIUnsupported(args.dut) { + decapNH.WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(args.dut)) + } + args.client.Modify().AddEntry(t, + decapNH, + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). + WithID(nhgIndex).AddNextHop(nhIndex, 1), + fluent.IPv4Entry().WithNetworkInstance(niDecapTeVrf). + WithPrefix(prefix).WithNextHopGroup(nhgIndex). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(args.dut)), + ) +} + +type flowArgs struct { + flowName string + outHdrSrcIP string + outHdrDstIPs []string + InnHdrSrcIP string + InnHdrDstIP []string + InnHdrSrcIPv6 string + InnHdrDstIPv6 []string + isInnHdrV4 bool + outHdrDscp uint32 + inHdrDscp uint32 +} + +func createFlow(flowValues *flowArgs) gosnappi.Flow { + rxNames := []string{} + for i := 0; i < *fpargs.DefaultVRFIPv4NHCount; i++ { + rxNames = append(rxNames, fmt.Sprintf(`dst%d.IPv4`, i)) + } + + flow := gosnappi.NewFlow().SetName(flowValues.flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{"atePort1.IPv4"}).SetRxNames(rxNames) + flow.Size().SetFixed(512) + flow.Rate().SetPps(100) + flow.Duration().FixedPackets().SetPackets(1000) + flow.Packet().Add().Ethernet().Src().SetValue(atePort1.MAC) + // Outer IP header + outerIPHdr := flow.Packet().Add().Ipv4() + outerIPHdr.Src().SetValue(flowValues.outHdrSrcIP) + outerIPHdr.Dst().SetValues(flowValues.outHdrDstIPs) + outerIPHdr.Priority().Dscp().Phb().SetValue(flowValues.outHdrDscp) + + if flowValues.isInnHdrV4 { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValues(flowValues.InnHdrDstIP) + innerIPHdr.Priority().Dscp().Phb().SetValue(flowValues.inHdrDscp) + } else { + innerIpv6Hdr := flow.Packet().Add().Ipv6() + innerIpv6Hdr.Src().SetValue(flowValues.InnHdrSrcIPv6) + innerIpv6Hdr.Dst().SetValues(flowValues.InnHdrDstIPv6) + innerIpv6Hdr.TrafficClass().SetValue(flowValues.inHdrDscp << 2) + } + return flow +} + +// installEncapEntries installs IPv4/IPv6 Entries in the VRF with the given nextHops and nextHopGroups using gRIBI. +func installEncapEntries(t *testing.T, vrf string, routeParams *routesParam, args *testArgs) { + // Provision next-hops + nextHopIndices := []uint64{} + for i := 0; i < routeParams.numUniqueNHs; i++ { + index := uint64(routeParams.startNHIndex + i) + args.client.Modify().AddEntry(t, fluent.NextHopEntry(). + WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). + WithIndex(index). + WithIPinIP(routeParams.tunnelSrcIP, routeParams.nextHops[i%len(routeParams.nextHops)]). + WithEncapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(routeParams.nextHopVRF). + WithElectionID(args.electionID.Low, args.electionID.High), + ) + nextHopIndices = append(nextHopIndices, index) + } + if err := awaitTimeout(args.ctx, args.client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries via client, got err: %v", err) + } + + // Provision next-hop-groups + nextHopGroupIndices := []uint64{} + for i := 0; i < routeParams.numUniqueNHGs; i++ { + index := uint64(routeParams.startNHGIndex + i) + nhgEntry := fluent.NextHopGroupEntry(). + WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). + WithID(index). + WithElectionID(args.electionID.Low, args.electionID.High) + if routeParams.backupNHG != 0 { + nhgEntry.WithBackupNHG(uint64(routeParams.backupNHG)) + } + for j := 0; j < routeParams.numNHPerNHG; j++ { + nhgEntry.AddNextHop(nextHopIndices[(i*routeParams.numNHPerNHG+j)%len(nextHopIndices)], uint64(routeParams.nextHopWeight[j])) + } + args.client.Modify().AddEntry(t, nhgEntry) + nextHopGroupIndices = append(nextHopGroupIndices, index) + } + if err := awaitTimeout(args.ctx, args.client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries via client, got err: %v", err) + } + + // Provision ipv4 entries in VRF + for i := range routeParams.ipEntries { + args.client.Modify().AddEntry(t, + fluent.IPv4Entry(). + WithPrefix(routeParams.ipEntries[i]+"/32"). + WithNetworkInstance(vrf). + WithNextHopGroup(nextHopGroupIndices[i%len(nextHopGroupIndices)]). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(args.dut))) + } + if err := awaitTimeout(args.ctx, args.client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries via client, got err: %v", err) + } + t.Logf("Installed entries VRF %s - IPv4 entry count: %d, next-hop-group count: %d (index %d - %d), next-hop count: %d (index %d - %d)", vrf, len(routeParams.ipEntries), len(nextHopGroupIndices), nextHopGroupIndices[0], nextHopGroupIndices[len(nextHopGroupIndices)-1], len(nextHopIndices), nextHopIndices[0], nextHopIndices[len(nextHopIndices)-1]) + + // Provision ipv6 entries in VRF + for i := range routeParams.ipv6Entries { + args.client.Modify().AddEntry(t, + fluent.IPv6Entry(). + WithPrefix(routeParams.ipv6Entries[i]+"/128"). + WithNetworkInstance(vrf). + WithNextHopGroup(nextHopGroupIndices[i%len(nextHopGroupIndices)]). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(args.dut))) + } + if err := awaitTimeout(args.ctx, args.client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries via client, got err: %v", err) + } + t.Logf("Installed entries VRF %s - IPv6 entry count: %d, next-hop-group count: %d (index %d - %d), next-hop count: %d (index %d - %d)", vrf, len(routeParams.ipv6Entries), len(nextHopGroupIndices), nextHopGroupIndices[0], nextHopGroupIndices[len(nextHopGroupIndices)-1], len(nextHopIndices), nextHopIndices[0], nextHopIndices[len(nextHopIndices)-1]) +} + +// configureDUT configures DUT interfaces and policy forwarding. Subinterfaces on DUT port2 are configured separately +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + d := &oc.Root{} + dutOCRoot := gnmi.OC() + + vrfs := []string{deviations.DefaultNetworkInstance(dut), niDecapTeVrf, + niEncapTeVrfA, niEncapTeVrfB, niEncapTeVrfC, niEncapTeVrfD, niTeVrf111, niTeVrf222} + createVrf(t, dut, vrfs) + + // configure Ethernet interfaces first + gnmi.Replace(t, dut, dutOCRoot.Interface(dp1.Name()).Config(), dutPort1.NewOCInterface(dp1.Name(), dut)) + configureInterfaceDUT(t, d, dut, dp2, "dst") + + // configure an L3 subinterface without vlan tagging under DUT port#1 + createSubifDUT(t, d, dut, dp1, 0, 0, dutPort1.IPv4, ipv4PrefixLen) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, dp1.Name(), deviations.DefaultNetworkInstance(dut), 0) + } +} + +// createVrf takes in a list of VRF names and creates them on the target devices. +func createVrf(t *testing.T, dut *ondatra.DUTDevice, vrfs []string) { + d := &oc.Root{} + for _, vrf := range vrfs { + if vrf != deviations.DefaultNetworkInstance(dut) { + // configure non-default VRFs + i := d.GetOrCreateNetworkInstance(vrf) + i.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), i) + } else { + // configure DEFAULT vrf + i := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + i.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Config(), i) + } + } +} + +// configureInterfaceDUT configures a single DUT port. +func configureInterfaceDUT(t *testing.T, d *oc.Root, dut *ondatra.DUTDevice, dutPort *ondatra.Port, desc string) { + ifName := dutPort.Name() + i := d.GetOrCreateInterface(ifName) + i.Description = ygot.String(desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + if deviations.ExplicitPortSpeed(dut) { + i.GetOrCreateEthernet().PortSpeed = fptest.GetIfSpeed(t, dutPort) + } + gnmi.Replace(t, dut, gnmi.OC().Interface(ifName).Config(), i) + t.Logf("DUT port %s configured", dutPort) +} + +// createSubifDUT creates a single L3 subinterface +func createSubifDUT(t *testing.T, d *oc.Root, dut *ondatra.DUTDevice, dutPort *ondatra.Port, index uint32, vlanID uint16, ipv4Addr string, ipv4SubintfPrefixLen int) *oc.Interface_Subinterface { + i := d.GetOrCreateInterface(dutPort.Name()) + s := i.GetOrCreateSubinterface(index) + if vlanID != 0 { + if deviations.DeprecatedVlanID(dut) { + s.GetOrCreateVlan().VlanId = oc.UnionUint16(vlanID) + } else { + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(vlanID) + } + } + s4 := s.GetOrCreateIpv4() + a := s4.GetOrCreateAddress(ipv4Addr) + a.PrefixLength = ygot.Uint8(uint8(ipv4SubintfPrefixLen)) + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + return s +} + +// configureDUTSubIfs configures DefaultVRFIPv4NHCount DUT subinterfaces on the target device +func configureDUTSubIfs(t *testing.T, dut *ondatra.DUTDevice, dutPort *ondatra.Port) []*nextHopIntfRef { + d := &oc.Root{} + nextHops := []*nextHopIntfRef{} + batchConfig := &gnmi.SetBatch{} + for i := 0; i < *fpargs.DefaultVRFIPv4NHCount; i++ { + index := uint32(i) + vlanID := uint16(i) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + vlanID = uint16(i) + 1 + } + dutIPv4 := incrementIP(subifBaseIP, (4*i)+2) + ateIPv4 := incrementIP(subifBaseIP, (4*i)+1) + mac, err := incrementMAC(atePort1.MAC, i+1) + if err != nil { + t.Fatalf("failed to increment MAC: %v", err) + } + gnmi.BatchUpdate(batchConfig, gnmi.OC().Interface(dutPort.Name()).Subinterface(index).Config(), createSubifDUT(t, d, dut, dutPort, index, vlanID, dutIPv4, ipv4PrefixLen)) + gnmi.BatchUpdate(batchConfig, gnmi.OC().Interface(dutPort.Name()).Subinterface(index).Config(), createStaticArpEntries(dutPort.Name(), index, ateIPv4, mac)) + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, dutPort.Name(), deviations.DefaultNetworkInstance(dut), index) + } + nextHops = append(nextHops, &nextHopIntfRef{ + nextHopIPAddress: ateIPv4, + subintfIndex: index, + intfName: dutPort.Name(), + }) + } + batchConfig.Set(t, dut) + return nextHops +} + +// configureATESubIfs configures *fpargs.DefaultVRFIPv4NHCount ATE subinterfaces on the target device +// It returns a slice of the corresponding ATE IPAddresses. +func configureATESubIfs(t *testing.T, top gosnappi.Config, atePort *ondatra.Port, dut *ondatra.DUTDevice) []string { + nextHops := []string{} + for i := 0; i < *fpargs.DefaultVRFIPv4NHCount; i++ { + vlanID := uint16(i) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + vlanID = uint16(i) + 1 + } + dutIPv4 := incrementIP(subifBaseIP, (4*i)+2) + ateIPv4 := incrementIP(subifBaseIP, (4*i)+1) + name := fmt.Sprintf(`dst%d`, i) + mac, err := incrementMAC(atePort1.MAC, i+1) + if err != nil { + t.Fatalf("failed to increment MAC: %v", err) + } + configureATE(t, top, atePort, vlanID, name, mac, dutIPv4, ateIPv4) + nextHops = append(nextHops, ateIPv4) + } + return nextHops +} + +// configureATE configures a single ATE layer 3 interface. +func configureATE(t *testing.T, top gosnappi.Config, atePort *ondatra.Port, vlanID uint16, Name, MAC, dutIPv4, ateIPv4 string) { + t.Helper() + + dev := top.Devices().Add().SetName(Name + ".Dev") + eth := dev.Ethernets().Add().SetName(Name + ".Eth").SetMac(MAC) + eth.Connection().SetPortName(atePort.ID()) + if vlanID != 0 { + eth.Vlans().Add().SetName(Name).SetId(uint32(vlanID)) + } + eth.Ipv4Addresses().Add().SetName(Name + ".IPv4").SetAddress(ateIPv4).SetGateway(dutIPv4).SetPrefix(uint32(atePort1.IPv4Len)) +} + +func configureATEPort1(t *testing.T, top gosnappi.Config) { + t.Helper() + + port1 := top.Ports().Add().SetName("port1") + iDut1Dev := top.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) +} + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, c *fluent.GRIBIClient, t testing.TB, timeout time.Duration) error { + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +// testArgs holds the objects needed by a test case. +type testArgs struct { + ctx context.Context + client *fluent.GRIBIClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + electionID gribi.Uint128 +} + +func TestGribiEncapDecapScaling(t *testing.T) { + dut := ondatra.DUT(t, "dut") + overrideScaleParams(dut) + + ate := ondatra.ATE(t, "ate") + ctx := context.Background() + gribic := dut.RawAPIs().GRIBI(t) + ap2 := ate.Port(t, "port2") + dp2 := dut.Port(t, "port2") + + top := gosnappi.NewConfig() + top.Ports().Add().SetName(ate.Port(t, "port2").ID()) + + configureDUT(t, dut) + // configure DefaultVRFIPv4NHCount L3 subinterfaces under DUT port#2 and assign them to DEFAULT vrf + // return slice containing interface name, subinterface index and ATE next hop IP that will be used for creating gRIBI next-hop entries + subIntfNextHops := configureDUTSubIfs(t, dut, dp2) + + configureATEPort1(t, top) + configureATESubIfs(t, top, ap2, dut) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + + // Connect gRIBI client to DUT referred to as gRIBI - using PRESERVE persistence and + // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithRedundancyMode(fluent.ElectedPrimaryClient).WithFIBACK() + + client.Start(ctx, t) + defer client.Stop(t) + + defer func() { + // Flush all entries after test. + if err := gribi.FlushAll(client); err != nil { + t.Error(err) + } + }() + + client.StartSending(ctx, t) + if err := awaitTimeout(ctx, client, t, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for client: %v", err) + } + eID := gribi.BecomeLeader(t, client) + + args := &testArgs{ + ctx: ctx, + client: client, + dut: dut, + ate: ate, + top: top, + electionID: eID, + } + + // Apply vrf_selection_policy_w to DUT port-1. + configureVrfSelectionPolicyW(t, dut) + + subIntfIPs := []string{} + for _, subIntf := range subIntfNextHops { + subIntfIPs = append(subIntfIPs, subIntf.nextHopIPAddress) + } + + vrfConfigs := tescale.BuildVRFConfig(dut, subIntfIPs, + tescale.Param{ + V4TunnelCount: *fpargs.V4TunnelCount, + V4TunnelNHGCount: *fpargs.V4TunnelNHGCount, + V4TunnelNHGSplitCount: *fpargs.V4TunnelNHGSplitCount, + EgressNHGSplitCount: *fpargs.EgressNHGSplitCount, + V4ReEncapNHGCount: *fpargs.V4ReEncapNHGCount, + }, + ) + for _, vrfConfig := range vrfConfigs { + // skip adding unwanted entries + if vrfConfig.Name == "vrf_rd" { + continue + } + entries := append(vrfConfig.NHs, vrfConfig.NHGs...) + entries = append(entries, vrfConfig.V4Entries...) + client.Modify().AddEntry(t, entries...) + if err := awaitTimeout(ctx, client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries, got err: %v", err) + } + t.Logf("Created %d NHs, %d NHGs, %d IPv4Entries in %s VRF", len(vrfConfig.NHs), len(vrfConfig.NHGs), len(vrfConfig.V4Entries), vrfConfig.Name) + } + + defaultIpv4Entries := []string{} + for _, v4Entry := range vrfConfigs[1].V4Entries { + ep, _ := v4Entry.EntryProto() + defaultIpv4Entries = append(defaultIpv4Entries, strings.Split(ep.GetIpv4().GetPrefix(), "/")[0]) + } + + // Inject 5000 IPv4Entry-ies and 5000 IPv6Entry-ies to each of the 4 encap VRFs. + pushEncapEntries(t, defaultIpv4Entries, args) + + if !deviations.GribiDecapMixedPlenUnsupported(dut) { + // Inject mixed length prefixes (48 entries) in the DECAP_TE_VRF. + decapEntries := pushDecapEntries(t, args) + // Send traffic and verify packets to DUT-1. + createAndSendTrafficFlows(t, args, decapEntries) + // Flush the DECAP_TE_VRF + if _, err := gribi.Flush(client, args.electionID, niDecapTeVrf); err != nil { + t.Error(err) + } + time.Sleep(240 * time.Second) + } + t.Log("installing scaled decap entries") + // Install decapIPv4ScaleCount entries with fixed prefix length of /32 in DECAP_TE_VRF. + decapScaleEntries := iputil.GenerateIPs(IPBlockDecap, decapIPv4ScaleCount) + pushDecapScaleEntries(t, args, decapScaleEntries) + // Send traffic and verify packets to DUT-1. + createAndSendTrafficFlows(t, args, decapScaleEntries) +} + +// createStaticArpEntries creates static ARP entries for the given subinterface. +func createStaticArpEntries(portName string, index uint32, ipv4Addr string, macAddr string) *oc.Interface_Subinterface { + d := &oc.Root{} + i := d.GetOrCreateInterface(portName) + s := i.GetOrCreateSubinterface(index) + s4 := s.GetOrCreateIpv4() + n4 := s4.GetOrCreateNeighbor(ipv4Addr) + n4.LinkLayerAddress = ygot.String(macAddr) + return s +} + +// generateIPv4Subnets creates IPv4 prefixes with a given seedBlock and subNets count +func generateIPv4Subnets(seedBlock string, subNets uint32) []string { + + _, netCIDR, _ := net.ParseCIDR(seedBlock) + maskSize, _ := netCIDR.Mask.Size() + incrSize := 0x00000001 << (32 - maskSize) + firstIP := binary.BigEndian.Uint32(netCIDR.IP) + entries := []string{} + for i := firstIP; subNets > 0; subNets-- { + ip := make(net.IP, 4) + binary.BigEndian.PutUint32(ip, i) + tip := netip.MustParsePrefix(fmt.Sprintf("%v/%d", ip, maskSize)) + if tip.Addr().IsValid() { + entries = append(entries, tip.String()) + } + i = i + uint32(incrSize) + } + return entries +} + +// seqIDOffset returns sequence ID offset added with seqIDBase (10), to avoid sequences +// like 1, 10, 11, 12,..., 2, 21, 22, ... while being sent by Ondatra to the DUT. +// It now generates sequences like 11, 12, 13, ..., 19, 20, 21,..., 99. +func seqIDOffset(dut *ondatra.DUTDevice, i uint32) uint32 { + if deviations.PfRequireSequentialOrderPbrRules(dut) { + return i + seqIDBase + } + return i +} + +// overrideScaleParams allows to override the default scale parameters based on the DUT vendor. +func overrideScaleParams(dut *ondatra.DUTDevice) { + if deviations.OverrideDefaultNhScale(dut) { + if dut.Vendor() == ondatra.CISCO { + *fpargs.V4TunnelCount = 1024 + encapNhSize = 2 + decapIPv4ScaleCount = 400 + } + } +} diff --git a/feature/gribi/otg_tests/encap_decap_scale/metadata.textproto b/feature/gribi/otg_tests/encap_decap_scale/metadata.textproto new file mode 100644 index 00000000000..105fd7697c4 --- /dev/null +++ b/feature/gribi/otg_tests/encap_decap_scale/metadata.textproto @@ -0,0 +1,51 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "0af851dd-7b11-43b7-a3d0-5c988b7de124" +plan_id: "TE-14.2" +description: "encap and decap scale" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + override_default_nh_scale: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + no_mix_of_tagged_and_untagged_subinterfaces: true + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + no_mix_of_tagged_and_untagged_subinterfaces: true + explicit_interface_ref_definition: true + gribi_decap_mixed_plen_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + omit_l2_mtu: true + decap_nh_with_nexthop_ni_unsupported: true + } +} diff --git a/feature/gribi/otg_tests/encap_frr/README.md b/feature/gribi/otg_tests/encap_frr/README.md new file mode 100644 index 00000000000..c16d78ca49d --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr/README.md @@ -0,0 +1,522 @@ +# TE-16.2: encapsulation FRR scenarios + +## Summary + +Test FRR behaviors with encapsulation scenarios. + +## Topology + +- ATE port-1 <------> port-1 DUT +- DUT port-2 <------> port-2 ATE +- DUT port-3 <------> port-3 ATE +- DUT port-4 <------> port-4 ATE +- DUT port-5 <------> port-5 ATE +- DUT port-6 <------> port-6 ATE +- DUT port-7 <------> port-7 ATE +- DUT port-8 <------> port-8 ATE + +## Baseline setup + +* Apply the following vrf selection policy to DUT port-1 + +``` +# DSCP value that will be matched to ENCAP_TE_VRF_A +* dscp_encap_a_1 = 10 +* dscp_encap_a_2 = 18 + +# DSCP value that will be matched to ENCAP_TE_VRF_B +* dscp_encap_b_1 = 20 +* dscp_encap_b_2 = 28 + +# DSCP value that will NOT be matched to any VRF for encapsulation. +* dscp_encap_no_match = 30 + +# Magic source IP addresses used in VRF selection policy +* ipv4_outer_src_111 = 198.51.100.111 +* ipv4_outer_src_222 = 198.51.100.222 + +# Magic destination MAC address +* magic_mac = 02:00:00:00:00:01` +``` + +``` +network-instances { + network-instance { + name: DEFAULT + policy-forwarding { + policies { + policy { + policy-id: "vrf_selection_policy_c" + rules { + rule { + sequence-id: 1 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 2 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 3 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 4 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 5 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 6 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 7 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 8 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 9 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 10 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 11 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 12 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 13 + ipv4 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 14 + ipv6 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 15 + ipv4 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 16 + ipv6 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 17 + action { + network-instance: "DEFAULT" + } + } + } + } + } + } + } +} +``` + +* Using gRIBI, install the following gRIBI AFTs, and validate the specified + behavior. + +``` +IPv4Entry {138.0.11.0/24 (ENCAP_TE_VRF_A)} -> NHG#101 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF, weight:1}, + {NH#102, DEFAULT VRF, weight:3}, +} +NH#101 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.1" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} +NH#102 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.10.113.2" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} + + +IPv4Entry {203.0.113.1/32 (TE_VRF_111)} -> NHG#1 (DEFAULT VRF) -> { + {NH#1, DEFAULT VRF, weight:1,ip_address=192.0.2.101}, + {NH#2, DEFAULT VRF, weight:3,ip_address=192.0.2.102}, + backup_next_hop_group: 1000 // re-encap to 203.0.113.100 +} +IPv4Entry {192.0.2.101/32 (DEFAULT VRF)} -> NHG#11 (DEFAULT VRF) -> { + {NH#11, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-2-interface}, + {NH#12, DEFAULT VRF, weight:3,mac_address:magic_mac, interface-ref:dut-port-3-interface}, +} +IPv4Entry {192.0.2.102/32 (DEFAUlT VRF)} -> NHG#12 (DEFAULT VRF) -> { + {NH#13, DEFAULT VRF, weight:2,mac_address:magic_mac, interface-ref:dut-port-4-interface}, +} + +NHG#1000 (DEFAULT VRF) { + {NH#1000, DEFAULT VRF} +} +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.100" + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} + +IPv4Entry {203.0.113.100/32 (TE_VRF_222)} -> NHG#2 (DEFAULT VRF) -> { + {NH#3, DEFAULT VRF, weight:1,ip_address=192.0.2.103}, + backup_next_hop_group: 2000 // decap and fallback to DEFAULT VRF +} +IPv4Entry {192.0.2.103/32 (DEFAULT VRF)} -> NHG#13 (DEFAULT VRF) -> { + {NH#14, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-5-interface}, +} + +// 203.10.113.2 is the tunnel IP address. Note that the NHG#3 is different than NHG#1. + +IPv4Entry {203.10.113.2/32 (TE_VRF_111)} -> NHG#3 (DEFAULT VRF) -> { + {NH#4, DEFAULT VRF, weight:1,ip_address=192.0.2.104}, + backup_next_hop_group: 1001 // re-encap to 203.0.113.101 +} +IPv4Entry {192.0.2.104/32 (DEFAULT VRF)} -> NHG#14 (DEFAULT VRF) -> { + {NH#15, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-6-interface}, +} +NHG#1001 (DEFAULT VRF) { + {NH#1001, DEFAULT VRF} +} +NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.101" + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} +IPv4Entry {203.0.113.101/32 (TE_VRF_222)} -> NHG#4 (DEFAULT VRF) -> { + {NH#5, DEFAULT VRF, weight:1,ip_address=192.0.2.105}, + backup_next_hop_group: 2000 // decap and fallback to DEFAULT VRF +} +IPv4Entry {192.0.2.105/32 (DEFAULT VRF)} -> NHG#15 (DEFAULT VRF) -> { + {NH#16, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-7-interface}, +} + +NHG#2000 (DEFAULT VRF) { + {NH#2000, DEFAULT VRF} +} +NH#2000 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} +``` + +* Install a BGP route resolved by ISIS in default VRF to route 138.0.11.8 + traffic out of DUT port-8. + +## Procedure + +At the start of each of the following scenarios, ensure: + +* all ports are up and baseline is reset as above. +* Send packets to DUT port-1. The outer v4 header has the destination + addresses 138.0.11.8. +* Validate that traffic is encapsulated to 203.0.113.1 and 203.0.113.2, and is + distributed per the hierarchical weights. + +#### Test-1, primary encap unviable but backup encap viable for single tunnel + +Tests that if the primary NHG for an encap tunnel is unviable, then the traffic +for that tunnel is re-encaped into its specified backup tunnel. + +1. Shutdown DUT port-2, port-3, and port-4. +2. Validate that corresponding traffic that was encapped to 203.0.113.1 should + now be encapped with 203.0.113.100. + +#### Test-2, primary and backup encap unviable for single tunnel + +Tests that if the primary NHGs of both the encap tunnel and its backup tunnel +are unviable, then the traffic for that tunnel is not encapped. Instead, that +fraction of traffic should be forwarded according to the BGP/IS-IS routes in the +DEFAULT VRF. + +1. Shutdown DUT port-2, port-3, port-4 and port-5. +2. Validate that corresponding traffic (25% of the total traffic) that was + encapped to 203.0.113.1 are no longer encapped, and forwarded per BGP-ISIS + routes (in the default VRF) out of DUT port-8. + +#### Test-3, primary encap unviable with backup to routing for single tunnel + +Tests that if the primary NHGs of both the encap tunnel is unviable, and its +backup specifies fallback to routing, then the traffic for that tunnel is not +encapped. Instead, that fraction of traffic should be forwarded according to the +BGP/IS-IS routes in the DEFAULT VRF. + +1. Update `NHG#1000` to the following: + +``` +NHG#1000 (Default VRF) { + {NH#1000, DEFAULT VRF} +} +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} +``` + +1. Validate that all traffic is distributed per the hierarchical weights. +2. Shutdown DUT port-2, port-3, and port-4. +3. Validate that corresponding traffic (25% of the total traffic) that was + encapped to 203.0.113.1 are no longer encapped, and forwarded per BGP-ISIS + routes (in the default VRF) out of DUT port-8. + +#### Test-4, primary encap unviable but backup encap viable for all tunnels + +Tests that if the primary NHG for all encap tunnels are unviable, then the +traffic is re-encaped into the specified backup tunnels. This test ensures that +the device does not withdraw this IPv4Entry and sends this traffic to routing. + +1. Shutdown DUT port-2, port-3, port-4 and port-6. +2. Validate that traffic is encapsulated to 203.0.113.100 and 203.0.113.101 per + the weights. + +#### Test-5, primary and backup encap unviable for all tunnels + +Tests that if the primary NHGs of both the encap tunnel and its backup tunnel +are unviable for all tunnels in the encap NHG, then the traffic for that cluster +prefix is not encapped. Instead, that traffic should be forwarded according to +the BGP/IS-IS routes in the DEFAULT VRF. This stresses the double failure +handling, and ensures that the fallback to DEFAULT is activated through the +backup NHGs of the tunnels instead of withdrawing the IPv4Entry. + +1. Shutdown DUT port-2, port-3, port-4, port-5, port-6 and port-7. +2. Validate that all traffic is no longer encapsulated, and is all egressing + out of DUT port-8 per the BGP-ISIS routes in the default VRF. + +#### Test-6, primary encap unviable with backup to routing for all tunnels + +Tests that if the primary NHGs of both the encap tunnel is unviable, and its +backup specifies fallback to routing, for all tunnels in the encap NHG, then the +traffic for that cluster prefix is not encapped. Instead, that traffic should be +forwarded according to the BGP/IS-IS routes in the DEFAULT VRF. This stresses +the double failure handling, and ensures that the fallback to DEFAULT is +activated through the backup NHGs of the tunnels instead of withdrawing the +IPv4Entry. + +1. Update `NHG#1000` and `NHG#1001` to the following: + +``` +NHG#1000 (Default VRF) { {NH#1000, DEFAULT VRF} } + +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} + +NHG#1001 (Default VRF) { {NH#1001, DEFAULT VRF} } + +NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} +``` + +1. Validate that all traffic is distributed per the hierarchical weights. +2. Shutdown DUT port-2, port-3, and port-4, and port-6. +3. Validate that all traffic is no longer encapsulated, and is all egressing + out of DUT port-8 per the BGP-ISIS routes in the default VRF. + +#### Test-7, no match in encap VRF + +Test that if there is no lookup match in the encap VRF, then the traffic should +be routed to the DEFAULT VRF for further lookup. + +1. In `ENCAP_TE_VRF_A`, Add an 0/0 static route pointing to the DEFAULT VRF. +2. Send traffic with destination address 20.0.0.1, which should produce no + match in `ENCAP_TE_VRF_A`. +3. Validate that the traffic is routed per the BGP-ISIS routes (in the DEFAULT + VR) out of DUT port-8. + +## Config Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Telemetry Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Required DUT platform + +- vRX diff --git a/feature/gribi/otg_tests/encap_frr/encap_frr_test.go b/feature/gribi/otg_tests/encap_frr/encap_frr_test.go new file mode 100644 index 00000000000..80138f55dd3 --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr/encap_frr_test.go @@ -0,0 +1,1234 @@ +// 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 encap_frr_test + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "net" + "os" + "sort" + "strconv" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "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" + baseScenario "github.com/openconfig/featureprofiles/internal/encapfrr" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + spb "github.com/openconfig/gnoi/system" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/testt" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Settings for configuring the baseline testbed with the test +// topology. +// +// ATE port-1 <------> port-1 DUT +// DUT port-2 <------> port-2 ATE +// DUT port-3 <------> port-3 ATE +// DUT port-4 <------> port-4 ATE +// DUT port-5 <------> port-5 ATE +// DUT port-6 <------> port-6 ATE +// DUT port-7 <------> port-7 ATE +// DUT port-8 <------> port-8 ATE + +const ( + plenIPv4 = 30 + plenIPv6 = 126 + dscpEncapA1 = 10 + ipv4OuterSrcAddr = "198.100.200.123" + ipv4InnerDst = "138.0.11.8" + ipv4OuterDst333 = "192.58.200.7" + noMatchEncapDest = "20.0.0.1" + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niRepairVrf = "REPAIR_VRF" + tolerancePct = 2 + tolerance = 0.2 + encapFlow = "encapFlow" + correspondingHopLimit = 64 + magicIP = "192.168.1.1" + magicMAC = "02:00:00:00:00:01" + maskLen24 = "24" + maskLen32 = "32" + 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" + niTEVRF111 = "TE_VRF_111" + niTEVRF222 = "TE_VRF_222" + ipv4OuterSrc111Addr = "198.51.100.111" + ipv4OuterSrc222Addr = "198.51.100.222" + gribiIPv4EntryVRF1111 = "203.0.113.1" + gribiIPv4EntryVRF1112 = "203.0.113.2" + gribiIPv4EntryVRF2221 = "203.0.113.100" + gribiIPv4EntryVRF2222 = "203.0.113.101" + gribiIPv4EntryEncapVRF = "138.0.11.0" + + 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" + + ateSrcPort = "ate:port1" + ateSrcPortMac = "02:00:01:01:01:01" + ateSrcNetName = "srcnet" + ateSrcNet = "198.51.100.0" + ateSrcNetCIDR = "198.51.100.0/24" + ateSrcNetFirstIP = "198.51.100.1" + ateSrcNetCount = 250 + ipOverIPProtocol = 4 + + checkEncap = true + wantLoss = true + + // Chassis reboot variables + oneSecondInNanoSecond = 1e9 + rebootDelay = 120 + // Maximum reboot time is 900 seconds (15 minutes). + maxRebootTime = 900 + // Maximum wait time for all components to be in responsive state + maxCompWaitTime = 900 +) + +var ( + portsIPv4 = map[string]string{ + "dut:port1": "192.0.2.1", + "ate:port1": "192.0.2.2", + + "dut:port2": "192.0.2.5", + "ate:port2": "192.0.2.6", + + "dut:port3": "192.0.2.9", + "ate:port3": "192.0.2.10", + + "dut:port4": "192.0.2.13", + "ate:port4": "192.0.2.14", + + "dut:port5": "192.0.2.17", + "ate:port5": "192.0.2.18", + + "dut:port6": "192.0.2.21", + "ate:port6": "192.0.2.22", + + "dut:port7": "192.0.2.25", + "ate:port7": "192.0.2.26", + + "dut:port8": "192.0.2.29", + "ate:port8": "192.0.2.30", + } + portsIPv6 = map[string]string{ + "dut:port1": "2001:db8::192:0:2:1", + "ate:port1": "2001:db8::192:0:2:2", + + "dut:port2": "2001:db8::192:0:2:5", + "ate:port2": "2001:db8::192:0:2:6", + + "dut:port3": "2001:db8::192:0:2:9", + "ate:port3": "2001:db8::192:0:2:a", + + "dut:port4": "2001:db8::192:0:2:d", + "ate:port4": "2001:db8::192:0:2:e", + + "dut:port5": "2001:db8::192:0:2:11", + "ate:port5": "2001:db8::192:0:2:12", + + "dut:port6": "2001:db8::192:0:2:15", + "ate:port6": "2001:db8::192:0:2:16", + + "dut:port7": "2001:db8::192:0:2:19", + "ate:port7": "2001:db8::192:0:2:1a", + + "dut:port8": "2001:db8::192:0:2:1d", + "ate:port8": "2001:db8::192:0:2:1e", + } + otgPortDevices []gosnappi.Device + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.11", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + loopbackIntfName string + atePortNamelist []string +) + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +type testArgs struct { + client *fluent.GRIBIClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgConfig gosnappi.Config + top gosnappi.Config + electionID gribi.Uint128 + otg *otg.OTG +} + +// incrementMAC increments the MAC by i. Returns error if the mac cannot be parsed or overflows the mac address space +func incrementMAC(mac string, i int) (string, error) { + macAddr, err := net.ParseMAC(mac) + if err != nil { + return "", err + } + convMac := binary.BigEndian.Uint64(append([]byte{0, 0}, macAddr...)) + convMac = convMac + uint64(i) + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, convMac) + if err != nil { + return "", err + } + newMac := net.HardwareAddr(buf.Bytes()[2:8]) + return newMac.String(), nil +} + +func sortPorts(ports []*ondatra.Port) []*ondatra.Port { + sort.Slice(ports, func(i, j int) bool { + idi, idj := ports[i].ID(), ports[j].ID() + li, lj := len(idi), len(idj) + if li == lj { + return idi < idj + } + return li < lj // "port2" < "port10" + }) + return ports +} + +// dutInterface builds a DUT interface ygot struct for a given port +// according to portsIPv4. Returns nil if the port has no IP address +// mapping. +func dutInterface(p *ondatra.Port, dut *ondatra.DUTDevice) *oc.Interface { + id := fmt.Sprintf("%s:%s", p.Device().ID(), p.ID()) + i := &oc.Interface{ + Name: ygot.String(p.Name()), + Description: ygot.String(p.String()), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + } + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + ipv4, ok := portsIPv4[id] + if !ok { + return nil + } + ipv6, ok := portsIPv6[id] + if !ok { + return nil + } + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + + a := s4.GetOrCreateAddress(ipv4) + a.PrefixLength = ygot.Uint8(plenIPv4) + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + a6 := s6.GetOrCreateAddress(ipv6) + a6.PrefixLength = ygot.Uint8(plenIPv6) + + return i +} + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice, dutPortList []*ondatra.Port) { + dc := gnmi.OC() + for _, dp := range dutPortList { + + if i := dutInterface(dp, dut); i != nil { + gnmi.Replace(t, dut, dc.Interface(dp.Name()).Config(), i) + } else { + t.Fatalf("No address found for port %v", dp) + } + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + for _, dp := range dut.Ports() { + fptest.AssignToNetworkInstance(t, dut, dp.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + } + if deviations.ExplicitPortSpeed(dut) { + for _, dp := range dut.Ports() { + fptest.SetPortSpeed(t, dp) + } + } + + loopbackIntfName = netutil.LoopbackInterface(t, dut, 0) + lo0 := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + loop1 := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, dc.Interface(loopbackIntfName).Config(), loop1) + } else { + v4, ok := ipv4Addrs[0].Val() + if ok { + dutlo0Attrs.IPv4 = v4.GetIp() + } + v6, ok := ipv6Addrs[0].Val() + if ok { + dutlo0Attrs.IPv6 = v6.GetIp() + } + t.Logf("Got DUT IPv4 loopback address: %v", dutlo0Attrs.IPv4) + t.Logf("Got DUT IPv6 loopback address: %v", dutlo0Attrs.IPv6) + } + if deviations.GRIBIMACOverrideWithStaticARP(dut) { + baseScenario.StaticARPWithSpecificIP(t, dut) + } +} + +// configureGribiRoute configures Gribi route as per the requirements +func configureGribiRoute(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, client *fluent.GRIBIClient) { + t.Helper() + + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, client) + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2000).WithDecapsulateHeader(fluent.IPinIP).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2000).AddNextHop(2000, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in TE_VRF_222 + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3).WithIPAddress(gribiIPv4EntryDefVRF3), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2).AddNextHop(3, 1).WithBackupNHG(2000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF222).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF2221+"/"+maskLen32).WithNextHopGroup(2), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(5).WithIPAddress(gribiIPv4EntryDefVRF5), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(4).AddNextHop(5, 1).WithBackupNHG(2000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF222).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF2222+"/"+maskLen32).WithNextHopGroup(4), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF222IPList := []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF2222} + for ip := range teVRF222IPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF222IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1000).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2221). + WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1000, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1001).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2222). + WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1001).AddNextHop(1001, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in TE_VRF_111 + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1).WithIPAddress(gribiIPv4EntryDefVRF1), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2).WithIPAddress(gribiIPv4EntryDefVRF2), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1).AddNextHop(1, 1).AddNextHop(2, 3).WithBackupNHG(1000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF111).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(4).WithIPAddress(gribiIPv4EntryDefVRF4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3).AddNextHop(4, 1).WithBackupNHG(1001), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF111).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(3), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF111IPList := []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112} + for ip := range teVRF111IPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF111IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for prefixes in ENCAP_TE_VRF_A + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(101).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1111). + WithNextHopNetworkInstance(niTEVRF111), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(102).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1112). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(101).AddNextHop(101, 1).AddNextHop(102, 3), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(101), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) +} + +func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName, dutAreaAddress, dutSysID string) { + t.Helper() + d := &oc.Root{} + dutConfIsisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + if deviations.ISISLevelEnabled(dut) { + isisLevel2.Enabled = ygot.Bool(true) + } + + isisIntf := isis.GetOrCreateInterface(intfName) + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + isisIntfLevelAfi := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfi.Metric = ygot.Uint32(200) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfi.Enabled = nil + } + + gnmi.Replace(t, dut, dutConfIsisPath.Config(), prot) +} + +func bgpCreateNbr(localAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutlo0Attrs.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) + pg1.PeerAs = ygot.Uint32(localAs) + + bgpNbr := bgp.GetOrCreateNeighbor(otgIsisPort8LoopV4) + bgpNbr.PeerGroup = ygot.String(peerGrpName1) + bgpNbr.PeerAs = ygot.Uint32(localAs) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbrT := bgpNbr.GetOrCreateTransport() + localAddressLeaf := dutlo0Attrs.IPv4 + if dut.Vendor() == ondatra.CISCO { + localAddressLeaf = loopbackIntfName + } + bgpNbrT.LocalAddress = ygot.String(localAddressLeaf) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + + return niProto +} + +func verifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntf string) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + dutIntf = dutIntf + ".0" + } + nbrPath := statePath.Interface(dutIntf) + query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", dutIntf) + t.Fatal("No IS-IS adjacencies reported.") + } +} + +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + nbrPath := bgpPath.Neighbor(otgIsisPort8LoopV4) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", otgIsisPort8LoopV4, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", otgIsisPort8LoopV4, state, want) + } +} + +// configureOTG configures the topology of the ATE. +func configureOTG(t testing.TB, otg *otg.OTG, atePorts []*ondatra.Port) gosnappi.Config { + t.Helper() + config := gosnappi.NewConfig() + pmd100GFRPorts := []string{} + for i, ap := range atePorts { + if ap.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, ap.ID()) + } + // DUT and ATE ports are connected by the same names. + dutid := fmt.Sprintf("dut:%s", ap.ID()) + ateid := fmt.Sprintf("ate:%s", ap.ID()) + + port := config.Ports().Add().SetName(ap.ID()) + atePortNamelist = append(atePortNamelist, port.Name()) + portName := fmt.Sprintf("atePort%s", strconv.Itoa(i)) + dev := config.Devices().Add().SetName(portName) + macAddress, _ := incrementMAC(ateSrcPortMac, i) + eth := dev.Ethernets().Add().SetName(portName + ".Eth").SetMac(macAddress) + eth.Connection().SetPortName(port.Name()) + eth.Ipv4Addresses().Add().SetName(portName + ".IPv4"). + SetAddress(portsIPv4[ateid]).SetGateway(portsIPv4[dutid]). + SetPrefix(plenIPv4) + eth.Ipv6Addresses().Add().SetName(portName + ".IPv6"). + SetAddress(portsIPv6[ateid]).SetGateway(portsIPv6[dutid]). + SetPrefix(plenIPv6) + + otgPortDevices = append(otgPortDevices, dev) + if i == 7 { + iDut8LoopV4 := dev.Ipv4Loopbacks().Add().SetName("Port8LoopV4").SetEthName(eth.Name()) + iDut8LoopV4.SetAddress(otgIsisPort8LoopV4) + iDut8LoopV6 := dev.Ipv6Loopbacks().Add().SetName("Port8LoopV6").SetEthName(eth.Name()) + iDut8LoopV6.SetAddress(otgIsisPort8LoopV6) + isisDut := dev.Isis().SetName("ISIS1").SetSystemId(otgSysID1) + isisDut.Basic().SetIpv4TeRouterId(portsIPv4[ateid]).SetHostname(isisDut.Name()).SetLearnedLspFilter(true) + isisDut.Interfaces().Add().SetEthName(dev.Ethernets().Items()[0].Name()). + SetName("devIsisInt1"). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + // Advertise OTG Port8 loopback address via ISIS. + isisPort2V4 := dev.Isis().V4Routes().Add().SetName("ISISPort8V4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(otgIsisPort8LoopV4).SetPrefix(32) + isisPort2V6 := dev.Isis().V6Routes().Add().SetName("ISISPort8V6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(otgIsisPort8LoopV6).SetPrefix(uint32(128)) + iDutBgp := dev.Bgp().SetRouterId(otgIsisPort8LoopV4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(iDut8LoopV4.Name()).Peers().Add().SetName(ap.ID() + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(dutlo0Attrs.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.Capability().SetIpv4Unicast(true).SetIpv6Unicast(true) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(port.Name() + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(otgIsisPort8LoopV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32). + SetCount(1).SetStep(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(noMatchEncapDest).SetPrefix(32). + SetCount(1).SetStep(1) + } + + } + config.Captures().Add().SetName("packetCapture"). + SetPortNames([]string{atePortNamelist[1], atePortNamelist[2], atePortNamelist[3], atePortNamelist[4], + atePortNamelist[5], atePortNamelist[6], atePortNamelist[7]}). + SetFormat(gosnappi.CaptureFormat.PCAP) + + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := config.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + pb, _ := config.Marshal().ToProto() + t.Log(pb.GetCaptures()) + return config +} + +func createFlow(t *testing.T, config gosnappi.Config, otg *otg.OTG, trafficDestIP string) { + t.Helper() + + config.Flows().Clear() + + flow1 := gosnappi.NewFlow().SetName(encapFlow) + flow1.Metrics().SetEnable(true) + flow1.TxRx().Device(). + SetTxNames([]string{otgPortDevices[0].Name() + ".IPv4"}). + SetRxNames([]string{otgPortDevices[1].Name() + ".IPv4", otgPortDevices[2].Name() + ".IPv4", otgPortDevices[3].Name() + ".IPv4", + otgPortDevices[4].Name() + ".IPv4", otgPortDevices[5].Name() + ".IPv4", otgPortDevices[6].Name() + ".IPv4", + otgPortDevices[7].Name() + ".IPv4", + }) + flow1.Size().SetFixed(512) + flow1.Rate().SetPps(100) + flow1.Duration().Continuous() + ethHeader1 := flow1.Packet().Add().Ethernet() + ethHeader1.Src().SetValue(ateSrcPortMac) + IPHeader := flow1.Packet().Add().Ipv4() + IPHeader.Src().Increment().SetCount(1000).SetStep("0.0.0.1").SetStart(ipv4OuterSrcAddr) + IPHeader.Dst().SetValue(trafficDestIP) + IPHeader.Priority().Dscp().Phb().SetValue(dscpEncapA1) + UDPHeader := flow1.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + + config.Flows().Append(flow1) + + t.Logf("Pushing traffic flows to OTG and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) +} + +func startCapture(t *testing.T, args *testArgs, capturePortList []string) gosnappi.ControlState { + t.Helper() + args.otgConfig.Captures().Clear() + args.otgConfig.Captures().Add().SetName("packetCapture"). + SetPortNames(capturePortList). + SetFormat(gosnappi.CaptureFormat.PCAP) + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + args.otg.SetControlState(t, cs) + return cs +} + +func sendTraffic(t *testing.T, args *testArgs, capturePortList []string, cs gosnappi.ControlState) { + t.Helper() + t.Logf("Starting traffic") + args.otg.StartTraffic(t) + time.Sleep(15 * time.Second) + t.Logf("Stop traffic") + args.otg.StopTraffic(t) + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + args.otg.SetControlState(t, cs) +} + +func verifyTraffic(t *testing.T, args *testArgs, capturePortList []string, loadBalancePercent []float64, wantLoss, checkEncap bool, headerDstIP map[string][]string) { + t.Helper() + t.Logf("Verifying flow metrics for the flow: encapFlow\n") + recvMetric := gnmi.Get(t, args.otg, gnmi.OTG().Flow(encapFlow).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + var lossPct uint64 + if txPackets != 0 { + lossPct = lostPackets * 100 / txPackets + } else { + t.Errorf("Traffic stats are not correct %v", recvMetric) + } + if wantLoss { + if lossPct < 100-tolerancePct { + t.Errorf("Traffic is expected to fail %s\n got %v, want 100%% failure", encapFlow, lossPct) + } else { + t.Logf("Traffic Loss Test Passed!") + } + } else { + if lossPct > tolerancePct { + t.Errorf("Traffic Loss Pct for Flow: %s\n got %v, want 0", encapFlow, lossPct) + } else { + t.Logf("Traffic Test Passed!") + } + } + t.Log("Verify packet load balancing as per the programmed weight") + validateTrafficDistribution(t, args.ate, loadBalancePercent) + var pcapFileList []string + for _, capturePort := range capturePortList { + bytes := args.otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(capturePort)) + pcapFileName, err := os.CreateTemp("", "pcap") + if err != nil { + t.Errorf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := pcapFileName.Write(bytes); err != nil { + t.Errorf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + pcapFileName.Close() + pcapFileList = append(pcapFileList, pcapFileName.Name()) + } + validatePackets(t, pcapFileList, checkEncap, headerDstIP) + args.otgConfig.Captures().Clear() + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) +} + +func validatePackets(t *testing.T, filename []string, checkEncap bool, headerDstIP map[string][]string) { + t.Helper() + for index, file := range filename { + fileStat, err := os.Stat(file) + if err != nil { + t.Errorf("Filestat for pcap file failed %s", err) + } + fileSize := fileStat.Size() + if fileSize > 0 { + handle, err := pcap.OpenOffline(file) + if err != nil { + t.Errorf("Unable to open the pcap file, error: %s", err) + } else { + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + if checkEncap { + validateTrafficEncap(t, packetSource, headerDstIP, index) + } + } + defer handle.Close() + } + } +} + +func validateTrafficEncap(t *testing.T, packetSource *gopacket.PacketSource, headerDstIP map[string][]string, index int) { + t.Helper() + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket, _ := ipLayer.(*layers.IPv4) + encapHeaderListLength := len(headerDstIP["outerIP"]) + if index <= encapHeaderListLength-1 { + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipInnerLayer != nil { + destIP := ipPacket.DstIP.String() + t.Logf("Outer dest ip in received packet %s", destIP) + if ipPacket.DstIP.String() != headerDstIP["outerIP"][index] { + t.Errorf("Packets are not encapsulated") + } + ipInnerPacket, _ := ipInnerLayer.(*layers.IPv4) + if ipInnerPacket.DstIP.String() != headerDstIP["innerIP"][index] { + t.Errorf("Packets are not encapsulated") + } + t.Logf("Traffic for encap routes passed.") + break + } else { + t.Errorf("Packet is not encapsulated") + } + } else if index >= encapHeaderListLength || encapHeaderListLength == 0 { + if ipPacket.DstIP.String() == otgIsisPort8LoopV4 { + continue + } else if ipPacket.DstIP.String() != headerDstIP["innerIP"][0] { + destIP := ipPacket.DstIP.String() + t.Logf("Dest ip in received packet %s", destIP) + t.Errorf("Packets are encapsulated which is not expected") + } else { + t.Logf("Traffic for non-encap routes passed.") + break + } + } + } +} + +func verifyPortStatus(t *testing.T, args *testArgs, portList []string, portStatus bool) { + wantStatus := oc.Interface_OperStatus_UP + if !portStatus { + wantStatus = oc.Interface_OperStatus_DOWN + } + for _, port := range portList { + p := args.dut.Port(t, port) + t.Log("Check for port status") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), 1*time.Minute, wantStatus) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if operStatus != wantStatus { + t.Errorf("Get(DUT %v oper status): got %v, want %v", port, operStatus, wantStatus) + } + } +} + +// setDUTInterfaceState sets the admin state on the dut interface +func setDUTInterfaceWithState(t testing.TB, dut *ondatra.DUTDevice, p *ondatra.Port, state bool) { + dc := gnmi.OC() + i := &oc.Interface{} + i.Enabled = ygot.Bool(state) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.Name = ygot.String(p.Name()) + gnmi.Update(t, dut, dc.Interface(p.Name()).Config(), i) +} + +func portState(t *testing.T, args *testArgs, portList []string, portEnabled bool) { + t.Logf("Change port enable state to %t", portEnabled) + if deviations.ATEPortLinkStateOperationsUnsupported(args.ate) { + for _, port := range portList { + p := args.dut.Port(t, port) + if portEnabled { + setDUTInterfaceWithState(t, args.dut, p, true) + } else { + setDUTInterfaceWithState(t, args.dut, p, false) + } + } + } else { + var portNames []string + for _, p := range portList { + portNames = append(portNames, args.ate.Port(t, p).ID()) + } + portStateAction := gosnappi.NewControlState() + if portEnabled { + portStateAction.Port().Link().SetPortNames(portNames).SetState(gosnappi.StatePortLinkState.UP) + } else { + portStateAction.Port().Link().SetPortNames(portNames).SetState(gosnappi.StatePortLinkState.DOWN) + } + args.ate.OTG().SetControlState(t, portStateAction) + } +} + +func normalize(xs []uint64) (ys []float64, sum uint64) { + for _, x := range xs { + sum += x + } + ys = make([]float64, len(xs)) + for i, x := range xs { + ys[i] = float64(x) / float64(sum) + } + return ys, sum +} + +func validateTrafficDistribution(t *testing.T, ate *ondatra.ATEDevice, wantWeights []float64) { + inFramesAllPorts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().PortAny().Counters().InFrames().State()) + // Skip source port, Port1. + gotWeights, _ := normalize(inFramesAllPorts[1:]) + + t.Log("got ratio:", gotWeights) + t.Log("want ratio:", wantWeights) + if diff := cmp.Diff(wantWeights, gotWeights, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } +} + +func FetchUniqueItems(t *testing.T, s []string) []string { + itemExisted := make(map[string]bool) + var uniqueList []string + for _, item := range s { + if _, ok := itemExisted[item]; !ok { + itemExisted[item] = true + uniqueList = append(uniqueList, item) + } else { + t.Logf("Detected duplicated item: %v", item) + } + } + return uniqueList +} + +func ChassisReboot(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + cases := []struct { + desc string + rebootRequest *spb.RebootRequest + }{ + { + desc: "Reboot chassis with delay", + rebootRequest: &spb.RebootRequest{ + Method: spb.RebootMethod_COLD, + Delay: rebootDelay * oneSecondInNanoSecond, + Message: "Reboot chassis with delay", + Force: true, + }}, + } + + versions := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().SoftwareVersion().State()) + expectedVersion := FetchUniqueItems(t, versions) + sort.Strings(expectedVersion) + t.Logf("DUT software version: %v", expectedVersion) + + preRebootCompStatus := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().OperStatus().State()) + t.Logf("DUT components status pre reboot: %v", preRebootCompStatus) + + preRebootCompDebug := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) + preCompMatrix := []string{} + for _, preComp := range preRebootCompDebug { + if preComp.GetOperStatus() != oc.PlatformTypes_COMPONENT_OPER_STATUS_UNSET { + preCompMatrix = append(preCompMatrix, preComp.GetName()+":"+preComp.GetOperStatus().String()) + } + } + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + t.Logf("Starting reboot: %v", tc.desc) + gnoiClient, err := dut.RawAPIs().BindingDUT().DialGNOI(context.Background()) + if err != nil { + t.Fatalf("Error dialing gNOI: %v", err) + } + bootTimeBeforeReboot := gnmi.Get(t, dut, gnmi.OC().System().BootTime().State()) + t.Logf("DUT boot time before reboot: %v", bootTimeBeforeReboot) + prevTime, err := time.Parse(time.RFC3339, gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State())) + if err != nil { + t.Fatalf("Failed parsing current-datetime: %s", err) + } + start := time.Now() + + t.Logf("Send reboot request: %v", tc.rebootRequest) + rebootResponse, err := gnoiClient.System().Reboot(context.Background(), tc.rebootRequest) + defer gnoiClient.System().CancelReboot(context.Background(), &spb.CancelRebootRequest{}) + t.Logf("Got reboot response: %v, err: %v", rebootResponse, err) + if err != nil { + t.Fatalf("Failed to reboot chassis with unexpected err: %v", err) + } + + if tc.rebootRequest.GetDelay() > 1 { + t.Logf("Validating DUT remains reachable for at least %d seconds", rebootDelay) + for { + time.Sleep(10 * time.Second) + t.Logf("Time elapsed %.2f seconds since reboot was requested.", time.Since(start).Seconds()) + if time.Since(start).Seconds() > rebootDelay { + t.Logf("Time elapsed %.2f seconds > %d reboot delay", time.Since(start).Seconds(), rebootDelay) + break + } + latestTime, err := time.Parse(time.RFC3339, gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State())) + if err != nil { + t.Fatalf("Failed parsing current-datetime: %s", err) + } + if latestTime.Before(prevTime) || latestTime.Equal(prevTime) { + t.Errorf("Get latest system time: got %v, want newer time than %v", latestTime, prevTime) + } + prevTime = latestTime + } + } + + startReboot := time.Now() + t.Logf("Wait for DUT to boot up by polling the telemetry output.") + for { + var currentTime string + t.Logf("Time elapsed %.2f seconds since reboot started.", time.Since(startReboot).Seconds()) + time.Sleep(30 * 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("Device rebooted successfully with received time: %v", currentTime) + break + } + + if uint64(time.Since(startReboot).Seconds()) > maxRebootTime { + t.Errorf("Check boot time: got %v, want < %v", time.Since(startReboot), maxRebootTime) + } + } + t.Logf("Device boot time: %.2f seconds", time.Since(startReboot).Seconds()) + + bootTimeAfterReboot := gnmi.Get(t, dut, gnmi.OC().System().BootTime().State()) + t.Logf("DUT boot time after reboot: %v", bootTimeAfterReboot) + if bootTimeAfterReboot <= bootTimeBeforeReboot { + t.Errorf("Get boot time: got %v, want > %v", bootTimeAfterReboot, bootTimeBeforeReboot) + } + + startComp := time.Now() + t.Logf("Wait for all the components on DUT to come up") + + for { + postRebootCompStatus := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().OperStatus().State()) + postRebootCompDebug := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) + postCompMatrix := []string{} + for _, postComp := range postRebootCompDebug { + if postComp.GetOperStatus() != oc.PlatformTypes_COMPONENT_OPER_STATUS_UNSET { + postCompMatrix = append(postCompMatrix, postComp.GetName()+":"+postComp.GetOperStatus().String()) + } + } + + if len(preRebootCompStatus) == len(postRebootCompStatus) { + t.Logf("All components on the DUT are in responsive state") + time.Sleep(10 * time.Second) + break + } + + if uint64(time.Since(startComp).Seconds()) > maxCompWaitTime { + t.Logf("DUT components status post reboot: %v", postRebootCompStatus) + if rebootDiff := cmp.Diff(preCompMatrix, postCompMatrix); rebootDiff != "" { + t.Logf("[DEBUG] Unexpected diff after reboot (-component missing from pre reboot, +component added from pre reboot): %v ", rebootDiff) + } + t.Fatalf("There's a difference in components obtained in pre reboot: %v and post reboot: %v.", len(preRebootCompStatus), len(postRebootCompStatus)) + } + time.Sleep(10 * time.Second) + } + + versions = gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().SoftwareVersion().State()) + swVersion := FetchUniqueItems(t, versions) + sort.Strings(swVersion) + t.Logf("DUT software version after reboot: %v", swVersion) + if diff := cmp.Diff(expectedVersion, swVersion); diff != "" { + t.Errorf("Software version differed (-want +got):\n%v", diff) + } + }) + } +} + +// TestEncapFrr is to test Test FRR behaviors with encapsulation scenarios +func TestEncapFrr(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + if dut.Vendor() == ondatra.CISCO { + ChassisReboot(t) + } + + gribic := dut.RawAPIs().GRIBI(t) + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + dutPorts := sortPorts(dut.Ports())[0:8] + atePorts := sortPorts(ate.Ports())[0:8] + + t.Log("Configure Default Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.BackupNHGRequiresVrfWithDecap(dut) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + pf := ni.GetOrCreatePolicyForwarding() + fp1 := pf.GetOrCreatePolicy("match-ipip") + fp1.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + fp1.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipOverIPProtocol) + fp1.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(deviations.DefaultNetworkInstance(dut)) + p1 := dut.Port(t, "port1") + intf := pf.GetOrCreateInterface(p1.Name()) + intf.ApplyVrfSelectionPolicy = ygot.String("match-ipip") + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), pf) + } + + configureDUT(t, dut, dutPorts) + + t.Log("Apply vrf selection policy to DUT port-1") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyC) + + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + baseScenario.StaticARPWithMagicUniversalIP(t, dut) + } + + t.Log("Install BGP route resolved by ISIS.") + t.Log("Configure ISIS on DUT") + configureISIS(t, dut, dut.Port(t, "port8").Name(), dutAreaAddress, dutSysID) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + otg := ate.OTG() + otgConfig := configureOTG(t, otg, atePorts) + + verifyISISTelemetry(t, dut, dutPorts[7].Name()) + verifyBgpTelemetry(t, dut) + + // Connect gRIBI client to DUT referred to as gRIBI - using PRESERVE persistence and + // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) + client.Start(ctx, t) + defer client.Stop(t) + + defer func() { + // Flush all entries after test. + if err := gribi.FlushAll(client); err != nil { + t.Error(err) + } + }() + + client.StartSending(ctx, t) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for clientA: %v", err) + } + eID := gribi.BecomeLeader(t, client) + + args := &testArgs{ + client: client, + dut: dut, + ate: ate, + otgConfig: otgConfig, + top: top, + electionID: eID, + otg: otg, + } + + testCases := baseScenario.TestCases(atePortNamelist, ipv4InnerDst) + for _, tc := range testCases { + t.Run(tc.Desc, func(t *testing.T) { + t.Log("Verify whether the ports are in up state") + portList := []string{"port2", "port3", "port4", "port5", "port6", "port7", "port8"} + verifyPortStatus(t, args, portList, true) + + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(client); err != nil { + t.Fatal(err) + } + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + configureGribiRoute(ctx, t, dut, client) + createFlow(t, otgConfig, otg, ipv4InnerDst) + captureState := startCapture(t, args, baseCapturePortList) + sendTraffic(t, args, baseCapturePortList, captureState) + baseHeaderDstIP := map[string][]string{"outerIP": {gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112}, "innerIP": {ipv4InnerDst, ipv4InnerDst}} + baseLoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + verifyTraffic(t, args, baseCapturePortList, baseLoadBalancePercent, !wantLoss, checkEncap, baseHeaderDstIP) + + if tc.TestID == "primaryBackupRoutingSingle" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1100).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1100, 1), + ) + if err := awaitTimeout(ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + } + if tc.TestID == "primaryBackupRoutingAll" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1200).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1201).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1200, 1), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1001).AddNextHop(1201, 1), + ) + if err := awaitTimeout(ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + } + if tc.TestID == "encapNoMatch" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1003).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1003).AddNextHop(1003, 1), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix("0.0.0.0/0").WithNextHopGroup(1003).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + createFlow(t, otgConfig, otg, noMatchEncapDest) + } + + captureState = startCapture(t, args, tc.CapturePortList) + if len(tc.DownPortList) > 0 { + t.Logf("Bring down ports %s", tc.DownPortList) + portState(t, args, tc.DownPortList, false) + defer portState(t, args, tc.DownPortList, true) + t.Log("Verify the port status after bringing down the ports") + verifyPortStatus(t, args, tc.DownPortList, false) + } + sendTraffic(t, args, tc.CapturePortList, captureState) + headerDstIP := map[string][]string{"outerIP": tc.EncapHeaderOuterIPList, "innerIP": tc.EncapHeaderInnerIPList} + verifyTraffic(t, args, tc.CapturePortList, tc.LoadBalancePercent, !wantLoss, checkEncap, headerDstIP) + }) + } +} diff --git a/feature/gribi/otg_tests/encap_frr/metadata.textproto b/feature/gribi/otg_tests/encap_frr/metadata.textproto new file mode 100644 index 00000000000..698d268910b --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr/metadata.textproto @@ -0,0 +1,55 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "496b64c7-d7d2-409e-a69c-e3701dd2253f" +plan_id: "TE-16.2" +description: "encapsulation FRR scenarios" +testbed: TESTBED_DUT_ATE_8LINKS + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + gribi_mac_override_with_static_arp: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + isis_instance_enabled_required: true + static_protocol_name: "STATIC" + gribi_mac_override_static_arp_static_route: true + omit_l2_mtu: true + backup_nhg_requires_vrf_with_decap: true + missing_isis_interface_afi_safi_enable: true + isis_interface_afi_unsupported: true + encap_tunnel_shut_backup_nhg_zero_traffic: true + } +} diff --git a/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/README.md b/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/README.md new file mode 100644 index 00000000000..06412bb00e6 --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/README.md @@ -0,0 +1,624 @@ +# TE-16.3: encapsulation FRR scenarios + +## Summary + +Test FRR behaviors with encapsulation scenarios. + +## Topology + +- ATE port-1 <------> port-1 DUT +- DUT port-2 <------> port-2 ATE +- DUT port-3 <------> port-3 ATE +- DUT port-4 <------> port-4 ATE +- DUT port-5 <------> port-5 ATE +- DUT port-6 <------> port-6 ATE +- DUT port-7 <------> port-7 ATE +- DUT port-8 <------> port-8 ATE + +## Baseline setup + +* Apply the following vrf selection policy to DUT port-1 + +``` +# DSCP value that will be matched to ENCAP_TE_VRF_A +* dscp_encap_a_1 = 10 +* dscp_encap_a_2 = 18 + +# DSCP value that will be matched to ENCAP_TE_VRF_B +* dscp_encap_b_1 = 20 +* dscp_encap_b_2 = 28 + +# DSCP value that will NOT be matched to any VRF for encapsulation. +* dscp_encap_no_match = 30 + +# Magic source IP addresses used in VRF selection policy +* ipv4_outer_src_111 = 198.51.100.111 +* ipv4_outer_src_222 = 198.51.100.222 + +# Magic destination MAC address +* magic_mac = 02:00:00:00:00:01` +``` + +``` +network-instances { + network-instance { + name: DEFAULT + policy-forwarding { + policies { + policy { + policy-id: "vrf_selection_policy_c" + rules { + rule { + sequence-id: 1 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 2 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 3 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 4 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 5 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 6 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 7 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 8 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 9 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 10 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 11 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 12 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 13 + ipv4 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 14 + ipv6 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 15 + ipv4 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 16 + ipv6 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 17 + action { + network-instance: "DEFAULT" + } + } + } + } + } + } + } +} +``` + +* Using gRIBI, install the following gRIBI AFTs, and validate the specified + behavior. + +``` +IPv4Entry {138.0.11.0/24 (ENCAP_TE_VRF_A)} -> NHG#101 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF, weight:1}, + {NH#102, DEFAULT VRF, weight:3}, + backup_next_hop_group: 2001 // fallback to DEFAULT VRF +} + +NHG#2001 (DEFAULT VRF) { + {NH#2001, DEFAULT VRF} +} +NH#2001 -> { + network_instance: "DEFAULT" +} + +NH#101 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.1" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} +NH#102 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.2" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} + + +IPv4Entry {203.0.113.1/32 (TE_VRF_111)} -> NHG#1 (DEFAULT VRF) -> { + {NH#1, DEFAULT VRF, weight:1,ip_address=192.0.2.101}, + {NH#2, DEFAULT VRF, weight:3,ip_address=192.0.2.102}, + backup_next_hop_group: 3000 // Go to REPAIR VRF +} +IPv4Entry {192.0.2.101/32 (DEFAULT VRF)} -> NHG#11 (DEFAULT VRF) -> { + {NH#11, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-2-interface}, + {NH#12, DEFAULT VRF, weight:3,mac_address:magic_mac, interface-ref:dut-port-3-interface}, +} +IPv4Entry {192.0.2.102/32 (DEFAUlT VRF)} -> NHG#12 (DEFAULT VRF) -> { + {NH#13, DEFAULT VRF, weight:2,mac_address:magic_mac, interface-ref:dut-port-4-interface}, +} + +NHG#3000 (DEFAULT VRF) { + {NH#3000, DEFAULT VRF} +} +NH#3000 -> { + network_instance: "REPAIR" +} + +IPv4Entry {203.0.113.1/32 (REPAIR)} -> NHG#1000 (DEFAULT VRF) -> { + {NH#1000, DEFAULT VRF} + backup_next_hop_group: 2000 // decap and fallback to DEFAULT VRF +} +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.100" + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} + +IPv4Entry {203.0.113.100/32 (TE_VRF_222)} -> NHG#2 (DEFAULT VRF) -> { + {NH#3, DEFAULT VRF, weight:1,ip_address=192.0.2.103}, + backup_next_hop_group: 2000 // decap and fallback to DEFAULT VRF +} +IPv4Entry {192.0.2.103/32 (DEFAULT VRF)} -> NHG#13 (DEFAULT VRF) -> { + {NH#14, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-5-interface}, +} + +// 203.0.113.2 is the tunnel IP address. Note that the NHG#3 is different than NHG#1. + +IPv4Entry {203.0.113.2/32 (TE_VRF_111)} -> NHG#3 (DEFAULT VRF) -> { + {NH#4, DEFAULT VRF, weight:1,ip_address=192.0.2.104}, + backup_next_hop_group: 3000 // Go to REPAIR VRF +} + +IPv4Entry {192.0.2.104/32 (DEFAULT VRF)} -> NHG#14 (DEFAULT VRF) -> { + {NH#15, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-6-interface}, +} + +IPv4Entry {203.0.113.2/32 (REPAIR)} -> NHG#1001 (DEFAULT VRF) -> { + {NH#1001, DEFAULT VRF} + backup_next_hop_group: 2000 // decap and fallback to DEFAULT VRF +} +NHG#1001 (DEFAULT VRF) { + {NH#1001, DEFAULT VRF} +} +NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.101" + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} + +IPv4Entry {203.0.113.101/32 (TE_VRF_222)} -> NHG#4 (DEFAULT VRF) -> { + {NH#5, DEFAULT VRF, weight:1,ip_address=192.0.2.105}, + backup_next_hop_group: 2000 // decap and fallback to DEFAULT VRF +} +IPv4Entry {192.0.2.105/32 (DEFAULT VRF)} -> NHG#15 (DEFAULT VRF) -> { + {NH#16, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-7-interface}, +} + +NHG#2000 (DEFAULT VRF) { + {NH#2000, DEFAULT VRF} +} +NH#2000 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} +``` + +* Install a BGP route resolved by ISIS in default VRF to route 138.0.11.8 + traffic out of DUT port-8. + +## Procedure + +At the start of each of the following scenarios, ensure: + +* All ports are up and baseline is reset as above. +* Send packets to DUT port-1. The outer v4 header has the destination + addresses 138.0.11.8. +* Validate that traffic is encapsulated to 203.0.113.1 and 203.0.113.2, and is + distributed per the hierarchical weights. + +Unless otherwise specified, all the tests below should use traffic with +`dscp_encap_a_1`. + +#### Test-1, primary encap unviable but backup encap viable for single tunnel + +Tests that if the primary NHG for an encap tunnel is unviable, then the traffic +for that tunnel is re-encaped into its specified backup tunnel. + +1. Shutdown DUT port-2, port-3, and port-4. +2. Validate that corresponding traffic that was encapped to 203.0.113.1 should + now be encapped with 203.0.113.100. + +#### Test-2, primary and backup encap unviable for single tunnel + +Tests that if the primary NHGs of both the encap tunnel and its backup tunnel +are unviable, then the traffic for that tunnel is not encapped. Instead, that +fraction of traffic should be forwarded according to the BGP/IS-IS routes in the +DEFAULT VRF. + +1. Shutdown DUT port-2, port-3, port-4 and port-5. +2. Validate that corresponding traffic (25% of the total traffic) that was + encapped to 203.0.113.1 are no longer encapped, and forwarded per BGP-ISIS + routes (in the default VRF) out of DUT port-8. + +#### Test-3, primary encap unviable with backup to routing for single tunnel + +Tests that if the primary NHGs of both the encap tunnel is unviable, and its +backup specifies fallback to routing, then the traffic for that tunnel is not +encapped. Instead, that fraction of traffic should be forwarded according to the +BGP/IS-IS routes in the DEFAULT VRF. + +1. Update `NHG#1000` to the following: + +``` +NHG#1000 (Default VRF) { + {NH#1000, DEFAULT VRF} +} +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} +``` + +1. Validate that all traffic is distributed per the hierarchical weights. +2. Shutdown DUT port-2, port-3, and port-4. +3. Validate that corresponding traffic (25% of the total traffic) that was + encapped to 203.0.113.1 are no longer encapped, and forwarded per BGP-ISIS + routes (in the default VRF) out of DUT port-8. + +#### Test-4, primary encap unviable but backup encap viable for all tunnels + +Tests that if the primary NHG for all encap tunnels are unviable, then the +traffic is re-encaped into the specified backup tunnels. This test ensures that +the device does not withdraw this IPv4Entry and sends this traffic to routing. + +1. Shutdown DUT port-2, port-3, port-4 and port-6. +2. Validate that traffic is encapsulated to 203.0.113.100 and 203.0.113.101 per + the weights. + +#### Test-5, primary and backup encap unviable for all tunnels + +Tests that if the primary NHGs of both the encap tunnel and its backup tunnel +are unviable for all tunnels in the encap NHG, then the traffic for that cluster +prefix is not encapped. Instead, that traffic should be forwarded according to +the BGP/IS-IS routes in the DEFAULT VRF. This stresses the double failure +handling, and ensures that the fallback to DEFAULT is activated through the +backup NHGs of the tunnels instead of withdrawing the IPv4Entry. + +1. Shutdown DUT port-2, port-3, port-4, port-5, port-6 and port-7. +2. Validate that all traffic is no longer encapsulated, and is all egressing + out of DUT port-8 per the BGP-ISIS routes in the default VRF. + +#### Test-6, primary encap unviable with backup to routing for all tunnels + +Tests that if the primary NHGs of both the encap tunnel is unviable, and its +backup specifies fallback to routing, for all tunnels in the encap NHG, then the +traffic for that cluster prefix is not encapped. Instead, that traffic should be +forwarded according to the BGP/IS-IS routes in the DEFAULT VRF. This stresses +the double failure handling, and ensures that the fallback to DEFAULT is +activated through the backup NHGs of the tunnels instead of withdrawing the +IPv4Entry. + +1. Update `NHG#1000` and `NHG#1001` to the following: + +``` +NHG#1000 (Default VRF) { {NH#1000, DEFAULT VRF} } + +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} + +NHG#1001 (Default VRF) { {NH#1001, DEFAULT VRF} } + +NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} +``` + +1. Validate that all traffic is distributed per the hierarchical weights. +2. Shutdown DUT port-2, port-3, and port-4, and port-6. +3. Validate that all traffic is no longer encapsulated, and is all egressing + out of DUT port-8 per the BGP-ISIS routes in the default VRF. + +#### Test-7, no match in encap VRF + +Test that if there is no lookup match in the encap VRF, then the traffic should +be routed to the DEFAULT VRF for further lookup. + +1. In `ENCAP_TE_VRF_A`, Add an 0/0 static route pointing to the DEFAULT VRF + using gNMI. +2. Send traffic with destination address 20.0.0.1, which should produce no + match in `ENCAP_TE_VRF_A`. +3. Validate that the traffic is routed per the BGP-ISIS routes (in the DEFAULT + VR) out of DUT port-8. + +#### Test-8, no match in TE_VRF_222 + +Tests that if the re-encaps point to tunnels that do not exist, then the traffic +should be routed to the `DEFAULT` VRF for further lookup. + +1. Update `NHG#1000` and `NHG#1001` to the following: + +``` +NHG#1000 (Default VRF) { {NH#1000, DEFAULT VRF} } + +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.100.113.100" // This route does not exist in TE_VRF_222 + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} + +NHG#1001 (Default VRF) { {NH#1001, DEFAULT VRF} } + +NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.100.113.101" // This route does not exist in TE_VRF_222 + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} +``` + +1. Validate that all traffic is distributed per the hierarchical weights. +2. Shutdown DUT port-2, port-3, and port-4, and port-6. +3. Validate that all traffic is no longer encapsulated, and is all egressing + out of DUT port-8 per the BGP-ISIS routes in the default VRF. + +#### Test-9, no match in TE_VRF_111 + +Tests that if the primary encaps point to tunnels that do not exist, then the +traffic should be routed to the `DEFAULT` VRF for further lookup. + +1. Validate that all traffic is distributed per the hierarchical weights. +2. Update NHG#101 to the following: + +``` +NHG#101 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF, weight:1}, + {NH#102, DEFAULT VRF, weight:3}, + backup_next_hop_group: 2001 // fallback to DEFAULT VRF +} + +NH#101 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.100.113.1" // This route does not exist in TE_VRF_111 + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} +NH#102 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.100.113.2" // This route does not exist in TE_VRF_111 + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} +``` + +1. Validate that all traffic is no longer encapsulated, and is all egressing + out of DUT port-8 per the BGP-ISIS routes in the default VRF. + +## Config Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Telemetry Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Required DUT platform + +- vRX diff --git a/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/encap_frr_with_reencap_vrf_test.go b/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/encap_frr_with_reencap_vrf_test.go new file mode 100644 index 00000000000..8bcefca44bb --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/encap_frr_with_reencap_vrf_test.go @@ -0,0 +1,1237 @@ +// 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 encap_frr_with_reencap_vrf_test + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "net" + "os" + "sort" + "strconv" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "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" + baseScenario "github.com/openconfig/featureprofiles/internal/encapfrr" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Settings for configuring the baseline testbed with the test +// topology. +// +// ATE port-1 <------> port-1 DUT +// DUT port-2 <------> port-2 ATE +// DUT port-3 <------> port-3 ATE +// DUT port-4 <------> port-4 ATE +// DUT port-5 <------> port-5 ATE +// DUT port-6 <------> port-6 ATE +// DUT port-7 <------> port-7 ATE +// DUT port-8 <------> port-8 ATE + +const ( + plenIPv4 = 30 + plenIPv6 = 126 + dscpEncapA1 = 10 + ipv4OuterSrcAddr = "198.100.200.123" + ipv4InnerDst = "138.0.11.8" + ipv4OuterDst333 = "192.58.200.7" + noMatchEncapDest = "20.0.0.1" + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niRepairVrf = "REPAIR_VRF" + tolerancePct = 2 + tolerance = 0.2 + encapFlow = "encapFlow" + correspondingHopLimit = 64 + magicIP = "192.168.1.1" + magicMAC = "02:00:00:00:00:01" + maskLen24 = "24" + maskLen32 = "32" + 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" + niTEVRF111 = "TE_VRF_111" + niTEVRF222 = "TE_VRF_222" + ipv4OuterSrc111Addr = "198.51.100.111" + ipv4OuterSrc222Addr = "198.51.100.222" + gribiIPv4EntryVRF1111 = "203.0.113.1" + gribiIPv4EntryVRF1112 = "203.0.113.2" + gribiIPv4EntryVRF1113 = "203.100.113.1" + gribiIPv4EntryVRF1114 = "203.100.113.2" + gribiIPv4EntryVRF2221 = "203.0.113.100" + gribiIPv4EntryVRF2222 = "203.0.113.101" + gribiIPv4EntryVRF2223 = "203.100.113.100" + gribiIPv4EntryVRF2224 = "203.100.113.101" + gribiIPv4EntryEncapVRF = "138.0.11.0" + + 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" + + ateSrcPort = "ate:port1" + ateSrcPortMac = "02:00:01:01:01:01" + ateSrcNetName = "srcnet" + ateSrcNet = "198.51.100.0" + ateSrcNetCIDR = "198.51.100.0/24" + ateSrcNetFirstIP = "198.51.100.1" + ateSrcNetCount = 250 + ipOverIPProtocol = 4 + + checkEncap = true + wantLoss = true +) + +var ( + portsIPv4 = map[string]string{ + "dut:port1": "192.0.2.1", + "ate:port1": "192.0.2.2", + + "dut:port2": "192.0.2.5", + "ate:port2": "192.0.2.6", + + "dut:port3": "192.0.2.9", + "ate:port3": "192.0.2.10", + + "dut:port4": "192.0.2.13", + "ate:port4": "192.0.2.14", + + "dut:port5": "192.0.2.17", + "ate:port5": "192.0.2.18", + + "dut:port6": "192.0.2.21", + "ate:port6": "192.0.2.22", + + "dut:port7": "192.0.2.25", + "ate:port7": "192.0.2.26", + + "dut:port8": "192.0.2.29", + "ate:port8": "192.0.2.30", + } + portsIPv6 = map[string]string{ + "dut:port1": "2001:db8::192:0:2:1", + "ate:port1": "2001:db8::192:0:2:2", + + "dut:port2": "2001:db8::192:0:2:5", + "ate:port2": "2001:db8::192:0:2:6", + + "dut:port3": "2001:db8::192:0:2:9", + "ate:port3": "2001:db8::192:0:2:a", + + "dut:port4": "2001:db8::192:0:2:d", + "ate:port4": "2001:db8::192:0:2:e", + + "dut:port5": "2001:db8::192:0:2:11", + "ate:port5": "2001:db8::192:0:2:12", + + "dut:port6": "2001:db8::192:0:2:15", + "ate:port6": "2001:db8::192:0:2:16", + + "dut:port7": "2001:db8::192:0:2:19", + "ate:port7": "2001:db8::192:0:2:1a", + + "dut:port8": "2001:db8::192:0:2:1d", + "ate:port8": "2001:db8::192:0:2:1e", + } + otgPortDevices []gosnappi.Device + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.11", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + loopbackIntfName string + atePortNamelist []string +) + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +type testArgs struct { + client *fluent.GRIBIClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgConfig gosnappi.Config + top gosnappi.Config + electionID gribi.Uint128 + otg *otg.OTG +} + +// incrementMAC increments the MAC by i. Returns error if the mac cannot be parsed or overflows the mac address space +func incrementMAC(mac string, i int) (string, error) { + macAddr, err := net.ParseMAC(mac) + if err != nil { + return "", err + } + convMac := binary.BigEndian.Uint64(append([]byte{0, 0}, macAddr...)) + convMac = convMac + uint64(i) + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, convMac) + if err != nil { + return "", err + } + newMac := net.HardwareAddr(buf.Bytes()[2:8]) + return newMac.String(), nil +} + +func sortPorts(ports []*ondatra.Port) []*ondatra.Port { + sort.Slice(ports, func(i, j int) bool { + idi, idj := ports[i].ID(), ports[j].ID() + li, lj := len(idi), len(idj) + if li == lj { + return idi < idj + } + return li < lj // "port2" < "port10" + }) + return ports +} + +// dutInterface builds a DUT interface ygot struct for a given port +// according to portsIPv4. Returns nil if the port has no IP address +// mapping. +func dutInterface(p *ondatra.Port, dut *ondatra.DUTDevice) *oc.Interface { + id := fmt.Sprintf("%s:%s", p.Device().ID(), p.ID()) + i := &oc.Interface{ + Name: ygot.String(p.Name()), + Description: ygot.String(p.String()), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + } + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + ipv4, ok := portsIPv4[id] + if !ok { + return nil + } + ipv6, ok := portsIPv6[id] + if !ok { + return nil + } + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + + a := s4.GetOrCreateAddress(ipv4) + a.PrefixLength = ygot.Uint8(plenIPv4) + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + a6 := s6.GetOrCreateAddress(ipv6) + a6.PrefixLength = ygot.Uint8(plenIPv6) + + return i +} + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice, dutPortList []*ondatra.Port) { + dc := gnmi.OC() + for _, dp := range dutPortList { + + if i := dutInterface(dp, dut); i != nil { + gnmi.Replace(t, dut, dc.Interface(dp.Name()).Config(), i) + } else { + t.Fatalf("No address found for port %v", dp) + } + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + for _, dp := range dut.Ports() { + fptest.AssignToNetworkInstance(t, dut, dp.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + } + if deviations.ExplicitPortSpeed(dut) { + for _, dp := range dut.Ports() { + fptest.SetPortSpeed(t, dp) + } + } + + loopbackIntfName = netutil.LoopbackInterface(t, dut, 0) + lo0 := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + loop1 := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, dc.Interface(loopbackIntfName).Config(), loop1) + } else { + v4, ok := ipv4Addrs[0].Val() + if ok { + dutlo0Attrs.IPv4 = v4.GetIp() + } + v6, ok := ipv6Addrs[0].Val() + if ok { + dutlo0Attrs.IPv6 = v6.GetIp() + } + t.Logf("Got DUT IPv4 loopback address: %v", dutlo0Attrs.IPv4) + t.Logf("Got DUT IPv6 loopback address: %v", dutlo0Attrs.IPv6) + } + if deviations.GRIBIMACOverrideWithStaticARP(dut) { + baseScenario.StaticARPWithSpecificIP(t, dut) + } +} + +// configureGribiRoute configures Gribi route as per the requirements +func configureGribiRoute(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, client *fluent.GRIBIClient) { + t.Helper() + + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, client) + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2000).WithDecapsulateHeader(fluent.IPinIP).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2000).AddNextHop(2000, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in TE_VRF_222 + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3).WithIPAddress(gribiIPv4EntryDefVRF3), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2).AddNextHop(3, 1).WithBackupNHG(2000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF222).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF2221+"/"+maskLen32).WithNextHopGroup(2), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(5).WithIPAddress(gribiIPv4EntryDefVRF5), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(4).AddNextHop(5, 1).WithBackupNHG(2000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF222).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF2222+"/"+maskLen32).WithNextHopGroup(4), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF222IPList := []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF2222} + for ip := range teVRF222IPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF222IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1000).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2221). + WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1000, 1).WithBackupNHG(2000), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1001).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2222). + WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1001).AddNextHop(1001, 1).WithBackupNHG(2000), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3000).WithNextHopNetworkInstance(niRepairVrf), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3000).AddNextHop(3000, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in TE_VRF_111 + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1).WithIPAddress(gribiIPv4EntryDefVRF1), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2).WithIPAddress(gribiIPv4EntryDefVRF2), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1).AddNextHop(1, 1).AddNextHop(2, 3).WithBackupNHG(3000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF111).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(4).WithIPAddress(gribiIPv4EntryDefVRF4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3).AddNextHop(4, 1).WithBackupNHG(3000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF111).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(3), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1000), + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(1001), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF111IPList := []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112} + for ip := range teVRF111IPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF111IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2001).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2001).AddNextHop(2001, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in ENCAP_TE_VRF_A + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(101).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1111). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(102).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1112). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(101).AddNextHop(101, 1).AddNextHop(102, 3).WithBackupNHG(2001), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(101), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) +} + +// configStaticArp configures static arp entries +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 +} + +func staticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + sb := &gnmi.SetBatch{} + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + portList := []*ondatra.Port{p2, p3, p4, p5, p6, p7} + for idx, p := range portList { + s := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(magicIP + "/32"), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + strconv.Itoa(idx): { + Index: ygot.String(strconv.Itoa(idx)), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(p.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(p.Name()).Config(), configStaticArp(p.Name(), magicIP, magicMAC)) + } + sb.Set(t, dut) +} + +func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName, dutAreaAddress, dutSysID string) { + t.Helper() + d := &oc.Root{} + dutConfIsisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + if deviations.ISISLevelEnabled(dut) { + isisLevel2.Enabled = ygot.Bool(true) + } + + isisIntf := isis.GetOrCreateInterface(intfName) + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + isisIntfLevelAfi := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfi.Metric = ygot.Uint32(200) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfi.Enabled = nil + } + + gnmi.Replace(t, dut, dutConfIsisPath.Config(), prot) +} + +func bgpCreateNbr(localAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutlo0Attrs.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) + pg1.PeerAs = ygot.Uint32(localAs) + + bgpNbr := bgp.GetOrCreateNeighbor(otgIsisPort8LoopV4) + bgpNbr.PeerGroup = ygot.String(peerGrpName1) + bgpNbr.PeerAs = ygot.Uint32(localAs) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbrT := bgpNbr.GetOrCreateTransport() + bgpNbrT.LocalAddress = ygot.String(dutlo0Attrs.IPv4) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + + return niProto +} + +func verifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntf string) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + dutIntf = dutIntf + ".0" + } + nbrPath := statePath.Interface(dutIntf) + query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", dutIntf) + t.Fatal("No IS-IS adjacencies reported.") + } +} + +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + nbrPath := bgpPath.Neighbor(otgIsisPort8LoopV4) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", otgIsisPort8LoopV4, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", otgIsisPort8LoopV4, state, want) + } +} + +// configureOTG configures the topology of the ATE. +func configureOTG(t testing.TB, otg *otg.OTG, atePorts []*ondatra.Port) gosnappi.Config { + t.Helper() + config := gosnappi.NewConfig() + pmd100GFRPorts := []string{} + for i, ap := range atePorts { + if ap.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, ap.ID()) + } + // DUT and ATE ports are connected by the same names. + dutid := fmt.Sprintf("dut:%s", ap.ID()) + ateid := fmt.Sprintf("ate:%s", ap.ID()) + + port := config.Ports().Add().SetName(ap.ID()) + atePortNamelist = append(atePortNamelist, port.Name()) + portName := fmt.Sprintf("atePort%s", strconv.Itoa(i)) + dev := config.Devices().Add().SetName(portName) + macAddress, _ := incrementMAC(ateSrcPortMac, i) + eth := dev.Ethernets().Add().SetName(portName + ".Eth").SetMac(macAddress) + eth.Connection().SetPortName(port.Name()) + eth.Ipv4Addresses().Add().SetName(portName + ".IPv4"). + SetAddress(portsIPv4[ateid]).SetGateway(portsIPv4[dutid]). + SetPrefix(plenIPv4) + eth.Ipv6Addresses().Add().SetName(portName + ".IPv6"). + SetAddress(portsIPv6[ateid]).SetGateway(portsIPv6[dutid]). + SetPrefix(plenIPv6) + + otgPortDevices = append(otgPortDevices, dev) + if i == 7 { + iDut8LoopV4 := dev.Ipv4Loopbacks().Add().SetName("Port8LoopV4").SetEthName(eth.Name()) + iDut8LoopV4.SetAddress(otgIsisPort8LoopV4) + iDut8LoopV6 := dev.Ipv6Loopbacks().Add().SetName("Port8LoopV6").SetEthName(eth.Name()) + iDut8LoopV6.SetAddress(otgIsisPort8LoopV6) + isisDut := dev.Isis().SetName("ISIS1").SetSystemId(otgSysID1) + isisDut.Basic().SetIpv4TeRouterId(portsIPv4[ateid]).SetHostname(isisDut.Name()).SetLearnedLspFilter(true) + isisDut.Interfaces().Add().SetEthName(dev.Ethernets().Items()[0].Name()). + SetName("devIsisInt1"). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + // Advertise OTG Port8 loopback address via ISIS. + isisPort2V4 := dev.Isis().V4Routes().Add().SetName("ISISPort8V4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(otgIsisPort8LoopV4).SetPrefix(32) + isisPort2V6 := dev.Isis().V6Routes().Add().SetName("ISISPort8V6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(otgIsisPort8LoopV6).SetPrefix(uint32(128)) + iDutBgp := dev.Bgp().SetRouterId(otgIsisPort8LoopV4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(iDut8LoopV4.Name()).Peers().Add().SetName(ap.ID() + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(dutlo0Attrs.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.Capability().SetIpv4Unicast(true).SetIpv6Unicast(true) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(port.Name() + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(otgIsisPort8LoopV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32). + SetCount(1).SetStep(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(noMatchEncapDest).SetPrefix(32). + SetCount(1).SetStep(1) + } + + } + config.Captures().Add().SetName("packetCapture"). + SetPortNames([]string{atePortNamelist[1], atePortNamelist[2], atePortNamelist[3], atePortNamelist[4], + atePortNamelist[5], atePortNamelist[6], atePortNamelist[7]}). + SetFormat(gosnappi.CaptureFormat.PCAP) + + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := config.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + pb, _ := config.Marshal().ToProto() + t.Log(pb.GetCaptures()) + return config +} + +func createFlow(t *testing.T, config gosnappi.Config, otg *otg.OTG, trafficDestIP string) { + t.Helper() + + config.Flows().Clear() + + flow1 := gosnappi.NewFlow().SetName(encapFlow) + flow1.Metrics().SetEnable(true) + flow1.TxRx().Device(). + SetTxNames([]string{otgPortDevices[0].Name() + ".IPv4"}). + SetRxNames([]string{otgPortDevices[1].Name() + ".IPv4", otgPortDevices[2].Name() + ".IPv4", otgPortDevices[3].Name() + ".IPv4", + otgPortDevices[4].Name() + ".IPv4", otgPortDevices[5].Name() + ".IPv4", otgPortDevices[6].Name() + ".IPv4", + otgPortDevices[7].Name() + ".IPv4", + }) + flow1.Size().SetFixed(512) + flow1.Rate().SetPps(100) + flow1.Duration().Continuous() + + ethHeader1 := flow1.Packet().Add().Ethernet() + ethHeader1.Src().SetValue(ateSrcPortMac) + + IPHeader := flow1.Packet().Add().Ipv4() + IPHeader.Src().Increment().SetCount(1000).SetStep("0.0.0.1").SetStart(ipv4OuterSrcAddr) + IPHeader.Dst().SetValue(trafficDestIP) + IPHeader.Priority().Dscp().Phb().SetValue(dscpEncapA1) + + UDPHeader := flow1.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + + config.Flows().Append(flow1) + + t.Logf("Pushing traffic flows to OTG and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) +} + +func startCapture(t *testing.T, args *testArgs, capturePortList []string) gosnappi.ControlState { + t.Helper() + args.otgConfig.Captures().Clear() + args.otgConfig.Captures().Add().SetName("packetCapture"). + SetPortNames(capturePortList). + SetFormat(gosnappi.CaptureFormat.PCAP) + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + args.otg.SetControlState(t, cs) + return cs +} + +func sendTraffic(t *testing.T, args *testArgs, capturePortList []string, cs gosnappi.ControlState) { + t.Helper() + otgutils.WaitForARP(t, args.otg, args.top, "IPv4") + t.Logf("Starting traffic") + args.otg.StartTraffic(t) + time.Sleep(15 * time.Second) + t.Logf("Stop traffic") + args.otg.StopTraffic(t) + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + args.otg.SetControlState(t, cs) +} + +func verifyTraffic(t *testing.T, args *testArgs, capturePortList []string, loadBalancePercent []float64, wantLoss, checkEncap bool, headerDstIP map[string][]string) { + t.Helper() + t.Logf("Verifying flow metrics for the flow: encapFlow\n") + recvMetric := gnmi.Get(t, args.otg, gnmi.OTG().Flow(encapFlow).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + var lossPct uint64 + if txPackets != 0 { + lossPct = lostPackets * 100 / txPackets + } else { + t.Errorf("Traffic stats are not correct %v", recvMetric) + } + if wantLoss { + if lossPct < 100-tolerancePct { + t.Errorf("Traffic is expected to fail %s\n got %v, want 100%% failure", encapFlow, lossPct) + } else { + t.Logf("Traffic Loss Test Passed!") + } + } else { + if lossPct > tolerancePct { + t.Errorf("Traffic Loss Pct for Flow: %s\n got %v, want 0", encapFlow, lossPct) + } else { + t.Logf("Traffic Test Passed!") + } + } + t.Log("Verify packet load balancing as per the programmed weight") + validateTrafficDistribution(t, args.ate, loadBalancePercent) + var pcapFileList []string + for _, capturePort := range capturePortList { + bytes := args.otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(capturePort)) + pcapFileName, err := os.CreateTemp("", "pcap") + if err != nil { + t.Errorf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := pcapFileName.Write(bytes); err != nil { + t.Errorf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + pcapFileName.Close() + pcapFileList = append(pcapFileList, pcapFileName.Name()) + } + validatePackets(t, pcapFileList, checkEncap, headerDstIP) + args.otgConfig.Captures().Clear() + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) +} + +func validatePackets(t *testing.T, filename []string, checkEncap bool, headerDstIP map[string][]string) { + t.Helper() + for index, file := range filename { + fileStat, err := os.Stat(file) + if err != nil { + t.Errorf("Filestat for pcap file failed %s", err) + } + fileSize := fileStat.Size() + if fileSize > 0 { + handle, err := pcap.OpenOffline(file) + if err != nil { + t.Errorf("Unable to open the pcap file, error: %s", err) + } else { + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + if checkEncap { + validateTrafficEncap(t, packetSource, headerDstIP, index) + } + } + defer handle.Close() + } + } +} + +func validateTrafficEncap(t *testing.T, packetSource *gopacket.PacketSource, headerDstIP map[string][]string, index int) { + t.Helper() + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket, _ := ipLayer.(*layers.IPv4) + encapHeaderListLength := len(headerDstIP["outerIP"]) + if index <= encapHeaderListLength-1 { + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipInnerLayer != nil { + destIP := ipPacket.DstIP.String() + t.Logf("Outer dest ip in received packet %s", destIP) + if ipPacket.DstIP.String() != headerDstIP["outerIP"][index] { + t.Errorf("Packets are not encapsulated") + } + ipInnerPacket, _ := ipInnerLayer.(*layers.IPv4) + if ipInnerPacket.DstIP.String() != headerDstIP["innerIP"][index] { + t.Errorf("Packets are not encapsulated") + } + t.Logf("Traffic for encap routes passed.") + break + } else { + t.Errorf("Packet is not encapsulated") + } + } else if index >= encapHeaderListLength || encapHeaderListLength == 0 { + if ipPacket.DstIP.String() == otgIsisPort8LoopV4 { + continue + } else if ipPacket.DstIP.String() != headerDstIP["innerIP"][0] { + destIP := ipPacket.DstIP.String() + t.Logf("Dest ip in received packet %s", destIP) + t.Errorf("Packets are encapsulated which is not expected") + } else { + t.Logf("Traffic for non-encap routes passed.") + break + } + } + } +} + +func verifyPortStatus(t *testing.T, args *testArgs, portList []string, portStatus bool) { + wantStatus := oc.Interface_OperStatus_UP + if !portStatus { + wantStatus = oc.Interface_OperStatus_DOWN + } + for _, port := range portList { + p := args.dut.Port(t, port) + t.Log("Check for port status") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), 1*time.Minute, wantStatus) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if operStatus != wantStatus { + t.Errorf("Get(DUT %v oper status): got %v, want %v", port, operStatus, wantStatus) + } + } +} + +// setDUTInterfaceState sets the admin state on the dut interface +func setDUTInterfaceWithState(t testing.TB, dut *ondatra.DUTDevice, p *ondatra.Port, state bool) { + dc := gnmi.OC() + i := &oc.Interface{} + i.Enabled = ygot.Bool(state) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.Name = ygot.String(p.Name()) + gnmi.Update(t, dut, dc.Interface(p.Name()).Config(), i) +} + +func portState(t *testing.T, args *testArgs, portList []string, portEnabled bool) { + t.Logf("Change port enable state to %t", portEnabled) + if deviations.ATEPortLinkStateOperationsUnsupported(args.ate) { + for _, port := range portList { + p := args.dut.Port(t, port) + if portEnabled { + setDUTInterfaceWithState(t, args.dut, p, true) + } else { + setDUTInterfaceWithState(t, args.dut, p, false) + } + } + } else { + var portNames []string + for _, p := range portList { + portNames = append(portNames, args.ate.Port(t, p).ID()) + } + portStateAction := gosnappi.NewControlState() + if portEnabled { + portStateAction.Port().Link().SetPortNames(portNames).SetState(gosnappi.StatePortLinkState.UP) + } else { + portStateAction.Port().Link().SetPortNames(portNames).SetState(gosnappi.StatePortLinkState.DOWN) + } + args.ate.OTG().SetControlState(t, portStateAction) + } +} + +func normalize(xs []uint64) (ys []float64, sum uint64) { + for _, x := range xs { + sum += x + } + ys = make([]float64, len(xs)) + for i, x := range xs { + ys[i] = float64(x) / float64(sum) + } + return ys, sum +} + +func validateTrafficDistribution(t *testing.T, ate *ondatra.ATEDevice, wantWeights []float64) { + inFramesAllPorts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().PortAny().Counters().InFrames().State()) + // Skip source port, Port1. + gotWeights, _ := normalize(inFramesAllPorts[1:]) + + t.Log("got ratio:", gotWeights) + t.Log("want ratio:", wantWeights) + if diff := cmp.Diff(wantWeights, gotWeights, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } +} + +// TestEncapFrr is to test Test FRR behaviors with encapsulation scenarios +func TestEncapFrr(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + + gribic := dut.RawAPIs().GRIBI(t) + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + dutPorts := sortPorts(dut.Ports())[0:8] + atePorts := sortPorts(ate.Ports())[0:8] + + t.Log("Configure Default Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.BackupNHGRequiresVrfWithDecap(dut) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + pf := ni.GetOrCreatePolicyForwarding() + fp1 := pf.GetOrCreatePolicy("match-ipip") + fp1.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + fp1.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipOverIPProtocol) + fp1.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(deviations.DefaultNetworkInstance(dut)) + p1 := dut.Port(t, "port1") + intf := pf.GetOrCreateInterface(p1.Name()) + intf.ApplyVrfSelectionPolicy = ygot.String("match-ipip") + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), pf) + } + + configureDUT(t, dut, dutPorts) + + t.Log("Apply vrf selection policy to DUT port-1") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyC) + + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + staticARPWithMagicUniversalIP(t, dut) + } + + t.Log("Install BGP route resolved by ISIS.") + t.Log("Configure ISIS on DUT") + configureISIS(t, dut, dut.Port(t, "port8").Name(), dutAreaAddress, dutSysID) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + otg := ate.OTG() + otgConfig := configureOTG(t, otg, atePorts) + + verifyISISTelemetry(t, dut, dutPorts[7].Name()) + verifyBgpTelemetry(t, dut) + + // Connect gRIBI client to DUT referred to as gRIBI - using PRESERVE persistence and + // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) + client.Start(ctx, t) + defer client.Stop(t) + + defer func() { + // Flush all entries after test. + if err := gribi.FlushAll(client); err != nil { + t.Error(err) + } + }() + + client.StartSending(ctx, t) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for clientA: %v", err) + } + eID := gribi.BecomeLeader(t, client) + + args := &testArgs{ + client: client, + dut: dut, + ate: ate, + otgConfig: otgConfig, + top: top, + electionID: eID, + otg: otg, + } + + testCases := baseScenario.TestCases(atePortNamelist, ipv4InnerDst) + testCases = append(testCases, + &baseScenario.TestCase{ + Desc: "Test-8: no match in TE_VRF_222", + DownPortList: []string{"port2", "port3", "port4", "port6"}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "teVrf222NoMatch", + }, + &baseScenario.TestCase{ + Desc: "Test-9: no match in TE_VRF_111", + DownPortList: []string{"port2", "port3", "port4", "port6"}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "teVrf111NoMatch", + }, + ) + for _, tc := range testCases { + t.Run(tc.Desc, func(t *testing.T) { + t.Log("Verify whether the ports are in up state") + portList := []string{"port2", "port3", "port4", "port5", "port6", "port7", "port8"} + verifyPortStatus(t, args, portList, true) + + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(client); err != nil { + t.Fatal(err) + } + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + configureGribiRoute(ctx, t, dut, args.client) + createFlow(t, otgConfig, otg, ipv4InnerDst) + captureState := startCapture(t, args, baseCapturePortList) + sendTraffic(t, args, baseCapturePortList, captureState) + baseHeaderDstIP := map[string][]string{"outerIP": {gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112}, "innerIP": {ipv4InnerDst, ipv4InnerDst}} + baseLoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + verifyTraffic(t, args, baseCapturePortList, baseLoadBalancePercent, !wantLoss, checkEncap, baseHeaderDstIP) + + if tc.TestID == "primaryBackupRoutingSingle" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1100).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1100, 1), + ) + if err := awaitTimeout(ctx, t, args.client, 2*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + } + if tc.TestID == "primaryBackupRoutingAll" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1200).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1201).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1200, 1), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1001).AddNextHop(1201, 1), + ) + if err := awaitTimeout(ctx, t, args.client, 2*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + } + if tc.TestID == "encapNoMatch" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1003).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1003).AddNextHop(1003, 1), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix("0.0.0.0/0").WithNextHopGroup(1003).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(ctx, t, args.client, 2*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + createFlow(t, otgConfig, otg, noMatchEncapDest) + } + if tc.TestID == "teVrf222NoMatch" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1300).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2223).WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1301).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2224).WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1300, 1), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1001).AddNextHop(1301, 1), + ) + if err := awaitTimeout(ctx, t, args.client, 2*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + res := args.client.Results(t) + operIndexList := []uint64{1000, 1001} + for _, operIndex := range operIndexList { + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationType(constants.Add). + WithNextHopOperation(operIndex). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationType(constants.Add). + WithNextHopGroupOperation(operIndex). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + } + if tc.TestID == "teVrf111NoMatch" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(201).WithEncapsulateHeader(fluent.IPinIP).WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1113). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(202).WithEncapsulateHeader(fluent.IPinIP).WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1114). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(101).AddNextHop(201, 1).AddNextHop(202, 3).WithBackupNHG(2001), + ) + if err := awaitTimeout(ctx, t, args.client, 2*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + res := args.client.Results(t) + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationType(constants.Add). + WithNextHopOperation(201). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationType(constants.Add). + WithNextHopOperation(202). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationType(constants.Add). + WithNextHopGroupOperation(101). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + captureState = startCapture(t, args, tc.CapturePortList) + if len(tc.DownPortList) > 0 { + t.Logf("Bring down ports %s", tc.DownPortList) + portState(t, args, tc.DownPortList, false) + defer portState(t, args, tc.DownPortList, true) + t.Log("Verify the port status after bringing down the ports") + verifyPortStatus(t, args, tc.DownPortList, false) + } + sendTraffic(t, args, tc.CapturePortList, captureState) + headerDstIP := map[string][]string{"outerIP": tc.EncapHeaderOuterIPList, "innerIP": tc.EncapHeaderInnerIPList} + + if deviations.EncapTunnelShutBackupNhgZeroTraffic(dut) { + if tc.TestID == "primarySingle" || tc.TestID == "primaryBackupSingle" || tc.TestID == "primaryBackupRoutingSingle" { + tc.LoadBalancePercent = []float64{0, 0, 0, 0, 1, 0, 0} + } else if tc.TestID == "primaryAll" { + tc.LoadBalancePercent = []float64{0, 0, 0, 0, 0, 0, 1} + } + } + verifyTraffic(t, args, tc.CapturePortList, tc.LoadBalancePercent, !wantLoss, checkEncap, headerDstIP) + }) + } +} diff --git a/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/metadata.textproto b/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/metadata.textproto new file mode 100644 index 00000000000..1f42b8e4509 --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/metadata.textproto @@ -0,0 +1,56 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "f8eee005-f5dc-4c58-a7f2-444571bcd49f" +plan_id: "TE-16.3" +description: "encapsulation FRR scenarios" +testbed: TESTBED_DUT_ATE_8LINKS + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + gribi_mac_override_with_static_arp: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + isis_instance_enabled_required: true + static_protocol_name: "STATIC" + gribi_mac_override_static_arp_static_route: true + omit_l2_mtu: true + backup_nhg_requires_vrf_with_decap: true + missing_isis_interface_afi_safi_enable: true + isis_interface_afi_unsupported: true + encap_tunnel_shut_backup_nhg_zero_traffic: true + } +} + diff --git a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md b/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md similarity index 80% rename from feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md rename to feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md index 94fb48612b5..f8017cf551f 100644 --- a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md +++ b/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md @@ -1,4 +1,4 @@ -# TE-9.1: FIB FAILURE DUE TO HARDWARE RESOURCE EXHAUST +# TE-9.3: FIB FAILURE DUE TO HARDWARE RESOURCE EXHAUST ## Summary @@ -23,10 +23,18 @@ Validate gRIBI FIB_FAILED functionality. * Pick any route that received FIB_PROGRAMMED. Validate that traffic hitting the route should be forwarded to port2 -## Protocol/RPC Parameter coverage - -* gRIBI - * Flush +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Config parameter coverage diff --git a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/fib_failed_due_to_hw_res_exhaust_test.go b/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/fib_failed_due_to_hw_res_exhaust_test.go similarity index 80% rename from feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/fib_failed_due_to_hw_res_exhaust_test.go rename to feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/fib_failed_due_to_hw_res_exhaust_test.go index e9eec391bff..541c0acb5ab 100644 --- a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/fib_failed_due_to_hw_res_exhaust_test.go +++ b/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/fib_failed_due_to_hw_res_exhaust_test.go @@ -19,6 +19,8 @@ import ( "encoding/binary" "fmt" "net" + "strconv" + "strings" "testing" "time" @@ -63,12 +65,18 @@ const ( tolerance = 50 plenIPv4 = 30 plenIPv6 = 126 + fibPassedTraffic = "fibPassedTraffic" + fibFailedTraffic = "fibFailedTraffic" + dstTrackingf1 = "dstTrackingf1" + dstTrackingf2 = "dstTrackingf2" ) var ( vendorSpecRoutecount = map[ondatra.Vendor]uint32{ + ondatra.ARISTA: 2500000, ondatra.JUNIPER: 2500000, - ondatra.NOKIA: 1600000, + ondatra.NOKIA: 2600000, + ondatra.CISCO: 2500000, } dutPort1 = attrs.Attributes{ Desc: "dutPort1", @@ -100,8 +108,9 @@ var ( IPv4Len: plenIPv4, IPv6Len: plenIPv6, } - fibPassedDstRoute string - fibFailedDstRoute string + fibPassedDstRoute string + fibFailedDstRoute string + fibFailedDstRouteInHex string ) func configureBGP(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { @@ -113,12 +122,12 @@ func configureBGP(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { g := bgp.GetOrCreateGlobal() g.As = ygot.Uint32(dutAS) g.RouterId = ygot.String(dutPort1.IPv4) - 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) pg := bgp.GetOrCreatePeerGroup("BGP-PEER-GROUP-V6") pg.PeerAs = ygot.Uint32(ateAS) pg.PeerGroupName = ygot.String("BGP-PEER-GROUP-V6") + pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) if deviations.RoutePolicyUnderAFIUnsupported(dut) { rpl := pg.GetOrCreateApplyPolicy() @@ -138,12 +147,10 @@ func configureBGP(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { bgpNbr.PeerGroup = ygot.String("BGP-PEER-GROUP-V6") af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) af6.Enabled = ygot.Bool(true) - af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - af4.Enabled = ygot.Bool(false) return niProto } -func configureOTG(t *testing.T, otg *otg.OTG) (gosnappi.BgpV6Peer, gosnappi.DeviceIpv6, gosnappi.Config) { +func configureOTG(t *testing.T, otg *otg.OTG, dstIPList []string) (gosnappi.BgpV6Peer, gosnappi.DeviceIpv6, gosnappi.Config) { t.Helper() config := gosnappi.NewConfig() port1 := config.Ports().Add().SetName("port1") @@ -151,7 +158,7 @@ func configureOTG(t *testing.T, otg *otg.OTG) (gosnappi.BgpV6Peer, gosnappi.Devi iDut1Dev := config.Devices().Add().SetName(atePort1.Name) iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - iDut1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port1.Name()) + iDut1Eth.Connection().SetPortName(port1.Name()) iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") @@ -159,16 +166,53 @@ func configureOTG(t *testing.T, otg *otg.OTG) (gosnappi.BgpV6Peer, gosnappi.Devi iDut2Dev := config.Devices().Add().SetName(atePort2.Name) iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) - iDut2Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port2.Name()) + iDut2Eth.Connection().SetPortName(port2.Name()) iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) iDut1Bgp := iDut1Dev.Bgp().SetRouterId(iDut1Ipv4.Address()) iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) - iDut1Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + flow1ipv4 := config.Flows().Add().SetName(fibPassedTraffic) + flow1ipv4.Metrics().SetEnable(true) + flow1ipv4.TxRx().Device(). + SetTxNames([]string{atePort1.Name + ".IPv4"}). + SetRxNames([]string{atePort2.Name + ".IPv4"}) + flow1ipv4.Size().SetFixed(512) + flow1ipv4.Rate().SetPps(100) + flow1ipv4.Duration().Continuous() + e1 := flow1ipv4.Packet().Add().Ethernet() + e1.Src().SetValue(atePort1.MAC) + v4 := flow1ipv4.Packet().Add().Ipv4() + v4.Src().SetValue(atePort1.IPv4) + v4.Dst().Increment().SetStart(dstIPList[0]) + + flow1ipv4.EgressPacket().Add().Ethernet() + ipTrackingf1 := flow1ipv4.EgressPacket().Add().Ipv4() + ipDstTrackingf1 := ipTrackingf1.Dst().MetricTags().Add() + ipDstTrackingf1.SetName(dstTrackingf1).SetOffset(22).SetLength(10) + + flow2ipv4 := config.Flows().Add().SetName(fibFailedTraffic) + flow2ipv4.Metrics().SetEnable(true) + flow2ipv4.TxRx().Device(). + SetTxNames([]string{atePort1.Name + ".IPv4"}). + SetRxNames([]string{atePort2.Name + ".IPv4"}) + flow2ipv4.Size().SetFixed(512) + flow2ipv4.Rate().SetPps(100) + flow2ipv4.Duration().Continuous() + e2 := flow2ipv4.Packet().Add().Ethernet() + e2.Src().SetValue(atePort1.MAC) + v4Flow2 := flow2ipv4.Packet().Add().Ipv4() + v4Flow2.Src().SetValue(atePort1.IPv4) + v4Flow2.Dst().SetValues(dstIPList[1:]) + + flow2ipv4.EgressPacket().Add().Ethernet() + ipTrackingf2 := flow2ipv4.EgressPacket().Add().Ipv4() + ipDstTrackingf2 := ipTrackingf2.Dst().MetricTags().Add() + ipDstTrackingf2.SetName(dstTrackingf2).SetOffset(22).SetLength(10) + t.Logf("Pushing config to ATE and starting protocols...") otg.PushConfig(t, config) time.Sleep(30 * time.Second) @@ -207,6 +251,8 @@ type testArgs struct { func TestFibFailDueToHwResExhaust(t *testing.T) { ctx := context.Background() dut := ondatra.DUT(t, "dut") + dstIPList := createIPv4Entries(t, fmt.Sprintf("%s/%d", dstIPBlock, 20)) + vipList := createIPv4Entries(t, fmt.Sprintf("%s/%d", vipBlock, 20)) configureDUT(t, dut) configureRoutePolicy(t, dut, "ALLOW", oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) @@ -218,6 +264,25 @@ func TestFibFailDueToHwResExhaust(t *testing.T) { dutConf := configureBGP(dut) gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + if deviations.BGPMissingOCMaxPrefixesConfiguration(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + cli := dut.RawAPIs().CLI(t) + _, err := cli.RunCommand(context.Background(), ` + configure + router bgp 64500 + neighbor BGP-PEER-GROUP-V6 maximum-routes 0 + exit + exit + exit + `) + if err != nil { + t.Fatal(err) + } + default: + t.Fatal("Unsupported vendor") + } + } }) ate := ondatra.ATE(t, "ate") @@ -225,8 +290,8 @@ func TestFibFailDueToHwResExhaust(t *testing.T) { var otgConfig gosnappi.Config var otgBgpPeer gosnappi.BgpV6Peer var otgIPv6Device gosnappi.DeviceIpv6 - otgBgpPeer, otgIPv6Device, otgConfig = configureOTG(t, otg) - + otgBgpPeer, otgIPv6Device, otgConfig = configureOTG(t, otg, dstIPList) + time.Sleep(30 * time.Second) verifyBgpTelemetry(t, dut) gribic := dut.RawAPIs().GRIBI(t) @@ -238,7 +303,7 @@ func TestFibFailDueToHwResExhaust(t *testing.T) { WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) client.Start(ctx, t) defer client.Stop(t) - + gribi.FlushAll(client) defer func() { // Flush all entries after test. if err := gribi.FlushAll(client); err != nil { @@ -269,7 +334,13 @@ func TestFibFailDueToHwResExhaust(t *testing.T) { otg: otg, } start := time.Now() - injectEntry(ctx, t, args) + // cleanup fib table + defer func() { + ate.OTG().StopProtocols(t) + time.Sleep(5 * time.Minute) + }() + + injectEntry(ctx, t, args, dstIPList, vipList) t.Logf("Main Function: Time elapsed %.2f seconds since start", time.Since(start).Seconds()) t.Log("Send traffic to any of the programmed entries and validate.") @@ -279,37 +350,7 @@ func TestFibFailDueToHwResExhaust(t *testing.T) { func sendTraffic(t *testing.T, args *testArgs) { // Ensure that traffic can be forwarded between ATE port-1 and ATE port-2. t.Helper() - t.Logf("TestBGP:start otg Traffic config") - flow1ipv4 := args.otgConfig.Flows().Add().SetName("Flow1") - flow1ipv4.Metrics().SetEnable(true) - flow1ipv4.TxRx().Device(). - SetTxNames([]string{atePort1.Name + ".IPv4"}). - SetRxNames([]string{atePort2.Name + ".IPv4"}) - flow1ipv4.Size().SetFixed(512) - flow1ipv4.Rate().SetPps(100) - flow1ipv4.Duration().SetChoice("continuous") - e1 := flow1ipv4.Packet().Add().Ethernet() - e1.Src().SetValue(atePort1.MAC) - v4 := flow1ipv4.Packet().Add().Ipv4() - v4.Src().SetValue(atePort1.IPv4) - v4.Dst().Increment().SetStart(fibPassedDstRoute) - - flow2ipv4 := args.otgConfig.Flows().Add().SetName("Flow2") - flow2ipv4.Metrics().SetEnable(true) - flow2ipv4.TxRx().Device(). - SetTxNames([]string{atePort1.Name + ".IPv4"}). - SetRxNames([]string{atePort2.Name + ".IPv4"}) - flow2ipv4.Size().SetFixed(512) - flow2ipv4.Rate().SetPps(100) - flow2ipv4.Duration().SetChoice("continuous") - e2 := flow2ipv4.Packet().Add().Ethernet() - e2.Src().SetValue(atePort1.MAC) - v4Flow2 := flow2ipv4.Packet().Add().Ipv4() - v4Flow2.Src().SetValue(atePort1.IPv4) - v4Flow2.Dst().Increment().SetStart(fibFailedDstRoute) - - args.otg.PushConfig(t, args.otgConfig) - args.otg.StartProtocols(t) + t.Logf("TestBGP:start otg Traffic") t.Logf("Starting traffic") args.otg.StartTraffic(t) @@ -317,11 +358,8 @@ func sendTraffic(t *testing.T, args *testArgs) { t.Logf("Stop traffic") args.otg.StopTraffic(t) - verifyTraffic(t, args, flow1ipv4.Name(), !wantLoss) - - if !deviations.GRIBISkipFIBFailedTrafficForwardingCheck(args.dut) { - verifyTraffic(t, args, flow2ipv4.Name(), wantLoss) - } + verifyTraffic(t, args, fibPassedTraffic, !wantLoss) + verifyTraffic(t, args, fibFailedTraffic, wantLoss) } func verifyTraffic(t *testing.T, args *testArgs, flowName string, wantLoss bool) { @@ -332,16 +370,31 @@ func verifyTraffic(t *testing.T, args *testArgs, flowName string, wantLoss bool) rxPackets := recvMetric.GetCounters().GetInPkts() lostPackets := txPackets - rxPackets var lossPct uint64 + trafficPassed := false if txPackets != 0 { lossPct = lostPackets * 100 / txPackets } else { t.Errorf("Traffic stats are not correct %v", recvMetric) } if wantLoss { - if lossPct < 100-tolerancePct { - t.Errorf("Traffic is expected to fail %s\n got %v, want 100%% failure", flowName, lossPct) + // If no rxPackets are received, the first route is fibFailedRoute, resulting in no packets being generated with tagged metrics. + if rxPackets > 0 { + etPath := gnmi.OTG().Flow(flowName).TaggedMetricAny() + ets := gnmi.GetAll(t, args.otg, etPath.State()) + for _, et := range ets { + tags := et.Tags + for _, tag := range tags { + if tag.GetTagName() == dstTrackingf2 && tag.GetTagValue().GetValueAsHex() == fibFailedDstRouteInHex { + trafficPassed = true + break + } + } + } + } + if trafficPassed { + t.Errorf("Traffic received on Failed FIB") } else { - t.Logf("Traffic Loss Test Passed!") + t.Logf("Traffic Test Passed!") } } else { if lossPct > tolerancePct { @@ -350,6 +403,7 @@ func verifyTraffic(t *testing.T, args *testArgs, flowName string, wantLoss bool) t.Logf("Traffic Test Passed!") } } + } func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { @@ -357,7 +411,7 @@ func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { var nbrIP = []string{atePort1.IPv6} t.Logf("Verifying BGP state.") bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - + time.Sleep(30 * time.Second) for _, nbr := range nbrIP { nbrPath := bgpPath.Neighbor(nbr) // Get BGP adjacency state. @@ -412,6 +466,7 @@ func injectBGPRoutes(t *testing.T, args *testArgs) { SetAddress(advertisedRoutesv6). SetPrefix(advertisedRoutesv6MaskLen). SetCount(vendorSpecRoutecount[args.dut.Vendor()]).SetStep(2) + bgpNeti1Bgp6PeerRoutes.Advanced().SetIncludeLocalPreference(false) args.otg.PushConfig(t, args.otgConfig) time.Sleep(30 * time.Second) @@ -446,15 +501,31 @@ func createIPv4Entries(t *testing.T, startIP string) []string { return entries } +func IPv4LastTenBitsToHex(ip string) string { + // Convert IPv4 address to a 32-bit integer + ipParts := strings.Split(ip, ".") + var ipInt uint32 + for i := 0; i < 4; i++ { + part, _ := strconv.Atoi(ipParts[i]) + ipInt = (ipInt << 8) | uint32(part) + } + + // Convert the IP address to binary string + ipBinary := fmt.Sprintf("%032b", ipInt) + // Extract the last 10 bits + last10Bits := ipBinary[len(ipBinary)-10:] + // Convert the last 10 bits to hexadecimal + last10Hex, _ := strconv.ParseInt(last10Bits, 2, 64) + return fmt.Sprintf("0x%03x", last10Hex) +} + // injectEntry programs gRIBI nh, nhg and ipv4 entry. -func injectEntry(ctx context.Context, t *testing.T, args *testArgs) { +func injectEntry(ctx context.Context, t *testing.T, args *testArgs, dstIPList []string, vipList []string) { t.Helper() - dstIPList := createIPv4Entries(t, fmt.Sprintf("%s/%d", dstIPBlock, 20)) - vipList := createIPv4Entries(t, fmt.Sprintf("%s/%d", vipBlock, 20)) j := uint64(0) routeAddLoop: - for i := uint64(1); i <= uint64(1500); i += 2 { + for i := uint64(1); i <= uint64(1000); i += 2 { vipNhIndex := i dstNhIndex := vipNhIndex + 1 @@ -478,20 +549,22 @@ routeAddLoop: if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { t.Logf("Could not program entries via client, got err, check error codes: %v", err) } - - for _, v := range args.client.Results(t) { + res := args.client.Results(t) + for _, v := range res[len(res)-6:] { if v.ProgrammingResult == aftspb.AFTResult_FIB_FAILED { t.Logf("FIB FAILED received %v", v.Details) fibFailedDstRoute = dstIPList[j] + fibFailedDstRouteInHex = IPv4LastTenBitsToHex(fibFailedDstRoute) break routeAddLoop } } - j = j + 1 + t.Logf("Programmed entries %s --> %s", dstIPList[j]+"/32", vipList[j]+"/32") + j++ // We are filling FIB with BGP routes. After FIB is full, trying to program // routes through gRIBI client. Since FIB is already full , we should get // FIB FAILED while programming gRIBI routes. Here we are trying to program // 1500 VIP/Dst entries along with unique NH/NHG entries. - if i >= 1498 { + if i >= 998 { t.Fatalf("FIB FAILED is not received as expected") } if j == 1 { diff --git a/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto b/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto new file mode 100644 index 00000000000..179ddbcc314 --- /dev/null +++ b/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto @@ -0,0 +1,46 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "4f04fb5b-5bc0-4214-a74d-2a30c0433078" +plan_id: "TE-9.3" +description: "FIB FAILURE DUE TO HARDWARE RESOURCE EXHAUST" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + skip_fib_failed_traffic_forwarding_check: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + bgp_missing_oc_max_prefixes_configuration: true + } +} +tags: TAGS_TRANSIT diff --git a/feature/gribi/otg_tests/get_rpc_test/get_rpc_test.go b/feature/gribi/otg_tests/get_rpc_test/get_rpc_test.go index 5b2a5b8ff16..874d27aaf6b 100644 --- a/feature/gribi/otg_tests/get_rpc_test/get_rpc_test.go +++ b/feature/gribi/otg_tests/get_rpc_test/get_rpc_test.go @@ -144,7 +144,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(ate.Port(t, "port1").ID()) i1 := top.Devices().Add().SetName(ate.Port(t, "port1").ID()) eth1 := i1.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i1.Name()) + eth1.Connection().SetPortName(i1.Name()) eth1.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4"). SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4). SetPrefix(uint32(atePort1.IPv4Len)) @@ -152,7 +152,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(ate.Port(t, "port2").ID()) i2 := top.Devices().Add().SetName(ate.Port(t, "port2").ID()) eth2 := i2.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i2.Name()) + eth2.Connection().SetPortName(i2.Name()) eth2.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4"). SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4). SetPrefix(uint32(atePort2.IPv4Len)) diff --git a/feature/gribi/otg_tests/gribi_route_test/README.md b/feature/gribi/otg_tests/gribi_route_test/README.md new file mode 100644 index 00000000000..2f709e4ab43 --- /dev/null +++ b/feature/gribi/otg_tests/gribi_route_test/README.md @@ -0,0 +1,174 @@ +# RT-14.2: GRIBI Route Test + +## Summary + +Ensure Traffic is Encap/Decap to NextHop based on Gribi structure. + +## Topology + +ATE port-1 <------> port-1 DUT +DUT port-2 <------> port-2 ATE +DUT port-3 <------> port-3 ATE + +## Variables +``` +# Magic source IP addresses used in this test + * ipv4_outer_src_111 = 198.51.100.111 + * ipv4PrefixEncapped = ipv4InnerDst = 138.0.11.8 + * ipv4PrefixNotEncapped = ipv4OuterDst222 = 198.50.100.65 +``` + +## Baseline + +### VRF Selection Policy + +``` +network-instances { + network-instance { + name: DEFAULT + policy-forwarding { + policies { + policy { + policy-id: "vrf_selection_policy_c" + rules { + rule { + sequence-id: 1 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_111" + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 2 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + network-instance: "TRANSIT_TE_VRF" + } + } + } + } + } + } + } +} +``` +``` +### Install the following gRIBI AFTs. + +- IPv4Entry {0.0.0.0/0 (TRANSIT_TE_VRF)} -> NHG#1 (DEFAULT VRF) -> { + {NH#1, DEFAULT VRF, weight:1}, + } + NH#1 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" + } +- IPv4Entry {198.50.100.64/32 (TRANSIT_TE_VRF)} -> NHG#2 (DEFAULT VRF) -> { + {NH#2, DEFAULT VRF, weight:1}, interface-ref:dut-port-2-interface, + } +- IPv4Entry {198.50.100.64/32 (TE_VRF_111)} -> NHG#3 (DEFAULT VRF) -> { + {NH#3, DEFAULT VRF, weight:1}, interface-ref:dut-port-2-interface, + } +- IPv4Entry {0.0.0.0/0 (ENCAP_TE_VRF_A)} -> NHG#5 (DEFAULT VRF) -> { + {NH#5, DEFAULT VRF, weight:1, interface-ref:dut-port-3-interface}, + } +- IPv4Entry {138.0.11.8/32 (ENCAP_TE_VRF_A)} -> NHG#4 (DEFAULT VRF) -> { + {NH#4, DEFAULT VRF, weight:1}, + } + NH#4 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "198.50.100.64" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" + } +``` +- Install a BGP route in default VRF to route traffic out of DUT port-3. + +## Procedure + +The DUT should be reset to the baseline after each of the following tests. + +Test-1, Match on source prefix, flow hits ENCAP_TE_VRF_A followed by TE_VRF_111 + +``` + 1. Send flow with source IP ipv4_outer_src_111 with destination IP ipv4PrefixEncapped. + 2. Verify v4 packet matched with tunnel prefix and encapped -> hit TE_VRF_111 + and egress via port-2. + 3. No traffic loss in steady state + +``` +Test-2, Match on source prefix, flow hits ENCAP_TE_VRF_A followed by Default VRF + +``` + 1. Send flow with source IP ipv4_outer_src_111 with destination IP ipv4PrefixNotEncapped. + 2. Verify v4 packet not matched with tunnel prefix and egress via port-3. + 3. No traffic loss in steady state + +``` +Test-3, Match on source prefix and protocol, flow hits TRANSIT_TE_VRF +match with Tunnel prefix /32 + +``` + 1. Send the following 4in4 flows to DUT port-1: + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4InnerDst` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_no_match` + * proto: `4` + 2. Verify packet matched with tunnel prefix and egress via port-2. + 3. No traffic loss in steady state + +``` +Test-4, Match on source prefix and protocol, flow hits TRANSIT_TE_VRF matched with 0/0 prefix, decap & sent to default vrf +``` + 1. Send the following 4in4 flows to DUT port-1: + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4InnerDst` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_match` + * proto: `4` + 2. Verify packet not matched with tunnel prefix, decap and failback + to default vrf. + 3. No traffic loss in steady state + +``` +## Config Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance + +## Telemetry Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/gribi/otg_tests/gribi_route_test/gribi_route_test.go b/feature/gribi/otg_tests/gribi_route_test/gribi_route_test.go new file mode 100644 index 00000000000..194bcc77717 --- /dev/null +++ b/feature/gribi/otg_tests/gribi_route_test/gribi_route_test.go @@ -0,0 +1,728 @@ +package gribi_route_test + +import ( + "context" + "fmt" + "log" + "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/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + niTransitTeVrf = "TRANSIT_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niTEVRF111 = "TE_VRF_111" + vrfPolC = "vrf_selection_policy_c" + seqIDBase = uint32(10) + ipv4OuterSrc111 = "198.51.100.111" + ipv4OuterSrc111WithMask = "198.51.100.111/32" + ipv4InnerDst = "138.0.11.8" + ipv4OuterDst111 = "198.50.100.64" + ipv4OuterDst222 = "198.50.100.65" + peerGrpName = "BGP-PEER-GROUP1" + asn = 65501 + tolerancePct = 2 + checkEncap = true +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT Port 1", + IPv4: "192.0.2.1", + IPv4Len: 30, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT Port 2", + IPv4: "192.0.2.5", + IPv4Len: 30, + } + dutPort3 = attrs.Attributes{ + Desc: "DUT Port 3", + IPv4: "192.0.2.9", + IPv4Len: 30, + } + + atePort1 = attrs.Attributes{ + Name: "port1", + MAC: "02:00:01:01:01:01", + Desc: "ATE Port 1", + IPv4: "192.0.2.2", + IPv4Len: 30, + } + atePort2 = attrs.Attributes{ + Name: "port2", + MAC: "02:00:02:01:01:01", + Desc: "ATE Port 2", + IPv4: "192.0.2.6", + IPv4Len: 30, + } + atePort3 = attrs.Attributes{ + Name: "port3", + MAC: "02:00:03:01:01:01", + Desc: "ATE Port 3", + IPv4: "192.0.2.10", + IPv4Len: 30, + } +) + +type bgpNeighbor struct { + Name string + dutIPv4 string + ateIPv4 string + MAC string +} + +var ( + bgpNbr1 = bgpNeighbor{ + Name: "port1", + dutIPv4: "192.0.2.1", + ateIPv4: "192.0.2.2", + MAC: "02:00:01:01:01:01", + } + bgpNbr2 = bgpNeighbor{ + Name: "port2", + dutIPv4: "192.0.2.5", + ateIPv4: "192.0.2.6", + MAC: "02:00:02:01:01:01", + } + bgpNbr3 = bgpNeighbor{ + Name: "port3", + dutIPv4: "192.0.2.9", + ateIPv4: "192.0.2.10", + MAC: "02:00:03:01:01:01", + } +) + +type packetValidation struct { + portName string + outDstIP []string + inHdrIP string + validateDecap bool + validateNoDecap bool + validateEncap bool +} + +type policyFwRule struct { + SeqID uint32 + family string + protocol oc.UnionUint8 + dscpSet []uint8 + sourceAddr string + ni string +} + +// testArgs holds the objects needed by a test case. +type testArgs struct { + dut *ondatra.DUTDevice + ctx context.Context + client *fluent.GRIBIClient + ate *ondatra.ATEDevice + otgConfig gosnappi.Config + otg *otg.OTG +} + +type flowArgs struct { + flowName string + outHdrSrcIP, outHdrDstIP string + InnHdrSrcIP, InnHdrDstIP string + InnHdrSrcIPv6, InnHdrDstIPv6 string + isIPInIP bool +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestGRIBIFailover(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + ate := ondatra.ATE(t, "ate") + top := configureOTG(t, ate) + t.Log("Configure VRF_Policy") + configureVrfSelectionPolicyC(t, dut) + t.Log("Configure GRIBI") + configureGribiRoute(t, dut) + + llAddress, found := gnmi.Watch(t, ate.OTG(), gnmi.OTG().Interface("port1.Eth").Ipv4Neighbor(dutPort1.IPv4).LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + return val.IsPresent() + }).Await(t) + if !found { + t.Fatalf("Could not get the LinkLayerAddress %s", llAddress) + } + dstMac, _ := llAddress.Val() + + verifyBgpTelemetry(t, dut) + + args := &testArgs{ + dut: dut, + ate: ate, + otgConfig: top, + otg: ate.OTG(), + } + t.Run("RT-14.2.1: Traffic Prefix Match to Tunnel Prefix, Encapped and Egress via Port2", func(t *testing.T) { + flow := createFlow(&flowArgs{flowName: "flow4in4", + InnHdrSrcIP: ipv4OuterSrc111, InnHdrDstIP: ipv4InnerDst}, dstMac) + sendTraffic(t, args, top, ate, flow, 30, []string{"port2"}) + if ok := verifyTrafficFlow(t, ate, flow); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + captureAndValidatePackets(t, args, &packetValidation{portName: atePort2.Name, + outDstIP: []string{ipv4OuterDst111}, inHdrIP: ipv4InnerDst, validateEncap: true}) + }) + + t.Run("RT-14.2.2: Traffic Prefix not Matched to Tunnel Prefix, Egress via Port3", func(t *testing.T) { + flow := createFlow(&flowArgs{flowName: "flow4in4", + InnHdrSrcIP: ipv4OuterSrc111, InnHdrDstIP: ipv4OuterDst222}, dstMac) + sendTraffic(t, args, top, ate, flow, 30, []string{"port3"}) + if ok := verifyTrafficFlow(t, ate, flow); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + }) + + t.Run("RT-14.2.3: Traffic Match to Transit_Vrf, Match Tunnel Prefix Egress to Port2", func(t *testing.T) { + flow := createFlow(&flowArgs{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, + InnHdrSrcIP: ipv4OuterSrc111, InnHdrDstIP: ipv4InnerDst, isIPInIP: true}, dstMac) + sendTraffic(t, args, top, ate, flow, 30, []string{"port2"}) + if ok := verifyTrafficFlow(t, ate, flow); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + captureAndValidatePackets(t, args, &packetValidation{portName: atePort2.Name, + outDstIP: []string{ipv4OuterDst111}, inHdrIP: ipv4InnerDst, validateNoDecap: true}) + }) + + t.Run("RT-14.2.4: Traffic Match to Transit_Vrf, noMatch Tunnel Prefix Egress to Port3", func(t *testing.T) { + flow := createFlow(&flowArgs{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst222, + InnHdrSrcIP: ipv4OuterSrc111, InnHdrDstIP: ipv4InnerDst, isIPInIP: true}, dstMac) + sendTraffic(t, args, top, ate, flow, 30, []string{"port3"}) + if ok := verifyTrafficFlow(t, ate, flow); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + captureAndValidatePackets(t, args, &packetValidation{portName: atePort3.Name, + outDstIP: []string{ipv4OuterDst222}, inHdrIP: ipv4InnerDst, validateDecap: true}) + }) +} + +// configureDUT configures port1-3 on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + fptest.ConfigureDefaultNetworkInstance(t, dut) + t.Logf("configureDUT") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p3.Name()).Config(), dutPort3.NewOCInterface(p3.Name(), dut)) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p1) + fptest.SetPortSpeed(t, p2) + fptest.SetPortSpeed(t, p3) + } + 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) + } + configNonDefaultNetworkInstance(t, dut) + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(asn, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) +} + +func bgpCreateNbr(localAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutPort3.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName) + pg1.PeerAs = ygot.Uint32(localAs) + + bgpNbr := bgp.GetOrCreateNeighbor(bgpNbr3.ateIPv4) + bgpNbr.PeerGroup = ygot.String(peerGrpName) + bgpNbr.PeerAs = ygot.Uint32(localAs) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbrT := bgpNbr.GetOrCreateTransport() + bgpNbrT.LocalAddress = ygot.String(bgpNbr3.dutIPv4) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + + return niProto +} + +// configureOTGBGP configure BGP on ATE +func configureOTGBGP(t *testing.T, dev gosnappi.Device, top gosnappi.Config, nbr bgpNeighbor) { + t.Helper() + iDutBgp := dev.Bgp().SetRouterId(nbr.ateIPv4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(nbr.Name + ".IPv4").Peers().Add().SetName(nbr.Name + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(nbr.dutIPv4).SetAsNumber(asn).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(false) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(nbr.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(nbr.ateIPv4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32).SetCount(1) +} + +func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + t.Logf("configureOTG") + config := gosnappi.NewConfig() + var dev gosnappi.Device + for i, ap := range []bgpNeighbor{bgpNbr1, bgpNbr2, bgpNbr3} { + // DUT and ATE ports are connected by the same names. + port := config.Ports().Add().SetName(ap.Name) + portName := fmt.Sprintf("port%s", strconv.Itoa(i+1)) + dev = config.Devices().Add().SetName(portName) + eth := dev.Ethernets().Add().SetName(portName + ".Eth").SetMac(ap.MAC) + eth.Connection().SetPortName(port.Name()) + eth.Ipv4Addresses().Add().SetName(portName + ".IPv4"). + SetAddress(ap.ateIPv4).SetGateway(ap.dutIPv4). + SetPrefix(30) + } + configureOTGBGP(t, dev, config, bgpNbr3) + ate.OTG().PushConfig(t, config) + ate.OTG().StartProtocols(t) + return config +} + +// seqIDOffset returns sequence ID offset added with seqIDBase (10), to avoid sequences +// like 1, 10, 11, 12,..., 2, 21, 22, ... while being sent by Ondatra to the DUT. +// It now generates sequences like 11, 12, 13, ..., 19, 20, 21,..., 99. +func seqIDOffset(dut *ondatra.DUTDevice, i uint32) uint32 { + if deviations.PfRequireSequentialOrderPbrRules(dut) { + return i + seqIDBase + } + return i +} + +// configureNetworkInstance configures vrfs DECAP_TE_VRF,ENCAP_TE_VRF_A,ENCAP_TE_VRF_B, +// TE_VRF_222, TE_VRF_111. +func configNonDefaultNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + c := &oc.Root{} + vrfs := []string{niTransitTeVrf, niEncapTeVrfA, niTEVRF111} + for _, vrf := range vrfs { + ni := c.GetOrCreateNetworkInstance(vrf) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), ni) + } + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Name().Config(), deviations.DefaultNetworkInstance(dut)) +} + +func configureVrfSelectionPolicyC(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + time.Sleep(100 * time.Second) + dutPolFwdPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() + + pfRule1 := &policyFwRule{SeqID: 1, family: "ipv4", protocol: 4, sourceAddr: ipv4OuterSrc111WithMask, + ni: niTransitTeVrf} + pfRule2 := &policyFwRule{SeqID: 2, family: "ipv4", sourceAddr: ipv4OuterSrc111WithMask, + ni: niEncapTeVrfA} + + pfRuleList := []*policyFwRule{pfRule1, pfRule2} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niP := ni.GetOrCreatePolicyForwarding() + niPf := niP.GetOrCreatePolicy(vrfPolC) + niPf.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + for _, pfRule := range pfRuleList { + pfR := niPf.GetOrCreateRule(seqIDOffset(dut, pfRule.SeqID)) + if pfRule.family == "ipv4" { + pfRProtoIP := pfR.GetOrCreateIpv4() + if pfRule.protocol != 0 { + pfRProtoIP.Protocol = oc.UnionUint8(pfRule.protocol) + } + if pfRule.sourceAddr != "" { + pfRProtoIP.SourceAddress = ygot.String(pfRule.sourceAddr) + } + } else if pfRule.family == "ipv6" { + pfRProtoIP := pfR.GetOrCreateIpv6() + if pfRule.dscpSet != nil { + pfRProtoIP.DscpSet = pfRule.dscpSet + } + } + + pfRAction := pfR.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(pfRule.ni) + } + p1 := dut.Port(t, "port1") + interfaceID := p1.Name() + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = interfaceID + ".0" + } + intf := niP.GetOrCreateInterface(interfaceID) + intf.ApplyVrfSelectionPolicy = ygot.String(vrfPolC) + intf.GetOrCreateInterfaceRef().Interface = ygot.String(p1.Name()) + intf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + intf.InterfaceRef = nil + } + // gnmi.Update(t, dut, gnmi.OC().NetworkInstance("DEFAULT").Name().Config(), "DEFAULT") + gnmi.Replace(t, dut, dutPolFwdPath.Config(), niP) +} + +func configureGribiRoute(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + ctx := context.Background() + gribic := dut.RawAPIs().GRIBI(t) + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(12, 0). + WithRedundancyMode(fluent.ElectedPrimaryClient).WithFIBACK() + client.Start(ctx, t) + defer client.Stop(t) + gribi.FlushAll(client) + defer gribi.FlushAll(client) + client.StartSending(ctx, t) + gribi.BecomeLeader(t, client) + + tcArgs := &testArgs{ + ctx: ctx, + client: client, + dut: dut, + } + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(1)).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(1)).AddNextHop(uint64(1), uint64(1)), + + fluent.IPv4Entry().WithNetworkInstance(niTransitTeVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix("0.0.0.0/0").WithNextHopGroup(uint64(1))) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 90*time.Second); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(2)).WithIPAddress(atePort2.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(2)).AddNextHop(uint64(2), uint64(1)), + + fluent.IPv4Entry().WithNetworkInstance(niTransitTeVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(ipv4OuterDst111+"/32").WithNextHopGroup(uint64(2))) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 90*time.Second); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList := []string{"0.0.0.0/0", ipv4OuterDst111 + "/32"} + for ip := range defaultVRFIPList { + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(3)).WithIPAddress(atePort2.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(3)).AddNextHop(uint64(3), uint64(1)), + + fluent.IPv4Entry().WithNetworkInstance(niTEVRF111). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(ipv4OuterDst111+"/32").WithNextHopGroup(uint64(3))) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 90*time.Second); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(ipv4OuterDst111+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + // Encap + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(4)).WithEncapsulateHeader(fluent.IPinIP).WithIPinIP(ipv4OuterSrc111, ipv4OuterDst111). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(4)).AddNextHop(uint64(4), uint64(1)), + + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(ipv4InnerDst+"/32").WithNextHopGroup(uint64(4))) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 90*time.Second); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(5)).WithIPAddress(atePort3.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(5)).AddNextHop(uint64(5), uint64(1)), + + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix("0.0.0.0/0").WithNextHopGroup(uint64(5))) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 90*time.Second); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList = []string{"0.0.0.0/0", ipv4InnerDst + "/32"} + for ip := range defaultVRFIPList { + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + +} + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +func createFlow(flowValues *flowArgs, dstMac string) gosnappi.Flow { + flow := gosnappi.NewFlow().SetName(flowValues.flowName) + flow.Metrics().SetEnable(true) + flow.Size().SetFixed(512) + flow.Rate().SetPps(100) + flow.Duration().Continuous() + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().SetValue(dstMac) + // Outer IP header + if flowValues.isIPInIP { + outerIPHdr := flow.Packet().Add().Ipv4() + outerIPHdr.Src().SetValue(flowValues.outHdrSrcIP) + outerIPHdr.Dst().SetValue(flowValues.outHdrDstIP) + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + } else { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + } + return flow +} + +// testTraffic sends traffic flow for duration seconds and returns the +// number of packets sent out. +func sendTraffic(t *testing.T, args *testArgs, top gosnappi.Config, ate *ondatra.ATEDevice, flow gosnappi.Flow, duration int, port []string) { + t.Helper() + top.Flows().Clear() + + args.otgConfig.Captures().Clear() + args.otgConfig.Captures().Add().SetName("packetCapture"). + SetPortNames(port). + SetFormat(gosnappi.CaptureFormat.PCAP) + + flow.TxRx().Port().SetTxName("port1").SetRxNames([]string{"port2", "port3"}) + flow.Metrics().SetEnable(true) + top.Flows().Append(flow) + + ate.OTG().PushConfig(t, top) + time.Sleep(30 * time.Second) + ate.OTG().StartProtocols(t) + time.Sleep(30 * time.Second) + + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + args.otg.SetControlState(t, cs) + + ate.OTG().StartTraffic(t) + time.Sleep(time.Duration(duration) * time.Second) + ate.OTG().StopTraffic(t) + + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + args.otg.SetControlState(t, cs) + otgutils.LogFlowMetrics(t, ate.OTG(), top) + otgutils.LogPortMetrics(t, ate.OTG(), top) +} + +// verifyTrafficFlow verify the each flow on ATE +func verifyTrafficFlow(t *testing.T, ate *ondatra.ATEDevice, flow gosnappi.Flow) bool { + rxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().InPkts().State()) + txPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State()) + lostPkt := txPkts - rxPkts + if got := (lostPkt * 100 / txPkts); got >= tolerancePct { + return false + } + return true +} + +// verifyBgpTelemetry verifies BGP telemetry. +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + nbrPath := bgpPath.Neighbor(bgpNbr3.ateIPv4) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", bgpNbr3.ateIPv4, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", bgpNbr3.ateIPv4, state, want) + } +} + +func captureAndValidatePackets(t *testing.T, args *testArgs, packetVal *packetValidation) { + 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) + } + if _, err := f.Write(bytes); err != nil { + t.Fatalf("ERROR: 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.validateDecap { + validateTrafficDecap(t, packetSource) + } + if packetVal.validateNoDecap { + validateTrafficNonDecap(t, packetSource, packetVal.outDstIP[0], packetVal.inHdrIP) + } + if packetVal.validateEncap { + validateTrafficEncap(t, packetSource, packetVal.outDstIP, packetVal.inHdrIP) + } + args.otgConfig.Captures().Clear() + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) +} + +func validateTrafficDecap(t *testing.T, packetSource *gopacket.PacketSource) { + t.Helper() + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket, _ := ipLayer.(*layers.IPv4) + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipInnerLayer != nil { + t.Errorf("Packets are not decapped, Inner IP header is not removed.") + } + } +} + +func validateTrafficNonDecap(t *testing.T, packetSource *gopacket.PacketSource, outDstIP, inHdrIP string) { + t.Helper() + t.Log("Validate traffic non decap routes") + var packetCheckCount uint32 = 1 + for packet := range packetSource.Packets() { + if packetCheckCount >= 5 { + break + } + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket, _ := ipLayer.(*layers.IPv4) + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipInnerLayer != nil { + if ipPacket.DstIP.String() != outDstIP { + t.Errorf("Negatice test for Decap failed. Traffic sent to route which does not match the decap route are decaped") + } + ipInnerPacket, _ := ipInnerLayer.(*layers.IPv4) + 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.") + break + } + } +} + +func validateTrafficEncap(t *testing.T, packetSource *gopacket.PacketSource, outDstIP []string, innerIP string) { + t.Helper() + t.Log("Validate traffic non decap routes") + var packetCheckCount uint32 = 1 + for packet := range packetSource.Packets() { + if packetCheckCount >= 5 { + break + } + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket, _ := ipLayer.(*layers.IPv4) + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipInnerLayer != nil { + if len(outDstIP) == 2 { + if ipPacket.DstIP.String() != outDstIP[0] || ipPacket.DstIP.String() != outDstIP[1] { + t.Errorf("Packets are not encapsulated as expected") + } + } else { + if ipPacket.DstIP.String() != outDstIP[0] { + t.Errorf("Packets are not encapsulated as expected") + } + } + t.Logf("Traffic for encap routes passed.") + break + } + } +} diff --git a/feature/gribi/otg_tests/gribi_route_test/metadata.textproto b/feature/gribi/otg_tests/gribi_route_test/metadata.textproto new file mode 100644 index 00000000000..333a3e00ed4 --- /dev/null +++ b/feature/gribi/otg_tests/gribi_route_test/metadata.textproto @@ -0,0 +1,55 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "8beaac46-9b7b-49c4-9bde-62ad630aa6c7" +plan_id: "RT-14.2" +description: "GRIBI Route Test" +testbed: TESTBED_DUT_ATE_4LINKS + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + gribi_mac_override_with_static_arp: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + ttl_copy_unsupported: true + isis_single_topology_required: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + aggregate_atomic_update: true + interface_enabled: true + missing_value_for_defaults: true + missing_isis_interface_afi_safi_enable: true + } +} + +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + omit_l2_mtu: true + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + missing_isis_interface_afi_safi_enable: true + isis_require_same_l1_metric_with_l2_metric: true + route_policy_under_afi_unsupported: true + static_protocol_name: "STATIC" + aggregate_atomic_update: true + missing_value_for_defaults: true + max_ecmp_paths: true + explicit_interface_in_default_vrf: false + } +} diff --git a/feature/gribi/otg_tests/gribi_scaling/README.md b/feature/gribi/otg_tests/gribi_scaling/README.md index cc55075e625..f286dec9da6 100644 --- a/feature/gribi/otg_tests/gribi_scaling/README.md +++ b/feature/gribi/otg_tests/gribi_scaling/README.md @@ -38,3 +38,16 @@ Validate gRIBI scaling requirements. * Validate that the entries are installed as FIB_PROGRAMMED * TODO: Add flows destinating to IPBlocks and ensure ATEPort2 receives it with no loss + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/gribi/otg_tests/gribi_scaling/gribi_scaling_test.go b/feature/gribi/otg_tests/gribi_scaling/gribi_scaling_test.go index f0a90d3639c..deb328c20c9 100644 --- a/feature/gribi/otg_tests/gribi_scaling/gribi_scaling_test.go +++ b/feature/gribi/otg_tests/gribi_scaling/gribi_scaling_test.go @@ -20,16 +20,18 @@ import ( "encoding/binary" "fmt" "net" + "strings" "testing" "time" "github.com/open-traffic-generator/snappi/gosnappi" + fpargs "github.com/openconfig/featureprofiles/internal/args" "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/gribigo/chk" - "github.com/openconfig/gribigo/constants" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/tescale" "github.com/openconfig/gribigo/fluent" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" @@ -46,30 +48,19 @@ func TestMain(m *testing.M) { // // The testbed consists of ate:port1 -> dut:port1 // and dut:port2 -> ate:port2. -// There are 64 SubInterfaces between dut:port2 +// There are 16 SubInterfaces between dut:port2 // and ate:port2 // // - ate:port1 -> dut:port1 subnet 192.0.2.0/30 -// - ate:port2 -> dut:port2 64 Sub interfaces: +// - ate:port2 -> dut:port2 16 Sub interfaces: // - ate:port2.0 -> dut:port2.0 VLAN-ID: 0 subnet 198.51.100.0/30 // - ate:port2.1 -> dut:port2.1 VLAN-ID: 1 subnet 198.51.100.4/30 // - ate:port2.2 -> dut:port2.2 VLAN-ID: 2 subnet 198.51.100.8/30 // - ate:port2.i -> dut:port2.i VLAN-ID i subnet 198.51.100.(4*i)/30 -// - ate:port2.63 -> dut:port2.63 VLAN-ID 63 subnet 198.51.100.252/30 +// - ate:port2.16 -> dut:port2.16 VLAN-ID 16 subnet 198.51.100.60/30 const ( ipv4PrefixLen = 30 // ipv4PrefixLen is the ATE and DUT interface IP prefix length. - vrf1 = "vrf1" - vrf2 = "vrf2" - vrf3 = "vrf3" - IPBlock1 = "198.18.0.1/18" // IPBlock1 represents the ipv4 entries in VRF1 - IPBlock2 = "198.18.64.1/18" // IPBlock2 represents the ipv4 entries in VRF2 - IPBlock3 = "198.18.128.1/18" // IPBlock3 represents the ipv4 entries in VRF3 - nhID1 = 65 // nhID1 is the starting nh index for entries in VRF1 - nhID2 = 1065 // nhID2 is the starting nh index for entries in VRF2 - nhID3 = 18565 // nhID3 is the starting nh index for entries in VRF3 - tunnelSrcIP = "198.18.204.1" // tunnelSrcIP represents Source IP of IPinIP Tunnel - tunnelDstIP = "198.18.208.1" // tunnelDstIP represents Dest IP of IPinIP Tunnel - policyName = "redirect-to-VRF1" + policyName = "redirect-to-vrf_t" ) var ( @@ -87,219 +78,12 @@ var ( } ) -// entryIndex captures all the parameters required for specifying : -// -// a. number of nextHops in a nextHopGroup -// b. number of IPEntries per nextHopGroup -type routesParam struct { - nhgIndex int // nhgIndex is the starting nhg Index for each IPBlock - maxNhCount int // maxNhCount is the max number of nexthops per nextHopGroup - maxIPCount int // maxIPCount is the max numbver of IPs per nextHopGroup - vrf string // vrf represents the name of the vrf string - nhID int // nhID is the starting nh Index for each nextHop range -} - -// pushIPv4Entries pushes IP entries in a specified VRF in the target DUT. -// It uses the parameters from entryIndex and virtualVIPs for programming entries. -func pushIPv4Entries(t *testing.T, virtualVIPs []string, indices []*routesParam, args *testArgs) { - - IPBlocks := make(map[string][]string) - IPBlocks[vrf1] = createIPv4Entries(IPBlock1) - IPBlocks[vrf2] = createIPv4Entries(IPBlock2) - IPBlocks[vrf3] = createIPv4Entries(IPBlock3) - nextHops := make(map[string][]string) - nextHops[vrf2] = buildL3NextHops(17500, virtualVIPs) - nextHops[vrf1] = virtualVIPs - nextHops[vrf3] = IPBlocks[vrf1][:500] - - for _, index := range indices { - installEntries(t, IPBlocks[index.vrf], nextHops[index.vrf], *index, args) - } -} - -// buildIndexList returns all indices required for installing entries in each VRF. -func buildIndexList() []*routesParam { - index1v4 := &routesParam{nhgIndex: 3, maxNhCount: 10, maxIPCount: 200, vrf: vrf1, nhID: nhID1} - index2v4 := &routesParam{nhgIndex: 103, maxNhCount: 35, maxIPCount: 60, vrf: vrf2, nhID: nhID2} - index3v4 := &routesParam{nhgIndex: 605, maxNhCount: 1, maxIPCount: 40, vrf: vrf3, nhID: nhID3} - - return []*routesParam{index1v4, index2v4, index3v4} -} - -// buildL3NextHop buids N number of NHs each reference (squentially) an IP from the provided IP block. -func buildL3NextHops(n int, ips []string) []string { - // repeatedNextHops will store the "n" times repeated ips []string - repeatedNextHops := []string{} - if n > len(ips) { - repeatCount := len(ips) / n - for min, max := 1, repeatCount; min < max; { - repeatedNextHops = append(repeatedNextHops, ips...) - min = min + 1 - } - repeatCount = len(ips) % n - if repeatCount > 0 { - repeatedNextHops = append(repeatedNextHops, ips[:repeatCount]...) - } - } - return repeatedNextHops -} - -// createIPv4Entries creates IPv4 Entries given the totalCount and starting prefix -func createIPv4Entries(startIP string) []string { - - _, netCIDR, _ := net.ParseCIDR(startIP) - netMask := binary.BigEndian.Uint32(netCIDR.Mask) - firstIP := binary.BigEndian.Uint32(netCIDR.IP) - lastIP := (firstIP & netMask) | (netMask ^ 0xffffffff) - entries := []string{} - for i := firstIP; i <= lastIP; i++ { - ip := make(net.IP, 4) - binary.BigEndian.PutUint32(ip, i) - - entries = append(entries, fmt.Sprint(ip)) - } - return entries -} - -// installEntries installs IPv4 Entries in the VRF with the given nextHops and nextHopGroups using gRIBI. -func installEntries(t *testing.T, ips []string, nexthops []string, index routesParam, args *testArgs) { - nextCount := 0 - localIndex := index.nhgIndex - for i, ateAddr := range nexthops { - ind := uint64(index.nhID + i) - if index.vrf == "vrf3" { - nh := fluent.NextHopEntry(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). - WithIndex(ind). - WithIPinIP(tunnelSrcIP, ateAddr). - WithDecapsulateHeader(fluent.IPinIP). - WithEncapsulateHeader(fluent.IPinIP). - WithNextHopNetworkInstance(vrf1). - WithElectionID(args.electionID.Low, args.electionID.High) - args.client.Modify().AddEntry(t, nh) - } else { - nh := fluent.NextHopEntry(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). - WithIndex(ind). - WithIPAddress(ateAddr). - WithElectionID(args.electionID.Low, args.electionID.High) - args.client.Modify().AddEntry(t, nh) - } - - nhg := fluent.NextHopGroupEntry(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). - WithID(uint64(localIndex)). - AddNextHop(ind, uint64(index.maxNhCount)). - WithElectionID(args.electionID.Low, args.electionID.High) - args.client.Modify().AddEntry(t, nhg) - nextCount = nextCount + 1 - if nextCount == index.maxNhCount { - localIndex = localIndex + 1 - nextCount = 0 - } - } - nhgCount := localIndex - index.nhgIndex - if nextCount == 0 { // last nhg without no nh needs to be ignored - nhgCount-- - } - // maxIPCount should be set based on the number of added nhg, - // otherwise ipv4entry may be added with invalid nhg id (Note. forward refrencing is not allowed) - index.maxIPCount = (len(ips) / nhgCount) + 1 - nextCount = 0 - localIndex = index.nhgIndex - for ip := range ips { - args.client.Modify().AddEntry(t, - fluent.IPv4Entry(). - WithPrefix(ips[ip]+"/32"). - WithNetworkInstance(index.vrf). - WithNextHopGroup(uint64(localIndex)). - WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(args.dut))) - nextCount = nextCount + 1 - if nextCount == index.maxIPCount { - localIndex = localIndex + 1 - nextCount = 0 - } - } - - time.Sleep(1 * time.Minute) - if err := awaitTimeout(args.ctx, args.client, t, 2*time.Minute); err != nil { - t.Fatalf("Could not program entries via clientA, got err: %v", err) - } - gr, err := args.client.Get(). - WithNetworkInstance(index.vrf). - WithAFT(fluent.IPv4). - Send() - if err != nil { - t.Fatalf("got unexpected error from get, got: %v", err) - } - nextCount = 0 - for ip := range ips { - chk.GetResponseHasEntries(t, gr, - fluent.IPv4Entry(). - WithNetworkInstance(index.vrf). - WithNextHopGroup(uint64(index.nhgIndex)). - WithPrefix(ips[ip]+"/32"), - ) - nextCount = nextCount + 1 - if nextCount == index.maxIPCount { - index.nhgIndex = index.nhgIndex + 1 - nextCount = 0 - } - } -} - -// pushDefaultEntries creates NextHopGroup entries using the 64 SubIntf address and creates 1000 IPV4 Entries. -func pushDefaultEntries(t *testing.T, args *testArgs, nextHops []string) []string { - for i := range nextHops { - index := uint64(i + 1) - args.client.Modify().AddEntry(t, - fluent.NextHopEntry(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). - WithIndex(index). - WithIPAddress(nextHops[i]). - WithElectionID(args.electionID.Low, args.electionID.High)) - - args.client.Modify().AddEntry(t, - fluent.NextHopGroupEntry(). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). - WithID(uint64(2)). - AddNextHop(index, 64). - WithElectionID(args.electionID.Low, args.electionID.High)) - } - time.Sleep(time.Minute) - virtualVIPs := createIPv4Entries("198.18.196.1/22") - - for ip := range virtualVIPs { - args.client.Modify().AddEntry(t, - fluent.IPv4Entry(). - WithPrefix(virtualVIPs[ip]+"/32"). - WithNetworkInstance(deviations.DefaultNetworkInstance(args.dut)). - WithNextHopGroup(uint64(2)). - WithElectionID(args.electionID.Low, args.electionID.High)) - } - if err := awaitTimeout(args.ctx, args.client, t, time.Minute); err != nil { - t.Fatalf("Could not program entries via clientA, got err: %v", err) - } - - for ip := range virtualVIPs { - chk.HasResult(t, args.client.Results(t), - fluent.OperationResult(). - WithIPv4Operation(virtualVIPs[ip]+"/32"). - WithOperationType(constants.Add). - WithProgrammingResult(fluent.InstalledInFIB). - AsResult(), - chk.IgnoreOperationID(), - ) - } - return virtualVIPs -} - func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { dp1 := dut.Port(t, "port1") dp2 := dut.Port(t, "port2") d := &oc.Root{} - vrfs := []string{deviations.DefaultNetworkInstance(dut), vrf1, vrf2, vrf3} + vrfs := []string{deviations.DefaultNetworkInstance(dut), tescale.VRFT, tescale.VRFR, tescale.VRFRD} createVrf(t, dut, vrfs) // configure Ethernet interfaces first @@ -314,7 +98,7 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { applyForwardingPolicy(t, dp1.Name()) - // configure 64 L3 subinterfaces under DUT port#2 and assign them to DEFAULT vrf + // configure 16 L3 subinterfaces under DUT port#2 and assign them to DEFAULT vrf configureDUTSubIfs(t, d, dut, dp2) } @@ -344,7 +128,7 @@ func configurePBF(dut *ondatra.DUTDevice) *oc.NetworkInstance_PolicyForwarding { vrfPolicy := pf.GetOrCreatePolicy(policyName) vrfPolicy.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) vrfPolicy.GetOrCreateRule(1).GetOrCreateIpv4().SourceAddress = ygot.String(atePort1.IPv4 + "/32") - vrfPolicy.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(vrf1) + vrfPolicy.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(tescale.VRFT) return pf } @@ -405,9 +189,9 @@ func createSubifDUT(t *testing.T, d *oc.Root, dut *ondatra.DUTDevice, dutPort *o gnmi.Replace(t, dut, gnmi.OC().Interface(ifName).Subinterface(index).Config(), s) } -// configureDUTSubIfs configures 64 DUT subinterfaces on the target device +// configureDUTSubIfs configures 16 DUT subinterfaces on the target device func configureDUTSubIfs(t *testing.T, d *oc.Root, dut *ondatra.DUTDevice, dutPort *ondatra.Port) { - for i := 0; i < 64; i++ { + for i := 0; i < 16; i++ { index := uint32(i) vlanID := uint16(i) if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { @@ -421,11 +205,11 @@ func configureDUTSubIfs(t *testing.T, d *oc.Root, dut *ondatra.DUTDevice, dutPor } } -// configureATESubIfs configures 64 ATE subinterfaces on the target device +// configureATESubIfs configures 16 ATE subinterfaces on the target device // It returns a slice of the corresponding ATE IPAddresses. func configureATESubIfs(t *testing.T, top gosnappi.Config, atePort *ondatra.Port, dut *ondatra.DUTDevice) []string { nextHops := []string{} - for i := 0; i < 64; i++ { + for i := 0; i < 16; i++ { vlanID := uint16(i) if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { vlanID = uint16(i) + 1 @@ -446,7 +230,7 @@ func configureATE(t *testing.T, top gosnappi.Config, atePort *ondatra.Port, vlan dev := top.Devices().Add().SetName(Name + ".Dev") eth := dev.Ethernets().Add().SetName(Name + ".Eth").SetMac(MAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(atePort.ID()) + eth.Connection().SetPortName(atePort.ID()) if vlanID != 0 { eth.Vlans().Add().SetName(Name).SetId(uint32(vlanID)) } @@ -460,16 +244,6 @@ func awaitTimeout(ctx context.Context, c *fluent.GRIBIClient, t testing.TB, time return c.Await(subctx, t) } -// testArgs holds the objects needed by a test case. -type testArgs struct { - ctx context.Context - client *fluent.GRIBIClient - dut *ondatra.DUTDevice - ate *ondatra.ATEDevice - top gosnappi.Config - electionID gribi.Uint128 -} - // incrementMAC increments the MAC by i. Returns error if the mac cannot be parsed or overflows the mac address space func incrementMAC(mac string, i int) (string, error) { macAddr, err := net.ParseMAC(mac) @@ -489,6 +263,7 @@ func incrementMAC(mac string, i int) (string, error) { func TestScaling(t *testing.T) { dut := ondatra.DUT(t, "dut") + overrideScaleParams(dut) ate := ondatra.ATE(t, "ate") ctx := context.Background() @@ -528,20 +303,108 @@ func TestScaling(t *testing.T) { if err := awaitTimeout(ctx, client, t, time.Minute); err != nil { t.Fatalf("Await got error during session negotiation for clientA: %v", err) } - eID := gribi.BecomeLeader(t, client) - - args := &testArgs{ - ctx: ctx, - client: client, - dut: dut, - ate: ate, - top: top, - electionID: eID, + gribi.BecomeLeader(t, client) + + vrfConfigs := tescale.BuildVRFConfig(dut, subIntfIPs, + tescale.Param{ + V4TunnelCount: *fpargs.V4TunnelCount, + V4TunnelNHGCount: *fpargs.V4TunnelNHGCount, + V4TunnelNHGSplitCount: *fpargs.V4TunnelNHGSplitCount, + EgressNHGSplitCount: *fpargs.EgressNHGSplitCount, + V4ReEncapNHGCount: *fpargs.V4ReEncapNHGCount, + }, + ) + createFlow(t, ate, top, vrfConfigs[1]) + var maxEntries int = 10000 + for _, vrfConfig := range vrfConfigs { + entries := append(vrfConfig.NHs, vrfConfig.NHGs...) + entries = append(entries, vrfConfig.V4Entries...) + // Breaking more than 10k gribi entries from 1 modify operation into multiple + // modify operations with 10k entries each. + if len(entries) > maxEntries { + index := 0 + for idx := 0; idx < len(entries)/maxEntries; idx++ { + client.Modify().AddEntry(t, entries[index:maxEntries+index]...) + if err := awaitTimeout(ctx, client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries, got err: %v", err) + } + index += maxEntries + } + // Program the remaining entries less than 10k in another modify operation. + if len(entries)%maxEntries != 0 { + client.Modify().AddEntry(t, entries[index:]...) + if err := awaitTimeout(ctx, client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries, got err: %v", err) + } + } + + } else { + client.Modify().AddEntry(t, entries...) + if err := awaitTimeout(ctx, client, t, 5*time.Minute); err != nil { + t.Fatalf("Could not program entries, got err: %v", err) + } + } + t.Logf("Created %d NHs, %d NHGs, %d IPv4Entries in %s VRF", len(vrfConfig.NHs), len(vrfConfig.NHGs), len(vrfConfig.V4Entries), vrfConfig.Name) + } + checkTraffic(t, ate, top) +} + +func createFlow(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, vrfTConf *tescale.VRFConfig) { + dstIPs := []string{} + for _, v4Entry := range vrfTConf.V4Entries { + ep, _ := v4Entry.EntryProto() + dstIPs = append(dstIPs, strings.Split(ep.GetIpv4().GetPrefix(), "/")[0]) + } + rxNames := []string{} + for i := 0; i < 16; i++ { + rxNames = append(rxNames, fmt.Sprintf(`dst%d.IPv4`, i)) + } + + top.Flows().Clear() + flow := top.Flows().Add().SetName("flow") + flow.Metrics().SetEnable(true) + flow.Size().SetFixed(512) + flow.Rate().SetPps(100) + flow.Duration().Continuous() + flow.TxRx().Device(). + SetTxNames([]string{"src.IPv4"}). + SetRxNames(rxNames) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(atePort1.IPv4) + v4.Dst().SetValues(dstIPs) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "flow") +} + +func checkTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) { + ate.OTG().StartTraffic(t) + time.Sleep(time.Second * 30) + ate.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, ate.OTG(), top) + otgutils.LogPortMetrics(t, ate.OTG(), top) + + t.Log("Checking flow telemetry...") + recvMetric := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow("flow").State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + lostPackets := txPackets - rxPackets + lossPct := lostPackets * 100 / txPackets + + if lossPct > 1 { + t.Errorf("FAIL- Got %v%% packet loss for %s ; expected < 1%%", lossPct, "flow") + } +} + +// overrideScaleParams allows to override the default scale parameters based on the DUT vendor. +func overrideScaleParams(dut *ondatra.DUTDevice) { + if deviations.OverrideDefaultNhScale(dut) { + if dut.Vendor() == ondatra.CISCO { + *fpargs.V4TunnelCount = 3328 + } } - // nextHops are ipv4 entries used for deriving nextHops for IPBlock1 and IPBlock2 - nextHops := pushDefaultEntries(t, args, subIntfIPs) - // indexList is the metadata of number of NH/NHG/IP count/VRF for each IPBlock - indexList := buildIndexList() - // pushIPv4Entries builds the scaling topology. - pushIPv4Entries(t, nextHops, indexList, args) } diff --git a/feature/gribi/otg_tests/gribi_scaling/metadata.textproto b/feature/gribi/otg_tests/gribi_scaling/metadata.textproto index c77d9563f11..393d30f1bd5 100644 --- a/feature/gribi/otg_tests/gribi_scaling/metadata.textproto +++ b/feature/gribi/otg_tests/gribi_scaling/metadata.textproto @@ -12,6 +12,7 @@ platform_exceptions: { deviations: { ipv4_missing_enabled: true interface_ref_interface_id_format: true + override_default_nh_scale: true } } platform_exceptions: { @@ -20,7 +21,6 @@ platform_exceptions: { } deviations: { no_mix_of_tagged_and_untagged_subinterfaces: true - explicit_interface_ref_definition: true explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true @@ -32,7 +32,6 @@ platform_exceptions: { } deviations: { no_mix_of_tagged_and_untagged_subinterfaces: true - explicit_interface_ref_definition: true } } platform_exceptions: { @@ -40,8 +39,8 @@ platform_exceptions: { vendor: ARISTA } deviations: { - deprecated_vlan_id: true interface_enabled: true default_network_instance: "default" + omit_l2_mtu: true } } diff --git a/feature/gribi/otg_tests/gribigo_compliance_test/README.md b/feature/gribi/otg_tests/gribigo_compliance_test/README.md index 03a1b975784..f5fb9926170 100644 --- a/feature/gribi/otg_tests/gribigo_compliance_test/README.md +++ b/feature/gribi/otg_tests/gribigo_compliance_test/README.md @@ -21,3 +21,22 @@ For each compliance test case in the test suite: * If the case expects a t.Fatal result, use testt.ExpectFatal. * If the case expects a t.Error result, use testt.ExpectError. * Otherwise, call the test case function directly. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths and RPC intended to be covered by this test. + +```yaml +paths: + /interfaces/interface/config/enabled: + + +rpcs: + gnmi: + gNMI.Subscribe: + ON_CHANGE: true + + gnoi: + system.System.Reboot: + +``` diff --git a/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go b/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go index 1e713748a1d..1792e620550 100644 --- a/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go +++ b/feature/gribi/otg_tests/gribigo_compliance_test/gribigo_compliance_test.go @@ -16,8 +16,10 @@ package gribigo_compliance_test import ( + "context" "strings" "testing" + "time" "flag" @@ -27,12 +29,15 @@ import ( "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/featureprofiles/internal/gribi" "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/gribigo/chk" "github.com/openconfig/gribigo/compliance" + "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/testt" + "github.com/openconfig/ygot/ygot" ) var ( @@ -42,6 +47,7 @@ var ( skipIdempotentDelete = flag.Bool("skip_idempotent_delete", true, "Skip tests for idempotent DELETE operations") skipNonDefaultNINHG = flag.Bool("skip_non_default_ni_nhg", true, "skip tests that add entries to non-default network-instance") skipMPLS = flag.Bool("skip_mpls", true, "skip tests that add mpls entries") + skipIPv6 = flag.Bool("skip_ipv6", true, "skip tests that add ipv6 entries") nonDefaultNI = flag.String("non_default_ni", "non-default-vrf", "non-default network-instance name") @@ -81,6 +87,14 @@ var ( } ) +type testArgs struct { + ctx context.Context + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + client *fluent.GRIBIClient + electionID gribi.Uint128 +} + func TestMain(m *testing.M) { fptest.RunTests(m) } @@ -105,6 +119,8 @@ func shouldSkip(tt *compliance.TestSpec) string { return "This RequiresIdempotentDelete test is skipped by --skip_idempotent_delete" case *skipMPLS && tt.In.RequiresMPLS: return "This RequiresMPLS test is skipped by --skip_mpls" + case *skipIPv6 && tt.In.RequiresIPv6: + return "This RequiresIPv6 test is skipped by --skip_ipv6" } return moreSkipReasons[tt.In.ShortName] } @@ -174,6 +190,58 @@ func TestCompliance(t *testing.T) { tt.In.Fn(c, t, opts...) }) } + ctx := context.Background() + c := fluent.NewClient() + c.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) + c.Start(ctx, t) + c.StartSending(ctx, t) + eID := gribi.BecomeLeader(t, c) + tcArgs := &testArgs{ + ctx: ctx, + client: c, + dut: dut, + ate: ate, + electionID: eID, + } + testAdditionalCompliance(tcArgs, t) +} + +// testAdditionalCompliance tests has additional compliance tests that are not covered by the compliance +// test suite. +func testAdditionalCompliance(tcArgs *testArgs, t *testing.T) { + + tests := []struct { + desc string + fn func(*testArgs, fluent.ProgrammingResult, testing.TB) + }{ + { + desc: "Add IPv4 Entry with NHG which point to NH IP which doesn't resolved with in topology", + fn: addNHGReferencingToUnresolvedNH, + }, + { + desc: "Add IPv4 Entry with NHG which point to NH with Down port", + fn: addNHGReferencingToDownPort, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + if err := gribi.FlushAll(tcArgs.client); err != nil { + t.Fatal(err) + } + tt.fn(tcArgs, fluent.InstalledInFIB, t) + }) + } +} + +func setDUTInterfaceWithState(t testing.TB, dut *ondatra.DUTDevice, p *ondatra.Port, state bool) { + dc := gnmi.OC() + i := &oc.Interface{} + i.Enabled = ygot.Bool(state) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.Name = ygot.String(p.Name()) + gnmi.Update(t, dut, dc.Interface(p.Name()).Config(), i) + } // configureDUT configures port1-3 on the DUT. @@ -222,3 +290,70 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { return top } + +// addNHGReferencingToUnresolvedNH tests a case where a nexthop group references a nexthop IP 1.0.0.1 +// that does not Resolved with in the topology +func addNHGReferencingToUnresolvedNH(tcArgs *testArgs, wantACK fluent.ProgrammingResult, t testing.TB) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(tcArgs.client); err != nil { + t.Fatal(err) + } + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(2000).WithIPAddress("1.0.0.1"), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(2000).AddNextHop(2000, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithPrefix("203.0.113.101/32").WithNextHopGroup(2000)) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 2*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithNextHopGroupOperation(2000). + WithOperationType(constants.Add). + WithProgrammingResult(wantACK). + AsResult(), + chk.IgnoreOperationID()) +} + +// addNHGReferencingToDownPort tests a case where a nexthop group references to nexthop port that is down. +// Entry must be expected to be installed in FIB. +func addNHGReferencingToDownPort(tcArgs *testArgs, wantACK fluent.ProgrammingResult, t testing.TB) { + t.Log("Setting port2 to down...") + p := tcArgs.dut.Port(t, "port2") + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(tcArgs.client); err != nil { + t.Fatal(err) + } + setDUTInterfaceWithState(t, tcArgs.dut, p, false) + + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(2000).WithIPAddress(atePort2.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(2000).AddNextHop(2000, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithPrefix("203.0.113.100/32").WithNextHopGroup(2000), + ) + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 2*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation("203.0.113.100/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) +} + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} diff --git a/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/README.md b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/README.md new file mode 100644 index 00000000000..026f4dd889f --- /dev/null +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/README.md @@ -0,0 +1,162 @@ +# TE-3.31: Hierarchical weight resolution with PBF + +## Summary + +Ensures that next-hop weights (for WCMP) are honored hierarchically in gRIBI +recursive resolution and traffic is load-shared according to these weights. + +## Procedure + +Configure ATE and DUT: + +* Connect ATE port-1 to DUT port-1. ATE port-2 to DUT port-2. + +* Create a non-default VRF (VRF-1) that contains no interfaces. + +* On DUT port-2 and ATE port-2 create 18 L3 sub-interfaces each with a /30 + subnet as below: + + * On DUT port-2, create subinterfaces with indices 1 to 18 mapped to VLAN + IDs 1 to 18 and corressponding IPv4 addresses 192.0.2.5, 192.0.2.9, ..., + 192.0.2.73 respectively. + + * On ATE port-2, create subinterfaces with indices 1 to 18 mapped to VLAN + IDs 1 to 18 and corresponding IPv4 addresses 192.0.2.6, 192.0.2.10, ..., + 192.0.2.74 and default gateways as 192.0.2.5, 192.0.2.9, ..., 192.0.2.73 + respectively. + +* On DUT port-1 and ATE port-1 create a single L3 interface. + +* On DUT, create a policy-based forwarding rule to redirect all traffic + received from DUT port-1 into VRF-1 (based on src. IP match criteria). + +* Add an empty decap VRF, `DECAP_TE_VRF`. + +* Add 4 empty encap VRFs, `ENCAP_TE_VRF_A`, `ENCAP_TE_VRF_B`, `ENCAP_TE_VRF_C` + and `ENCAP_TE_VRF_D`. + +* Replace the existing VRF selection policy with `vrf_selection_policy_w` as + in + +Test case for basic hierarchical weight: + +* Establish gRIBI client connection with DUT with PERSISTENCE, make it become + leader and install the following Entries: + + * IPv4Entry 203.0.113.0/32 in VRF-1, pointing to NextHopGroup(NHG#1) in + default VRF, with two NextHops(NH#1, NH#2) in default VRF: + + * NH#1 with weight:1, pointing to 192.0.2.111 + + * NH#2 with weight:3, pointing to 192.0.2.222 + + * IPv4Entry 192.0.2.111/32 in default VRF, pointing to NextHopGroup(NHG#2) + in default VRF, with two NextHops(NH#10, NH#11) in default VRF: + + * NH#10 with weight:1, pointing to 192.0.2.10 + + * NH#11 with weight:3, pointing to 192.0.2.14 + + * IPv4Entry 192.0.2.222/32 in default VRF, pointing to NextHopGroup(NHG#3) + in default VRF, with two NextHops(NH#100, NH#101) in default VRF: + + * NH#100 with weight:3, pointing to 192.0.2.18 + + * NH#101 with weight:5, pointing to 192.0.2.22 + +* Validate with traffic: + + * NH10: (1/4) * (1/4) = 6.25% traffic received by ATE port-2 VLAN 1 + + * NH11: (1/4) * (3/4) = 18.75% traffic received by ATE port-2 VLAN 2 + + * NH100: (3/4) * (3/8) = 28.12% traffic received by ATE port-2 VLAN 3 + + * NH101: (3/4) * (5/8) = 46.87% traffic received by ATE port-2 VLAN 4 + + * A tolerance of 0.2% is allowed for each VLAN for now, since we only test + for 2 mins. + +Test case for hierarchical weight in boundary scenarios, with maximum expected +WCMP width of 16 nexthops: + +* Flush previous gRIBI Entries for all NIs and establish a new connection with + DUT with PERSISTENCE and install the following Entries: + + * IPv4Entry 203.0.113.0/32 in VRF-1, pointing to NextHopGroup(NHG#1) in + default VRF, with two NextHops(NH#1, NH#2) in default VRF: + + * NH#1 with weight:1, pointing to 192.0.2.111 + + * NH#2 with weight:31, pointing to 192.0.2.222 + + * IPv4Entry 192.0.2.111/32 in default VRF, pointing to NextHopGroup(NHG#2) + in default VRF, with two NextHops(NH#10, NH#11) in default VRF: + + * NH#10 with weight:3, pointing to 192.0.2.10 + + * NH#11 with weight:5, pointing to 192.0.2.14 + + * IPv4Entry 192.0.2.222/32 in default VRF, pointing to NextHopGroup(NHG#3) + in default VRF, with 16 NextHops(NH#100, NH#101, ..., NH#115), all with + weight: 16 except NHG#100 is of weight 1, in default VRF: + + * NH#100 with weight:1, pointing to 192.0.2.18 + + * NH#101 with weight:16, pointing to 192.0.2.22 + + * ... + + * NH#115 with weight:16, pointing to 192.0.2.79 + +* Validate with traffic: + + * NH10: (1/32) * (3/8) ~ 1.171% traffic received by ATE port-2 VLAN 1 + + * NH11: (1/32) * (5/8) ~ 1.953% traffic received by ATE port-2 VLAN 2 + + * NH100: (31/32) * (1/241) ~ 0.402% traffic received by ATE port-2 VLAN 3 + + * for each VLAN ID in 4...18: + + * NH: (31/32) * (16/241) ~ 6.432% traffic received by ATE port-2 VLAN + ID + + * A tolerance of 0.2% is allowed for each VLAN for now, since we only test + for 2 mins. + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Minimum DUT platform requirement + +vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: N/A + + ## State paths: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight: + +rpcs: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` \ No newline at end of file diff --git a/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/hierarchical_weight_resolution_pbf_test.go b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/hierarchical_weight_resolution_pbf_test.go new file mode 100644 index 00000000000..bde47271652 --- /dev/null +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/hierarchical_weight_resolution_pbf_test.go @@ -0,0 +1,751 @@ +// 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 hierarchical_weight_resolution_test implements TE-3.3 of the Popgate vendor testplan +package hierarchical_weight_resolution_pbf_test + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "net" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +type attributes struct { + attrs.Attributes + numSubIntf uint32 + ip func(vlan uint8) string + gateway func(vlan uint8) string +} + +type nhInfo struct { + index uint64 + weight uint64 +} + +const ( + ipv4EntryPrefix = "203.0.113.0/32" + ipv4FlowIP = "203.0.113.0" + innerSrcIPv4Start = "198.18.0.0" + innerDstIPv4Start = "198.19.0.0" + ipv4PrefixLen = 30 + ipv4FlowCount = 65000 + nhEntryIP1 = "192.0.2.111" + nhEntryIP2 = "192.0.2.222" + nonDefaultVRF = "TE_VRF_111" + policyName = "redirect-to-VRF1" + ipipProtocol = 4 + decapFlowSrc = "198.51.100.111" + dscpEncapA1 = 10 +) + +var ( + dutPort1 = attributes{ + Attributes: attrs.Attributes{ + Desc: "dutPort1", + Name: "port1", + IPv4: dutPort1IPv4(0), + IPv4Len: ipv4PrefixLen, + }, + numSubIntf: 0, + ip: dutPort1IPv4, + } + + atePort1 = attributes{ + Attributes: attrs.Attributes{ + Name: "port1", + MAC: "02:00:01:01:01:01", + IPv4: atePort1IPv4(0), + IPv4Len: ipv4PrefixLen, + }, + numSubIntf: 0, + ip: atePort1IPv4, + gateway: dutPort1IPv4, + } + + dutPort2 = attributes{ + Attributes: attrs.Attributes{ + Desc: "dutPort2", + Name: "port2", + IPv4: dutPort2IPv4(0), + IPv4Len: ipv4PrefixLen, + }, + numSubIntf: 18, + ip: dutPort2IPv4, + } + + atePort2 = attributes{ + Attributes: attrs.Attributes{ + Name: "port2", + MAC: "02:00:02:01:01:01", + IPv4: atePort2IPv4(0), + IPv4Len: ipv4PrefixLen, + }, + numSubIntf: 18, + ip: atePort2IPv4, + gateway: dutPort2IPv4, + } + + // nhgIPv4EntryMap maps NextHopGroups to the ipv4 entries pointing to that NextHopGroup. + nhgIPv4EntryMap = map[uint64]string{ + 1: ipv4EntryPrefix, + 2: cidr(nhEntryIP1, 32), + 3: cidr(nhEntryIP2, 32), + } + // 'tolerance' is the maximum difference that is allowed between the observed + // traffic distribution and the required traffic distribution. + tolerance = 0.2 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// dutPort1IPv4 returns ip address 192.0.2.1, for every vlanID. +func dutPort1IPv4(uint8) string { + return "192.0.2.1" +} + +// atePort1IPv4 returns ip address 192.0.2.2, for every vlanID +func atePort1IPv4(uint8) string { + return "192.0.2.2" +} + +// dutPort2IPv4 returns ip addresses starting 192.0.2.5, increasing by 4 +// for every vlanID. +func dutPort2IPv4(vlan uint8) string { + return fmt.Sprintf("192.0.2.%d", vlan*4+5) +} + +// atePort2IPv4 returns ip addresses starting 192.0.2.6, increasing by 4 +// for every vlanID. +func atePort2IPv4(vlan uint8) string { + return fmt.Sprintf("192.0.2.%d", vlan*4+6) +} + +// cidr taks as input the IPv4 address and the Mask and returns the IP string in +// CIDR notation. +func cidr(ipv4 string, ones int) string { + return ipv4 + "/" + strconv.Itoa(ones) +} + +// filterPacketReceived uses ATE:EgressTracking bucket counters to create a map +// with bucket-label as the Key and the percentage of packets-received for that +// bucket as the Value. +func filterPacketReceived(t *testing.T, flow string, ate *ondatra.ATEDevice) map[string]float64 { + t.Helper() + + // Check the egress packets + vlanTags := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().Flow(flow).TaggedMetricAny().State()) + tags := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().Flow(flow).TaggedMetricAny().TagsAny().State()) + t.Logf("There are a total of %v vlans", len(tags)) + + inPkts := map[string]uint64{} + for i, tag := range tags { + vlanHex := strings.Replace(tag.GetTagValue().GetValueAsHex(), "0x", "", -1) + vlanDec, _ := strconv.ParseUint(vlanHex, 16, 64) + inPkts[strconv.Itoa(int(vlanDec))] = vlanTags[i].GetCounters().GetInPkts() + } + inPct := map[string]float64{} + total := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow).Counters().InPkts().State()) + for k, v := range inPkts { + inPct[k] = (float64(v) / float64(total)) * 100.0 + } + return inPct +} + +// configureGRIBIClient configures a new GRIBI client with PRESERVE and FIB_ACK. +func configureGRIBIClient(t *testing.T, dut *ondatra.DUTDevice) *fluent.GRIBIClient { + t.Helper() + gribic := dut.RawAPIs().GRIBI(t) + + // Configure the gRIBI client. + c := fluent.NewClient() + c.Connection(). + WithStub(gribic). + WithRedundancyMode(fluent.ElectedPrimaryClient). + WithInitialElectionID(1 /* low */, 0 /* hi */). + WithPersistence(). + WithFIBACK() + + return c +} + +// nextHopEntry configures a fluent.GRIBIEntry for a NextHopEntry. +func nextHopEntry(index uint64, networkInstance string, ipAddr string) fluent.GRIBIEntry { + return fluent.NextHopEntry(). + WithNetworkInstance(networkInstance). + WithIndex(index). + WithIPAddress(ipAddr) +} + +// nextHopGroupEntry configures a fluent.GRIBIEntry for a NextHopGroupEntry. +func nextHopGroupEntry(index uint64, networkInstance string, nhs []nhInfo) fluent.GRIBIEntry { + x := fluent.NextHopGroupEntry(). + WithNetworkInstance(networkInstance). + WithID(index) + for _, nh := range nhs { + x.AddNextHop(nh.index, nh.weight) + } + return x +} + +// ipv4Entry configures a fluent.GRIBIEntry for an IPv4Entry. +func ipv4Entry(prefix string, networkInstance string, nhgIndex uint64, nextHopGroupNetworkInstance string) fluent.GRIBIEntry { + return fluent.IPv4Entry(). + WithPrefix(prefix). + WithNetworkInstance(networkInstance). + WithNextHopGroup(nhgIndex). + WithNextHopGroupNetworkInstance(nextHopGroupNetworkInstance) +} + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, c *fluent.GRIBIClient, t testing.TB, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +// configSubinterfaceDUT configures the Sub Interfaces of an Interfaces, +// starting from Sub Interface 1. Each Subinterface is configured with a +// unique VlanID starting from 1 and an IP address. The starting IP Address +// for Subinterface(1) = dutPort.ip(1) = dutPort.ip + 4. +func (a *attributes) configSubinterfaceDUT(t *testing.T, intf *oc.Interface, dut *ondatra.DUTDevice) { + t.Helper() + if deviations.RequireRoutedSubinterface0(dut) { + s0 := intf.GetOrCreateSubinterface(0).GetOrCreateIpv4() + s0.Enabled = ygot.Bool(true) + } + for i := uint32(1); i <= a.numSubIntf; i++ { + ip := a.ip(uint8(i)) + + s := intf.GetOrCreateSubinterface(i) + if deviations.InterfaceEnabled(dut) { + s.Enabled = ygot.Bool(true) + } + if deviations.DeprecatedVlanID(dut) { + s.GetOrCreateVlan().VlanId = oc.UnionUint16(i) + } else { + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(uint16(i)) + } + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + s4a := s4.GetOrCreateAddress(ip) + s4a.PrefixLength = ygot.Uint8(a.IPv4Len) + t.Logf("Adding DUT Subinterface with ID: %d, Vlan ID: %d and IPv4 address: %s", i, i, ip) + } +} + +// configInterfaceDUT configures the DUT interface with the provided IP Address. +// Sub Interfaces are also configured if numSubIntf > 0. +func (a *attributes) configInterfaceDUT(t *testing.T, d *ondatra.DUTDevice) { + t.Helper() + p := d.Port(t, a.Name) + i := &oc.Interface{Name: ygot.String(p.Name())} + + if a.numSubIntf > 0 { + i.Description = ygot.String(a.Desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + if deviations.InterfaceEnabled(d) { + i.Enabled = ygot.Bool(true) + } + } else { + i = a.NewOCInterface(p.Name(), d) + } + + if deviations.ExplicitPortSpeed(d) { + i.GetOrCreateEthernet().PortSpeed = fptest.GetIfSpeed(t, p) + } + + a.configSubinterfaceDUT(t, i, d) + intfPath := gnmi.OC().Interface(p.Name()) + gnmi.Update(t, d, intfPath.Config(), i) + fptest.LogQuery(t, "DUT", intfPath.Config(), gnmi.Get(t, d, intfPath.Config())) +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + // configure NI. + configureNetworkInstance(t, dut) + + // Configure DUT ports. + dutPort1.configInterfaceDUT(t, dut) + dutPort2.configInterfaceDUT(t, dut) + + // assign subinterfaces to DEFAULT network instance if needed (deviation-based). + dutPort1.assignSubifsToDefaultNetworkInstance(t, dut) + dutPort2.assignSubifsToDefaultNetworkInstance(t, dut) + + // apply PBF to src interface. + dp1 := dut.Port(t, dutPort1.Name) + applyForwardingPolicy(t, dp1.Name()) +} + +// configureNetworkInstance creates and configures non-default and default NIs. +func configureNetworkInstance(t *testing.T, d *ondatra.DUTDevice) { + t.Helper() + + // configure non-default VRF + ni := &oc.NetworkInstance{ + Name: ygot.String(nonDefaultVRF), + Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF, + } + dni := gnmi.OC().NetworkInstance(nonDefaultVRF) + gnmi.Replace(t, d, dni.Config(), ni) + fptest.LogQuery(t, "NI", dni.Config(), gnmi.Get(t, d, dni.Config())) + + // configure PBF in DEFAULT vrf + defNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(d)) + fptest.ConfigureDefaultNetworkInstance(t, d) + gnmi.Replace(t, d, defNIPath.PolicyForwarding().Config(), configurePBF(d)) + +} + +// assignSubifsToDefaultNetworkInstance assign subinterfaces to the default network instance when ExplicitInterfaceInDefaultVRF is enabled. +func (a *attributes) assignSubifsToDefaultNetworkInstance(t *testing.T, d *ondatra.DUTDevice) { + p := d.Port(t, a.Name) + if deviations.ExplicitInterfaceInDefaultVRF(d) { + if a.numSubIntf == 0 { + fptest.AssignToNetworkInstance(t, d, p.Name(), deviations.DefaultNetworkInstance(d), 0) + } else { + for i := uint32(1); i <= a.numSubIntf; i++ { + fptest.AssignToNetworkInstance(t, d, p.Name(), deviations.DefaultNetworkInstance(d), i) + } + } + } +} + +// configurePBF returns a fully configured network-instance PF struct. +func configurePBF(dut *ondatra.DUTDevice) *oc.NetworkInstance_PolicyForwarding { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + pf := ni.GetOrCreatePolicyForwarding() + vrfPolicy := pf.GetOrCreatePolicy(policyName) + vrfPolicy.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + vrfPolicy.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipipProtocol) + vrfPolicy.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(nonDefaultVRF) + return pf +} + +// applyForwardingPolicy applies the forwarding policy on the interface. +func applyForwardingPolicy(t *testing.T, ingressPort string) { + t.Logf("Applying forwarding policy on interface %v ... ", ingressPort) + d := &oc.Root{} + dut := ondatra.DUT(t, "dut") + 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(policyName) + pfCfg.GetOrCreateInterfaceRef().Interface = ygot.String(ingressPort) + pfCfg.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + pfCfg.InterfaceRef = nil + } + gnmi.Replace(t, dut, pfPath.Config(), pfCfg) +} + +// configureATE configures Ethernet + IPv4 on the ATE. If the number of +// Subinterfaces(numSubIntf) > 0, we then create additional sub-interfaces +// each with a unique VlanID starting from 1. The IPv4 addresses start with +// ATE:Port.IPv4 and then nextIP(ATE:Port.IPv4, 4) for each sub interface. +func (a *attributes) configureATE(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice) { + t.Helper() + p := ate.Port(t, a.Name) + + // Configure source port on ATE : Port1. + + top.Ports().Add().SetName(p.ID()) + if a.numSubIntf == 0 { + ip := a.ip(0) + gateway := a.gateway(0) + dev := top.Devices().Add().SetName(a.Name) + eth := dev.Ethernets().Add().SetName(a.Name + ".Eth").SetMac(a.MAC) + eth.Connection().SetPortName(p.ID()) + ipObj := eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") + ipObj.SetAddress(ip).SetGateway(gateway).SetPrefix(uint32(a.IPv4Len)) + t.Logf("Adding ATE Ipv4 address: %s with gateway: %s", cidr(ip, int(a.IPv4Len)), gateway) + } + // Configure destination port on ATE : Port2. + for i := uint32(1); i <= a.numSubIntf; i++ { + name := fmt.Sprintf(`dst%d`, i) + ip := a.ip(uint8(i)) + gateway := a.gateway(uint8(i)) + mac, err := incrementMAC(a.MAC, int(i)+1) + if err != nil { + t.Fatalf("Failed to generate mac address with error %s", err) + } + + dev := top.Devices().Add().SetName(name + ".Dev") + eth := dev.Ethernets().Add().SetName(name + ".Eth").SetMac(mac) + eth.Connection().SetPortName(p.ID()) + eth.Vlans().Add().SetName(name).SetId(uint32(i)) + eth.Ipv4Addresses().Add().SetName(name + ".IPv4").SetAddress(ip).SetGateway(gateway).SetPrefix(uint32(a.IPv4Len)) + + t.Logf("Adding ATE Ipv4 address: %s with gateway: %s and VlanID: %d", cidr(ip, 30), gateway, i) + } + // } +} + +// incrementMAC increments the MAC by i. Returns error if the mac cannot be parsed or overflows the mac address space +func incrementMAC(mac string, i int) (string, error) { + macAddr, err := net.ParseMAC(mac) + if err != nil { + return "", err + } + convMac := binary.BigEndian.Uint64(append([]byte{0, 0}, macAddr...)) + convMac = convMac + uint64(i) + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, convMac) + if err != nil { + return "", err + } + newMac := net.HardwareAddr(buf.Bytes()[2:8]) + return newMac.String(), nil +} + +// testTraffic creates a traffic flow with ATE source & destination endpoints +// and configures a VlanID filter for output frames. The IPv4 header for the +// flow contains the ATE:Port1 address as source and the configured gRIBI- +// IndirectEntry as the destination. The function also takes as input a map of +// that is wanted and compares it to the actual +// traffic test result. +func testTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) map[string]float64 { + + dut := ondatra.DUT(t, "dut") + dstMac := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + top.Flows().Clear().Items() + flowipv4 := top.Flows().Add().SetName("flow") + flowipv4.Metrics().SetEnable(true) + flowipv4.TxRx().Port().SetTxName(atePort1.Name).SetRxNames([]string{atePort2.Name}) + flowipv4.Size().SetFixed(100) + e1 := flowipv4.Packet().Add().Ethernet() + e1.Src().SetValue(atePort1.MAC) + e1.Dst().SetValue(dstMac) + v4 := flowipv4.Packet().Add().Ipv4() + v4.Src().SetValue(decapFlowSrc) + v4.Priority().Dscp().Phb().SetValue(dscpEncapA1) + v4.Dst().SetValue(ipv4FlowIP) + v4Inner := flowipv4.Packet().Add().Ipv4() + v4Inner.Src().Increment().SetStart(innerSrcIPv4Start).SetCount(ipv4FlowCount) + v4Inner.Dst().Increment().SetStart(innerDstIPv4Start).SetCount(ipv4FlowCount) + flowipv4.EgressPacket().Add().Ethernet() + vlan := flowipv4.EgressPacket().Add().Vlan() + vlanTag := vlan.Id().MetricTags().Add() + vlanTag.SetName("EgressVlanIdTrackingFlow") + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + + // Run traffic for 2 minutes. + ate.OTG().StartTraffic(t) + time.Sleep(1 * time.Minute) + ate.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + recvMetric := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flowipv4.Name()).State()) + txPkts := float32(recvMetric.GetCounters().GetOutPkts()) + rxPkts := float32(recvMetric.GetCounters().GetInPkts()) + lossPct := (txPkts - rxPkts) * 100 / txPkts + if txPkts == 0 { + t.Fatalf("TxPkts == 0, want > 0.") + } + if lossPct > 0 && recvMetric.GetCounters().GetOutPkts() > 0 { + t.Fatalf("Loss Pct for %s got %v, want 0", flowipv4.Name(), lossPct) + } + + // Compare traffic distribution with the wanted results. + results := filterPacketReceived(t, "flow", ate) + t.Logf("Filters: %v", results) + return results +} + +// aftNextHopWeights queries AFT telemetry using Get() and returns +// the weights. If not-found, an empty list is returned. +func aftNextHopWeights(t *testing.T, dut *ondatra.DUTDevice, nhg uint64, networkInstance string) []uint64 { + aft := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(networkInstance).Afts().State()) + var nhgD *oc.NetworkInstance_Afts_NextHopGroup + for _, nhgData := range aft.NextHopGroup { + if nhgData.GetProgrammedId() == nhg { + nhgD = nhgData + break + } + } + + if nhgD == nil { + return []uint64{} + } + + got := []uint64{} + for _, nhD := range nhgD.NextHop { + got = append(got, nhD.GetWeight()) + } + + return got +} + +// testBasicHierarchicalWeight tests and validates traffic through 4 Vlans. +func testBasicHierarchicalWeight(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, + ate *ondatra.ATEDevice, top gosnappi.Config, gRIBI *fluent.GRIBIClient) { + defaultVRF := deviations.DefaultNetworkInstance(dut) + + // Set up NH#10, NH#11, NHG#2, IPv4Entry(192.0.2.111). + nh10 := nextHopEntry(10, defaultVRF, atePort2.ip(1)) + nh11 := nextHopEntry(11, defaultVRF, atePort2.ip(2)) + nhg2 := nextHopGroupEntry(2, defaultVRF, []nhInfo{{index: 10, weight: 1}, {index: 11, weight: 3}}) + ipEntry2 := ipv4Entry(nhgIPv4EntryMap[2], defaultVRF, 2, defaultVRF) + + gRIBI.Modify().AddEntry(t, nh10, nh11, nhg2, ipEntry2) + + // Set up NH#100, NH#101, NHG#3, IPv4Entry(192.0.2.222). + nh100 := nextHopEntry(100, defaultVRF, atePort2.ip(3)) + nh101 := nextHopEntry(101, defaultVRF, atePort2.ip(4)) + nhg3 := nextHopGroupEntry(3, defaultVRF, []nhInfo{{index: 100, weight: 3}, {index: 101, weight: 5}}) + ipEntry3 := ipv4Entry(nhgIPv4EntryMap[3], defaultVRF, 3, defaultVRF) + + gRIBI.Modify().AddEntry(t, nh100, nh101, nhg3, ipEntry3) + + // Set up NH#1, NH#2, NHG#1, IPv4Entry(198.18.196.1/22). + nh1 := nextHopEntry(1, defaultVRF, nhEntryIP1) + nh2 := nextHopEntry(2, defaultVRF, nhEntryIP2) + nhg1 := nextHopGroupEntry(1, defaultVRF, []nhInfo{{index: 1, weight: 1}, {index: 2, weight: 3}}) + ipEntry1 := ipv4Entry(nhgIPv4EntryMap[1], nonDefaultVRF, 1, defaultVRF) + + gRIBI.Modify().AddEntry(t, nh1, nh2, nhg1, ipEntry1) + + if err := awaitTimeout(ctx, gRIBI, t, time.Minute); err != nil { + t.Fatalf("Could not program entries via gRIBI, got err: %v", err) + } + + // Validate entries were installed in FIB. + for _, route := range nhgIPv4EntryMap { + chk.HasResult(t, gRIBI.Results(t), + fluent.OperationResult(). + WithIPv4Operation(route). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Test traffic flows correctly and + wantWeights := map[string]float64{ + "1": 6.25, + "2": 18.75, + "3": 28.12, + "4": 46.87, + } + t.Run("testTraffic", func(t *testing.T) { + got := testTraffic(t, ate, top) + if diff := cmp.Diff(wantWeights, got, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } + }) + + t.Run("validateAFTWeights", func(t *testing.T) { + for nhg, weights := range map[uint64][]uint64{ + 2: {1, 3}, + 3: {3, 5}, + } { + got := aftNextHopWeights(t, dut, nhg, defaultVRF) + ok := cmp.Equal(weights, got, cmpopts.SortSlices(func(a, b uint64) bool { return a < b })) + if !ok { + t.Errorf("Valid weights not present for NI: %s, NHG: %d, got: %v, want: %v", defaultVRF, nhg, got, weights) + } + } + }) + + // Flush gRIBI routes after test. + if err := gribi.FlushAll(gRIBI); err != nil { + t.Error(err) + } +} + +// testHierarchicalWeightBoundaryScenario tests and validates traffic through all 18 Vlans. +func testHierarchicalWeightBoundaryScenario(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, + ate *ondatra.ATEDevice, top gosnappi.Config, gRIBI *fluent.GRIBIClient) { + defaultVRF := deviations.DefaultNetworkInstance(dut) + + // Set up NH#10, NH#11, NHG#2, IPv4Entry(192.0.2.111). + nh10 := nextHopEntry(10, defaultVRF, atePort2.ip(1)) + nh11 := nextHopEntry(11, defaultVRF, atePort2.ip(2)) + nhg2 := nextHopGroupEntry(2, defaultVRF, []nhInfo{{index: 10, weight: 3}, {index: 11, weight: 5}}) + ipEntry2 := ipv4Entry(nhgIPv4EntryMap[2], defaultVRF, 2, defaultVRF) + + gRIBI.Modify().AddEntry(t, nh10, nh11, nhg2, ipEntry2) + + // Set up NH#100..NH#116, NHG#3, IPv4Entry(192.0.2.222). + nextHopWeights := []nhInfo{} + nhIdx := uint64(100) + gribiEntries := []fluent.GRIBIEntry{} + for i := 0; i < 16; i++ { + nh := nextHopEntry(nhIdx, defaultVRF, atePort2.ip(uint8(3+i))) + gribiEntries = append(gribiEntries, nh) + if i == 0 { + nextHopWeights = append(nextHopWeights, nhInfo{index: nhIdx, weight: 1}) + } else { + nextHopWeights = append(nextHopWeights, nhInfo{index: nhIdx, weight: 16}) + } + nhIdx++ + } + nhg3 := nextHopGroupEntry(3, defaultVRF, nextHopWeights) + ipEntry3 := ipv4Entry(nhgIPv4EntryMap[3], defaultVRF, 3, defaultVRF) + gribiEntries = append(gribiEntries, nhg3, ipEntry3) + + gRIBI.Modify().AddEntry(t, gribiEntries...) + + // Set up NH#1, NH#2, NHG#1, IPv4Entry(198.18.196.1/22). + nh1 := nextHopEntry(1, defaultVRF, nhEntryIP1) + nh2 := nextHopEntry(2, defaultVRF, nhEntryIP2) + nhg1 := nextHopGroupEntry(1, defaultVRF, []nhInfo{{index: 1, weight: 1}, {index: 2, weight: 31}}) + ipEntry1 := ipv4Entry(nhgIPv4EntryMap[1], nonDefaultVRF, 1, defaultVRF) + + gRIBI.Modify().AddEntry(t, nh1, nh2, nhg1, ipEntry1) + + if err := awaitTimeout(ctx, gRIBI, t, time.Minute); err != nil { + t.Fatalf("Could not program entries via gRIBI, got err: %v", err) + } + + // Validate entries were installed in FIB. + for _, route := range nhgIPv4EntryMap { + chk.HasResult(t, gRIBI.Results(t), + fluent.OperationResult(). + WithIPv4Operation(route). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + wantWeights := map[string]float64{ + "1": 1.171, + "2": 1.953, + "3": 0.402, + } + // 6.432 weight for vlans 4 to 18. + for i := 4; i <= 18; i++ { + wantWeights[strconv.Itoa(i)] = 6.432 + } + t.Run("testTraffic", func(t *testing.T) { + got := testTraffic(t, ate, top) + + if deviations.HierarchicalWeightResolutionTolerance(dut) != tolerance { + tolerance = deviations.HierarchicalWeightResolutionTolerance(dut) + } + if diff := cmp.Diff(wantWeights, got, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } + }) + + t.Run("validateAFTWeights", func(t *testing.T) { + for nhg, weights := range map[uint64][]uint64{ + 2: {3, 5}, + 3: {1, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}, + } { + got := aftNextHopWeights(t, dut, nhg, defaultVRF) + ok := cmp.Equal(weights, got, cmpopts.SortSlices(func(a, b uint64) bool { return a < b })) + if !ok { + t.Errorf("Valid weights not present for NI: %s, NHG: %d, got: %v, want: %v", defaultVRF, nhg, got, weights) + } + } + }) + + // Flush gRIBI routes after test. + if err := gribi.FlushAll(gRIBI); err != nil { + t.Error(err) + } +} + +func TestHierarchicalWeightResolution(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + ctx := context.Background() + + // Configure ATE ports and start Ethernet+IPv4. + top := gosnappi.NewConfig() + atePort1.configureATE(t, top, ate) + atePort2.configureATE(t, top, ate) + + ate.OTG().PushConfig(t, top) + + // configure DUT. + configureDUT(t, dut) + + ate.OTG().StartProtocols(t) + + // Configure gRIBI with FIB_ACK. + gRIBI := configureGRIBIClient(t, dut) + + gRIBI.Start(ctx, t) + defer gRIBI.Stop(t) + + defer func() { + // Flush all gRIBI routes after test. + if err := gribi.FlushAll(gRIBI); err != nil { + t.Error(err) + } + }() + + gRIBI.StartSending(ctx, t) + if err := awaitTimeout(ctx, gRIBI, t, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for gRIBI: %v", err) + } + gribi.BecomeLeader(t, gRIBI) + + // Flush existing gRIBI routes before test. + if err := gribi.FlushAll(gRIBI); err != nil { + t.Fatal(err) + } + + t.Run("TestBasicHierarchicalWeightWithVrfPolW", func(t *testing.T) { + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyW) + testBasicHierarchicalWeight(ctx, t, dut, ate, top, gRIBI) + }) + + t.Run("TestHierarchicalWeightBoundaryScenarioWithVrfPolW", func(t *testing.T) { + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyW) + testHierarchicalWeightBoundaryScenario(ctx, t, dut, ate, top, gRIBI) + }) + + ate.OTG().StopProtocols(t) +} diff --git a/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/metadata.textproto b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/metadata.textproto new file mode 100644 index 00000000000..cb1bef25af4 --- /dev/null +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/metadata.textproto @@ -0,0 +1,48 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "bf5df0ee-79b7-460b-8146-3a1351fd56d7" +plan_id: "TE-3.31" +description: "Hierarchical weight resolution with PBF" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + hierarchical_weight_resolution_tolerance: 1.5 + ipv4_missing_enabled: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + hierarchical_weight_resolution_tolerance: 0.4 + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + 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" + } +} +tags: TAGS_DATACENTER_EDGE diff --git a/feature/gribi/ate_tests/hierarchical_weight_resolution_test/README.md b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/README.md similarity index 80% rename from feature/gribi/ate_tests/hierarchical_weight_resolution_test/README.md rename to feature/gribi/otg_tests/hierarchical_weight_resolution_test/README.md index 382aeec9941..d206b7660d1 100644 --- a/feature/gribi/ate_tests/hierarchical_weight_resolution_test/README.md +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/README.md @@ -25,9 +25,10 @@ Configure ATE and DUT: 192.0.2.74 and default gateways as 192.0.2.5, 192.0.2.9, ..., 192.0.2.73 respectively. -* On DUT port-1 and ATE port-1 create a single L3 interface. +* On DUT port-1 and ATE port-1 create a single L3 interface. -* On DUT, create a policy-based forwarding rule to redirect all traffic received from DUT port-1 into VRF-1 (based on src. IP match criteria). +* On DUT, create a policy-based forwarding rule to redirect all traffic + received from DUT port-1 into VRF-1 (based on src. IP match criteria). Test case for basic hierarchical weight: @@ -110,7 +111,8 @@ WCMP width of 16 nexthops: * for each VLAN ID in 4...18: - * NH: (31/32) * (16/241) ~ 6.432% traffic received by ATE port-2 VLAN ID + * NH: (31/32) * (16/241) ~ 6.432% traffic received by ATE port-2 VLAN + ID * A tolerance of 0.2% is allowed for each VLAN for now, since we only test for 2 mins. @@ -119,21 +121,42 @@ WCMP width of 16 nexthops: N/A -## Telemetry Parameter Coverage +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## State Paths ## + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` -TODO: -/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight +## Minimum DUT platform requirement -## Protocol/RPC Parameter coverage +* vRX - virtual router device -* gRIBI: - * Modify() - * ModifyRequest: - * AFTOperation: - * next_hop_group - * NextHopGroupKey: id - * NextHopGroup: weight +## OpenConfig Path and RPC Coverage -## Minimum DUT platform requirement +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: N/A + + ## State paths: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight: -vRX +rpcs: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` \ No newline at end of file diff --git a/feature/gribi/ate_tests/hierarchical_weight_resolution_test/hierarchical_weight_resolution_test.go b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/hierarchical_weight_resolution_test.go similarity index 82% rename from feature/gribi/ate_tests/hierarchical_weight_resolution_test/hierarchical_weight_resolution_test.go rename to feature/gribi/otg_tests/hierarchical_weight_resolution_test/hierarchical_weight_resolution_test.go index 2e18c26622f..4d0bc5d8d34 100644 --- a/feature/gribi/ate_tests/hierarchical_weight_resolution_test/hierarchical_weight_resolution_test.go +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/hierarchical_weight_resolution_test.go @@ -16,18 +16,24 @@ package hierarchical_weight_resolution_test import ( + "bytes" "context" + "encoding/binary" "fmt" + "net" "strconv" + "strings" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/open-traffic-generator/snappi/gosnappi" "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/otgutils" "github.com/openconfig/gribigo/chk" "github.com/openconfig/gribigo/constants" "github.com/openconfig/gribigo/fluent" @@ -78,6 +84,7 @@ var ( atePort1 = attributes{ Attributes: attrs.Attributes{ Name: "port1", + MAC: "02:00:01:01:01:01", IPv4: atePort1IPv4(0), IPv4Len: ipv4PrefixLen, }, @@ -100,6 +107,7 @@ var ( atePort2 = attributes{ Attributes: attrs.Attributes{ Name: "port2", + MAC: "02:00:02:01:01:01", IPv4: atePort2IPv4(0), IPv4Len: ipv4PrefixLen, }, @@ -157,15 +165,19 @@ func cidr(ipv4 string, ones int) string { func filterPacketReceived(t *testing.T, flow string, ate *ondatra.ATEDevice) map[string]float64 { t.Helper() - flowPath := gnmi.OC().Flow(flow) - filters := gnmi.GetAll(t, ate, flowPath.EgressTrackingAny().State()) + // Check the egress packets + vlanTags := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().Flow(flow).TaggedMetricAny().State()) + tags := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().Flow(flow).TaggedMetricAny().TagsAny().State()) + t.Logf("There are a total of %v vlans", len(tags)) inPkts := map[string]uint64{} - for _, f := range filters { - inPkts[f.GetFilter()] = f.GetCounters().GetInPkts() + for i, tag := range tags { + vlanHex := strings.Replace(tag.GetTagValue().GetValueAsHex(), "0x", "", -1) + vlanDec, _ := strconv.ParseUint(vlanHex, 16, 64) + inPkts[strconv.Itoa(int(vlanDec))] = vlanTags[i].GetCounters().GetInPkts() } inPct := map[string]float64{} - total := gnmi.Get(t, ate, flowPath.Counters().OutPkts().State()) + total := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow).Counters().InPkts().State()) for k, v := range inPkts { inPct[k] = (float64(v) / float64(total)) * 100.0 } @@ -367,32 +379,63 @@ func applyForwardingPolicy(t *testing.T, ingressPort string) { gnmi.Replace(t, dut, pfPath.Config(), pfCfg) } -// ConfigureATE configures Ethernet + IPv4 on the ATE. If the number of +// configureATE configures Ethernet + IPv4 on the ATE. If the number of // Subinterfaces(numSubIntf) > 0, we then create additional sub-interfaces // each with a unique VlanID starting from 1. The IPv4 addresses start with // ATE:Port.IPv4 and then nextIP(ATE:Port.IPv4, 4) for each sub interface. -func (a *attributes) ConfigureATE(t *testing.T, top *ondatra.ATETopology, ate *ondatra.ATEDevice) { +func (a *attributes) configureATE(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice) { t.Helper() p := ate.Port(t, a.Name) + // Configure source port on ATE : Port1. + + top.Ports().Add().SetName(p.ID()) if a.numSubIntf == 0 { ip := a.ip(0) gateway := a.gateway(0) - intf := top.AddInterface(ip).WithPort(p) - intf.IPv4().WithAddress(cidr(ip, 30)) - intf.IPv4().WithDefaultGateway(gateway) - t.Logf("Adding ATE Ipv4 address: %s with gateway: %s", cidr(ip, 30), gateway) + dev := top.Devices().Add().SetName(a.Name) + eth := dev.Ethernets().Add().SetName(a.Name + ".Eth").SetMac(a.MAC) + eth.Connection().SetPortName(p.ID()) + ipObj := eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") + ipObj.SetAddress(ip).SetGateway(gateway).SetPrefix(uint32(a.IPv4Len)) + t.Logf("Adding ATE Ipv4 address: %s with gateway: %s", cidr(ip, int(a.IPv4Len)), gateway) } // Configure destination port on ATE : Port2. for i := uint32(1); i <= a.numSubIntf; i++ { + name := fmt.Sprintf(`dst%d`, i) ip := a.ip(uint8(i)) gateway := a.gateway(uint8(i)) - intf := top.AddInterface(ip).WithPort(p) - intf.IPv4().WithAddress(cidr(ip, 30)) - intf.IPv4().WithDefaultGateway(gateway) - intf.Ethernet().WithVLANID(uint16(i)) + mac, err := incrementMAC(a.MAC, int(i)+1) + if err != nil { + t.Fatalf("Failed to generate mac address with error %s", err) + } + + dev := top.Devices().Add().SetName(name + ".Dev") + eth := dev.Ethernets().Add().SetName(name + ".Eth").SetMac(mac) + eth.Connection().SetPortName(p.ID()) + eth.Vlans().Add().SetName(name).SetId(uint32(i)) + eth.Ipv4Addresses().Add().SetName(name + ".IPv4").SetAddress(ip).SetGateway(gateway).SetPrefix(uint32(a.IPv4Len)) + t.Logf("Adding ATE Ipv4 address: %s with gateway: %s and VlanID: %d", cidr(ip, 30), gateway, i) } + // } +} + +// incrementMAC increments the MAC by i. Returns error if the mac cannot be parsed or overflows the mac address space +func incrementMAC(mac string, i int) (string, error) { + macAddr, err := net.ParseMAC(mac) + if err != nil { + return "", err + } + convMac := binary.BigEndian.Uint64(append([]byte{0, 0}, macAddr...)) + convMac = convMac + uint64(i) + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, convMac) + if err != nil { + return "", err + } + newMac := net.HardwareAddr(buf.Bytes()[2:8]) + return newMac.String(), nil } // testTraffic creates a traffic flow with ATE source & destination endpoints @@ -401,48 +444,49 @@ func (a *attributes) ConfigureATE(t *testing.T, top *ondatra.ATETopology, ate *o // IndirectEntry as the destination. The function also takes as input a map of // that is wanted and compares it to the actual // traffic test result. -func testTraffic(t *testing.T, ate *ondatra.ATEDevice, top *ondatra.ATETopology) map[string]float64 { - allIntf := top.Interfaces() - - // ATE source endpoint. - srcEndPoint := allIntf[atePort1.IPv4] - - // ATE destination endpoints. - dstEndPoints := []ondatra.Endpoint{} - for i := uint32(1); i <= atePort2.numSubIntf; i++ { - dstIP := atePort2.ip(uint8(i)) - dstEndPoints = append(dstEndPoints, allIntf[dstIP]) - } - - // Configure Ethernet+IPv4 headers. - ethHeader := ondatra.NewEthernetHeader() - ipv4Header := ondatra.NewIPv4Header() - ipv4Header.WithSrcAddress(atePort1.IPv4) - ipv4Header.WithDstAddress(ipv4FlowIP) - innerIpv4Header := ondatra.NewIPv4Header() - innerIpv4Header.SrcAddressRange().WithMin(innerSrcIPv4Start).WithCount(ipv4FlowCount).WithStep("0.0.0.1") - innerIpv4Header.DstAddressRange().WithMin(innerDstIPv4Start).WithCount(ipv4FlowCount).WithStep("0.0.0.1") - - // Ethernet header: - // - Destination MAC (6 octets) - // - Source MAC (6 octets) - // - Optional 802.1q VLAN tag (4 octets) - // - Frame size (2 octets) - flow := ate.Traffic().NewFlow("flow"). - WithSrcEndpoints(srcEndPoint). - WithDstEndpoints(dstEndPoints...). - WithHeaders(ethHeader, ipv4Header, innerIpv4Header) - - // VlanID is the last 12 bits in the 802.1q VLAN tag. - // Offset for VlanID: ((6+6+4) * 8)-12 = 116. - flow.EgressTracking().WithOffset(116).WithWidth(12).WithCount(18) +func testTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) map[string]float64 { + + dut := ondatra.DUT(t, "dut") + dstMac := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + top.Flows().Clear().Items() + flowipv4 := top.Flows().Add().SetName("flow") + flowipv4.Metrics().SetEnable(true) + flowipv4.TxRx().Port().SetTxName(atePort1.Name).SetRxNames([]string{atePort2.Name}) + flowipv4.Size().SetFixed(100) + e1 := flowipv4.Packet().Add().Ethernet() + e1.Src().SetValue(atePort1.MAC) + e1.Dst().SetValue(dstMac) + v4 := flowipv4.Packet().Add().Ipv4() + v4.Src().SetValue(atePort1.IPv4) + v4.Dst().SetValue(ipv4FlowIP) + v4Inner := flowipv4.Packet().Add().Ipv4() + v4Inner.Src().Increment().SetStart(innerSrcIPv4Start).SetCount(ipv4FlowCount) + v4Inner.Dst().Increment().SetStart(innerDstIPv4Start).SetCount(ipv4FlowCount) + flowipv4.EgressPacket().Add().Ethernet() + vlan := flowipv4.EgressPacket().Add().Vlan() + vlanTag := vlan.Id().MetricTags().Add() + vlanTag.SetName("EgressVlanIdTrackingFlow") + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") // Run traffic for 2 minutes. - ate.Traffic().Start(t, flow) - time.Sleep(2 * time.Minute) - ate.Traffic().Stop(t) + ate.OTG().StartTraffic(t) + time.Sleep(1 * time.Minute) + ate.OTG().StopTraffic(t) + + otgutils.LogFlowMetrics(t, ate.OTG(), top) - gnmi.Await(t, ate, gnmi.OC().Flow("flow").LossPct().State(), time.Minute, 0) + recvMetric := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flowipv4.Name()).State()) + txPkts := float32(recvMetric.GetCounters().GetOutPkts()) + rxPkts := float32(recvMetric.GetCounters().GetInPkts()) + lossPct := (txPkts - rxPkts) * 100 / txPkts + if txPkts == 0 { + t.Fatalf("TxPkts == 0, want > 0.") + } + if lossPct > 0 && recvMetric.GetCounters().GetOutPkts() > 0 { + t.Fatalf("Loss Pct for %s got %v, want 0", flowipv4.Name(), lossPct) + } // Compare traffic distribution with the wanted results. results := filterPacketReceived(t, "flow", ate) @@ -476,7 +520,7 @@ func aftNextHopWeights(t *testing.T, dut *ondatra.DUTDevice, nhg uint64, network // testBasicHierarchicalWeight tests and validates traffic through 4 Vlans. func testBasicHierarchicalWeight(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, - ate *ondatra.ATEDevice, top *ondatra.ATETopology, gRIBI *fluent.GRIBIClient) { + ate *ondatra.ATEDevice, top gosnappi.Config, gRIBI *fluent.GRIBIClient) { defaultVRF := deviations.DefaultNetworkInstance(dut) // Set up NH#10, NH#11, NHG#2, IPv4Entry(192.0.2.111). @@ -554,7 +598,7 @@ func testBasicHierarchicalWeight(ctx context.Context, t *testing.T, dut *ondatra // testHierarchicalWeightBoundaryScenario tests and validates traffic through all 18 Vlans. func testHierarchicalWeightBoundaryScenario(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, - ate *ondatra.ATEDevice, top *ondatra.ATETopology, gRIBI *fluent.GRIBIClient) { + ate *ondatra.ATEDevice, top gosnappi.Config, gRIBI *fluent.GRIBIClient) { defaultVRF := deviations.DefaultNetworkInstance(dut) // Set up NH#10, NH#11, NHG#2, IPv4Entry(192.0.2.111). @@ -653,15 +697,17 @@ func TestHierarchicalWeightResolution(t *testing.T) { ate := ondatra.ATE(t, "ate") ctx := context.Background() + // Configure ATE ports and start Ethernet+IPv4. + top := gosnappi.NewConfig() + atePort1.configureATE(t, top, ate) + atePort2.configureATE(t, top, ate) + + ate.OTG().PushConfig(t, top) + // configure DUT. configureDUT(t, dut) - // Configure ATE ports and start Ethernet+IPv4. - top := ate.Topology().New() - atePort1.ConfigureATE(t, top, ate) - atePort2.ConfigureATE(t, top, ate) - top.Push(t) - top.StartProtocols(t) + ate.OTG().StartProtocols(t) // Configure gRIBI with FIB_ACK. gRIBI := configureGRIBIClient(t, dut) @@ -695,5 +741,5 @@ func TestHierarchicalWeightResolution(t *testing.T) { testHierarchicalWeightBoundaryScenario(ctx, t, dut, ate, top, gRIBI) }) - top.StopProtocols(t) + ate.OTG().StopProtocols(t) } diff --git a/feature/gribi/ate_tests/hierarchical_weight_resolution_test/metadata.textproto b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/metadata.textproto similarity index 88% rename from feature/gribi/ate_tests/hierarchical_weight_resolution_test/metadata.textproto rename to feature/gribi/otg_tests/hierarchical_weight_resolution_test/metadata.textproto index 9e94485f3be..fb0c549af67 100644 --- a/feature/gribi/ate_tests/hierarchical_weight_resolution_test/metadata.textproto +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/metadata.textproto @@ -13,7 +13,6 @@ platform_exceptions: { hierarchical_weight_resolution_tolerance: 1.5 ipv4_missing_enabled: true interface_ref_interface_id_format: true - } } platform_exceptions: { @@ -22,7 +21,6 @@ platform_exceptions: { } deviations: { hierarchical_weight_resolution_tolerance: 0.4 - explicit_interface_ref_definition: true } } platform_exceptions: { @@ -30,7 +28,6 @@ platform_exceptions: { vendor: NOKIA } deviations: { - explicit_interface_ref_definition: true explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true @@ -42,8 +39,8 @@ platform_exceptions: { } deviations: { omit_l2_mtu: true - deprecated_vlan_id: true interface_enabled: true default_network_instance: "default" } } +tags: TAGS_TRANSIT diff --git a/feature/gribi/otg_tests/ipv4_entry_test/README.md b/feature/gribi/otg_tests/ipv4_entry_test/README.md index 429945f5de6..2eba21c0f13 100644 --- a/feature/gribi/otg_tests/ipv4_entry_test/README.md +++ b/feature/gribi/otg_tests/ipv4_entry_test/README.md @@ -73,3 +73,16 @@ N/A * AFTResult: * id * status + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/gribi/otg_tests/ipv4_entry_test/ipv4_entry_test.go b/feature/gribi/otg_tests/ipv4_entry_test/ipv4_entry_test.go index 2552f0a4ac4..7b44b9d500d 100644 --- a/feature/gribi/otg_tests/ipv4_entry_test/ipv4_entry_test.go +++ b/feature/gribi/otg_tests/ipv4_entry_test/ipv4_entry_test.go @@ -78,14 +78,14 @@ var ( IPv4Len: 30, } dutPort2DummyIP = attrs.Attributes{ - Desc: "DUT Port 2", - IPv4: "192.0.2.21", - IPv4Len: 30, + Desc: "DUT Port 2", + IPv4Sec: "192.0.2.21", + IPv4LenSec: 30, } dutPort3DummyIP = attrs.Attributes{ - Desc: "DUT Port 3", - IPv4: "192.0.2.41", - IPv4Len: 30, + Desc: "DUT Port 3", + IPv4Sec: "192.0.2.41", + IPv4LenSec: 30, } atePort1 = attrs.Attributes{ @@ -344,6 +344,7 @@ func TestIPv4Entry(t *testing.T) { t.Run(fmt.Sprintf("Persistence=%s", persist), func(t *testing.T) { for _, tc := range cases { + newGoodFlows, newBadFlows := createTrafficFlows(t, ate, tc.wantGoodFlows, tc.wantBadFlows) t.Run(tc.desc, func(t *testing.T) { if tc.gribiMACOverrideWithStaticARPStaticRoute { staticARPWithMagicUniversalIP(t, dut) @@ -444,8 +445,7 @@ func TestIPv4Entry(t *testing.T) { for _, wantResult := range tc.wantOperationResults { chk.HasResult(t, c.Results(t), wantResult, chk.IgnoreOperationID()) } - - validateTrafficFlows(t, ate, tc.wantGoodFlows, tc.wantBadFlows) + validateTrafficFlows(t, ate, newGoodFlows, newBadFlows) }) } }) @@ -505,7 +505,6 @@ func createFlow(t *testing.T, name string, ate *ondatra.ATEDevice, ateTop gosnap for _, dst := range dsts { rxEndpoints = append(rxEndpoints, dst.Name+".IPv4") } - otg := ate.OTG() flowipv4 := ateTop.Flows().Add().SetName(name) flowipv4.Metrics().SetEnable(true) e1 := flowipv4.Packet().Add().Ethernet() @@ -514,19 +513,19 @@ func createFlow(t *testing.T, name string, ate *ondatra.ATEDevice, ateTop gosnap v4 := flowipv4.Packet().Add().Ipv4() v4.Src().SetValue(atePort1.IPv4) v4.Dst().Increment().SetStart(dstPfxMin).SetCount(dstPfxCount) - otg.PushConfig(t, ateTop) - otg.StartProtocols(t) return name } -func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good, bad []string) { - ateTop := ate.OTG().FetchConfig(t) - if len(good) == 0 && len(bad) == 0 { - return - } - +func createTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good, bad []string) (newGood, newBad []string) { var newGoodFlows, newBadFlows []string allFlows := append(good, bad...) + otg := ate.OTG() + ateTop := otg.FetchConfig(t) + if len(good) == 0 && len(bad) == 0 { + otg.PushConfig(t, ateTop) + otg.StartProtocols(t) + return newGoodFlows, newBadFlows + } ateTop.Flows().Clear().Items() for _, flow := range allFlows { if flow == "port2Flow" { @@ -556,6 +555,21 @@ func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good, bad []stri } } + otg.PushConfig(t, ateTop) + otg.StartProtocols(t) + return newGoodFlows, newBadFlows +} + +func validateTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, good, bad []string) { + if len(good) == 0 && len(bad) == 0 { + return + } + + newGoodFlows := good + newBadFlows := bad + + ateTop := ate.OTG().FetchConfig(t) + ate.OTG().StartTraffic(t) time.Sleep(15 * time.Second) ate.OTG().StopTraffic(t) diff --git a/feature/gribi/otg_tests/ipv4_entry_test/metadata.textproto b/feature/gribi/otg_tests/ipv4_entry_test/metadata.textproto index b3036f4968f..1fa1844f144 100644 --- a/feature/gribi/otg_tests/ipv4_entry_test/metadata.textproto +++ b/feature/gribi/otg_tests/ipv4_entry_test/metadata.textproto @@ -11,7 +11,7 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true - gribi_mac_override_with_static_arp: true + gribi_mac_override_static_arp_static_route: true } } platform_exceptions: { diff --git a/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/README.md b/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/README.md index 978740decbb..a3f6c51fcda 100644 --- a/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/README.md +++ b/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/README.md @@ -2,7 +2,8 @@ ## Summary -Validate IPv4 support in gRIBI using an Aggregate Port as a static route Next Hop. +Validate IPv4 support in gRIBI using an Aggregate Port as a static route Next +Hop. ## Procedure @@ -17,26 +18,34 @@ Validate IPv4 support in gRIBI using an Aggregate Port as a static route Next Ho * Single IPv4Entry -> NHG -> NH with MAC Override. - * Install 198.51.100.0/24 to NextHopGroup containing one NextHop which - is a static route to the ATE LAG port containing ports 2-3, and - override the destination MAC to a specified value. - - * Forward packets between ATE port-1 and ATE LAG (destined to - 198.51.100.0/24 i.e. packets with destination IP starting - 198.51.100.1 up to 198.51.100.255) and determine that packets are - forwarded successfully. - - * Disable ATE port-2 and forward packets between ATE port-1 and ATE - LAG (destined to 198.51.100.0/24 ) and determine that packets are - forwarded successfully. - - * Disable ATE port-2 and port-3 and forward packets between ATE port-1 - and ATE LAG (destined to 198.51.100.0/24 ) and determine that - packets are lost 100%. - - * Re-enable both ATE port-2 and port-3 and forward packets between ATE - port-1 and ATE LAG (destined to 198.51.100.0/24 ) and determine that - packets are forwarded successfully again. + * Configure a static ARP entry for the LAG interface pointing the + synthetic IP 192.0.2.22 to the neighbor's MAC address + 02:00:00:00:00:01. + * Configure a static route matching 192.0.2.22/32 to the interface ref + of the LAG. + * The gRIBI NH entry uses 192.0.2.22 to select the LAG as the egress + interface. + + * Install 198.51.100.0/24 to NextHopGroup containing one NextHop which is + a static route to the ATE LAG port containing ports 2-3, and override + the destination MAC to a specified value. + + * Forward packets between ATE port-1 and ATE LAG (destined to + 198.51.100.0/24 i.e. packets with destination IP starting 198.51.100.1 + up to 198.51.100.255) and determine that packets are forwarded + successfully. + + * Disable ATE port-2 and forward packets between ATE port-1 and ATE LAG + (destined to 198.51.100.0/24 ) and determine that packets are forwarded + successfully. + + * Disable ATE port-2 and port-3 and forward packets between ATE port-1 and + ATE LAG (destined to 198.51.100.0/24 ) and determine that packets are + lost 100%. + + * Re-enable both ATE port-2 and port-3 and forward packets between ATE + port-1 and ATE LAG (destined to 198.51.100.0/24 ) and determine that + packets are forwarded successfully again. ## Config Parameter coverage diff --git a/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/ipv4_entry_with_aggregate_ports_test.go b/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/ipv4_entry_with_aggregate_ports_test.go index 6ea5a791eaa..7e8bdaf7c50 100644 --- a/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/ipv4_entry_with_aggregate_ports_test.go +++ b/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/ipv4_entry_with_aggregate_ports_test.go @@ -1,3 +1,17 @@ +// 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 ipv4_entry_with_aggregate_ports_test import ( @@ -194,23 +208,32 @@ func createFlow(t *testing.T, name string, ate *ondatra.ATEDevice, ateTop gosnap func configureGRIBIPrefixes(t *testing.T, dut *ondatra.DUTDevice, aggID string) { t.Helper() - // Static route to nh1IPAddr which is the ATE Lag port. - s := &oc.NetworkInstance_Protocol_Static{ - Prefix: ygot.String(nh1IpAddr + "/32"), - NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ - "0": { - Index: ygot.String("0"), - InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ - Interface: ygot.String(aggID), + e1 := fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(nh1ID).WithInterfaceRef(aggID).WithMacAddress(staticDstMAC) + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) || deviations.GRIBIMACOverrideWithStaticARP(dut) { + // Static route to nh1IPAddr which is the ATE Lag port. + spID := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.Update(t, dut, spID.Config(), &oc.NetworkInstance_Protocol{ + Name: ygot.String(deviations.StaticProtocolName(dut)), + Identifier: oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + }) + s := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(nh1IpAddr + "/32"), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + "0": { + Index: ygot.String("0"), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(aggID), + }, }, }, - }, + } + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.Replace(t, dut, sp.Static(nh1IpAddr+"/32").Config(), s) + e1 = fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(nh1ID).WithInterfaceRef(aggID).WithIPAddress(nh1IpAddr).WithMacAddress(staticDstMAC) } - sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) - gnmi.Replace(t, dut, sp.Static(nh1IpAddr+"/32").Config(), s) - e1 := fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). - WithIndex(nh1ID).WithInterfaceRef(aggID).WithIPAddress(nh1IpAddr).WithMacAddress(staticDstMAC) e2 := fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). WithID(nhgID).AddNextHop(nh1ID, 1) e3 := fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). @@ -299,7 +322,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, agg top.Ports().Add().SetName(p1.ID()) srcDev := top.Devices().Add().SetName(atePort1.Name) srcEth := srcDev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p1.ID()) + srcEth.Connection().SetPortName(p1.ID()) srcEth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4").SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) ateAggPorts := []*ondatra.Port{ @@ -316,7 +339,7 @@ func configureATEBundle(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Confi t.Helper() agg := top.Lags().Add().SetName(lagName) lagID, _ := strconv.Atoi(aggID) - agg.Protocol().SetChoice("static").Static().SetLagId(uint32(lagID)) + agg.Protocol().Static().SetLagId(uint32(lagID)) for i, p := range aggPorts { port := top.Ports().Add().SetName(p.ID()) newMac, err := incrementMAC(ateDst.MAC, i+1) @@ -328,7 +351,7 @@ func configureATEBundle(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Confi dstDev := top.Devices().Add().SetName(agg.Name() + ".dev") dstEth := dstDev.Ethernets().Add().SetName(lagName + ".Eth").SetMac(ateDst.MAC) - dstEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.LAG_NAME).SetLagName(agg.Name()) + dstEth.Connection().SetLagName(agg.Name()) dstEth.Ipv4Addresses().Add().SetName(lagName + ".IPv4").SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) } @@ -384,14 +407,6 @@ func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, aggPorts []*ondatr agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_STATIC gnmi.Replace(t, dut, gnmi.OC().Interface(aggID).Config(), agg) - // Static ARP configuration with neighbor IP as nh1IPAddr - if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) || deviations.GRIBIMACOverrideWithStaticARP(dut) { - ipv4 := agg.GetOrCreateSubinterface(0).GetOrCreateIpv4() - n4 := ipv4.GetOrCreateNeighbor(nh1IpAddr) - n4.LinkLayerAddress = ygot.String(staticDstMAC) - gnmi.Replace(t, dut, gnmi.OC().Interface(aggID).Config(), agg) - } - for _, port := range aggPorts { d := &oc.Root{} i := d.GetOrCreateInterface(port.Name()) @@ -403,6 +418,14 @@ func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, aggPorts []*ondatr } gnmi.Replace(t, dut, gnmi.OC().Interface(port.Name()).Config(), i) } + + // Static ARP configuration with neighbor IP as nh1IPAddr + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) || deviations.GRIBIMACOverrideWithStaticARP(dut) { + ipv4 := agg.GetOrCreateSubinterface(0).GetOrCreateIpv4() + n4 := ipv4.GetOrCreateNeighbor(nh1IpAddr) + n4.LinkLayerAddress = ygot.String(staticDstMAC) + gnmi.Replace(t, dut, gnmi.OC().Interface(aggID).Config(), agg) + } } // awaitTimeout calls a fluent client Await, adding a timeout to the context. diff --git a/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/metadata.textproto b/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/metadata.textproto index c84b014a0e7..896ce8a0e42 100644 --- a/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/metadata.textproto +++ b/feature/gribi/otg_tests/ipv4_entry_with_aggregate_ports_test/metadata.textproto @@ -1,4 +1,4 @@ -# proto-file: third_party/openconfig/featureprofiles/proto/metadata.proto +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata uuid: "6cd082df-8344-4d75-b148-db572c387433" diff --git a/feature/gribi/otg_tests/mpls_forwarding/README.md b/feature/gribi/otg_tests/mpls_forwarding/README.md index 1bc17d692c6..170e6a6da7f 100644 --- a/feature/gribi/otg_tests/mpls_forwarding/README.md +++ b/feature/gribi/otg_tests/mpls_forwarding/README.md @@ -11,16 +11,30 @@ behaviour on the DUT. * Configure DUT `port-1` to be connected to ATE `port-1`, and DUT `port-2` to be connected to ATE `port-2`. ATE `port-2` is configured to have an assigned - address of `192.0.2.2`, and the interface is enabled. + address of `198.18.2.2`, and the interface is enabled. * For label stack depths beginning at `baseLabel`, with `numLabels` addition labels: - Program a `LabelEntry` matching outer label 100 pointing to a NHG containing a single NH. - - Program a `NextHopEntry` which points to `192.0.2.2` pushing `[baseLabel, + - Program a `NextHopEntry` which points to `192.18.2.2` pushing `[baseLabel, ..., baseLabel+numLabels]` onto the MPLS label stack. * Run an MPLS flow matching label 100's forwarding entry and validate that is received at the destination port. +### TE-10.2: Push MPLS Labels to IP payload + +* Configure DUT `port-1` to be connected to ATE `port-1`, and DUT `port-2` to + be connected to ATE `port-2`. ATE `port-2` is configured to have an assigned + address of `198.18.2.2`, and the interface is enabled. +* For label stack depth beginning at `baseLabel`, with `numLabels` addition + labels: + - Program a IPv4Entry matcing IP Prefix `192.168.0.0/24` pointing to a NHG + containing a single NH. + - Program a `NextHopEntry` which points to `192.18.2.2` pushing `[baseLabel, + ..., baseLabel+numLabels]` onto the MPLS label stack. +* Run an IP flow matching IP Prefix `192.168.0.0/24` and validate that it is + received at the destination port. + ## Protocol/RPC Parameter coverage * gRIBI: @@ -30,6 +44,8 @@ behaviour on the DUT. * `id` * `network_instance` * `op`: `ADD` + * `ip` + * `next_hop_group` * `mpls` * `next_hop_group` * `next_hop_group` diff --git a/feature/gribi/otg_tests/mpls_forwarding/gribi_mpls_forwarding_test.go b/feature/gribi/otg_tests/mpls_forwarding/gribi_mpls_forwarding_test.go index be09c573d94..1c124f4d247 100644 --- a/feature/gribi/otg_tests/mpls_forwarding/gribi_mpls_forwarding_test.go +++ b/feature/gribi/otg_tests/mpls_forwarding/gribi_mpls_forwarding_test.go @@ -56,3 +56,31 @@ func TestMPLSLabelPushDepth(t *testing.T) { }) } } + +// TestMPLSPushToIP validates the gRIBI actions that are used to push N labels onto +// on IP Packet. +func TestMPLSPushToIP(t *testing.T) { + gribic := ondatra.DUT(t, "dut").RawAPIs().GRIBI(t) + c := fluent.NewClient() + c.Connection().WithStub(gribic) + + for numLabels := 1; numLabels <= maximumStackDepth; numLabels++ { + t.Run(fmt.Sprintf("TE-10.2: Push MPLS labels to IP: Push %d labels", numLabels), func(t *testing.T) { + labels := []uint32{} + for i := 0; i < numLabels; i++ { + labels = append(labels, uint32(baseLabel+i)) + } + + mplsT := mplsutil.New(c, mplsutil.PushToIP, deviations.DefaultNetworkInstance(ondatra.DUT(t, "dut")), &mplsutil.Args{ + LabelsToPush: labels, + }) + + mplsT.ConfigureDevices(t, ondatra.DUT(t, "dut"), ondatra.ATE(t, "ate")) + mplsT.ProgramGRIBI(t) + mplsT.ValidateProgramming(t) + mplsT.ConfigureFlows(t, ondatra.ATE(t, "ate")) + mplsT.RunFlows(t, ondatra.ATE(t, "ate"), 10*time.Second, lossTolerance) + mplsT.Cleanup(t) + }) + } +} diff --git a/feature/gribi/otg_tests/mpls_in_udp/README.md b/feature/gribi/otg_tests/mpls_in_udp/README.md new file mode 100644 index 00000000000..b2f8fc44704 --- /dev/null +++ b/feature/gribi/otg_tests/mpls_in_udp/README.md @@ -0,0 +1,320 @@ +# TE-18.1 gRIBI MPLS in UDP Encapsulation and Decapsulation + +Create AFT entries using gRIBI to match on next hop group in a +network-instance and encapsulate the matching packets in MPLS in UDP. + +Create a policy routing configuration using gNMI to decapsulate MPLS +in UDP packets which are sent to a loopback address and apply to +the DUT. + +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 +expected to be able to set the destination UDP port. + +## Topology + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## 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 = "6635" +outer_dscp = "26" +outer_ip-ttl = "64" + +## Procedure + +### TE-18.1.1 Match and Encapsulate using gRIBI aft modify + +#### gRIBI RPC content + +The gRIBI client should send this proto message to the DUT to create AFT +entries. + +```proto +# +# aft entries used for network instance "NI_A" +IPv6Entry {2001:DB8:2::2/128 (NI_A)} -> NHG#100 (DEFAULT VRF) +IPv4Entry {203.0.113.2/32 (NI_A)} -> NHG#100 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF} +} + +# this nexthop specifies a MPLS in UDP encapsulation +NH#101 -> { + encap_-_headers { + encap_header { + index: 1 + mpls { + pushed_mpls_label_stack: [101,] + } + } + encap_header { + index: 2 + udp_v6 { + src_ip: "outer_ipv6_src" + dst_ip: "outer_ipv6_dst_A" + dst_udp_port: "outer_dst_udp_port" + ip_ttl: "outer_ip-ttl" + dscp: "outer_dscp" + } + } + } + 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 + * Validate destination IPs are outer_ipv6_dst_A and outer_ipv6_dst_B + * Validate MPLS label is set + +### TE-18.1.2 Validate prefix match rule for MPLS in GRE encap using default route + +Canonical OpenConfig for policy forwarding, matching IP prefix with action +encapsulate in GRE. + +```json +{ + "openconfig-network-instance": { + "network-instances": [ + { + "afts": { + "policy-forwarding": { + "policies": [ + { + "config": { + "policy-id": "default encap rule", + "type": "PBR_POLICY" + }, + "policy": "default encap rule", + "rules": [ + { + "action": { + "encapsulate-headers": [ + { + "encapsulate-header": null, + "gre": { + "config": { + "destination-ip": "outer_ipv6_dst_def", + "dscp": "outer_dscp", + "id": "default_dst_1", + "ip-ttl": "outer_ip-ttl", + "source-ip": "outer_ipv6_src" + } + }, + "mpls": { + "mpls-label-stack": [ + 100 + ] + } + } + ], + "config": { + "network-instance": "DEFAULT" + } + }, + "config": { + "sequence-id": 1, + }, + "ipv6": { + "config": { + "destination-address": "inner_ipv6_default" + } + }, + "rule": 1 + } + ] + } + ] + } + }, + "network-instance": "group_A" + } + ] + } +} +``` + +* Generate the policy forwarding configuration +* Push the configuration to DUT using gnmi.Set with REPLACE option +* Configure ATE port 1 with traffic flow which does not match any AFT next hop route +* Generate traffic from ATE port 1 to ATE port 2 +* Validate ATE port 2 receives GRE traffic with correct inner and outer IPs + +### TE-18.1.3 - MPLS in GRE decapsulation set by gNMI + +Canonical OpenConfig for policy forwarding, matching IP prefix with action +decapsulate in GRE. # TODO: Move to dedicated README + +```yaml +openconfig-network-instance: + network-instances: + - network-instance: "DEFAULT" + afts: + policy-forwarding: + policies: + policy: "default decap rule" + config: + policy-id: "default decap rule" + type: PBR_POLICY + rules: + rule: 1 + config: + sequence-id: 1 + ipv6: + config: + destination-address: "decap_loopback_ipv6" + action: + decapsulate-mpls-in-gre: TRUE # TODO: add to OC model/PR in progress +``` + +* Push the gNMI the policy forwarding configuration +* Push the configuration to DUT using gnmi.Set with REPLACE option +* Configure ATE port 1 with traffic flow which matches the decap loopback IP address +* Generate traffic from ATE port 1 +* Validate ATE port 2 receives packets with correct VLAN and the inner inner_decap_ipv6 + +### TE-18.1.4 - MPLS in UDP decapsulation set by gNMI + +Canonical OpenConfig for policy forwarding, matching IP prefix with action +decapsulate MPLS in UDP. # TODO: Move to dedicated README + +```yaml +openconfig-network-instance: + network-instances: + - network-instance: "DEFAULT" + afts: + policy-forwarding: + policies: + policy: "default decap rule" + config: + policy-id: "default decap rule" + type: PBR_POLICY + rules: + rule: 1 + config: + sequence-id: 1 + ipv6: + config: + destination-address: "decap_loopback_ipv6" + action: + decapsulate-mpls-in-udp: TRUE +``` + +* Push the gNMI the policy forwarding configuration +* Push the configuration to DUT using gnmi.Set with REPLACE option +* Configure ATE port 1 with traffic flow + * Flow should have a packet encap format : outer_decap_udp_ipv6 <- MPLS label <- inner_decap_ipv6 +* Generate traffic from ATE port 1 +* Validate ATE port 2 receives the innermost IPv4 traffic with correct VLAN and inner_decap_ipv6 + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + +# afts state paths set via gRIBI + # TODO: need new OC for user defined next-hop-group/state/id, needed for policy-forwarding rules pointing to a NHG + # /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/next-hop-group-id: + + # TODO: new OC path for aft NHG pointing to a different network-instance + # /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/network-instance: + + # Paths added for TE-18.1.1 Match and Encapsulate using gRIBI aft modify + /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-v4/state/src-ip: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v4/state/dst-ip: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v4/state/dst-udp-port: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v4/state/ip-ttl: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v4/state/dscp: + + /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/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: + + # Paths added for TE-18.1.2 Validate prefix match rule for MPLS in GRE encap using default route + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/sequence-id: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encap-headers/encap-header/mpls/config/mpls-label-stack: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encap-headers/encap-header/gre/config/destination-ip: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encap-headers/encap-header/gre/config/dscp: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encap-headers/encap-header/gre/config/id: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encap-headers/encap-header/gre/config/ip-ttl: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encap-headers/encap-header/gre/config/source-ip: + + # Paths added for TE-18.1.3 - MPLS in GRE decapsulation set by gNMI + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/destination-address: + # TODO: /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-mpls-in-gre: + + # Paths added for TE-18.1.4 - MPLS in UDP decapsulation set by gNMI + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-mpls-in-udp: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true + gribi: + gRIBI.Modify: + afts:next-hops:next-hop:encap-headers:encap-header:udp_v6: + afts:next-hops:next-hop:encap-headers:encap-header:mpls: + gRIBI.Flush: +``` + +## Required DUT platform + +* FFF diff --git a/feature/gribi/otg_tests/mpls_in_udp_scale/README.md b/feature/gribi/otg_tests/mpls_in_udp_scale/README.md new file mode 100644 index 00000000000..e2f42b8c481 --- /dev/null +++ b/feature/gribi/otg_tests/mpls_in_udp_scale/README.md @@ -0,0 +1,145 @@ +# 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/ordering_ack_test/ordering_ack_test.go b/feature/gribi/otg_tests/ordering_ack_test/ordering_ack_test.go index 3ba8832bb08..accd99b5ef9 100644 --- a/feature/gribi/otg_tests/ordering_ack_test/ordering_ack_test.go +++ b/feature/gribi/otg_tests/ordering_ack_test/ordering_ack_test.go @@ -150,7 +150,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(ate.Port(t, "port1").ID()) i1 := top.Devices().Add().SetName(ate.Port(t, "port1").ID()) eth1 := i1.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i1.Name()) + eth1.Connection().SetPortName(i1.Name()) eth1.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4"). SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4). SetPrefix(uint32(ateSrc.IPv4Len)) @@ -158,7 +158,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(ate.Port(t, "port2").ID()) i2 := top.Devices().Add().SetName(ate.Port(t, "port2").ID()) eth2 := i2.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i2.Name()) + eth2.Connection().SetPortName(i2.Name()) eth2.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4"). SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4). SetPrefix(uint32(ateDst.IPv4Len)) @@ -185,10 +185,10 @@ func testTraffic( flowipv4.TxRx().Port(). SetTxName(ate.Port(t, "port1").ID()). SetRxName(ate.Port(t, "port2").ID()) - flowipv4.Duration().SetChoice("continuous") + flowipv4.Duration().Continuous() e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(ateSrc.MAC) - e1.Dst().SetChoice("value").SetValue(dstMac) + e1.Dst().SetValue(dstMac) v4 := flowipv4.Packet().Add().Ipv4() v4.Src().SetValue(ateSrc.IPv4) v4.Dst().Increment().SetStart(ateDstNetStartIp).SetCount(ateDstNetAddressCount) diff --git a/feature/gribi/otg_tests/persistence_mode_test/persistence_mode_test.go b/feature/gribi/otg_tests/persistence_mode_test/persistence_mode_test.go index d6bccfdb944..fa33c4bc91c 100644 --- a/feature/gribi/otg_tests/persistence_mode_test/persistence_mode_test.go +++ b/feature/gribi/otg_tests/persistence_mode_test/persistence_mode_test.go @@ -132,7 +132,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(ate.Port(t, "port1").ID()) i1 := top.Devices().Add().SetName(ate.Port(t, "port1").ID()) eth1 := i1.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i1.Name()) + eth1.Connection().SetPortName(i1.Name()) eth1.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4"). SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4). SetPrefix(uint32(atePort1.IPv4Len)) @@ -140,7 +140,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { top.Ports().Add().SetName(ate.Port(t, "port2").ID()) i2 := top.Devices().Add().SetName(ate.Port(t, "port2").ID()) eth2 := i2.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i2.Name()) + eth2.Connection().SetPortName(i2.Name()) eth2.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4"). SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4). SetPrefix(uint32(atePort2.IPv4Len)) @@ -164,10 +164,10 @@ func testTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, srcE flowipv4.TxRx().Port(). SetTxName(ate.Port(t, "port1").ID()). SetRxName(ate.Port(t, "port2").ID()) - flowipv4.Duration().SetChoice("continuous") + flowipv4.Duration().Continuous() e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(atePort1.MAC) - e1.Dst().SetChoice("value").SetValue(dstMac) + e1.Dst().SetValue(dstMac) v4 := flowipv4.Packet().Add().Ipv4() v4.Src().SetValue(atePort1.IPv4) v4.Dst().Increment().SetStart("203.0.113.1").SetCount(250) diff --git a/feature/experimental/gribi/otg_tests/route_addition_during_failover_test/README.md b/feature/gribi/otg_tests/route_addition_during_failover_test/README.md similarity index 91% rename from feature/experimental/gribi/otg_tests/route_addition_during_failover_test/README.md rename to feature/gribi/otg_tests/route_addition_during_failover_test/README.md index afb33fb929e..c2e05fd3e6c 100644 --- a/feature/experimental/gribi/otg_tests/route_addition_during_failover_test/README.md +++ b/feature/gribi/otg_tests/route_addition_during_failover_test/README.md @@ -36,11 +36,18 @@ Validate gRIBI route persistence during SSO * Send traffic from ATE port-1 to prefixes in `IPBlock2` and ensure traffic flows 100% and reaches ATE port-2. -## Protocol/RPC Parameter coverage - -* gNOI: - * System - * SwitchControlProcessor +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Config parameter coverage diff --git a/feature/experimental/gribi/otg_tests/route_addition_during_failover_test/metadata.textproto b/feature/gribi/otg_tests/route_addition_during_failover_test/metadata.textproto similarity index 96% rename from feature/experimental/gribi/otg_tests/route_addition_during_failover_test/metadata.textproto rename to feature/gribi/otg_tests/route_addition_during_failover_test/metadata.textproto index 66ccbf4fc07..f879dee9680 100644 --- a/feature/experimental/gribi/otg_tests/route_addition_during_failover_test/metadata.textproto +++ b/feature/gribi/otg_tests/route_addition_during_failover_test/metadata.textproto @@ -29,7 +29,6 @@ platform_exceptions: { } deviations: { gnoi_subcomponent_path: true - deprecated_vlan_id: true interface_enabled: true default_network_instance: "default" } diff --git a/feature/experimental/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 similarity index 99% rename from feature/experimental/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go rename to feature/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go index 8f4fe0a0aff..fb92e0f4856 100644 --- a/feature/experimental/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 @@ -349,7 +349,7 @@ func configureSubinterfaceDUT(t *testing.T, d *oc.Root, dutPort *ondatra.Port, i func configureATE(t *testing.T, top gosnappi.Config, atePort *ondatra.Port, vlanID uint16, Name, MAC, dutIPv4, ateIPv4 string) { dev := top.Devices().Add().SetName(Name + ".Dev") eth := dev.Ethernets().Add().SetName(Name + ".Eth").SetMac(MAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(atePort.ID()) + eth.Connection().SetPortName(atePort.ID()) if vlanID != 0 { eth.Vlans().Add().SetName(Name).SetId(uint32(vlanID)) } @@ -386,7 +386,7 @@ func createTrafficFlow(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config flow.TxRx().Port().SetTxName("port1").SetRxName("port2") e1 := flow.Packet().Add().Ethernet() e1.Src().SetValue(atePort1.MAC) - e1.Dst().SetChoice("value").SetValue(dstMac) + e1.Dst().SetValue(dstMac) v4 := flow.Packet().Add().Ipv4() v4.Src().SetValue(atePort1.IPv4) v4.Dst().Increment().SetStart(flowArgs.flowStartAddress).SetCount(flowArgs.flowCount) @@ -661,6 +661,7 @@ func TestRouteAdditionDuringFailover(t *testing.T) { // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. // Check vars for WithInitialElectionID. + client.Stop(t) t.Log("Reconnect gRIBi client after switchover on new master.") client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(eID.Low, eID.High). WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) diff --git a/feature/experimental/gribi/otg_tests/route_removal_during_failover_test/README.md b/feature/gribi/otg_tests/route_removal_during_failover_test/README.md similarity index 92% rename from feature/experimental/gribi/otg_tests/route_removal_during_failover_test/README.md rename to feature/gribi/otg_tests/route_removal_during_failover_test/README.md index 0ff42794cf1..da9a134e0d0 100644 --- a/feature/experimental/gribi/otg_tests/route_removal_during_failover_test/README.md +++ b/feature/gribi/otg_tests/route_removal_during_failover_test/README.md @@ -30,11 +30,18 @@ Validate gRIBI route flush during SSO * Send traffic from ATE port-1 to prefixes in IPBlock1 and ensure traffic flows 100% and reaches ATE port-2. -## Protocol/RPC Parameter coverage - -* gNOI: - * System - * SwitchControlProcessor +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Config parameter coverage diff --git a/feature/experimental/gribi/otg_tests/route_removal_during_failover_test/metadata.textproto b/feature/gribi/otg_tests/route_removal_during_failover_test/metadata.textproto similarity index 96% rename from feature/experimental/gribi/otg_tests/route_removal_during_failover_test/metadata.textproto rename to feature/gribi/otg_tests/route_removal_during_failover_test/metadata.textproto index 596c16cd50d..6f5779483cf 100644 --- a/feature/experimental/gribi/otg_tests/route_removal_during_failover_test/metadata.textproto +++ b/feature/gribi/otg_tests/route_removal_during_failover_test/metadata.textproto @@ -38,7 +38,6 @@ platform_exceptions: { } deviations: { gnoi_subcomponent_path: true - deprecated_vlan_id: true interface_enabled: true default_network_instance: "default" } diff --git a/feature/experimental/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 similarity index 99% rename from feature/experimental/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go rename to feature/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go index ee9874f4844..db63e96f39d 100644 --- a/feature/experimental/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 @@ -298,7 +298,7 @@ func configureSubinterfaceDUT(d *oc.Root, dutPort *ondatra.Port, index uint32, v func configureATE(top gosnappi.Config, atePort *ondatra.Port, Name string, vlanID uint16, dutIPv4, ateIPv4, ateMAC string) { dev := top.Devices().Add().SetName(Name + ".Dev") eth := dev.Ethernets().Add().SetName(Name + ".Eth").SetMac(ateMAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(atePort.ID()) + eth.Connection().SetPortName(atePort.ID()) if vlanID != 0 { eth.Vlans().Add().SetName(Name).SetId(uint32(vlanID)) } @@ -333,7 +333,7 @@ func createTrafficFlow(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config flow.TxRx().Port().SetTxName("port1").SetRxName("port2") e1 := flow.Packet().Add().Ethernet() e1.Src().SetValue(atePort1.MAC) - e1.Dst().SetChoice("value").SetValue(dstMac) + e1.Dst().SetValue(dstMac) v4 := flow.Packet().Add().Ipv4() v4.Src().SetValue(atePort1.IPv4) v4.Dst().Increment().SetStart("198.18.196.1").SetCount(1020) @@ -622,6 +622,7 @@ func TestRouteRemovalDuringFailover(t *testing.T) { // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. // Check vars for WithInitialElectionID. + client.Stop(t) t.Log("Reconnect gRIBi client after switchover on new master.") client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(eID.Low, eID.High). WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) diff --git a/feature/gribi/otg_tests/route_removal_via_flush_test/route_removal_via_flush_test.go b/feature/gribi/otg_tests/route_removal_via_flush_test/route_removal_via_flush_test.go index c354054d75f..5c3c5050763 100644 --- a/feature/gribi/otg_tests/route_removal_via_flush_test/route_removal_via_flush_test.go +++ b/feature/gribi/otg_tests/route_removal_via_flush_test/route_removal_via_flush_test.go @@ -315,7 +315,7 @@ func testTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) floa SetTxNames([]string{atePort1.Name + ".IPv4"}). SetRxNames([]string{atePort2.Name + ".IPv4"}) - flowipv4.Duration().SetChoice("continuous") + flowipv4.Duration().Continuous() e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(atePort1.MAC) v4 := flowipv4.Packet().Add().Ipv4() diff --git a/feature/gribi/otg_tests/static_lsp/README.md b/feature/gribi/otg_tests/static_lsp/README.md deleted file mode 100644 index 6df7598eb58..00000000000 --- a/feature/gribi/otg_tests/static_lsp/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# TE-9.1: MPLS based forwarding Static LSP - -## Summary - -Validate static lsp functionality. - -## Procedure - -* Create topology ATE1–DUT1-ATE2 -* Enable MPLS forwarding and create egress static LSP to pop the label and forward to ATE2: -* Match incoming label (1000001) -* Set IP next-hop -* Set egress interface -* Set the action to pop label -* Start 2 traffic flows with specified MPLS tags IPv4-MPLS[1000002]-MPLS[1000001] -* Verify that traffic is received at ATE2 with MPLS label [1000001] removed - - -## Config Parameter coverage - -* /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/config -* /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/config/next-hop -* /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/push-label -* /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state \ No newline at end of file diff --git a/feature/gribi/otg_tests/static_lsp_test/README.md b/feature/gribi/otg_tests/static_lsp_test/README.md new file mode 100644 index 00000000000..c48cd24acb8 --- /dev/null +++ b/feature/gribi/otg_tests/static_lsp_test/README.md @@ -0,0 +1,36 @@ +# TE-9.2: MPLS based forwarding Static LSP + +## Summary + +Validate static lsp functionality. + +## Procedure + +* Create topology ATE1–DUT1-ATE2 +* Enable MPLS forwarding and create egress static LSP to pop the label and forward to ATE2: +* Match incoming label (1000001) +* Set IP next-hop +* Set egress interface +* Set the action to pop label +* Start 2 traffic flows with specified MPLS tags IPv4-MPLS[1000002]-MPLS[1000001] +* Verify that traffic is received at ATE2 with MPLS label [1000001] removed + +## 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/mpls/lsps/static-lsps/static-lsp/egress/config/next-hop: + /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/push-label: + + ## State paths + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/next-hop: + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/incoming-label: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: diff --git a/feature/gribi/otg_tests/static_lsp_test/metadata.textproto b/feature/gribi/otg_tests/static_lsp_test/metadata.textproto new file mode 100644 index 00000000000..5d5dbf8904f --- /dev/null +++ b/feature/gribi/otg_tests/static_lsp_test/metadata.textproto @@ -0,0 +1,15 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "021610e4-9b59-48c2-a9b9-2f204fa4c2a7" +plan_id: "TE-9.2" +description: "MPLS based forwarding Static LSP" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} diff --git a/feature/gribi/otg_tests/static_lsp_test/static_lsp_test.go b/feature/gribi/otg_tests/static_lsp_test/static_lsp_test.go new file mode 100644 index 00000000000..46ad3a5a7c1 --- /dev/null +++ b/feature/gribi/otg_tests/static_lsp_test/static_lsp_test.go @@ -0,0 +1,376 @@ +package static_lsp_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/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 ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + mplsLabel1 = 1000001 + mplsLabel2 = 1000002 + mplsLabel3 = 1000003 + + tolerance = 0.01 // 1% Traffic Tolerance +) + +var ( + ateSrc = attrs.Attributes{ + Name: "ateSrc", + MAC: "02:11:01:00:00:01", + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutSrc = attrs.Attributes{ + Desc: "DUT to ATE source", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutDst = attrs.Attributes{ + Desc: "DUT to ATE destination", + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + ateDst = attrs.Attributes{ + Name: "ateDst", + MAC: "02:12:01:00:00:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + i.Description = ygot.String(a.Desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s := i.GetOrCreateSubinterface(0) + + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + s4a := s4.GetOrCreateAddress(a.IPv4) + s4a.PrefixLength = ygot.Uint8(ipv4PrefixLen) + + return i +} + +// configureDUT configures port1, port2 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())} + i1.Enabled = ygot.Bool(true) + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutSrc, 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, &dutDst, dut)) + +} + +// configureATE configures port1 and port2 on the ATE. +func configureOTG(t *testing.T) gosnappi.Config { + t.Helper() + top := gosnappi.NewConfig() + port1 := top.Ports().Add().SetName("port1") + port2 := top.Ports().Add().SetName("port2") + + // Port1 Configuration. + 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. + 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)) + + // enable packet capture on this port + top.Captures().Add().SetName("mplsPackCapture").SetPortNames([]string{port2.Name()}).SetFormat(gosnappi.CaptureFormat.PCAP) + + return top + +} + +// configureStaticLSP configures a static MPLS LSP with the provided parameters. +func configureStaticLSP(t *testing.T, dut *ondatra.DUTDevice, lspName string, incomingLabel uint32, nextHopIP string) { + d := &oc.Root{} + dni := deviations.DefaultNetworkInstance(dut) + defPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + gnmi.Update(t, dut, defPath.Config(), &oc.NetworkInstance{ + Name: ygot.String(deviations.DefaultNetworkInstance(dut)), + Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE, + }) + mplsCfg := d.GetOrCreateNetworkInstance(dni).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) +} + +func createTrafficFlow(t *testing.T, + ate *ondatra.ATEDevice, + dut *ondatra.DUTDevice, + top gosnappi.Config, + label1 uint32, label2 uint32) gosnappi.Flow { + + // get dut mac interface for traffic mpls flow + dutDstInterface := dut.Port(t, "port1").Name() + dstMac := gnmi.Get(t, dut, gnmi.OC().Interface(dutDstInterface).Ethernet().MacAddress().State()) + t.Logf("DUT remote mac address is %s", dstMac) + + // Create a traffic flow with MPLS + flowName := fmt.Sprintf("MPLS-%d-MPLS-%d::", mplsLabel1, mplsLabel2) + mplsFlow := top.Flows().Add().SetName(flowName) + mplsFlow.TxRx().Port(). + SetTxName(ate.Port(t, "port1").ID()). + SetRxNames([]string{ate.Port(t, "port2").ID()}) + + mplsFlow.Metrics().SetEnable(true) + mplsFlow.Rate().SetPps(500) + mplsFlow.Size().SetFixed(512) + mplsFlow.Duration().Continuous() + + // Set up ethernet layer. + eth := mplsFlow.Packet().Add().Ethernet() + eth.Src().SetValue(ateSrc.MAC) + eth.Dst().SetValue(dstMac) + + // Set up MPLS layer with destination label 100. + mpls := mplsFlow.Packet().Add().Mpls() + mpls.Label().SetValue(label1) + mpls.BottomOfStack().SetValue(0) + + // Set up MPLS layer with destination label 100. + mpls2 := mplsFlow.Packet().Add().Mpls() + mpls2.Label().SetValue(label2) + mpls2.BottomOfStack().SetValue(1) + + ip4 := mplsFlow.Packet().Add().Ipv4() + ip4.Src().SetValue(ateSrc.IPv4) + ip4.Dst().SetValue(ateDst.IPv4) + ip4.Version().SetValue(4) + + return mplsFlow + +} + +func ValidatePackets(t *testing.T, + filename string, + expectedLabel uint32, + tolerancePercentage float64) { + + handle, err := pcap.OpenOffline(filename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + var expectedMPLSPackets int + var unexpectedMPLSPackets int + + // Convert string to net.IP for comparison + targetSrcIP := net.ParseIP(ateSrc.IPv4) + targetDstIP := net.ParseIP(ateDst.IPv4) + + t.Logf("Checking Packets to verify MPLS label was popped and searching for "+ + "src %s and dst %s", ateSrc.IPv4, ateDst.IPv4) + + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + t.Log("IP layer not found in packet") + continue + } + ipv4, _ := ipLayer.(*layers.IPv4) + + // Compare the source IP address in the packet to the target source IP + if ipv4.SrcIP.Equal(targetSrcIP) && ipv4.DstIP.Equal(targetDstIP) { + // Now check for an MPLS layer + var mpls *layers.MPLS + mplsLayer := packet.Layer(layers.LayerTypeMPLS) + if mplsLayer != nil { + mplsPkt, _ := mplsLayer.(*layers.MPLS) + // check if the expected label is found + if mplsPkt.Label == expectedLabel { + expectedMPLSPackets++ + } else { + t.Errorf("Unexpected Label Found MPLS packet with label: %v", mplsPkt.Label) + unexpectedMPLSPackets++ + } + } else { + // increment the unexpected packet counter + unexpectedMPLSPackets++ + t.Errorf("Found MPLS packet with label: %v", mpls.Label) + + } + } + } + + // Calculate the tolerance based on the number of expected MPLS packets + pktCount := int(float64(expectedMPLSPackets) * (tolerancePercentage / 100)) + + // Check if the unexpected packets are within the tolerance + if unexpectedMPLSPackets > pktCount { + t.Errorf("Test failed: found %d unexpected MPLS packets, "+ + "which is above the tolerance of 1%% of expected MPLS packets (%d)", unexpectedMPLSPackets, pktCount) + } else { + t.Logf("Test Passed: processed (%d) expected packets with top label "+ + "popped and label RX on OTG is (%d) and found (%d) unexpected packets "+ + "with a tolerance of %d", expectedMPLSPackets, expectedLabel, + unexpectedMPLSPackets, pktCount) + } + t.Log("Finished checking packets for source IP.") +} + +// Send traffic and validate traffic. +func verifyTrafficStreams(t *testing.T, + ate *ondatra.ATEDevice, + top gosnappi.Config, + otg *otg.OTG, + mplsFlow gosnappi.Flow) { + t.Helper() + + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) + + t.Log("starting traffic for 30 seconds") + ate.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + t.Log("Stopping traffic and waiting 10 seconds for traffic stats to complete") + ate.OTG().StopTraffic(t) + time.Sleep(10 * time.Second) + + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + txPkts := float32(gnmi.Get(t, otg, gnmi.OTG().Flow(mplsFlow.Name()).Counters().OutPkts().State())) + rxPkts := float32(gnmi.Get(t, otg, gnmi.OTG().Flow(mplsFlow.Name()).Counters().InPkts().State())) + + // Calculate the acceptable lower and upper bounds for rxPkts + lowerBound := txPkts * (1 - tolerance) + upperBound := txPkts * (1 + tolerance) + + if rxPkts < lowerBound || rxPkts > upperBound { + t.Fatalf("Received packets are outside of the acceptable range: %v (1%% tolerance from %v)", rxPkts, txPkts) + } else { + t.Logf("Received packets are within the acceptable range: %v (1%% tolerance from %v)", rxPkts, txPkts) + } + + // create packet capture pcap file + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(top.Ports().Items()[1].Name())) + f, err := os.CreateTemp("", "pcap") + if err != nil { + t.Fatalf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, fileOutput := f.Write(bytes); fileOutput != nil { + t.Fatalf("ERROR: Could not write bytes to pcap file: %v\n", fileOutput) + } + + // Log the file name + t.Logf("Created temporary pcap file at: %s\n", f.Name()) + + if _, fileOutput := f.Write(bytes); fileOutput != nil { + t.Fatalf("ERROR: Could not write bytes to pcap file: %v\n", fileOutput) + } + + fileClose := f.Close() + if fileClose != nil { + return + } + ValidatePackets(t, f.Name(), mplsLabel2, tolerance) + +} + +// TestMplsStaticLabel +func TestMplsStaticLabel(t *testing.T) { + var top gosnappi.Config + var mplsFlow gosnappi.Flow + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otgObj := ate.OTG() + + t.Run("configureDUT Interfaces", func(t *testing.T) { + // Configure the DUT + configureDUT(t, dut) + + }) + + t.Run("ConfigureOTG", func(t *testing.T) { + t.Logf("Configure ATE") + top = configureOTG(t) + + }) + + t.Run("Configure static LSP on DUT", func(t *testing.T) { + // configure static lsp from ateSrc to ateDst + configureStaticLSP(t, dut, "lsp1", mplsLabel1, ateDst.IPv4) + // configure static lsp from ateDst to ateSrc + configureStaticLSP(t, dut, "lsp2", mplsLabel3, ateSrc.IPv4) + + }) + + t.Run("Build OTG Traffic Flow", func(t *testing.T) { + // Build MPLS Traffic Flow + mplsFlow = createTrafficFlow(t, ate, dut, top, mplsLabel1, mplsLabel2) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + t.Logf("OTG Config is %s\n", top.String()) + + }) + + t.Run("Verify Static Label Traffic Flow", func(t *testing.T) { + verifyTrafficStreams(t, ate, top, otgObj, mplsFlow) + + }) + +} diff --git a/feature/gribi/otg_tests/supervisor_failure_test/README.md b/feature/gribi/otg_tests/supervisor_failure_test/README.md index d6d404d3473..53f8bd30b2e 100644 --- a/feature/gribi/otg_tests/supervisor_failure_test/README.md +++ b/feature/gribi/otg_tests/supervisor_failure_test/README.md @@ -29,26 +29,38 @@ Ensure that gRIBI entries are persisted over supervisor failure. the prefix `203.0.113.0/24` pointing to ATE port-2 is present and traffic flows 100% from ATE port-1 to ATE port-2. -## Protocol/RPC Parameter coverage - -* gNOI: - * System - * SwitchControlProcessor - -## Config parameter coverage - -## Telemery parameter coverage - -* CHASSIS: - - * /components/component[name=]/state/last-reboot-time - * /components/component[name=]/state/last-reboot-reason - -* CONTROLLER_CARD: - - * /components/component[name=]/state/redundant-role - * /components/component[name=]/state/last-switchover-time - * /components/component[name=]/state/last-switchover-reason/trigger - * /components/component[name=]/state/last-switchover-reason/details - * /components/component[name=]/state/last-reboot-time - * /components/component[name=]/state/last-reboot-reason +## 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 Parameter coverage + + ## Telemetry Parameter coverage + + /components/component/state/last-reboot-time: + platform_type: ["CHASSIS", "CONTROLLER_CARD"] + /components/component/state/last-reboot-reason: + platform_type: ["CHASSIS", "CONTROLLER_CARD" ] + /components/component/state/redundant-role: + platform_type: [ "CONTROLLER_CARD" ] + /components/component/state/last-switchover-time: + platform_type: [ "CONTROLLER_CARD" ] + /components/component/state/last-switchover-reason/trigger: + platform_type: [ "CONTROLLER_CARD" ] + /components/component/state/last-switchover-reason/details: + platform_type: [ "CONTROLLER_CARD" ] + +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + gNMI.Subscribe: + gnoi: + system.System.SwitchControlProcessor: +``` + +## Minimum DUT Required + +vRX - Virtual Router Device 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 091c7b0c78e..4dd22321943 100644 --- a/feature/gribi/otg_tests/supervisor_failure_test/supervisor_failure_test.go +++ b/feature/gribi/otg_tests/supervisor_failure_test/supervisor_failure_test.go @@ -220,9 +220,10 @@ func findSecondaryController(t *testing.T, dut *ondatra.DUTDevice, controllers [ } // validateTelemetry validates telemetry sensors -func validateTelemetry(t *testing.T, dut *ondatra.DUTDevice, primaryAfterSwitch string) { +func validateTelemetry(t *testing.T, dut *ondatra.DUTDevice, primaryAfterSwitch, secondaryAfterSwitch string) { t.Log("Validate OC Switchover time/reason.") primary := gnmi.OC().Component(primaryAfterSwitch) + secondary := gnmi.OC().Component(secondaryAfterSwitch) if !gnmi.Lookup(t, dut, primary.LastSwitchoverTime().State()).IsPresent() { t.Errorf("primary.LastSwitchoverTime().Lookup(t).IsPresent(): got false, want true") } else { @@ -244,16 +245,16 @@ func validateTelemetry(t *testing.T, dut *ondatra.DUTDevice, primaryAfterSwitch t.Errorf("primary.GetLastSwitchoverReason().GetTrigger(): got %s, want %s.", got, want) } - if !gnmi.Lookup(t, dut, primary.LastRebootTime().State()).IsPresent() { - t.Errorf("primary.LastRebootTime.().Lookup(t).IsPresent(): got false, want true") + if !gnmi.Lookup(t, dut, secondary.LastRebootTime().State()).IsPresent() { + t.Errorf("secondary.LastRebootTime.().Lookup(t).IsPresent(): got false, want true") } else { - lastrebootTime := gnmi.Get(t, dut, primary.LastRebootTime().State()) + lastrebootTime := gnmi.Get(t, dut, secondary.LastRebootTime().State()) t.Logf("Found lastRebootTime.GetDetails(): %v", lastrebootTime) } - if !gnmi.Lookup(t, dut, primary.LastRebootReason().State()).IsPresent() { - t.Errorf("primary.LastRebootReason.().Lookup(t).IsPresent(): got false, want true") + if !gnmi.Lookup(t, dut, secondary.LastRebootReason().State()).IsPresent() { + t.Errorf("secondary.LastRebootReason.().Lookup(t).IsPresent(): got false, want true") } else { - lastrebootReason := gnmi.Get(t, dut, primary.LastRebootReason().State()) + lastrebootReason := gnmi.Get(t, dut, secondary.LastRebootReason().State()) t.Logf("Found lastRebootReason.GetDetails(): %v", lastrebootReason) } } @@ -312,6 +313,7 @@ func TestSupFailure(t *testing.T) { t.Logf("Starting traffic") ate.OTG().StartTraffic(t) time.Sleep(15 * time.Second) + ate.OTG().StopTraffic(t) otgutils.LogFlowMetrics(t, ate.OTG(), top) verifyTraffic(t, args.ate) @@ -353,8 +355,8 @@ func TestSupFailure(t *testing.T) { // Old secondary controller becomes primary after switchover. primaryAfterSwitch := secondaryBeforeSwitch - - validateTelemetry(t, dut, primaryAfterSwitch) + secondaryAfterSwitch := secondaryBeforeSwitch + validateTelemetry(t, dut, primaryAfterSwitch, secondaryAfterSwitch) // Assume Controller Switchover happened, ensure traffic flows without loss. // Verify the entry for 203.0.113.0/24 is active through AFT Telemetry. // Try starting the gribi client twice as switchover may reset the connection. diff --git a/feature/gribi/otg_tests/vrf_policy_driven_te/README.md b/feature/gribi/otg_tests/vrf_policy_driven_te/README.md new file mode 100644 index 00000000000..7fa0f46bec3 --- /dev/null +++ b/feature/gribi/otg_tests/vrf_policy_driven_te/README.md @@ -0,0 +1,869 @@ +# TE-17.1: VRF selection policy driven TE + +## Summary + +Test VRF selection logic involving different decapsulation and encapsulation lookup scenarios via gRIBI. + +## Topology + +ATE port-1 <------> port-1 DUT +DUT port-2 <------> port-2 ATE +DUT port-3 <------> port-3 ATE +DUT port-4 <------> port-4 ATE +DUT port-5 <------> port-5 ATE +DUT port-6 <------> port-6 ATE +DUT port-7 <------> port-7 ATE +DUT port-8 <------> port-8 ATE + +## Variables + +``` +# DSCP value that will be matched to ENCAP_TE_VRF_A +* dscp_encap_a_1 = 10 +* dscp_encap_a_2 = 18 + +# DSCP value that will be matched to ENCAP_TE_VRF_B +* dscp_encap_b_1 = 20 +* dscp_encap_b_2 = 28 + +# DSCP value that will NOT be matched to any VRF for encapsulation. +* dscp_encap_no_match = 30 + +# Magic source IP addresses used in VRF selection policy +* ipv4_outer_src_111 = 198.51.100.111 +* ipv4_outer_src_222 = 198.51.100.222 + +# Magic destination MAC address +* magic_mac = 02:00:00:00:00:01` +``` + +vrf_selection_policy_c +``` +network-instances { + network-instance { + name: DEFAULT + policy-forwarding { + policies { + policy { + policy-id: "vrf_selection_policy_c" + rules { + rule { + sequence-id: 1 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 2 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 3 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 4 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 5 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 6 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 7 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 8 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 9 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 10 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 11 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 12 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 13 + ipv4 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 14 + ipv6 { + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + } + action { + network-instance: "ENCAP_TE_VRF_A" + } + } + rule { + sequence-id: 15 + ipv4 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 16 + ipv6 { + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + } + action { + network-instance: "ENCAP_TE_VRF_B" + } + } + rule { + sequence-id: 17 + action { + network-instance: "DEFAULT" + } + } + } + } + } + } + } +} +``` + +vrf_selection_policy_w +``` +network-instances { + network-instance { + name: DEFAULT + policy-forwarding { + policies { + policy { + policy-id: "vrf_selection_policy_w" + rules { + rule { + sequence-id: 1 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 2 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 3 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 4 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_a_1, dscp_encap_a_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_A" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 5 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 6 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 7 + ipv4 { + protocol: 4 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 8 + ipv4 { + protocol: 41 + dscp-set: [dscp_encap_b_1, dscp_encap_b_2] + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "ENCAP_TE_VRF_B" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 9 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 10 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_222" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_222" + } + } + rule { + sequence-id: 11 + ipv4 { + protocol: 4 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 12 + ipv4 { + protocol: 41 + source-address: "ipv4_outer_src_111" + } + action { + decap-network-instance: "DECAP_TE_VRF" + post-network-instance: "DEFAULT" + decap-fallback-network-instance: "TE_VRF_111" + } + } + rule { + sequence-id: 13 + action { + network-instance: "DEFAULT" + } + } + } + } + } + } + } +} +``` + +## Baseline + +* Install the following gRIBI AFTs. + +``` +IPv4Entry {138.0.11.0/24 (ENCAP_TE_VRF_A)} -> NHG#101 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF, weight:1}, + {NH#102, DEFAULT VRF, weight:3}, + backup_next_hop_group: 200 // in case specific vendor implementation or bugs pruned the NHs. +} +IPv4Entry {138.0.11.0/24 (ENCAP_TE_VRF_B)} -> NHG#102 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF, weight:3}, + {NH#102, DEFAULT VRF, weight:1}, + backup_next_hop_group: 200 // in case specific vendor implementation or bugs pruned the NHs. +} + +IPv6Entry {2001:db8::138:0:11:0/126 (ENCAP_TE_VRF_A)} -> NHG#101 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF, weight:1}, + {NH#102, DEFAULT VRF, weight:3}, + backup_next_hop_group: 200 // in case specific vendor implementation or bugs pruned the NHs. +} +IPv6Entry {2001:db8::138:0:11:0/126 (ENCAP_TE_VRF_B)} -> NHG#102 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF, weight:3}, + {NH#102, DEFAULT VRF, weight:1}, + backup_next_hop_group: 200 // in case specific vendor implementation or bugs pruned the NHs. +} + +NH#101 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.1" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} +NH#102 -> { + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.10.113.2" + src_ip: "ipv4_outer_src_111" + } + network_instance: "TE_VRF_111" +} + +NHG#200 (Default VRF) { + {NH#200, DEFAULT VRF, weight:1} +} +NH#200 -> { + network_instance: "DEFAULT" +} + +IPv4Entry {203.0.113.1/32 (TE_VRF_111)} -> NHG#1 (DEFAULT VRF) -> { + {NH#1, DEFAULT VRF, weight:1,ip_address=192.0.2.101}, + {NH#2, DEFAULT VRF, weight:3,ip_address=192.0.2.102}, + backup_next_hop_group: 1000 // re-encap to 203.0.113.100 +} +IPv4Entry {192.0.2.101/32 (DEFAULT VRF)} -> NHG#11 (DEFAULT VRF) -> { + {NH#11, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-2-interface}, + {NH#12, DEFAULT VRF, weight:3,mac_address:magic_mac, interface-ref:dut-port-3-interface}, +} +IPv4Entry {192.0.2.102/32 (DEFAUlT VRF)} -> NHG#12 (DEFAULT VRF) -> { + {NH#13, DEFAULT VRF, weight:2,mac_address:magic_mac, interface-ref:dut-port-4-interface}, +} + +NHG#1000 (Default VRF) { + {NH#1000, DEFAULT VRF} +} +NH#1000 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.100" + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} + +IPv4Entry {203.0.113.100/32 (TE_VRF_222)} -> NHG#2 (DEFAULT VRF) -> { + {NH#3, DEFAULT VRF, weight:1,ip_address=192.0.2.103}, + backup_next_hop_group: 1001 // decap to DEFAULT VRF +} +IPv4Entry {192.0.2.103/32 (DEFAULT VRF)} -> NHG#13 (DEFAULT VRF) -> { + {NH#14, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-5-interface}, +} +NHG#1001 (Default VRF) { + {NH#1001, DEFAULT VRF, weight:1} +} +NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + network_instance: "DEFAULT" +} + +// 203.10.113.2 is the tunnel IP address. Note that the NHG#3 is different than NHG#1. + +IPv4Entry {203.10.113.2/32 (TE_VRF_111)} -> NHG#3 (DEFAULT VRF) -> { + {NH#4, DEFAULT VRF, weight:1,ip_address=192.0.2.104}, + backup_next_hop_group: 1002 // re-encap to 203.10.113.101 +} +IPv4Entry {192.0.2.104/32 (DEFAULT VRF)} -> NHG#14 (DEFAULT VRF) -> { + {NH#15, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-6-interface}, +} +NHG#1002 (DEFAULT VRF) { + {NH#1002, DEFAULT VRF} +} +NH#1002 -> { + decapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + encapsulate_header: OPENCONFIGAFTTYPESENCAPSULATIONHEADERTYPE_IPV4 + ip_in_ip { + dst_ip: "203.0.113.101" + src_ip: "ipv4_outer_src_222" + } + network_instance: "TE_VRF_222" +} +IPv4Entry {203.0.113.101/32 (TE_VRF_222)} -> NHG#4 (DEFAULT VRF) -> { + {NH#5, DEFAULT VRF, weight:1,ip_address=192.0.2.105}, + backup_next_hop_group: 1001 // decap to DEFAULT VRF +} +IPv4Entry {192.0.2.105/32 (DEFAULT VRF)} -> NHG#15 (DEFAULT VRF) -> { + {NH#16, DEFAULT VRF, weight:1,mac_address:magic_mac, interface-ref:dut-port-7-interface}, +} + +``` + +* Install a BGP route resolved by ISIS in default VRF to rout traffic out of DUT port-8. + +* Install an 0/0 static route in ENCAP_VRF_A and ENCAP_VRF_B pointing to the DEFAULT VRF. + +* Install an 0/0 ipv6 static route in ENCAP_VRF_A and ENCAP_VRF_B pointing to the DEFAULT VRF. + +## Procedure + +The DUT should be reset to the baseline after each of the following tests. + +#### Test-1, match on source and protocol, no match on DSCP; flow VRF_DECAP hit -> DEFAULT + +1. Using gRIBI to install the following entries in the `DECAP_TE_VRF`: + + ``` + IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#1001 (DEFAULT VRF) -> { + {NH#1001, DEFAULT VRF, weight:1} + } + NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + } + + ``` + +2. Apply vrf selection policy `vrf_selection_policy_w` to DUT port-1. + +3. Send the following 6in4 and 4in4 flows to DUT port-1: + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_no_match` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_no_match` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_no_match` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_no_match` + * proto: `41` + ``` + +4. Verify that the packets have their outer v4 header stripped and are forwarded out of DUT port-8 per the BGP-ISIS routes in the DEFAULT VRF. + +5. Verify that the TTL value is copied from the outer header to the inner header. + +6. Change the subnet mask from /24 and repeat the test for the masks /32, /22, and /28 and verify again that the packets are decapped and forwarded correctly. + +7. Repeat the test with packets with a destination address 203.0.113.1/32 that does not match the decap entry, and verify that such packets are not decapped. + +#### Test-2, match on source, protocol and DSCP, VRF_DECAP hit -> VRF_ENCAP_A miss -> DEFAULT + +1. Using gRIBI to install the following entries in the `DECAP_TE_VRF`: + + ``` + IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#1001 (DEFAULT VRF) -> { + {NH#1001, DEFAULT VRF, weight:1} + } + NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + } + ``` + +2. Apply vrf selection policy `vrf_selection_policy_w` to DUT port-1. + +3. Send the following 6in4 and 4in4 flows to DUT port-1: + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_no_match` + * dscp: `dscp_encap_a_1` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_a_1` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_no_match` + * dscp: `dscp_encap_a_1` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_a_1` + * proto: `41` + ``` + +4. Verify that the packets have their outer v4 header stripped and are forwarded out of DUT port-8 per the BGP-ISIS routes in the DEFAULT VRF. + +5. Verify that the TTL value is copied from the outer header to the inner header. + +6. Change the subnet mask from /24 and repeat the test for the masks /32, /22, and /28 and verify again that the packets are decapped and forwarded correctly. + +#### Test-3, Mixed Prefix Decap gRIBI Entries + +Support for decap actions with mixed prefixes installed through gRIBI + +1. Add the following gRIBI entries: + + ``` + IPv4Entry {192.51.128.0/22 (DECAP_TE_VRF)} -> NHG#1001 (DEFAULT VRF) -> { + {NH#1001, DEFAULT VRF, weight:1} + } + IPv4Entry {192.55.200.3/32 (DECAP_TE_VRF)} -> NHG#1001 (DEFAULT VRF) -> { + {NH#1001, DEFAULT VRF, weight:1} + } + + NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + } + ``` +2. Apply vrf selection policy `vrf_selection_policy_w` to DUT port-1. + +3. Send the following 6in4 and 4in4 flows to DUT port-1: + + ``` + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_no_match` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `192.55.200.3` + * dscp: `dscp_encap_no_match` + * proto: `41` + + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_no_match` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `192.51.128.5` + * dscp: `dscp_encap_no_match` + * proto: `4` + ``` + +4. Verify that the packets have their outer v4 header stripped, and are forwarded according to the route in the DEFAULT VRF that matches the inner IP address. + +5. Repeat the test with packets with a destination address 203.0.113.1/32 that does not match the decap route, and verify that such packets are not decapped. + +#### Test-4: Tunneled traffic with no decap + +Ensures that tunneled traffic is correctly forwarded when there is no match in the DECAP_VRF. The intent of this test is to ensure that the VRF selection policy correctly sends these packets to either `TE_VRF_111` or `TE_VRF_222`. + +1. Apply vrf selection policy `vrf_selection_policy_c` to DUT port-1. +2. Send 4in4 (IP protocol 4) and 6in4 (IP protocol 41) packets to DUT port-1 where + * The outer v4 header has the destination address 203.0.113.1. + * The outer v4 header has the source address ipv4_outer_src_111. + * The outer v4 header has DSCP value has `dscp_encap_no_match` and `dscp_encap_match` +3. We should expect that all egress packets (100%) are IPinIP encapped with 203.0.113.1 as the outer header, and egress on DUT port-2, port-3 and port-4 per the hierarchical weight. +4. Send 4in4 (IP protocol 4) and 6in4 (IP protocol 41) packets to DUT port-2 where + * The outer v4 header has the destination address 203.0.113.100. + * The outer v4 header has the source address ipv4_outer_src_222. + * The outer v4 header has DSCP value has `dscp_encap_no_match` and `dscp_encap_match` +We should expect that the egress traffic are 100% encapped with 203.0.113.100 as the outer header, and egress on DUT port-5. + +#### Test-5: match on "default term", send to default VRF + +Tests support for TE disabled IPinIP IPv4 (IP protocol 4) cluster traffic arriving on WAN facing ports. Specifically, this test verifies the tunnel traffic identification using ipv4_outer_src_111 and ipv4_outer_src_222 in the VRF selection policy. + +1. Apply vrf selection policy `vrf_selection_policy_w` to DUT port-1. +2. Send 6in4 and 4in4 packets to DUT port-1, where: + * The outer v4 header has the destination address 138.0.11.8. + * The outer v4 header has the source address that’s not ipv4_outer_src_111 or ipv4_outer_src_222. For example, we can use 198.100.200.123. +3. We should expect that all egress packets: + * 100% are still IPinIP (4in4) with outer v4 destination address as `138.0.11.8`. + * and, egressed out of DUT port-8 per the route in the DEFAULT VRF. +4. Send v4 packet with protocol `17` (not 6in4 or 4in4), where: + * The outer v4 header has the destination address 138.0.11.8. + * 50% of the packets with source address as ipv4_outer_src_111. + * 50% of the packets with source address as ipv4_outer_src_222. +5. We should expect that all egress packets: + * 100% are still of protocl `17` and with outer v4 destination address as `138.0.11.8`. + * and, egressed out of DUT port-8 per the route in the DEFAULT VRF. +6. Remove the matching route (e.g. stop the BGP routes) in the DEFAULT VRF and verify that the traffic are dropped. + +#### Test-6, decap then encap + +1. Using gRIBI to install the following entries in the `DECAP_TE_VRF`: + + ``` + IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#1001 (DEFAULT VRF) -> { + {NH#1001, DEFAULT VRF, weight:1} + } + NH#1001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + } + ``` + +2. Apply vrf selection policy `vrf_selection_policy_w` to DUT port-1. + +3. Send the following packets to DUT port-1: + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_a_1` + * outter_src: `ipv4_outter_src_222` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_a_1` + * proto: `4` + ``` + + ``` + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_a_1` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_a_1` + * proto: `41` + ``` + +4. We should expect that all egress packets: + + * are IPinIP encapped with outer source IP as `ipv4_outter_src_111` and dscp value `dscp_encap_a_1`. + * 1/4 are with 203.0.113.1 as the outer header destination IP. + * 3/4 are with 203.10.113.2 as the outer header destination IPs. + * egress on DUT port-2, port-3, port-4 and port-6 per the hierarchical weight. + +5. Send the following packets to DUT port -1 + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_b_1` + * outter_src: `ipv4_outter_src_111` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_b_1` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_b_1` + * outter_src: `ipv4_outter_src_222` + * outter_dst: `ipv4_outter_decap_match` + * dscp: `dscp_encap_b_1` + * proto: `41` + ``` + +6. We should expect that all egress packets: + + * are IPinIP encapped with outer source IP as `ipv4_outter_src_111` and dscp value `dscp_encap_b_1`. + * 3/4 are with 203.0.113.1 as the outer header destination IP. + * 1/4 are with 203.10.113.2 as the outer header destination IPs. + * egress on DUT port-2, port-3, port-4 and port-6 per the hierarchical weight. + + +## Config Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Telemetry Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Protocol/RPC Parameter Coverage + +* gRIBI: + * Modify + * ModifyRequest + +## Required DUT platform + +vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths + /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/protocol: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/source-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/protocol: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/source-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decap-network-instance: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/post-decap-network-instance: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decap-fallback-network-instance: + + ## State paths + /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/session-state: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: + 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 new file mode 100644 index 00000000000..4c73a0c21cd --- /dev/null +++ b/feature/gribi/otg_tests/vrf_policy_driven_te/metadata.textproto @@ -0,0 +1,55 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "944d8eb1-a5dd-46ea-b398-cadd9574d695" +plan_id: "TE-17.1" +description: "VRF selection policy driven TE" +testbed: TESTBED_DUT_ATE_8LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + gribi_mac_override_with_static_arp: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true + ttl_copy_unsupported: true + isis_single_topology_required: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + ttl_copy_unsupported: true + gribi_decap_mixed_plen_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + gnoi_subcomponent_path: true + interface_enabled: true + static_protocol_name: "STATIC" + default_network_instance: "default" + gribi_mac_override_static_arp_static_route: true + missing_isis_interface_afi_safi_enable: true + isis_interface_afi_unsupported: true + isis_instance_enabled_required: true + } +} 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 new file mode 100644 index 00000000000..e8ed9a01ae0 --- /dev/null +++ b/feature/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go @@ -0,0 +1,2282 @@ +// 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 vrf_policy_driven_te_test + +import ( + "context" + "fmt" + "log" + "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/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Settings for configuring the baseline testbed with the test +// topology. +// +// ATE port-1 <------> port-1 DUT +// DUT port-2 <------> port-2 ATE +// DUT port-3 <------> port-3 ATE +// DUT port-4 <------> port-4 ATE +// DUT port-5 <------> port-5 ATE +// DUT port-6 <------> port-6 ATE +// DUT port-7 <------> port-7 ATE +// DUT port-8 <------> port-8 ATE + +const ( + plenIPv4 = 30 + plenIPv6 = 126 + 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) +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + MAC: "02:00:01:01:01:01", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:2:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + MAC: "02:00:02:01:01:01", + IPv6: "2001:db8::192:2:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv6: "2001:db8::192:3:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort3 = attrs.Attributes{ + Name: "atePort3", + IPv4: "192.0.2.10", + MAC: "02:00:03:01:01:01", + IPv6: "2001:db8::192:3:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv6: "2001:db8::192:4:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort4 = attrs.Attributes{ + Name: "atePort4", + IPv4: "192.0.2.14", + MAC: "02:00:04:01:01:01", + IPv6: "2001:db8::192:4:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort5 = attrs.Attributes{ + Desc: "dutPort5", + IPv4: "192.0.2.17", + IPv6: "2001:db8::192:5:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort5 = attrs.Attributes{ + Name: "atePort5", + IPv4: "192.0.2.18", + MAC: "02:00:05:01:01:01", + IPv6: "2001:db8::192:5:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort6 = attrs.Attributes{ + Desc: "dutPort6", + IPv4: "192.0.2.21", + IPv6: "2001:db8::192:6:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort6 = attrs.Attributes{ + Name: "atePort6", + IPv4: "192.0.2.22", + MAC: "02:00:06:01:01:01", + IPv6: "2001:db8::192:6:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort7 = attrs.Attributes{ + Desc: "dutPort7", + IPv4: "192.0.2.25", + IPv6: "2001:db8::192:7:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort7 = attrs.Attributes{ + Name: "atePort7", + IPv4: "192.0.2.26", + MAC: "02:00:07:01:01:01", + IPv6: "2001:db8::192:7:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort8 = attrs.Attributes{ + Desc: "dutPort8", + IPv4: "192.0.2.29", + IPv6: "2001:db8::192:8:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort8 = attrs.Attributes{ + Name: "atePort8", + IPv4: "192.0.2.30", + MAC: "02:00:08:01:01:01", + IPv6: "2001:db8::192:8:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.11", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + dutPort2DummyIP = attrs.Attributes{ + Desc: "dutPort2", + IPv4Sec: "192.0.2.33", + IPv4LenSec: plenIPv4, + } + + otgPort2DummyIP = attrs.Attributes{ + Desc: "otgPort2", + IPv4: "192.0.2.34", + IPv4Len: plenIPv4, + } + + dutPort3DummyIP = attrs.Attributes{ + Desc: "dutPort3", + IPv4Sec: "192.0.2.37", + IPv4LenSec: plenIPv4, + } + + otgPort3DummyIP = attrs.Attributes{ + Desc: "otgPort3", + IPv4: "192.0.2.38", + IPv4Len: plenIPv4, + } + + dutPort4DummyIP = attrs.Attributes{ + Desc: "dutPort4", + IPv4Sec: "192.0.2.41", + IPv4LenSec: plenIPv4, + } + + otgPort4DummyIP = attrs.Attributes{ + Desc: "otgPort4", + IPv4: "192.0.2.42", + IPv4Len: plenIPv4, + } + + dutPort5DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.45", + IPv4LenSec: plenIPv4, + } + + otgPort5DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.46", + IPv4Len: plenIPv4, + } + dutPort6DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.49", + IPv4LenSec: plenIPv4, + } + + otgPort6DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.50", + IPv4Len: plenIPv4, + } + dutPort7DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.53", + IPv4LenSec: plenIPv4, + } + + otgPort7DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.54", + IPv4Len: plenIPv4, + } + loopbackIntfName string + // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 + // Below code will be uncommented once ixia issue is fixed. + //tolerance = 0.2 + bgp4Peer gosnappi.BgpV4Peer +) + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +type testArgs struct { + ctx context.Context + client *fluent.GRIBIClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgConfig gosnappi.Config + top gosnappi.Config + electionID gribi.Uint128 + otg *otg.OTG +} + +type policyFwRule struct { + SeqId uint32 + family string + protocol oc.UnionUint8 + dscpSet []uint8 + sourceAddr string + decapNi string + postDecapNi string + decapFallbackNi string + ni string +} + +func configureVrfSelectionPolicyW(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + dutPolFwdPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() + + pfRule1 := &policyFwRule{SeqId: 1, protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule2 := &policyFwRule{SeqId: 2, protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule3 := &policyFwRule{SeqId: 3, protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + pfRule4 := &policyFwRule{SeqId: 4, protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + + pfRule5 := &policyFwRule{SeqId: 5, protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule6 := &policyFwRule{SeqId: 6, protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule7 := &policyFwRule{SeqId: 7, protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + pfRule8 := &policyFwRule{SeqId: 8, protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + + pfRule9 := &policyFwRule{SeqId: 9, protocol: 4, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule10 := &policyFwRule{SeqId: 10, protocol: 41, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule11 := &policyFwRule{SeqId: 11, protocol: 4, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + pfRule12 := &policyFwRule{SeqId: 12, protocol: 41, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + + pfRuleList := []*policyFwRule{pfRule1, pfRule2, pfRule3, pfRule4, pfRule5, pfRule6, + pfRule7, pfRule8, pfRule9, pfRule10, pfRule11, pfRule12} + + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niP := ni.GetOrCreatePolicyForwarding() + niPf := niP.GetOrCreatePolicy(vrfPolW) + niPf.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + + for _, pfRule := range pfRuleList { + pfR := niPf.GetOrCreateRule(seqIDOffset(dut, pfRule.SeqId)) + pfRProtoIPv4 := pfR.GetOrCreateIpv4() + pfRProtoIPv4.Protocol = oc.UnionUint8(pfRule.protocol) + if pfRule.dscpSet != nil { + pfRProtoIPv4.DscpSet = pfRule.dscpSet + } + pfRProtoIPv4.SourceAddress = ygot.String(pfRule.sourceAddr) + pfRAction := pfR.GetOrCreateAction() + pfRAction.DecapNetworkInstance = ygot.String(pfRule.decapNi) + pfRAction.PostDecapNetworkInstance = ygot.String(pfRule.postDecapNi) + pfRAction.DecapFallbackNetworkInstance = ygot.String(pfRule.decapFallbackNi) + } + if deviations.PfRequireMatchDefaultRule(dut) { + pfR13 := niPf.GetOrCreateRule(seqIDOffset(dut, 13)) + pfR13.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4) + pfRAction := pfR13.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + pfR14 := niPf.GetOrCreateRule(seqIDOffset(dut, 14)) + pfR14.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6) + pfRAction = pfR14.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } else { + pfR := niPf.GetOrCreateRule(13) + pfRAction := pfR.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } + + p1 := dut.Port(t, "port1") + interfaceID := p1.Name() + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = interfaceID + ".0" + } + + intf := niP.GetOrCreateInterface(interfaceID) + intf.ApplyVrfSelectionPolicy = ygot.String(vrfPolW) + intf.GetOrCreateInterfaceRef().Interface = ygot.String(p1.Name()) + intf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + intf.InterfaceRef = nil + } + gnmi.Replace(t, dut, dutPolFwdPath.Config(), niP) +} + +func configureVrfSelectionPolicyC(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + dutPolFwdPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() + + pfRule1 := &policyFwRule{SeqId: 1, family: "ipv4", protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule2 := &policyFwRule{SeqId: 2, family: "ipv4", protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf222} + pfRule3 := &policyFwRule{SeqId: 3, family: "ipv4", protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + pfRule4 := &policyFwRule{SeqId: 4, family: "ipv4", protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfA, decapFallbackNi: niTeVrf111} + + pfRule5 := &policyFwRule{SeqId: 5, family: "ipv4", protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule6 := &policyFwRule{SeqId: 6, family: "ipv4", protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf222} + pfRule7 := &policyFwRule{SeqId: 7, family: "ipv4", protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + pfRule8 := &policyFwRule{SeqId: 8, family: "ipv4", protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niEncapTeVrfB, decapFallbackNi: niTeVrf111} + + pfRule9 := &policyFwRule{SeqId: 9, family: "ipv4", protocol: 4, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule10 := &policyFwRule{SeqId: 10, family: "ipv4", protocol: 41, sourceAddr: ipv4OuterSrc222WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf222} + pfRule11 := &policyFwRule{SeqId: 11, family: "ipv4", protocol: 4, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + pfRule12 := &policyFwRule{SeqId: 12, family: "ipv4", protocol: 41, sourceAddr: ipv4OuterSrc111WithMask, + decapNi: niDecapTeVrf, postDecapNi: niDefault, decapFallbackNi: niTeVrf111} + + pfRule13 := &policyFwRule{SeqId: 13, family: "ipv4", dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + ni: niEncapTeVrfA} + pfRule14 := &policyFwRule{SeqId: 14, family: "ipv6", dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + ni: niEncapTeVrfA} + pfRule15 := &policyFwRule{SeqId: 15, family: "ipv4", dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + ni: niEncapTeVrfB} + pfRule16 := &policyFwRule{SeqId: 16, family: "ipv6", dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, + ni: niEncapTeVrfB} + + pfRuleList := []*policyFwRule{pfRule1, pfRule2, pfRule3, pfRule4, pfRule5, pfRule6, + pfRule7, pfRule8, pfRule9, pfRule10, pfRule11, pfRule12, pfRule13, pfRule14, + pfRule15, pfRule16} + + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niP := ni.GetOrCreatePolicyForwarding() + niPf := niP.GetOrCreatePolicy(vrfPolC) + niPf.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + + for _, pfRule := range pfRuleList { + pfR := niPf.GetOrCreateRule(seqIDOffset(dut, pfRule.SeqId)) + + if pfRule.family == "ipv4" { + pfRProtoIP := pfR.GetOrCreateIpv4() + if pfRule.protocol != 0 { + pfRProtoIP.Protocol = oc.UnionUint8(pfRule.protocol) + } + if pfRule.sourceAddr != "" { + pfRProtoIP.SourceAddress = ygot.String(pfRule.sourceAddr) + } + if pfRule.dscpSet != nil { + pfRProtoIP.DscpSet = pfRule.dscpSet + } + } else if pfRule.family == "ipv6" { + pfRProtoIP := pfR.GetOrCreateIpv6() + if pfRule.dscpSet != nil { + pfRProtoIP.DscpSet = pfRule.dscpSet + } + } + + pfRAction := pfR.GetOrCreateAction() + if pfRule.decapNi != "" { + pfRAction.DecapNetworkInstance = ygot.String(pfRule.decapNi) + } + if pfRule.postDecapNi != "" { + pfRAction.PostDecapNetworkInstance = ygot.String(pfRule.postDecapNi) + } + if pfRule.decapFallbackNi != "" { + pfRAction.DecapFallbackNetworkInstance = ygot.String(pfRule.decapFallbackNi) + } + if pfRule.ni != "" { + pfRAction.NetworkInstance = ygot.String(pfRule.ni) + } + } + + if deviations.PfRequireMatchDefaultRule(dut) { + pfR17 := niPf.GetOrCreateRule(seqIDOffset(dut, 17)) + pfR17.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4) + pfRAction := pfR17.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + pfR18 := niPf.GetOrCreateRule(seqIDOffset(dut, 18)) + pfR18.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6) + pfRAction = pfR18.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } else { + pfR := niPf.GetOrCreateRule(17) + pfRAction := pfR.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } + + p1 := dut.Port(t, "port1") + interfaceID := p1.Name() + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = interfaceID + ".0" + } + intf := niP.GetOrCreateInterface(interfaceID) + intf.ApplyVrfSelectionPolicy = ygot.String(vrfPolC) + intf.GetOrCreateInterfaceRef().Interface = ygot.String(p1.Name()) + intf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + intf.InterfaceRef = nil + } + gnmi.Replace(t, dut, dutPolFwdPath.Config(), niP) +} + +// configStaticArp configures static arp entries +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 +} + +func staticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + portList := []*ondatra.Port{p2, p3, p4, p5, p6, p7} + for idx, p := range portList { + s := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(magicIp + "/32"), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + strconv.Itoa(idx): { + Index: ygot.String(strconv.Itoa(idx)), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(p.Name()), + }, + }, + }, + } + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.Update(t, dut, sp.Static(magicIp+"/32").Config(), s) + gnmi.Update(t, dut, gnmi.OC().Interface(p.Name()).Config(), configStaticArp(p.Name(), magicIp, magicMac)) + } +} + +// staticARPWithSpecificIP configures secondary IPs and static ARP. +func staticARPWithSpecificIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2DummyIP.NewOCInterface(p2.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), dutPort3DummyIP.NewOCInterface(p3.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), dutPort4DummyIP.NewOCInterface(p4.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), dutPort5DummyIP.NewOCInterface(p5.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p6.Name()).Config(), dutPort6DummyIP.NewOCInterface(p6.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p7.Name()).Config(), dutPort7DummyIP.NewOCInterface(p7.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), configStaticArp(p2.Name(), otgPort2DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), configStaticArp(p3.Name(), otgPort3DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), configStaticArp(p4.Name(), otgPort4DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), configStaticArp(p5.Name(), otgPort5DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p6.Name()).Config(), configStaticArp(p6.Name(), otgPort6DummyIP.IPv4, magicMac)) + gnmi.Update(t, dut, gnmi.OC().Interface(p7.Name()).Config(), configStaticArp(p7.Name(), otgPort7DummyIP.IPv4, magicMac)) +} + +// seqIDOffset returns sequence ID offset added with seqIDBase (10), to avoid sequences +// like 1, 10, 11, 12,..., 2, 21, 22, ... while being sent by Ondatra to the DUT. +// It now generates sequences like 11, 12, 13, ..., 19, 20, 21,..., 99. +func seqIDOffset(dut *ondatra.DUTDevice, i uint32) uint32 { + if deviations.PfRequireSequentialOrderPbrRules(dut) { + return i + seqIDBase + } + return i +} + +// configureNetworkInstance configures vrfs DECAP_TE_VRF,ENCAP_TE_VRF_A,ENCAP_TE_VRF_B, +// TE_VRF_222, TE_VRF_111. +func configNonDefaultNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + c := &oc.Root{} + vrfs := []string{niDecapTeVrf, niEncapTeVrfA, niEncapTeVrfB, niTeVrf222, niTeVrf111} + for _, vrf := range vrfs { + ni := c.GetOrCreateNetworkInstance(vrf) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), ni) + } +} + +// configureDUT configures port1-8 on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := gnmi.OC() + + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + p8 := dut.Port(t, "port8") + portList := []*ondatra.Port{p1, p2, p3, p4, p5, p6, p7, p8} + portNameList := []string{"port1", "port2", "port3", "port4", "port5", "port6", "port7", "port8"} + + for idx, a := range []attrs.Attributes{dutPort1, dutPort2, dutPort3, dutPort4, dutPort5, dutPort6, dutPort7, dutPort8} { + p := portList[idx] + intf := a.NewOCInterface(p.Name(), dut) + gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), intf) + } + + // Configure loopback interface. + loopbackIntfName = netutil.LoopbackInterface(t, dut, 0) + lo0 := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + loop1 := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, d.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) + } + + for _, p := range portList { + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + } + for _, pName := range portNameList { + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, pName)) + } + } + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + staticARPWithMagicUniversalIP(t, dut) + } else if deviations.GRIBIMACOverrideWithStaticARP(dut) { + staticARPWithSpecificIP(t, dut) + } +} + +func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfList []string, dutAreaAddress, dutSysID string) { + t.Helper() + d := &oc.Root{} + dutConfIsisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISSingleTopologyRequired(dut) { + afv6 := globalISIS.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) + } + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + if deviations.ISISLevelEnabled(dut) { + isisLevel2.Enabled = ygot.Bool(true) + } + for _, intfName := range intfList { + isisIntf := isis.GetOrCreateInterface(intfName) + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv4.Metric = ygot.Uint32(200) + isisIntfLevelAfiv4.Enabled = ygot.Bool(true) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv6.Metric = ygot.Uint32(200) + isisIntfLevelAfiv6.Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfiv4.Enabled = nil + isisIntfLevelAfiv6.Enabled = nil + } + } + gnmi.Replace(t, dut, dutConfIsisPath.Config(), prot) +} + +func bgpCreateNbr(localAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutlo0Attrs.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) + pg1.PeerAs = ygot.Uint32(localAs) + + bgpNbr := bgp.GetOrCreateNeighbor(otgIsisPort8LoopV4) + bgpNbr.PeerGroup = ygot.String(peerGrpName1) + bgpNbr.PeerAs = ygot.Uint32(localAs) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbrT := bgpNbr.GetOrCreateTransport() + localAddressLeaf := dutlo0Attrs.IPv4 + if dut.Vendor() == ondatra.CISCO { + localAddressLeaf = loopbackIntfName + } + bgpNbrT.LocalAddress = ygot.String(localAddressLeaf) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + + return niProto +} + +func verifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntf string) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + dutIntf = dutIntf + ".0" + } + nbrPath := statePath.Interface(dutIntf) + query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", dutIntf) + t.Fatal("No IS-IS adjacencies reported.") + } +} + +func createFlow(flowValues *flowArgs) gosnappi.Flow { + flow := gosnappi.NewFlow().SetName(flowValues.flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{"atePort1.IPv4"}) + flow.TxRx().Device().SetRxNames([]string{"atePort2.IPv4", "atePort3.IPv4", "atePort4.IPv4", + "atePort5.IPv4", "atePort6.IPv4", "atePort7.IPv4", "atePort8.IPv4"}) + flow.Size().SetFixed(512) + flow.Rate().SetPps(100) + flow.Duration().Continuous() + flow.Packet().Add().Ethernet().Src().SetValue(atePort1.MAC) + // Outer IP header + 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) + } + if flowValues.udp { + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + + if flowValues.proto != 0 { + innerIpHdr := flow.Packet().Add().Ipv4() + innerIpHdr.Protocol().SetValue(flowValues.proto) + innerIpHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIpHdr.Dst().SetValue(flowValues.InnHdrDstIP) + } else { + if flowValues.isInnHdrV4 { + 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) + // } + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } else { + innerIpv6Hdr := flow.Packet().Add().Ipv6() + innerIpv6Hdr.Src().SetValue(flowValues.InnHdrSrcIPv6) + innerIpv6Hdr.Dst().SetValue(flowValues.InnHdrDstIPv6) + // 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) + // } + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + } + return flow +} + +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + nbrPath := bgpPath.Neighbor(otgIsisPort8LoopV4) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", otgIsisPort8LoopV4, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", otgIsisPort8LoopV4, state, want) + } +} + +func programAftWithMagicIp(t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port2").Name()). + WithIPAddress(magicIp), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port3").Name()). + WithIPAddress(magicIp), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF1+"/"+maskLen32).WithNextHopGroup(11), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port4").Name()). + WithIPAddress(magicIp), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF2+"/"+maskLen32).WithNextHopGroup(12), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port5").Name()). + WithIPAddress(magicIp), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF3+"/"+maskLen32).WithNextHopGroup(13), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port6").Name()). + WithIPAddress(magicIp), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF4+"/"+maskLen32).WithNextHopGroup(14), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port7").Name()). + WithIPAddress(magicIp), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF5+"/"+maskLen32).WithNextHopGroup(15), + ) +} + +func programGRIBIWithDummyIP(t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port2").Name()). + WithIPAddress(otgPort2DummyIP.IPv4), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port3").Name()). + WithIPAddress(otgPort3DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF1+"/"+maskLen32).WithNextHopGroup(11), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port4").Name()). + WithIPAddress(otgPort4DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF2+"/"+maskLen32).WithNextHopGroup(12), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port5").Name()). + WithIPAddress(otgPort5DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF3+"/"+maskLen32).WithNextHopGroup(13), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port6").Name()). + WithIPAddress(otgPort6DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF4+"/"+maskLen32).WithNextHopGroup(14), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port7").Name()). + WithIPAddress(otgPort7DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF5+"/"+maskLen32).WithNextHopGroup(15), + ) +} + +func configGribiBaselineAFT(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + programAftWithMagicIp(t, dut, args) + } else if deviations.GRIBIMACOverrideWithStaticARP(dut) { + programGRIBIWithDummyIP(t, dut, args) + } else { + // Programming AFT entries for prefixes in DEFAULT VRF + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port2").Name()), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port3").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF1+"/"+maskLen32).WithNextHopGroup(11), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port4").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF2+"/"+maskLen32).WithNextHopGroup(12), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port5").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF3+"/"+maskLen32).WithNextHopGroup(13), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port6").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF4+"/"+maskLen32).WithNextHopGroup(14), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMac).WithInterfaceRef(dut.Port(t, "port7").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF5+"/"+maskLen32).WithNextHopGroup(15), + ) + } + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList := []string{gribiIPv4EntryDefVRF1, gribiIPv4EntryDefVRF2, gribiIPv4EntryDefVRF3, gribiIPv4EntryDefVRF4, gribiIPv4EntryDefVRF5} + for ip := range defaultVRFIPList { + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for backup NHG + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1000).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222, gribiIPv4EntryVRF2221). + WithNextHopNetworkInstance(niTeVrf222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1000, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1001).WithDecapsulateHeader(fluent.IPinIP). + WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1001).AddNextHop(1001, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1002).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222, gribiIPv4EntryVRF2222). + WithNextHopNetworkInstance(niTeVrf222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1002).AddNextHop(1002, 1), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in TE_VRF_222 + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3).WithIPAddress(gribiIPv4EntryDefVRF3), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2).AddNextHop(3, 1).WithBackupNHG(1001), + fluent.IPv4Entry().WithNetworkInstance(niTeVrf222). + WithPrefix(gribiIPv4EntryVRF2221+"/"+maskLen32).WithNextHopGroup(2). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(5).WithIPAddress(gribiIPv4EntryDefVRF5), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(4).AddNextHop(5, 1).WithBackupNHG(1001), + fluent.IPv4Entry().WithNetworkInstance(niTeVrf222). + WithPrefix(gribiIPv4EntryVRF2222+"/"+maskLen32).WithNextHopGroup(4). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF222IPList := []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF2222} + for ip := range teVRF222IPList { + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF222IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for prefixes in TE_VRF_111 + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1).WithIPAddress(gribiIPv4EntryDefVRF1), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2).WithIPAddress(gribiIPv4EntryDefVRF2), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1).AddNextHop(1, 1).AddNextHop(2, 3).WithBackupNHG(1000), + fluent.IPv4Entry().WithNetworkInstance(niTeVrf111). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(4).WithIPAddress(gribiIPv4EntryDefVRF4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3).AddNextHop(4, 1).WithBackupNHG(1002), + fluent.IPv4Entry().WithNetworkInstance(niTeVrf111). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(3). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF111IPList := []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112} + for ip := range teVRF111IPList { + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF111IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for prefixes in ENCAP_TE_VRF_A + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(200).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(200).AddNextHop(200, 1), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(101).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111, gribiIPv4EntryVRF1111). + WithNextHopNetworkInstance(niTeVrf111), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(102).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111, gribiIPv4EntryVRF1112). + WithNextHopNetworkInstance(niTeVrf111), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(101).AddNextHop(101, 1).AddNextHop(102, 3).WithBackupNHG(200), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(101). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(101). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv6Operation(gribiIPv6EntryEncapVRF+"/126"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + args.client.Modify().AddEntry(t, + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(102).AddNextHop(101, 3).AddNextHop(102, 1).WithBackupNHG(200), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv6Operation(gribiIPv6EntryEncapVRF+"/126"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + // Install an 0/0 static 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)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(60).AddNextHop(60, 1), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix("0.0.0.0/0").WithNextHopGroup(60). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(65).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(65).AddNextHop(65, 1), + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix("::/0").WithNextHopGroup(65). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv4Operation("0.0.0.0/0").WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv6Operation("::/0").WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) + + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(61).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(61).AddNextHop(61, 1), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix("0.0.0.0/0").WithNextHopGroup(61). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(66).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(66).AddNextHop(66, 1), + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix("::/0").WithNextHopGroup(66). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv4Operation("0.0.0.0/0").WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv6Operation("::/0").WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) +} + +func configureGribiRoute(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs, prefWithMask string) { + t.Helper() + // Using gRIBI, install an IPv4Entry for the prefix 192.51.100.1/24 that points to a + // NextHopGroup that contains a single NextHop that specifies decapsulating the IPv4 + // header and specifies the DEFAULT network instance.This IPv4Entry should be installed + // into the DECAP_TE_VRF. + + args.client.Modify().AddEntry(t, + fluent.IPv4Entry().WithNetworkInstance(niDecapTeVrf). + WithPrefix(prefWithMask).WithNextHopGroup(1001). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv4Operation(prefWithMask).WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) +} + +func configureGribiMixedPrefEntries(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs, prefList []string) { + t.Helper() + + for _, pref := range prefList { + args.client.Modify().AddEntry(t, + fluent.IPv4Entry().WithNetworkInstance(niDecapTeVrf). + WithPrefix(pref).WithNextHopGroup(1001). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + } + + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + for _, pref := range prefList { + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv4Operation(pref).WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) + } +} + +func configureOTG(t *testing.T, otg *otg.OTG, ate *ondatra.ATEDevice) gosnappi.Config { + t.Helper() + config := gosnappi.NewConfig() + + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + port3 := config.Ports().Add().SetName("port3") + port4 := config.Ports().Add().SetName("port4") + port5 := config.Ports().Add().SetName("port5") + port6 := config.Ports().Add().SetName("port6") + port7 := config.Ports().Add().SetName("port7") + port8 := config.Ports().Add().SetName("port8") + + pmd100GFRPorts := []string{} + for _, p := range config.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 := config.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + iDut3Dev := config.Devices().Add().SetName(atePort3.Name) + iDut3Eth := iDut3Dev.Ethernets().Add().SetName(atePort3.Name + ".Eth").SetMac(atePort3.MAC) + iDut3Eth.Connection().SetPortName(port3.Name()) + iDut3Ipv4 := iDut3Eth.Ipv4Addresses().Add().SetName(atePort3.Name + ".IPv4") + iDut3Ipv4.SetAddress(atePort3.IPv4).SetGateway(dutPort3.IPv4).SetPrefix(uint32(atePort3.IPv4Len)) + iDut3Ipv6 := iDut3Eth.Ipv6Addresses().Add().SetName(atePort3.Name + ".IPv6") + iDut3Ipv6.SetAddress(atePort3.IPv6).SetGateway(dutPort3.IPv6).SetPrefix(uint32(atePort3.IPv6Len)) + + iDut4Dev := config.Devices().Add().SetName(atePort4.Name) + iDut4Eth := iDut4Dev.Ethernets().Add().SetName(atePort4.Name + ".Eth").SetMac(atePort4.MAC) + iDut4Eth.Connection().SetPortName(port4.Name()) + iDut4Ipv4 := iDut4Eth.Ipv4Addresses().Add().SetName(atePort4.Name + ".IPv4") + iDut4Ipv4.SetAddress(atePort4.IPv4).SetGateway(dutPort4.IPv4).SetPrefix(uint32(atePort4.IPv4Len)) + iDut4Ipv6 := iDut4Eth.Ipv6Addresses().Add().SetName(atePort4.Name + ".IPv6") + iDut4Ipv6.SetAddress(atePort4.IPv6).SetGateway(dutPort4.IPv6).SetPrefix(uint32(atePort4.IPv6Len)) + + iDut5Dev := config.Devices().Add().SetName(atePort5.Name) + iDut5Eth := iDut5Dev.Ethernets().Add().SetName(atePort5.Name + ".Eth").SetMac(atePort5.MAC) + iDut5Eth.Connection().SetPortName(port5.Name()) + iDut5Ipv4 := iDut5Eth.Ipv4Addresses().Add().SetName(atePort5.Name + ".IPv4") + iDut5Ipv4.SetAddress(atePort5.IPv4).SetGateway(dutPort5.IPv4).SetPrefix(uint32(atePort5.IPv4Len)) + iDut5Ipv6 := iDut5Eth.Ipv6Addresses().Add().SetName(atePort5.Name + ".IPv6") + iDut5Ipv6.SetAddress(atePort5.IPv6).SetGateway(dutPort5.IPv6).SetPrefix(uint32(atePort5.IPv6Len)) + + iDut6Dev := config.Devices().Add().SetName(atePort6.Name) + iDut6Eth := iDut6Dev.Ethernets().Add().SetName(atePort6.Name + ".Eth").SetMac(atePort6.MAC) + iDut6Eth.Connection().SetPortName(port6.Name()) + iDut6Ipv4 := iDut6Eth.Ipv4Addresses().Add().SetName(atePort6.Name + ".IPv4") + iDut6Ipv4.SetAddress(atePort6.IPv4).SetGateway(dutPort6.IPv4).SetPrefix(uint32(atePort6.IPv4Len)) + iDut6Ipv6 := iDut6Eth.Ipv6Addresses().Add().SetName(atePort6.Name + ".IPv6") + iDut6Ipv6.SetAddress(atePort6.IPv6).SetGateway(dutPort6.IPv6).SetPrefix(uint32(atePort6.IPv6Len)) + + iDut7Dev := config.Devices().Add().SetName(atePort7.Name) + iDut7Eth := iDut7Dev.Ethernets().Add().SetName(atePort7.Name + ".Eth").SetMac(atePort7.MAC) + iDut7Eth.Connection().SetPortName(port7.Name()) + iDut7Ipv4 := iDut7Eth.Ipv4Addresses().Add().SetName(atePort7.Name + ".IPv4") + iDut7Ipv4.SetAddress(atePort7.IPv4).SetGateway(dutPort7.IPv4).SetPrefix(uint32(atePort7.IPv4Len)) + iDut7Ipv6 := iDut7Eth.Ipv6Addresses().Add().SetName(atePort7.Name + ".IPv6") + iDut7Ipv6.SetAddress(atePort7.IPv6).SetGateway(dutPort7.IPv6).SetPrefix(uint32(atePort7.IPv6Len)) + + iDut8Dev := config.Devices().Add().SetName(atePort8.Name) + iDut8Eth := iDut8Dev.Ethernets().Add().SetName(atePort8.Name + ".Eth").SetMac(atePort8.MAC) + iDut8Eth.Connection().SetPortName(port8.Name()) + iDut8Ipv4 := iDut8Eth.Ipv4Addresses().Add().SetName(atePort8.Name + ".IPv4") + iDut8Ipv4.SetAddress(atePort8.IPv4).SetGateway(dutPort8.IPv4).SetPrefix(uint32(atePort8.IPv4Len)) + iDut8Ipv6 := iDut8Eth.Ipv6Addresses().Add().SetName(atePort8.Name + ".IPv6") + iDut8Ipv6.SetAddress(atePort8.IPv6).SetGateway(dutPort8.IPv6).SetPrefix(uint32(atePort8.IPv6Len)) + // Configure Loopback on port8. + iDut8LoopV4 := iDut8Dev.Ipv4Loopbacks().Add().SetName("Port8LoopV4").SetEthName(iDut8Eth.Name()) + iDut8LoopV4.SetAddress(otgIsisPort8LoopV4) + iDut8LoopV6 := iDut8Dev.Ipv6Loopbacks().Add().SetName("Port8LoopV6").SetEthName(iDut8Eth.Name()) + iDut8LoopV6.SetAddress(otgIsisPort8LoopV6) + + // Enable ISIS and BGP Protocols on port 8. + isisDut := iDut8Dev.Isis().SetName("ISIS1").SetSystemId(otgSysID1) + isisDut.Basic().SetIpv4TeRouterId(atePort8.IPv4).SetHostname(isisDut.Name()).SetLearnedLspFilter(true) + isisDut.Interfaces().Add().SetEthName(iDut8Dev.Ethernets().Items()[0].Name()). + SetName("devIsisInt1"). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + // Advertise OTG Port8 loopback address via ISIS. + isisPort2V4 := iDut8Dev.Isis().V4Routes().Add().SetName("ISISPort8V4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(otgIsisPort8LoopV4).SetPrefix(32) + isisPort2V6 := iDut8Dev.Isis().V6Routes().Add().SetName("ISISPort8V6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(otgIsisPort8LoopV6).SetPrefix(uint32(128)) + + iDutBgp := iDut8Dev.Bgp().SetRouterId(otgIsisPort8LoopV4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(iDut8LoopV4.Name()).Peers().Add().SetName(atePort8.Name + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(dutlo0Attrs.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.Capability().SetIpv4Unicast(true).SetIpv6Unicast(true) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgp4Peer = iDutBgp4Peer + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(atePort8.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(otgIsisPort8LoopV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32). + SetCount(1).SetStep(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDstNoEncap).SetPrefix(32). + SetCount(1).SetStep(1) + + bgpNeti1Bgp6PeerRoutes := iDutBgp4Peer.V6Routes().Add().SetName(atePort8.Name + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(otgIsisPort8LoopV6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDst).SetPrefix(128). + SetCount(1).SetStep(1) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDstNoEncap).SetPrefix(128). + SetCount(1).SetStep(1) + + t.Logf("Pushing config to ATE and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + return config +} + +func updateBgpRoutes(t *testing.T, args *testArgs, deleteRoute bool) { + t.Helper() + config := args.otgConfig + if deleteRoute { + bgp4Peer.V4Routes().Clear() + } else { + bgpNeti1Bgp4PeerRoutes := bgp4Peer.V4Routes().Add().SetName(atePort8.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(otgIsisPort8LoopV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32). + SetCount(1).SetStep(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDstNoEncap).SetPrefix(32). + SetCount(1).SetStep(1) + } + args.otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) +} + +func sendTraffic(t *testing.T, args *testArgs, capturePortList []string, flowList []gosnappi.Flow) { + t.Helper() + + args.otgConfig.Flows().Clear() + for _, flow := range flowList { + args.otgConfig.Flows().Append(flow) + } + + args.otgConfig.Captures().Clear() + args.otgConfig.Captures().Add().SetName("packetCapture"). + SetPortNames(capturePortList). + SetFormat(gosnappi.CaptureFormat.PCAP) + + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(60 * time.Second) + + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + args.otg.SetControlState(t, cs) + + t.Logf("Starting traffic") + args.otg.StartTraffic(t) + time.Sleep(15 * time.Second) + t.Logf("Stop traffic") + args.otg.StopTraffic(t) + + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + args.otg.SetControlState(t, cs) +} + +func verifyTraffic(t *testing.T, args *testArgs, flowList []string, wantLoss bool) { + t.Helper() + for _, flowName := range flowList { + t.Logf("Verifying flow metrics for the flow %s\n", flowName) + recvMetric := gnmi.Get(t, args.otg, gnmi.OTG().Flow(flowName).State()) + txPackets := recvMetric.GetCounters().GetOutPkts() + rxPackets := recvMetric.GetCounters().GetInPkts() + + lostPackets := txPackets - rxPackets + var lossPct uint64 + if txPackets != 0 { + lossPct = lostPackets * 100 / txPackets + } else { + t.Errorf("Traffic stats are not correct %v", recvMetric) + } + if wantLoss { + if lossPct < 100-tolerancePct { + t.Errorf("Traffic is expected to fail %s\n got %v, want 100%% failure", flowName, lossPct) + } else { + t.Logf("Traffic Loss Test Passed!") + } + } else { + if lossPct > tolerancePct { + t.Errorf("Traffic Loss Pct for Flow: %s\n got %v, want 0", flowName, lossPct) + } else { + t.Logf("Traffic Test Passed!") + } + } + } +} + +type packetValidation struct { + portName string + outDstIP []string + inHdrIP string + validateDecap bool + validateTTL bool + validateNoDecap bool + validateEncap bool +} + +func captureAndValidatePackets(t *testing.T, args *testArgs, packetVal *packetValidation) { + 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) + } + if _, err := f.Write(bytes); err != nil { + t.Fatalf("ERROR: 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) + } + if packetVal.validateDecap { + validateTrafficDecap(t, packetSource) + } + if packetVal.validateNoDecap { + validateTrafficNonDecap(t, packetSource, packetVal.outDstIP[0], packetVal.inHdrIP) + } + if packetVal.validateEncap { + validateTrafficEncap(t, packetSource, 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) { + t.Helper() + dut := ondatra.DUT(t, "dut") + var packetCheckCount uint32 = 0 + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer != nil && packetCheckCount <= 3 { + packetCheckCount++ + ipPacket, _ := ipLayer.(*layers.IPv4) + if !deviations.TTLCopyUnsupported(dut) { + if ipPacket.TTL != correspondingTTL { + t.Errorf("IP TTL value is altered to: %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.") + } + if ipv6InnerLayer != nil { + t.Errorf("Packets are not decapped, Inner IPv6 header is not removed.") + } + } + } +} + +func validateTrafficDecap(t *testing.T, packetSource *gopacket.PacketSource) { + t.Helper() + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + 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 ipv6InnerLayer != nil { + t.Errorf("Packets are not decapped, Inner IPv6 header is not removed.") + } + } +} + +func validateTrafficNonDecap(t *testing.T, packetSource *gopacket.PacketSource, outDstIP, inHdrIP string) { + t.Helper() + t.Log("Validate traffic non decap routes") + var packetCheckCount uint32 = 1 + for packet := range packetSource.Packets() { + if packetCheckCount >= 5 { + break + } + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket, _ := ipLayer.(*layers.IPv4) + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipInnerLayer != nil { + if ipPacket.DstIP.String() != outDstIP { + t.Errorf("Negatice test for Decap failed. Traffic sent to route which does not match the decap route are decaped") + } + ipInnerPacket, _ := ipInnerLayer.(*layers.IPv4) + 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.") + break + } + } +} + +func validateTrafficEncap(t *testing.T, packetSource *gopacket.PacketSource, outDstIP []string, innerIP string) { + t.Helper() + t.Log("Validate traffic non decap routes") + var packetCheckCount uint32 = 1 + for packet := range packetSource.Packets() { + if packetCheckCount >= 5 { + break + } + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket, _ := ipLayer.(*layers.IPv4) + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipInnerLayer != nil { + if len(outDstIP) == 2 { + if ipPacket.DstIP.String() != outDstIP[0] || ipPacket.DstIP.String() != outDstIP[1] { + t.Errorf("Packets are not encapsulated as expected") + } + } else { + if ipPacket.DstIP.String() != outDstIP[0] { + t.Errorf("Packets are not encapsulated as expected") + } + } + t.Logf("Traffic for encap routes passed.") + break + } + } +} + +// TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 +// Below code will be uncommented once ixia issue is fixed. + +// normalize normalizes the input values so that the output values sum +// to 1.0 but reflect the proportions of the input. For example, +// input [1, 2, 3, 4] is normalized to [0.1, 0.2, 0.3, 0.4]. +/* func normalize(xs []uint64) (ys []float64, sum uint64) { + for _, x := range xs { + sum += x + } + ys = make([]float64, len(xs)) + for i, x := range xs { + ys[i] = float64(x) / float64(sum) + } + return ys, sum +} */ + +// TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 +// Below code will be uncommented once ixia issue is fixed. + +/* func validateTrafficDistribution(t *testing.T, ate *ondatra.ATEDevice, wantWeights []float64) { + inFramesAllPorts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().PortAny().Counters().InFrames().State()) + // Skip source port, Port1. + gotWeights, _ := normalize(inFramesAllPorts[1:]) + + t.Log("got ratio:", gotWeights) + t.Log("want ratio:", wantWeights) + if diff := cmp.Diff(wantWeights, gotWeights, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } +} */ + +type flowArgs struct { + flowName string + outHdrSrcIP, outHdrDstIP string + 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 +} + +// testGribiDecapMatchSrcProtoNoMatchDSCP is to validate subtest test1. +// Test-1 match on source and protocol no match on DSCP; flow VRF_DECAP hit -> DEFAULT +func testGribiDecapMatchSrcProtoNoMatchDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + cases := []struct { + desc string + prefixWithMask string + }{{ + desc: "Mask Length 24", + prefixWithMask: "192.51.100.0/24", + }, { + desc: "Mask Length 32", + prefixWithMask: "192.51.100.64/32", + }, { + desc: "Mask Length 28", + prefixWithMask: "192.51.100.64/28", + }, { + desc: "Mask Length 22", + prefixWithMask: "192.51.100.0/22", + }} + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + configGribiBaselineAFT(ctx, t, dut, args) + + t.Run("Program gRIBi route", func(t *testing.T) { + configureGribiRoute(ctx, t, dut, args, tc.prefixWithMask) + }) + // Send both 6in4 and 4in4 packets. Verify that the packets have their outer + // v4 header stripped and are forwarded according to the route in the DEFAULT + // VRF that matches the inner IP address. + portList := []string{"port8"} + //dstPorts := []attrs.Attributes{atePort2, atePort3, atePort4, atePort5, atePort6, atePort7, atePort8} + t.Run("Create ip-in-ip and ipv6-in-ip flows, send traffic and verify decap functionality", + func(t *testing.T) { + + flow1 := createFlow(&flowArgs{flowName: flow4in4, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}) + + flow2 := createFlow(&flowArgs{flowName: flow6in4, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, InnHdrSrcIPv6: atePort1.IPv6, + InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false, outHdrDscp: []uint32{dscpEncapNoMatch}}) + + 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}) + }) + + // Test with packets with a destination address that does not match + // the decap route, and verify that such packets are not decapped. + portList = []string{"port4"} + t.Run("Send traffic to non decap route and verify the behavior", + func(t *testing.T) { + flow3 := createFlow(&flowArgs{flowName: flowNegTest, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: gribiIPv4EntryVRF1111, isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst}) + sendTraffic(t, args, portList, []gosnappi.Flow{flow3}) + verifyTraffic(t, args, []string{flowNegTest}, !wantLoss) + captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], + outDstIP: []string{gribiIPv4EntryVRF1111}, inHdrIP: ipv4InnerDst, validateNoDecap: true}) + }) + }) + } +} + +// testGribiDecapMatchSrcProtoDSCP is to validate subtest 2. +// Test-2, match on source, protocol and DSCP, VRF_DECAP hit -> VRF_ENCAP_A miss -> DEFAULT +func testGribiDecapMatchSrcProtoDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + cases := []struct { + desc string + prefixWithMask string + }{{ + desc: "Mask Length 24", + prefixWithMask: "192.51.100.0/24", + }, { + desc: "Mask Length 32", + prefixWithMask: "192.51.100.64/32", + }, { + desc: "Mask Length 28", + prefixWithMask: "192.51.100.64/28", + }, { + desc: "Mask Length 22", + prefixWithMask: "192.51.100.0/22", + }} + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + configGribiBaselineAFT(ctx, t, dut, args) + + t.Run("Program gRIBi route", func(t *testing.T) { + configureGribiRoute(ctx, t, dut, args, tc.prefixWithMask) + }) + // Send both 6in4 and 4in4 packets. Verify that the packets have their outer + // v4 header stripped and are forwarded according to the route in the DEFAULT + // VRF that matches the inner IP address. + portList := []string{"port8"} + //dstPorts := []attrs.Attributes{atePort2, atePort3, atePort4, atePort5, atePort6, atePort7, atePort8} + t.Run("Create ip-in-ip and ipv6-in-ip flows, send traffic and verify decap functionality", + func(t *testing.T) { + + flow1 := createFlow(&flowArgs{flowName: flow4in4, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDstNoEncap, isInnHdrV4: true}) + + flow2 := createFlow(&flowArgs{flowName: flow6in4, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, InnHdrSrcIPv6: atePort1.IPv6, + InnHdrDstIPv6: ipv6InnerDstNoEncap, isInnHdrV4: false, outHdrDscp: []uint32{dscpEncapA1}}) + + 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}) + }) + }) + } +} + +// testGribiDecapMixedLenPref is to validate subtest 3. +// Test-3, Mixed Prefix Decap gRIBI Entries. +func testGribiDecapMixedLenPref(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + var testPref1 string = "192.51.128.0/22" + var testPref2 string = "192.55.200.3/32" + + var traffiDstIP1 string = "192.55.200.3" + var traffiDstIP2 string = "192.51.128.5" + + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + configGribiBaselineAFT(ctx, t, dut, args) + + t.Run("Program gRIBi route", func(t *testing.T) { + configureGribiMixedPrefEntries(ctx, t, dut, args, []string{testPref1, testPref2}) + }) + // Send both 6in4 and 4in4 packets. Verify that the packets have their outer + // v4 header stripped and are forwarded according to the route in the DEFAULT + // VRF that matches the inner IP address. + 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}}) + + flow2 := createFlow(&flowArgs{flowName: "flow2", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: traffiDstIP2, InnHdrSrcIP: atePort1.IPv4, + InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, outHdrDscp: []uint32{dscpEncapNoMatch}}) + + 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}) + }) + + // Test with packets with a destination address that does not match + // the decap route, and verify that such packets are not decapped. + portList = []string{"port4"} + t.Run("Send traffic to non decap route and verify the behavior", func(t *testing.T) { + flow4 := createFlow(&flowArgs{flowName: flowNegTest, + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: gribiIPv4EntryVRF1111, isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst}) + sendTraffic(t, args, portList, []gosnappi.Flow{flow4}) + verifyTraffic(t, args, []string{flowNegTest}, !wantLoss) + captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], + outDstIP: []string{gribiIPv4EntryVRF1111}, inHdrIP: ipv4InnerDst, validateNoDecap: true}) + }) +} + +// testTunnelTrafficNoDecap is validate Test-4: Tunneled traffic with no decap: +// Ensures that tunneled traffic is correctly forwarded when there is no match in the DECAP_VRF. +// The intent of this test is to ensure that the VRF selection policy correctly sends these +// packets to either TE_VRF_111 or TE_VRF_222. +func testTunnelTrafficNoDecap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + configGribiBaselineAFT(ctx, t, dut, args) + + // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 + // Below code will be uncommented once ixia issue is fixed. + /* + portList := []string{"port2"} + + flow1 := createFlow(&flowArgs{flowName: "flow1", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: gribiIPv4EntryVRF1111, outHdrDscp: []uint32{dscpEncapNoMatch, dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true, inHdrDscp: []uint32{dscpEncapA1}}) + + flow2 := createFlow(&flowArgs{flowName: "flow2", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: gribiIPv4EntryVRF1111, InnHdrSrcIPv6: atePort1.IPv6, inHdrDscp: []uint32{dscpEncapA1}, + InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false, udp: true, outHdrDscp: []uint32{dscpEncapNoMatch, dscpEncapA1}}) + + sendTraffic(t, args, portList, []gosnappi.Flow{flow1, flow2}) + verifyTraffic(t, args, []string{"flow1", "flow2"}, !wantLoss) + captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], + outDstIP: []string{gribiIPv4EntryVRF1111}, inHdrIP: ipv4InnerDst, validateEncap: true}) + + wantWeights := []float64{ + 0.0625, // 6.25 Port2 + 0.1875, // 18.75 Port3 + 0.75, // 75.0 Port4 + 0, // 0 Port5 + 0, // 0 Port6 + 0, // 0 Port7 + 0, // 0 Port8 + } + validateTrafficDistribution(t, args.ate, wantWeights) + + flow3 := createFlow(&flowArgs{flowName: "flow3", + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIP: gribiIPv4EntryVRF2221, outHdrDscp: []uint32{dscpEncapNoMatch, dscpEncapB1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true}) + + flow4 := createFlow(&flowArgs{flowName: "flow4", + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIP: gribiIPv4EntryVRF2221, InnHdrSrcIPv6: atePort1.IPv6, + InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false, udp: true, outHdrDscp: []uint32{dscpEncapNoMatch, dscpEncapB1}}) + + sendTraffic(t, args, portList, []gosnappi.Flow{flow3, flow4}) + verifyTraffic(t, args, []string{"flow3", "flow4"}, !wantLoss) + captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], + outDstIP: []string{gribiIPv4EntryVRF2221}, inHdrIP: ipv4InnerDst, validateEncap: true}) + + // Verify received pkts on DUT port-5. + outTrafficCounters := gnmi.OTG().Port("port1").State() + outPkts := gnmi.Get(t, args.ate.OTG(), outTrafficCounters).GetCounters().GetOutFrames() + + inTrafficCounters := gnmi.OTG().Port("port5").State() + inPkts := gnmi.Get(t, args.ate.OTG(), inTrafficCounters).GetCounters().GetInFrames() + + if (outPkts - inPkts) < tolerancePct { + t.Error("Traffic did not egressed through DUT port5") + } + */ +} + +// testTunnelTrafficMatchDefaultTerm is to validate subtest 5. +// Test-5: match on "default term", send to default VRF +// Tests support for TE disabled IPinIP IPv4 (IP protocol 4) cluster traffic arriving on WAN +// facing ports. Specifically, this test verifies the tunnel traffic identification using +// ipv4_outer_src_111 and ipv4_outer_src_222 in the VRF selection policy. +func testTunnelTrafficMatchDefaultTerm(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + configGribiBaselineAFT(ctx, t, dut, args) + + portList := []string{"port8"} + + flow1 := createFlow(&flowArgs{flowName: "flow1", + outHdrSrcIP: ipv4OuterSrc333, outHdrDstIP: ipv4InnerDst, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst2, isInnHdrV4: true}) + + flow2 := createFlow(&flowArgs{flowName: "flow2", + outHdrSrcIP: ipv4OuterSrc333, outHdrDstIP: ipv4InnerDst, InnHdrSrcIPv6: atePort1.IPv6, + InnHdrDstIPv6: ipv6InnerDst2, isInnHdrV4: false}) + + sendTraffic(t, args, portList, []gosnappi.Flow{flow1, flow2}) + verifyTraffic(t, args, []string{"flow1", "flow2"}, !wantLoss) + + // Verify received pkts on DUT port-8 per the route in the DEFAULT VRF. + outTrafficCounters := gnmi.OTG().Port("port1").State() + outPkts := gnmi.Get(t, args.ate.OTG(), outTrafficCounters).GetCounters().GetOutFrames() + + inTrafficCounters := gnmi.OTG().Port("port8").State() + inPkts := gnmi.Get(t, args.ate.OTG(), inTrafficCounters).GetCounters().GetInFrames() + + if (outPkts - inPkts) < tolerancePct { + t.Error("Traffic did not egressed through Default VRF") + } + + flow3 := createFlow(&flowArgs{flowName: "flow3", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4InnerDst, proto: 17, udp: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst2, isInnHdrV4: true}) + + flow4 := createFlow(&flowArgs{flowName: "flow4", + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIP: ipv4InnerDst, proto: 17, udp: true, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst2, isInnHdrV4: true}) + + sendTraffic(t, args, portList, []gosnappi.Flow{flow3, flow4}) + verifyTraffic(t, args, []string{"flow3", "flow4"}, !wantLoss) + + // Verify received pkts on DUT port-8 per the route in the DEFAULT VRF. + outTrafficCounters = gnmi.OTG().Port("port1").State() + outPkts = gnmi.Get(t, args.ate.OTG(), outTrafficCounters).GetCounters().GetOutFrames() + + inTrafficCounters = gnmi.OTG().Port("port8").State() + inPkts = gnmi.Get(t, args.ate.OTG(), inTrafficCounters).GetCounters().GetInFrames() + + if (outPkts - inPkts) < tolerancePct { + t.Error("Traffic did not egressed through Default VRF") + } + + // Remove the matching route (e.g. stop the BGP routes) in + // the DEFAULT VRF and verify that the traffic are dropped. + updateBgpRoutes(t, args, routeDelete) + sendTraffic(t, args, portList, []gosnappi.Flow{flow3, flow4}) + verifyTraffic(t, args, []string{"flow3", "flow4"}, wantLoss) + + // Add deleted bgp routes. + updateBgpRoutes(t, args, !routeDelete) +} + +// testTunnelTrafficDecapEncap is to validate subtest Test-6 decap then encap. +func testTunnelTrafficDecapEncap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + configGribiBaselineAFT(ctx, t, dut, args) + + t.Run("Program gRIBi decap route", func(t *testing.T) { + configureGribiRoute(ctx, t, dut, args, ipv4OuterDst111+"/32") + }) + + // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 + // Below code will be uncommented once ixia issue is fixed. + /* + portList := []string{"port2"} + + flow1 := createFlow(&flowArgs{flowName: "flow1", + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true, inHdrDscp: []uint32{dscpEncapA1}}) + + flow2 := createFlow(&flowArgs{flowName: "flow2", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false, udp: true, inHdrDscp: []uint32{dscpEncapA1}}) + + sendTraffic(t, args, portList, []gosnappi.Flow{flow1, flow2}) + verifyTraffic(t, args, []string{"flow1", "flow2"}, !wantLoss) + + captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], + outDstIP: []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112}, inHdrIP: ipv4InnerDst, validateEncap: true}) + + wantWeights := []float64{ + 0.0156, // 1.56 Port2 + 0.0468, // 4.68 Port3 + 0.1875, // 18.75 Port4 + 0, // 0 Port5 + 0.75, // 75.0 Port6 + 0, // 0 Port7 + 0, // 0 Port8 + } + validateTrafficDistribution(t, args.ate, wantWeights) + + flow3 := createFlow(&flowArgs{flowName: "flow3", + outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true, inHdrDscp: []uint32{dscpEncapB1}}) + + flow4 := createFlow(&flowArgs{flowName: "flow4", + outHdrSrcIP: ipv4OuterSrc222, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true, inHdrDscp: []uint32{dscpEncapB1}}) + + sendTraffic(t, args, portList, []gosnappi.Flow{flow3, flow4}) + verifyTraffic(t, args, []string{"flow3", "flow4"}, !wantLoss) + + captureAndValidatePackets(t, args, &packetValidation{portName: args.otgConfig.Ports().Items()[1].Name(), + outDstIP: []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112}, inHdrIP: ipv4InnerDst, validateEncap: true}) + + wantWeights = []float64{ + 0.0468, // 4.68 Port2 + 0.1406, // 14.06 Port3 + 0.5625, // 56.25 Port4 + 0, // 0 Port5 + 0.25, // 25 Port6 + 0, // 0 Port7 + 0, // 0 Port8 + } + validateTrafficDistribution(t, args.ate, wantWeights) + */ +} + +// TestMatchSourceAndProtoNoMatchDSCP is to test support for decap/encap for gRIBI routes. +// Test VRF selection logic involving different decapsulation and encapsulation lookup scenarios +// via gRIBI. +func TestGribiDecap(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + gribic := dut.RawAPIs().GRIBI(t) + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + + // Baseline config + t.Run("Configure Default Network Instance", func(t *testing.T) { + fptest.ConfigureDefaultNetworkInstance(t, dut) + }) + + t.Run("Configure Non-Default Network Instances", func(t *testing.T) { + configNonDefaultNetworkInstance(t, dut) + }) + + t.Run("Configure interfaces on DUT", func(t *testing.T) { + configureDUT(t, dut) + }) + + t.Run("Apply vrf selectioin policy W to DUT port-1", func(t *testing.T) { + configureVrfSelectionPolicyW(t, dut) + }) + + 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) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + otg := ate.OTG() + var otgConfig gosnappi.Config + t.Run("Configure OTG", func(t *testing.T) { + otgConfig = configureOTG(t, otg, ate) + }) + + verifyISISTelemetry(t, dut, dut.Port(t, "port8").Name()) + verifyBgpTelemetry(t, dut) + + // Connect gRIBI client to DUT referred to as gRIBI - using PRESERVE persistence and + // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) + client.Start(ctx, t) + defer client.Stop(t) + + defer func() { + if err := gribi.FlushAll(client); err != nil { + t.Error(err) + } + }() + + client.StartSending(ctx, t) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for clientA: %v", err) + } + eID := gribi.BecomeLeader(t, client) + + args := &testArgs{ + ctx: ctx, + client: client, + dut: dut, + ate: ate, + otgConfig: otgConfig, + top: top, + electionID: eID, + otg: otg, + } + + t.Run("Test-1: Match on source and protocol, no match on DSCP; flow VRF_DECAP hit -> DEFAULT", func(t *testing.T) { + testGribiDecapMatchSrcProtoNoMatchDSCP(ctx, t, dut, args) + }) + t.Run("Test-2: match on source, protocol and DSCP, VRF_DECAP hit -> VRF_ENCAP_A miss -> DEFAULT", func(t *testing.T) { + testGribiDecapMatchSrcProtoDSCP(ctx, t, dut, args) + }) + + 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.") + 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.") + configureVrfSelectionPolicyW(t, dut) + + t.Run("Test-5: Match on default term and send to default VRF", func(t *testing.T) { + testTunnelTrafficMatchDefaultTerm(ctx, t, dut, args) + }) + + t.Run("Test-6: Decap then encap", func(t *testing.T) { + testTunnelTrafficDecapEncap(ctx, t, dut, args) + }) +} diff --git a/feature/gribi/otg_tests/weighted_balancing_test/port_flap_rebalanced_test.go b/feature/gribi/otg_tests/weighted_balancing_test/port_flap_rebalanced_test.go index f228fcd0da3..a0c4681c119 100644 --- a/feature/gribi/otg_tests/weighted_balancing_test/port_flap_rebalanced_test.go +++ b/feature/gribi/otg_tests/weighted_balancing_test/port_flap_rebalanced_test.go @@ -66,7 +66,7 @@ func testNextHopRemaining( top gosnappi.Config, ) { // Generate and analyze traffic. - atePorts, inPkts, outPkts := generateTraffic(t, ate, top) + atePorts, inPkts, outPkts := runTraffic(t, ate, top) t.Logf("atePorts = %v", atePorts) t.Logf("inPkts = %v", inPkts) t.Logf("outPkts = %v", outPkts) @@ -151,10 +151,12 @@ func TestPortFlap(t *testing.T) { // Turn down ports one by one. dt := gnmi.OC() + createTraffic(t, ate, top) for i := len(atePorts); i >= 2; i-- { numUps := i - 1 numDowns := len(atePorts) - i testName := fmt.Sprintf("%d Up, %d Down", numUps, numDowns) + if i < len(atePorts) { dp := dut.Port(t, atePorts[i].ID()) if deviations.ATEPortLinkStateOperationsUnsupported(ate) { diff --git a/feature/gribi/otg_tests/weighted_balancing_test/setup_test.go b/feature/gribi/otg_tests/weighted_balancing_test/setup_test.go index d86de00be8f..7552b9339c9 100644 --- a/feature/gribi/otg_tests/weighted_balancing_test/setup_test.go +++ b/feature/gribi/otg_tests/weighted_balancing_test/setup_test.go @@ -187,6 +187,12 @@ func configureDUT(t testing.TB, dut *ondatra.DUTDevice) { dc := gnmi.OC() for _, dp := range dut.Ports() { if i := dutInterface(dp, dut); i != nil { + if dp.PMD() == ondatra.PMD100GBASEFR { + e := i.GetOrCreateEthernet() + e.AutoNegotiate = ygot.Bool(false) + e.DuplexMode = oc.Ethernet_DuplexMode_FULL + e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } gnmi.Replace(t, dut, dc.Interface(dp.Name()).Config(), i) } else { t.Fatalf("No address found for port %v", dp) @@ -208,7 +214,11 @@ func configureDUT(t testing.TB, dut *ondatra.DUTDevice) { func configureATE(t testing.TB, ate *ondatra.ATEDevice) gosnappi.Config { t.Helper() config := gosnappi.NewConfig() + pmd100GFRPorts := []string{} for i, ap := range ate.Ports() { + if ap.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, ap.ID()) + } // DUT and ATE ports are connected by the same names. dutid := fmt.Sprintf("dut:%s", ap.ID()) ateid := fmt.Sprintf("ate:%s", ap.ID()) @@ -217,11 +227,18 @@ func configureATE(t testing.TB, ate *ondatra.ATEDevice) gosnappi.Config { dev := config.Devices().Add().SetName(ateid) macAddress, _ := incrementMAC(ateSrcPortMac, i) eth := dev.Ethernets().Add().SetName(ateid + ".Eth").SetMac(macAddress) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap.ID()) + eth.Connection().SetPortName(ap.ID()) eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4"). SetAddress(portsIPv4[ateid]).SetGateway(portsIPv4[dutid]). SetPrefix(plen) } + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := config.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } ate.OTG().PushConfig(t, config) return config } @@ -292,10 +309,7 @@ func sortPorts(ports []*ondatra.Port) []*ondatra.Port { return ports } -// generateTraffic generates traffic from ateSrcNetCIDR to -// ateDstNetCIDR, then returns the atePorts as well as the number of -// packets received (inPkts) and sent (outPkts) across the atePorts. -func generateTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) (atePorts []*ondatra.Port, inPkts []uint64, outPkts []uint64) { +func createTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) { re, _ := regexp.Compile(".+:([a-zA-Z0-9]+)") dutString := "dut:" + re.FindStringSubmatch(ateSrcPort)[1] gwIp := portsIPv4[dutString] @@ -312,29 +326,31 @@ func generateTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Confi if *randomSrcIP { ipv4.Src().SetValues(generateRandomIPList(t, ateSrcNetFirstIP+"/32", ateSrcNetCount)) } else { - ipv4.Src().SetChoice("increment").Increment().SetStart(ateSrcNetFirstIP).SetCount(uint32(ateSrcNetCount)) + ipv4.Src().Increment().SetStart(ateSrcNetFirstIP).SetCount(uint32(ateSrcNetCount)) } if *randomDstIP { ipv4.Dst().SetValues(generateRandomIPList(t, ateDstNetFirstIP+"/32", ateDstNetCount)) } else { - ipv4.Dst().SetChoice("increment").Increment().SetStart(ateDstNetFirstIP).SetCount(uint32(ateDstNetCount)) + ipv4.Dst().Increment().SetStart(ateDstNetFirstIP).SetCount(uint32(ateDstNetCount)) } tcp := flow.Packet().Add().Tcp() if *randomSrcPort { tcp.SrcPort().SetValues((generateRandomPortList(65534))) } else { - tcp.SrcPort().SetChoice("increment").Increment().SetStart(1).SetCount(65534) + tcp.SrcPort().Increment().SetStart(1).SetCount(65534) } if *randomDstPort { tcp.DstPort().SetValues(generateRandomPortList(65534)) } else { - tcp.DstPort().SetChoice("increment").Increment().SetStart(1).SetCount(65534) + tcp.DstPort().Increment().SetStart(1).SetCount(65534) } flow.Size().SetFixed(200) ate.OTG().PushConfig(t, config) ate.OTG().StartProtocols(t) +} +func runTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) (atePorts []*ondatra.Port, inPkts []uint64, outPkts []uint64) { if *trafficPause != 0 { t.Logf("Pausing before traffic at %v for %v", time.Now(), *trafficPause) time.Sleep(*trafficPause) @@ -362,10 +378,17 @@ func generateTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Confi } } } - return atePorts, inPkts, outPkts } +// generateTraffic generates traffic from ateSrcNetCIDR to +// ateDstNetCIDR, then returns the atePorts as well as the number of +// packets received (inPkts) and sent (outPkts) across the atePorts. +func generateTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) (atePorts []*ondatra.Port, inPkts []uint64, outPkts []uint64) { + createTraffic(t, ate, config) + return runTraffic(t, ate, config) +} + // normalize normalizes the input values so that the output values sum // to 1.0 but reflect the proportions of the input. For example, // input [1, 2, 3, 4] is normalized to [0.1, 0.2, 0.3, 0.4]. diff --git a/feature/interface/aggregate/feature.textproto b/feature/interface/aggregate/feature.textproto index 8be6a9fbbc5..5345b167a3f 100644 --- a/feature/interface/aggregate/feature.textproto +++ b/feature/interface/aggregate/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/README.md b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/README.md index e7d548bf238..29c5cfd2c02 100644 --- a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/README.md +++ b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/README.md @@ -1,17 +1,18 @@ -# RT-5.7 Aggregate Not Viable All +# RT-5.7: Aggregate Not Viable All [TODO: test automation/coding; issue https://github.com/openconfig/featureprofiles/issues/1655] ## Summary Test forwarding-viable with LAG and routing -Ensure that when **all LAG member** become set with forwarding-viable == FALSE. -- forwarding-viable=false impact **only** transmit traffic on all member port. -- All member ports set with forwarding-viable=false can receive all type of - traffic and forward it normally (same as with forwarding-viable=true) -- ISIS adjacency is established on LAG port w/ all member set to - forwarding-viable == FALSE -- Traffic that normally egress LAG with all members set to forwarding-viable == - FALSE is forwarded by the next best egress interface/LAG. +Ensure that when --all LAG member-- become set with forwarding-viable == FALSE + +- forwarding-viable=false impact --only-- transmit traffic on all member port +- All member ports set with forwarding-viable=false can receive all type of traffic \ + and forward it normally (same as with forwarding-viable=true) +- ISIS adjacency is established on LAG port w/ all member set to forwarding-viable \ + == FALSE +- Traffic that normally egress LAG with all members set to forwarding-viable == FALSE \ + is forwarded by the next best egress interface/LAG. ## Procedure @@ -22,117 +23,160 @@ Ensure that when **all LAG member** become set with forwarding-viable == FALSE. | | | + - - - + + - - - -| | | | | + - - - + + - - - -| .-------.| |.-------. | | +-------+-+--------+ ( pfx2 ) -( pfx1 ) | . | | p7 : ; p7 | `-------'| +( pfx1 ) | . | | p6 : ; p6 | `-------'| |`-------' | p1 ; : p1 | DUT | ' | .-------.| | |----+-+-----| | | ( pfx3 ) -| | | | | | p8 . p8 | `-------'| +| | | | | | p7 . p7 | `-------'| | | | | | +-------;-:--------+ .-------.| | | : ; | | | | | ( pfx4 ) | | ' | | | | | `-------'| | | LAG_1 | +-------+-+--------+ | -+------------+ +-------------+ p9 : ; p9 +--------------+ ++------------+ +-------------+ p8 : ; p8 +--------------+ ' LAG_3 ``` -- Connect ATE port-1 to DUT port-1, and ATE ports 2 through 7 to DUT ports 2-7, - and ATE ports 8, 9 to DUT ports 8, 9 +- Connect ATE port-1 to DUT port-1, and ATE ports 2 through 8 to DUT ports 2-8 - Configure ATE and DUT ports 1 to be LAG_1 w/ LACP running. -- Configure ATE and DUT ports 2-7 to be LAG_2 w/ LACP running. -- Configure ATE and DUT ports 8-9 to be LAG_3 w/ LACP running. +- Configure ATE and DUT ports 2-6 to be LAG_2 w/ LACP running. +- Configure ATE and DUT ports 7-8 to be LAG_3 w/ LACP running. - Establish ISIS adjacencies on LAG_1, LAG_2, LAG_3. 1. Advertise one network prefix (pfx1) from ATE LAG_1 1. Advertise one network prefix (pfx2) from ATE LAG_2 and ATE LAG_3. -- Establish iBGP between ATE and DUT over LGA_1 using LAG_1 interface IPs and advertise prefix pfx3 with BGP NH from pfx2 range. -- Programm via gRIBI route for prefix pfx4 with single NHG pointing LAG_2 (al - ports are forwarding-viable at this point). - -- For ISIS cost of LAG_2 lower then ISIS cost of LAG_3: - - Run traffic: - - From prefix pfx1 to all three: pfx2, pfx3, pfx4 - - From prefix pfx2 to: pfx1 - - Make the forwarding-viable transitions from TRUE --> FALSE on ports 3-7 - within the LAG_2 on the DUT - - ensure that only DUT port 2 of LAG ports has bidirectional traffic. - - Ensure there is no traffic transmitted out of DUT ports 3-7 - - ensure that traffic is received on all port2-7 and delivered to ATE port1 - - ensure there are no packet losses in steady state (no congestion). - - Ensure there is no traffic received on DUT LAG_3 - - Ensure there is no traffic transmitted on DUT LAG_3 - - Disable/deactive laser on ATE port2; All LAG_2 members are either down (port2) or - set with forwarding-viable=FALSE - - Ensure ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 - - Ensure there is no traffic transmitted out of DUT ports 2-7 (LAG_2) - - ensure that traffic is received on all port3-7 and delivered to ATE LAG_1 - - ensure there are no packet losses in steady state (no congestion) for - traffic from ATE LAG_2 to ATE LAG_1 (pfx_1). - - ensure there are no packet losses in steady state (no congestion) for - traffic from ATE LAG_1 to ATE LAG_3 (pfx_2, pfx3). - - Ensure there is no traffic received on DUT LAG_3 - - Ensure that traffic from ATE port1 to pfx2, pfx3 are transmitted via DUT - LAG3 - - Ensure that traffic from ATE port1 to pfx4 are discarded on DUT - - Make the forwarding-viable transitions from FALSE --> TRUE on a ports 7 - within the LAG_2 on the DUT - - ensure that only DUT port 7 of LAG ports has bidirectional traffic. - - Ensure there is no traffic transmitted out of DUT ports 2-6 - - ensure that traffic is received on all port3-7 and delivered to ATE port1 - - ensure there are no packet losses in steady state (no congestion). - - Ensure there is no traffic received on DUT LAG_3 - - Ensure there is no traffic transmitted on DUT LAG_3 - - Enable/activate laser on ATE port2; Make the forwarding-viable transitions - from FALSE --> TRUE on a ports 3-7 +- Establish iBGP between ATE and DUT over LGA using LAG interface IPs and + advertise prefix pfx3 with BGP NH from pfx2 range. +- Programm via gRIBI route for prefix pfx4 with NHG pointing to NH LAG_2 & backup + to NHG pointing to NH LAG_3(all ports are forwarding-viable at this point) + with equal weight. + +## RT-5.7.1: For ISIS cost of LAG_2 lower than ISIS cost of LAG_3: +#### Run traffic: +- From prefix pfx1 to all three: pfx2, pfx3, pfx4 +- From prefix pfx2 to: pfx1 + +#### RT-5.7.1.1: Make the forwarding-viable transitions from TRUE --> FALSE on ports 3-6 within the LAG_2 on the DUT +- Ensure that only DUT port 2 of LAG ports has bidirectional traffic. +- Ensure there is no traffic transmitted out of DUT ports 3-6 +- Ensure that traffic is received on all port2-6 and delivered to ATE port1 +- Ensure there are no packet losses in steady state (no congestion). +- Ensure there is no traffic received on DUT LAG_3 +- Ensure there is no traffic transmitted on DUT LAG_3 + +#### RT-5.7.1.2: Verify forwarding-viable behavior on an aggregate interface with all members down or set with forwarding-viable=FALSE. +- Ensure ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 +- Set with forwarding-viable=FALSE on port 2 (All Ports now on LAG_2 are set with FV=false) +- Ensure that the ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 +- Ensure there is no layer3 traffic transmitted out of DUT ports 2-6 (LAG_2) +- Ensure that traffic is received on all port3-6 and delivered to ATE LAG_1 +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_2 to ATE LAG_1 (pfx_1). +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_1 to ATE LAG_3 (pfx_2, pfx3). +- Ensure there is no traffic received on DUT LAG_3 +- Ensure that traffic from ATE port1 to pfx2, pfx3 are transmitted via DUT LAG3 +- Ensure that traffic from ATE port1 to pfx4 are transmitted out through backup NHG pointing to NH LAG3 + +#### RT-5.7.1.3: Make the forwarding-viable transitions from FALSE --> TRUE on a ports 6 within the LAG_2 on the DUT +- Ensure that only DUT port 6 of LAG ports has bidirectional traffic. +- Ensure there is no traffic transmitted out of DUT ports 2-6 +- Ensure that traffic is received on all port3-6 and delivered to ATE port1 +- Ensure there are no packet losses in steady state (no congestion). +- Ensure there is no traffic received on DUT LAG_3 +- Ensure there is no traffic transmitted on DUT LAG_3 + +#### RT-5.7.1.4: Verify forwarding-viable behavior on an aggregate interface with some members are down with all member are set with forwarding-viable=FALSE. +- Ensure ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 +- Set with forwarding-viable=FALSE on port 2 and make down other ports on LAG_2 + (All Ports now on LAG_2 are set with FV=false and some ports are down) +- Ensure that the ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 +- Ensure there is no layer3 traffic transmitted out of DUT ports 2-6 (LAG_2) +- Ensure that traffic is received on all port3-6 and delivered to ATE LAG_1 +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_2 to ATE LAG_1 (pfx_1). +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_1 to ATE LAG_3 (pfx_2, pfx3). +- Ensure there is no traffic received on DUT LAG_3 +- Ensure that traffic from ATE port1 to pfx2, pfx3 are transmitted via DUT LAG3 +- Ensure that traffic from ATE port1 to pfx4 are transmitted out through NHG pointing to NH LAG3 + -- For ISIS cost of LAG_2 equall to ISIS cost of LAG_3 - - Run traffic: - - From prefix pfx1 to all three: pfx2, pfx3, pfx4 - - From prefix pfx2 to: pfx1 - - Make the forwarding-viable transitions from TRUE --> FALSE on ports 3-7 - within the LAG_2 on the DUT - - ensure that only DUT port 2 of LAG_2 and all ports of LAG_3 ports has bidirectional - traffic. The traffic split between LAG_2 and LAG_3 should be 50:50. - - Ensure there is no traffic transmitted out of DUT ports 3-7 - - ensure that traffic is received on all port2-7 and ports8-9 and delivered to ATE port1 - - ensure there are no packet losses in steady state (no congestion). - - Disable/deactive laser on ATE port2; All LAG_2 members are either down (port2) or - set with forwarding-viable=FALSE. - - Ensure ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 - - Ensure there is no traffic transmitted out of DUT ports 2-7 (LAG_2) - - ensure that traffic received on all port3-7 and ports8-9 is delivered to ATE LAG_1 - - ensure there are no packet losses in steady state (no congestion) for - traffic from ATE LAG_2, LAG_3 to ATE LAG_1 (pfx_1). - - ensure there are no packet losses in steady state (no congestion) for - traffic from ATE LAG_1 to ATE LAG_3 (pfx_2, pfx3). - - Ensure that traffic from ATE port1 to pfx2, pfx3 are transmitted via DUT - LAG3 - - Ensure that traffic from ATE port1 to pfx4 are discarded on DUT - - Make the forwarding-viable transitions from FALSE --> TRUE on a ports 7 - within the LAG_2 on the DUT - - ensure that only DUT port 7 of LAG_2 and all ports of LAG_3 ports has bidirectional traffic. - - Ensure there is no traffic transmitted out of DUT ports 2-6 - - ensure that traffic received on all port3-7 and ports8-9 is delivered to ATE port1 - - ensure there are no packet losses in steady state (no congestion). - - Enable/activate laser on ATE port2; Make the forwarding-viable transitions - from FALSE --> TRUE on a ports 3-6 - -### Deviation option - -It is foreseen that implementation may drop ISIS adjacency if all members of LAG -are set with forwarding-viable = FALSE. This scenario may be -handled via the yet to be defined deviation `logicalInterfaceUPonNonViableAll`. - -## Config Parameter coverage - -- /interfaces/interface/ethernet/config/aggregate-id -- /interfaces/interface/ethernet/config/forwarding-viable -- /interfaces/interface/aggregation/config/lag-type -- /lacp/config/system-priority -- /lacp/interfaces/interface/config/name -- /lacp/interfaces/interface/config/interval -- /lacp/interfaces/interface/config/lacp-mode -- /lacp/interfaces/interface/config/system-id-mac -- /lacp/interfaces/interface/config/system-priority +## RT-5.7.2: For ISIS cost of LAG_2 equal to ISIS cost of LAG_3 +#### Run traffic: +- From prefix pfx1 to all three: pfx2, pfx3, pfx4 +- From prefix pfx2 to: pfx1 + +#### RT-5.7.2.1: Make the forwarding-viable transitions from TRUE --> FALSE on ports 3-6 within the LAG_2 on the DUT +- Ensure that only DUT port 2 of LAG_2 and all ports of LAG_3 ports has bidirectional + traffic. +- The traffic split between LAG_2 and LAG_3 should be 50:50 +- Ensure there is no traffic transmitted out of DUT ports 3-6 +- Ensure that traffic is received on all port2-6 and ports7-8 and delivered to ATE port1 +- Ensure there are no packet losses in steady state (no congestion) + +#### RT-5.7.2.2: Verify forwarding-viable behavior on an aggregate interface with all are set with forwarding-viable=FALSE. +- Ensure ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 +- Set with forwarding-viable=FALSE on port 2 (All Ports now on LAG_2 are set with FV=false) +- Ensure that the ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 +- Ensure there is no traffic transmitted out of DUT ports 2-6 (LAG_2) +- Ensure that traffic received on all port3-6 and ports7-8 is delivered to ATE LAG_1 +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_2, LAG_3 to ATE LAG_1 (pfx_1). +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_1 to ATE LAG_3 (pfx_2, pfx3). +- Ensure that traffic from ATE port1 to pfx2, pfx3 are transmitted via DUT LAG3 +- Ensure that traffic from ATE port1 to pfx4 are transmitted out through NHG pointing to NH LAG3 + +#### RT-5.7.2.3: Make the forwarding-viable transitions from FALSE --> TRUE on a ports 6 within the LAG_2 on the DUT +- Ensure that only DUT port 6 of LAG_2 and all ports of LAG_3 ports has bidirectional traffic. +- Ensure there is no traffic transmitted out of DUT ports 2-6 +- Ensure that traffic received on all port3-6 and ports7-8 is delivered to ATE port1 +- Ensure there are no packet losses in steady state (no congestion). + +#### RT-5.7.2.4: Verify forwarding-viable behavior on an aggregate interface with some members are down with all member are set with forwarding-viable=FALSE. +- Ensure ISIS adjacency is UP on DUT LAG_2 and ATE LAG_2 +- Set with forwarding-viable=FALSE and make down other ports on LAG_2 + (All Ports now on LAG_2 are set with FV=false and some ports are down) +- Ensure that the ISIS adjacency times out on DUT LAG_2 and ATE LAG_2 +- Ensure there is no traffic transmitted out of DUT ports 2-6 (LAG_2) +- Ensure that traffic received on all port3-6 and ports7-8 is delivered to ATE LAG_1 +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_2, LAG_3 to ATE LAG_1 (pfx_1). +- Ensure there are no packet losses in steady state (no congestion) for + traffic from ATE LAG_1 to ATE LAG_3 (pfx_2, pfx3). +- Ensure that traffic from ATE port1 to pfx2, pfx3 are transmitted via DUT LAG3 +- Ensure that traffic from ATE port1 to pfx4 are transmitted out through NH pointing to LAG3 + + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths and RPC intended to be covered by this test. + +```yaml +paths: + /interfaces/interface/ethernet/config/aggregate-id: + ## Config forwarding Viable to True/false + /interfaces/interface/config/forwarding-viable: + ## Define Lag type + /interfaces/interface/aggregation/config/lag-type: + ## Configure LACP + /lacp/config/system-priority: + /lacp/interfaces/interface/config/name: + /lacp/interfaces/interface/config/lacp-mode: + /lacp/interfaces/interface/config/interval: + /lacp/interfaces/interface/config/system-id-mac: + /lacp/interfaces/interface/config/system-priority: + + +rpcs: + gnmi: + gNMI.Subscribe: + ON_CHANGE: true + + gnoi: + system.System.Reboot: + +``` ## Telemetry Parameter coverage diff --git a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go new file mode 100644 index 00000000000..46229d70220 --- /dev/null +++ b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go @@ -0,0 +1,1240 @@ +// 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. + +// For LACP. Make the following forwarding-viable transitions on a port within the LAG on the DUT. +// 1. Tag the forwarding-viable=true to allow all the ports to pass traffic in +// port-channel. +// 2. Transition from forwarding-viable=true to forwarding-viable=false. +// For each condition above, ensure following two things: +// - traffic is load-balanced across the remaining interfaces in the LAG. +// - there is no packet tx on port with forwarding-viable=false. +// - there is packet rx on the port and process it to destination with forwarding-viable=false. + +// What is forwarding viable ? +// If set to false, the interface is not used for forwarding traffic, +// but as long as it is up, the interface still maintains its layer-2 adjacencies and runs its configured layer-2 functions (e.g. LLDP, etc.). + +package aggregate_all_not_forwarding_viable_test + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "net" + "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/otgutils" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/gnmi/oc/ocpath" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PLen = 30 + ipv6PLen = 126 + isisInstance = "DEFAULT" + dutAreaAddress = "49.0001" + ateAreaAddress = "49" + dutSysID = "1920.0000.2001" + asn = 64501 + acceptRoutePolicy = "PERMIT-ALL" + trafficPPS = 1000 + srcTrafficV4 = "100.0.1.1" + srcTrafficV6 = "2002:db8:64:64::1" + dstTrafficV4 = "100.0.2.1" + dstTrafficV6 = "2003:db8:64:64::1" + v4Count = 254 + v6Count = 100000000 + lagTypeLACP = oc.IfAggregate_AggregationType_LACP + ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + LAG1 = "lag1" + LAG2 = "lag2" + LAG3 = "lag3" + niTeVrf111 = "TE_VRF_111" + niRepairVrf = "REPAIR_VRF" + pfx1AdvV4WithMask = "100.0.1.0/24" + niTEVRF222 = "TE_VRF_222" + ipv4OuterSrc111Addr = "198.51.100.111" + gribiIPv4EntryVRF111 = "203.0.113.1" + ipv4OuterSrc222Addr = "198.51.100.222" + gribiIPv4EntryVRF222 = "203.0.113.100" +) + +type aggPortData struct { + dutIPv4 string + ateIPv4 string + dutIPv6 string + ateIPv6 string + ateAggName string + ateAggMAC string + ateISISSysID string + ateLagCount uint32 +} + +type ipAddr struct { + ip string + prefix uint32 +} + +// testArgs holds the objects needed by a test case. +type testArgs struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + ctx context.Context + client *fluent.GRIBIClient +} + +var ( + agg1 = &aggPortData{ + dutIPv4: "192.0.2.1", + ateIPv4: "192.0.2.2", + dutIPv6: "2001:db8::1", + ateIPv6: "2001:db8::2", + ateAggName: LAG1, + ateAggMAC: "02:00:01:01:01:01", + ateISISSysID: "640000000002", + ateLagCount: 1, + } + agg2 = &aggPortData{ + dutIPv4: "192.0.2.5", + ateIPv4: "192.0.2.6", + dutIPv6: "2001:db8::5", + ateIPv6: "2001:db8::6", + ateAggName: LAG2, + ateAggMAC: "02:00:01:01:02:01", + ateISISSysID: "640000000003", + ateLagCount: 2, + } + agg3 = &aggPortData{ + dutIPv4: "192.0.2.9", + ateIPv4: "192.0.2.10", + dutIPv6: "2001:db8::9", + ateIPv6: "2001:db8::a", + ateAggName: LAG3, + ateAggMAC: "02:00:01:01:03:01", + ateISISSysID: "640000000004", + ateLagCount: 1, + } + + dutLoopback = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "192.0.2.21", + IPv6: "2001:db8::21", + IPv4Len: 32, + IPv6Len: 128, + } + + pfx1AdvV4 = &ipAddr{ip: "100.0.1.0", prefix: 24} + pfx1AdvV6 = &ipAddr{ip: "2002:db8:64:64::0", prefix: 64} + pfx2AdvV4 = &ipAddr{ip: "100.0.2.0", prefix: 24} + pfx2AdvV6 = &ipAddr{ip: "2003:db8:64:64::0", prefix: 64} + pfx3AdvV4 = &ipAddr{ip: "100.0.3.0", prefix: 24} + pfx4AdvV4 = &ipAddr{ip: "100.0.4.0", prefix: 24} + pmd100GFRPorts []string + dutPortList []*ondatra.Port + atePortList []*ondatra.Port + rxPktsBeforeTraffic map[*ondatra.Port]uint64 + txPktsBeforeTraffic map[*ondatra.Port]uint64 + trafficDistributionWeights = []uint64{50, 50} + ecmpTolerance = uint64(1) + ipRange = []uint32{250, 500} + + dutAggMac []string +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// TestAggregateAllNotForwardingViable Test forwarding-viable with LAG and routing +func TestAggregateAllNotForwardingViable(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + aggIDs := configureDUT(t, dut) + configNonDefaultNetworkInstance(t, dut) + changeMetric(t, dut, aggIDs[2], 30) + top := configureATE(t, ate) + + installGRIBIRoutes(t, dut, ate, top) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + for _, aggID := range aggIDs { + gnmi.Await(t, dut, gnmi.OC().Interface(aggID).OperStatus().State(), 60*time.Second, oc.Interface_OperStatus_UP) + } + flows := createFlows(t, ate, top) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + + for _, agg := range []*aggPortData{agg1, agg2, agg3} { + bgpPath := ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + gnmi.Await(t, dut, bgpPath.Neighbor(agg.ateIPv4).SessionState().State(), time.Minute, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + } + + t.Logf("ISIS cost of LAG_2 lower then ISIS cost of LAG_3 Test-01") + t.Run("RT-5.7.1.1: Setting Forwarding-Viable to False on Lag2 all ports except port 2", func(t *testing.T) { + configForwardingViable(t, dut, dutPortList[2:agg2.ateLagCount+1], false) + startTraffic(t, dut, ate, top) + if err := checkBidirectionalTraffic(t, dut, dutPortList[1:2]); err != nil { + t.Fatal(err) + } + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[2:agg2.ateLagCount+1], dutPortList[2:agg2.ateLagCount+1]); err != nil { + t.Fatal(err) + } + // Ensure there is no traffic received/transmiited on DUT LAG_3 + if got := validateLag3Traffic(t, dut, ate, dutPortList[(agg2.ateLagCount+1):]); got == true { + t.Fatal("Packets are Received and Transmitted on LAG_3") + } + if ok := verifyTrafficFlow(t, ate, flows, false); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + }) + t.Run("RT-5.7.1.2: Setting Forwarding-Viable to False for Lag2 all ports", func(t *testing.T) { + // Ensure ISIS Adjacency is up on LAG_2 + if ok := awaitAdjacency(t, dut, aggIDs[1], []oc.E_Isis_IsisInterfaceAdjState{oc.Isis_IsisInterfaceAdjState_UP}); !ok { + t.Fatal("ISIS Adjacency is Down on LAG_2") + } + configForwardingViable(t, dut, dutPortList[1:agg2.ateLagCount+1], false) + // Ensure ISIS Adjacency is Down on LAG_2 + + if ok := awaitAdjacency(t, dut, aggIDs[1], []oc.E_Isis_IsisInterfaceAdjState{oc.Isis_IsisInterfaceAdjState_INIT, oc.Isis_IsisInterfaceAdjState_DOWN}); !ok { + if presence := gnmi.LookupAll(t, dut, ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis().Interface(aggIDs[1]).LevelAny().AdjacencyAny().AdjacencyState().State()); len(presence) > 0 { + t.Fatalf("ISIS Adjacency is Established on LAG_2 ") + } + } + startTraffic(t, dut, ate, top) + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount+1], dutPortList[1:agg2.ateLagCount+1]); err != nil { + t.Fatal(err) + } + // Ensure that traffic from ATE port1 to pfx4 transmitted out using LAG3 + if ok := verifyTrafficFlow(t, ate, flows[1:2], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[1].Name()) + } + // Ensure there is no traffic received on DUT LAG_3 + if got := validateLag3Traffic(t, dut, ate, dutPortList[(agg2.ateLagCount+1):]); got == true { + t.Fatal("Packets are Received on DUT LAG_3") + } + if ok := verifyTrafficFlow(t, ate, flows[0:1], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[0].Name()) + } + }) + + t.Run("RT-5.7.1.3: Setting Forwarding-Viable to True for Lag2 one of the port", func(t *testing.T) { + configForwardingViable(t, dut, dutPortList[agg2.ateLagCount:agg2.ateLagCount+1], true) + startTraffic(t, dut, ate, top) + if err := checkBidirectionalTraffic(t, dut, dutPortList[agg2.ateLagCount:agg2.ateLagCount+1]); err != nil { + t.Fatal(err) + } + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount], dutPortList[1:agg2.ateLagCount]); err != nil { + t.Fatal(err) + } + // Ensure there is no traffic received/transmiited on DUT LAG_3 + if got := validateLag3Traffic(t, dut, ate, dutPortList[(agg2.ateLagCount+1):]); got == true { + t.Fatal("Packets are Received and Transmitted on LAG_3") + } + if ok := verifyTrafficFlow(t, ate, flows, false); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + }) + + // Reset Forwarding-Viable to True for all the ports of LAG_2 + configForwardingViable(t, dut, dutPortList[1:agg2.ateLagCount], true) + + t.Run("RT-5.7.1.4: Setting Forwarding-Viable to False and Down some Port on Lag2", func(t *testing.T) { + // Ensure ISIS Adjacency is up on LAG_2 + if ok := awaitAdjacency(t, dut, aggIDs[1], []oc.E_Isis_IsisInterfaceAdjState{oc.Isis_IsisInterfaceAdjState_UP}); !ok { + t.Fatal("ISIS Adjacency is Down on LAG_2") + } + configForwardingViable(t, dut, dutPortList[1:agg2.ateLagCount+1], false) + // Ensure ISIS Adjacency is Down on LAG_2 + + if len(dut.Ports()) > 4 { + t.Logf("Bring Down Port2 and Port3") + setDUTInterfaceWithState(t, dut, []*ondatra.Port{dut.Port(t, "port2"), dut.Port(t, "port3")}, false) + } else { + t.Logf("Bring Down Port2") + setDUTInterfaceWithState(t, dut, []*ondatra.Port{dut.Port(t, "port2")}, false) + } + + // Ensure LAG2 is UP when all member are Forwarding unviable + gnmi.Await(t, dut, gnmi.OC().Interface(aggIDs[1]).OperStatus().State(), 60*time.Second, oc.Interface_OperStatus_UP) + startTraffic(t, dut, ate, top) + if len(dut.Ports()) > 4 { + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount+1], dutPortList[3:agg2.ateLagCount+1]); err != nil { + t.Fatal(err) + } + } else { + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount], dutPortList[2:agg2.ateLagCount]); err != nil { + t.Fatal(err) + } + } + // Ensure that traffic from ATE port1 to pfx4 transmitted out using LAG3 + if ok := verifyTrafficFlow(t, ate, flows[1:2], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[1].Name()) + } + // Ensure there is no traffic received on DUT LAG_3 + if got := validateLag3Traffic(t, dut, ate, dutPortList[(agg2.ateLagCount+1):]); got == true { + t.Fatal("Packets are Received on DUT LAG_3") + } + if ok := verifyTrafficFlow(t, ate, flows[0:1], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[0].Name()) + } + }) + t.Logf("Bring Up the Port2 and Port3") + setDUTInterfaceWithState(t, dut, []*ondatra.Port{dut.Port(t, "port2"), dut.Port(t, "port3")}, true) + + // Reset Forwarding-Viable to True for all the ports of LAG_2 + configForwardingViable(t, dut, dutPortList[1:agg2.ateLagCount+1], true) + // Change ISIS metric Equal for Both LAG_2 and LAG_3 + changeMetric(t, dut, aggIDs[2], 20) + + t.Logf("ISIS cost of LAG_2 equal to ISIS cost of LAG_3 Test-02") + t.Run("RT-5.7.2.1: Setting Forwarding-Viable to False for Lag2 ports except port 2", func(t *testing.T) { + configForwardingViable(t, dut, dutPortList[2:agg2.ateLagCount+1], false) + flows = append(flows, configureFlows(t, top, pfx2AdvV4, pfx1AdvV4, "pfx2ToPfx1Lag3", agg3, []*aggPortData{agg1}, dutAggMac[2], ipRange[0])) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + startTraffic(t, dut, ate, top) + if err := checkBidirectionalTraffic(t, dut, dutPortList[1:2]); err != nil { + t.Fatal(err) + } + if err := checkBidirectionalTraffic(t, dut, dutPortList[(agg2.ateLagCount+1):]); err != nil { + t.Fatal(err) + } + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[2:(agg2.ateLagCount+1)], dutPortList[2:(agg2.ateLagCount+1)]); err != nil { + t.Fatal(err) + } + // Ensure Load WECMP on LAG_2 and LAG_3 for prefix's pfx2, pfx3 and pfx4 + weights := trafficRXWeights(t, ate, []string{agg2.ateAggName, agg3.ateAggName}, flows[0]) + for idx, weight := range trafficDistributionWeights { + if got, want := weights[idx], weight; got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP Percentage for Aggregate Index: %d: got %d, want %d", idx+1, got, want) + } + } + if ok := verifyTrafficFlow(t, ate, flows, false); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + }) + + t.Run("RT-5.7.2.2: Setting Forwarding-Viable to False for Lag2 all ports", func(t *testing.T) { + // Ensure ISIS Adjacency is up on LAG_2 + if ok := awaitAdjacency(t, dut, aggIDs[1], []oc.E_Isis_IsisInterfaceAdjState{oc.Isis_IsisInterfaceAdjState_UP}); !ok { + t.Fatal("ISIS Adjacency is Down on LAG_2") + } + configForwardingViable(t, dut, dutPortList[1:agg2.ateLagCount+1], false) + // Ensure ISIS Adjacency is Down on LAG_2 + if ok := awaitAdjacency(t, dut, aggIDs[1], []oc.E_Isis_IsisInterfaceAdjState{oc.Isis_IsisInterfaceAdjState_INIT, oc.Isis_IsisInterfaceAdjState_DOWN}); !ok { + if presence := gnmi.LookupAll(t, dut, ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis().Interface(aggIDs[1]).LevelAny().AdjacencyAny().AdjacencyState().State()); len(presence) > 0 { + t.Fatalf("ISIS Adjacency is Established on LAG_2") + } + } + startTraffic(t, dut, ate, top) + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:(agg2.ateLagCount+1)], dutPortList[1:(agg2.ateLagCount+1)]); err != nil { + t.Fatal(err) + } + // Ensure that traffic from ATE port1 to pfx4 are discarded on DUT + if ok := verifyTrafficFlow(t, ate, flows[1:2], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[1].Name()) + } + // Ensure there is traffic received on DUT LAG_3 + if got := validateLag3Traffic(t, dut, ate, dutPortList[(agg2.ateLagCount+1):]); got == false { + t.Fatal("Packets are not Received on LAG_3") + } + if ok := verifyTrafficFlow(t, ate, flows[0:1], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[0].Name()) + } + }) + + t.Run("RT-5.7.2.3: Setting Forwarding-Viable to True for Lag2 one of the port", func(t *testing.T) { + configForwardingViable(t, dut, dutPortList[agg2.ateLagCount:(agg2.ateLagCount+1)], true) + startTraffic(t, dut, ate, top) + if err := checkBidirectionalTraffic(t, dut, dutPortList[agg2.ateLagCount:]); err != nil { + t.Fatal(err) + } + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount], dutPortList[1:agg2.ateLagCount]); err != nil { + t.Fatal(err) + } + if ok := verifyTrafficFlow(t, ate, flows, false); !ok { + t.Fatal("Packet Dropped, LossPct for flow ") + } + }) + + // Reset Forwarding-Viable to True for all the ports of LAG_2 + configForwardingViable(t, dut, dutPortList[1:agg2.ateLagCount], true) + + t.Run("RT-5.7.2.4: Setting Forwarding-Viable to False and Down some Port on Lag2", func(t *testing.T) { + // Ensure ISIS Adjacency is up on LAG_2 + if ok := awaitAdjacency(t, dut, aggIDs[1], []oc.E_Isis_IsisInterfaceAdjState{oc.Isis_IsisInterfaceAdjState_UP}); !ok { + t.Fatal("ISIS Adjacency is Down on LAG_2") + } + configForwardingViable(t, dut, dutPortList[1:agg2.ateLagCount+1], false) + // Ensure ISIS Adjacency is Down on LAG_2 + + if len(dut.Ports()) > 4 { + t.Logf("Bring Down Port2 and Port3") + setDUTInterfaceWithState(t, dut, []*ondatra.Port{dut.Port(t, "port2"), dut.Port(t, "port3")}, false) + } else { + t.Logf("Bring Down Port2") + setDUTInterfaceWithState(t, dut, []*ondatra.Port{dut.Port(t, "port2")}, false) + } + // Ensure LAG2 is UP when all member are Forwarding unviable + gnmi.Await(t, dut, gnmi.OC().Interface(aggIDs[1]).OperStatus().State(), 60*time.Second, oc.Interface_OperStatus_UP) + startTraffic(t, dut, ate, top) + if len(dut.Ports()) > 4 { + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount+1], dutPortList[3:agg2.ateLagCount+1]); err != nil { + t.Fatal(err) + } + } else { + if err := confirmNonViableForwardingTraffic(t, dut, ate, atePortList[1:agg2.ateLagCount], dutPortList[2:agg2.ateLagCount]); err != nil { + t.Fatal(err) + } + } + // Ensure that traffic from ATE port1 to pfx4 transmitted out using LAG3 + if ok := verifyTrafficFlow(t, ate, flows[1:2], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[1].Name()) + } + // Ensure there is traffic received on DUT LAG_3 + if got := validateLag3Traffic(t, dut, ate, dutPortList[(agg2.ateLagCount+1):]); got == false { + t.Fatal("Packets are Received on DUT LAG_3") + } + if ok := verifyTrafficFlow(t, ate, flows[0:1], true); !ok { + t.Fatal("Packet Dropped, LossPct for flow ", flows[0].Name()) + } + }) +} + +func setDUTInterfaceWithState(t testing.TB, dut *ondatra.DUTDevice, ports []*ondatra.Port, state bool) { + dc := gnmi.OC() + i := &oc.Interface{} + i.Enabled = ygot.Bool(state) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + for _, p := range ports { + i.Name = ygot.String(p.Name()) + gnmi.Update(t, dut, dc.Interface(p.Name()).Config(), i) + } +} + +// configureNetworkInstance configures vrfs DECAP_TE_VRF,ENCAP_TE_VRF_A,ENCAP_TE_VRF_B, +// TE_VRF_222, TE_VRF_111. +func configNonDefaultNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + c := &oc.Root{} + vrfs := []string{niTeVrf111, niRepairVrf, niTEVRF222} + for _, vrf := range vrfs { + ni := c.GetOrCreateNetworkInstance(vrf) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), ni) + } +} + +// configureDUT configures DUT +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) []string { + + t.Helper() + fptest.ConfigureDefaultNetworkInstance(t, dut) + if len(dut.Ports()) < 4 { + t.Fatalf("Testbed requires at least 4 ports, got %d", len(dut.Ports())) + } + if len(dut.Ports()) > 4 { + agg2.ateLagCount = uint32(len(dut.Ports()) - 3) + agg3.ateLagCount = 2 + if dut.Vendor() != ondatra.CISCO { + trafficDistributionWeights = []uint64{33, 67} + } + } + var aggIDs []string + for _, a := range []*aggPortData{agg1, agg2, agg3} { + d := gnmi.OC() + aggID := netutil.NextAggregateInterface(t, dut) + aggIDs = append(aggIDs, aggID) + portList := initializePort(t, dut, a) + + if deviations.AggregateAtomicUpdate(dut) { + clearAggregate(t, dut, aggID, a, portList) + setupAggregateAtomically(t, dut, aggID, a, portList) + } + 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) + + aggInt := &oc.Interface{Name: ygot.String(aggID)} + configAggregateDUT(dut, aggInt, a) + + aggPath := d.Interface(aggID) + fptest.LogQuery(t, aggID, aggPath.Config(), aggInt) + gnmi.Replace(t, dut, aggPath.Config(), aggInt) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, aggID, deviations.DefaultNetworkInstance(dut), 0) + } + for _, port := range portList { + i := &oc.Interface{Name: ygot.String(port.Name())} + i.Type = ethernetCsmacd + e := i.GetOrCreateEthernet() + e.AggregateId = ygot.String(aggID) + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + if port.PMD() == ondatra.PMD100GBASEFR { + e.AutoNegotiate = ygot.Bool(false) + e.DuplexMode = oc.Ethernet_DuplexMode_FULL + e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } + + configMemberDUT(dut, i, port, aggID) + iPath := d.Interface(port.Name()) + fptest.LogQuery(t, port.String(), iPath.Config(), i) + gnmi.Replace(t, dut, iPath.Config(), i) + } + + if deviations.ExplicitPortSpeed(dut) { + for _, dp := range portList { + fptest.SetPortSpeed(t, dp) + } + } + } + + configureRoutingPolicy(t, dut) + configureDUTISIS(t, dut, aggIDs) + + if !deviations.MaxEcmpPaths(dut) { + isisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + gnmi.Update(t, dut, isisPath.Global().MaxEcmpPaths().Config(), 2) + } + configureDUTBGP(t, dut, aggIDs) + return aggIDs +} + +// configDstMemberDUT enables destination ports, add other details like description, +// port and aggregate ID. +func configMemberDUT(dut *ondatra.DUTDevice, i *oc.Interface, p *ondatra.Port, aggID string) { + i.Description = ygot.String(p.String()) + i.Type = ethernetCsmacd + + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + e := i.GetOrCreateEthernet() + e.AggregateId = ygot.String(aggID) +} + +// initializePort initializes ports for aggregate on DUT +func initializePort(t *testing.T, dut *ondatra.DUTDevice, a *aggPortData) []*ondatra.Port { + var portList []*ondatra.Port + var portIdx uint32 + switch a.ateAggName { + case LAG1: + portList = append(portList, dut.Port(t, fmt.Sprintf("port%d", portIdx+1))) + dutPortList = append(dutPortList, dut.Port(t, fmt.Sprintf("port%d", portIdx+1))) + case LAG2: + for portIdx < a.ateLagCount { + portList = append(portList, dut.Port(t, fmt.Sprintf("port%d", portIdx+2))) + dutPortList = append(dutPortList, dut.Port(t, fmt.Sprintf("port%d", portIdx+2))) + portIdx++ + } + case LAG3: + for portIdx < a.ateLagCount { + portList = append(portList, dut.Port(t, fmt.Sprintf("port%d", portIdx+agg2.ateLagCount+2))) + dutPortList = append(dutPortList, dut.Port(t, fmt.Sprintf("port%d", portIdx+agg2.ateLagCount+2))) + portIdx++ + } + } + return portList +} + +// configDstAggregateDUT configures port-channel destination ports +func configAggregateDUT(dut *ondatra.DUTDevice, i *oc.Interface, a *aggPortData) { + i.Description = ygot.String(a.ateAggName) + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + a4 := s4.GetOrCreateAddress(a.dutIPv4) + a4.PrefixLength = ygot.Uint8(ipv4PLen) + + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + s6.GetOrCreateAddress(a.dutIPv6).PrefixLength = ygot.Uint8(ipv6PLen) + i.Type = ieee8023adLag + g := i.GetOrCreateAggregation() + g.LagType = lagTypeLACP +} + +// setupAggregateAtomically setup port-channel based on LAG type. +func setupAggregateAtomically(t *testing.T, dut *ondatra.DUTDevice, aggID string, agg *aggPortData, portList []*ondatra.Port) { + d := &oc.Root{} + d.GetOrCreateLacp().GetOrCreateInterface(aggID) + + aggr := d.GetOrCreateInterface(aggID) + aggr.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_LACP + aggr.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag + + for _, port := range portList { + i := d.GetOrCreateInterface(port.Name()) + i.GetOrCreateEthernet().AggregateId = ygot.String(aggID) + + i.Type = oc.IETFInterfaces_InterfaceType_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) +} + +// clearAggregate delete any previously existing members of aggregate. +func clearAggregate(t *testing.T, dut *ondatra.DUTDevice, aggID string, agg *aggPortData, portList []*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 portList { + resetBatch := &gnmi.SetBatch{} + gnmi.BatchDelete(resetBatch, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) + gnmi.BatchDelete(resetBatch, gnmi.OC().Interface(port.Name()).ForwardingViable().Config()) + resetBatch.Set(t, dut) + } +} + +// configureDUTBGP configure BGP on DUT +func configureDUTBGP(t *testing.T, dut *ondatra.DUTDevice, aggIDs []string) { + t.Helper() + + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutLoopback.IPv4) + global.As = ygot.Uint32(asn) + 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) + pgName := "BGP-PEER-GROUP1" + pg := bgp.GetOrCreatePeerGroup(pgName) + pg.PeerAs = ygot.Uint32(asn) + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pg.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{acceptRoutePolicy}) + rpl.SetImportPolicy([]string{acceptRoutePolicy}) + } else { + af4 := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + rpl := af4.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{acceptRoutePolicy}) + rpl.SetImportPolicy([]string{acceptRoutePolicy}) + + af6 := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + rpl = af6.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{acceptRoutePolicy}) + rpl.SetImportPolicy([]string{acceptRoutePolicy}) + } + + for _, a := range []*aggPortData{agg1, agg2, agg3} { + bgpNbrV4 := bgp.GetOrCreateNeighbor(a.ateIPv4) + bgpNbrV4.PeerGroup = ygot.String(pgName) + bgpNbrV4.PeerAs = ygot.Uint32(asn) + bgpNbrV4.Enabled = ygot.Bool(true) + bgpNbrV4T := bgpNbrV4.GetOrCreateTransport() + + bgpNbrV4T.LocalAddress = ygot.String(a.dutIPv4) + af4 := bgpNbrV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + + af6 := bgpNbrV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(false) + + bgpNbrV6 := bgp.GetOrCreateNeighbor(a.ateIPv6) + bgpNbrV6.PeerGroup = ygot.String(pgName) + bgpNbrV6.PeerAs = ygot.Uint32(asn) + bgpNbrV6.Enabled = ygot.Bool(true) + bgpNbrV6T := bgpNbrV6.GetOrCreateTransport() + + bgpNbrV6T.LocalAddress = ygot.String(a.dutIPv6) + af4 = bgpNbrV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(false) + af6 = bgpNbrV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + } + + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config(), niProto) +} + +// configureRoutingPolicy configure routing policy on DUT +func configureRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(acceptRoutePolicy) + stmt, _ := pdef.AppendNewStatement("20") + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinition(acceptRoutePolicy).Config(), pdef) +} + +// configureDUTISIS configure ISIS on DUT +func configureDUTISIS(t *testing.T, dut *ondatra.DUTDevice, aggIDs []string) { + t.Helper() + d := &oc.Root{} + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + + lspBit := globalISIS.GetOrCreateLspBit().GetOrCreateOverloadBit() + lspBit.SetBit = ygot.Bool(false) + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + + for _, aggID := range aggIDs { + isisIntf := isis.GetOrCreateInterface(aggID) + isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(aggID) + isisIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + + if deviations.InterfaceRefConfigUnsupported(dut) { + isisIntf.InterfaceRef = nil + } + + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Enabled = ygot.Bool(true) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Metric = ygot.Uint32(20) + isisIntfLevelAfiv6.Metric = ygot.Uint32(20) + + isisIntfLevelAfiv6.Enabled = ygot.Bool(true) + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfiv4.Enabled = nil + isisIntfLevelAfiv6.Enabled = nil + } + } + gnmi.Update(t, dut, gnmi.OC().Config(), d) + +} + +// changeMetric change metric for ISIS on Interface +func changeMetric(t *testing.T, dut *ondatra.DUTDevice, intf string, metric uint32) { + t.Logf("Updating ISIS metric of LAG2 equal to LAG3 ") + d := &oc.Root{} + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + isis := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).GetOrCreateIsis() + isisIntfLevel := isis.GetOrCreateInterface(intf).GetOrCreateLevel(2) + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv4.Metric = ygot.Uint32(metric) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv6.Metric = ygot.Uint32(metric) + + if deviations.ISISRequireSameL1MetricWithL2Metric(dut) { + l1 := isis.GetOrCreateInterface(intf).GetOrCreateLevel(1) + l1V4 := l1.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + l1V4.Metric = ygot.Uint32(metric) + l1V6 := l1.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + l1V6.Metric = ygot.Uint32(metric) + } + gnmi.Update(t, dut, gnmi.OC().Config(), d) +} + +// configureATE configure ATE +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + t.Helper() + top := gosnappi.NewConfig() + + for _, a := range []*aggPortData{agg1, agg2, agg3} { + var portList []*ondatra.Port + var portIdx uint32 + switch a.ateAggName { + case LAG1: + portList = append(portList, ate.Port(t, fmt.Sprintf("port%d", portIdx+1))) + atePortList = append(atePortList, ate.Port(t, fmt.Sprintf("port%d", portIdx+1))) + case LAG2: + for portIdx < a.ateLagCount { + portList = append(portList, ate.Port(t, fmt.Sprintf("port%d", portIdx+2))) + atePortList = append(atePortList, ate.Port(t, fmt.Sprintf("port%d", portIdx+2))) + portIdx++ + } + case LAG3: + for portIdx < a.ateLagCount { + portList = append(portList, ate.Port(t, fmt.Sprintf("port%d", portIdx+agg2.ateLagCount+2))) + atePortList = append(atePortList, ate.Port(t, fmt.Sprintf("port%d", portIdx+agg2.ateLagCount+2))) + portIdx++ + } + } + configureOTGPorts(t, ate, top, portList, a) + } + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := top.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + return top +} + +// configureOTGPorts define ATE ports +func configureOTGPorts(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, portList []*ondatra.Port, a *aggPortData) []string { + agg := top.Lags().Add().SetName(a.ateAggName) + agg.Protocol().Lacp().SetActorKey(1).SetActorSystemPriority(1).SetActorSystemId(a.ateAggMAC) + lagDev := top.Devices().Add().SetName(agg.Name() + ".Dev") + lagEth := lagDev.Ethernets().Add().SetName(agg.Name() + ".Eth").SetMac(a.ateAggMAC) + lagEth.Connection().SetLagName(agg.Name()) + lagEth.Ipv4Addresses().Add().SetName(agg.Name() + ".IPv4").SetAddress(a.ateIPv4).SetGateway(a.dutIPv4).SetPrefix(ipv4PLen) + lagEth.Ipv6Addresses().Add().SetName(agg.Name() + ".IPv6").SetAddress(a.ateIPv6).SetGateway(a.dutIPv6).SetPrefix(ipv6PLen) + for aggIdx, pList := range portList { + top.Ports().Add().SetName(pList.ID()) + if pList.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, pList.ID()) + } + newMac, err := incrementMAC(a.ateAggMAC, aggIdx+1) + if err != nil { + t.Fatal(err) + } + lagPort := agg.Ports().Add().SetPortName(pList.ID()) + lagPort.Ethernet().SetMac(newMac).SetName(a.ateAggName + "." + strconv.Itoa(aggIdx)) + lagPort.Lacp().SetActorActivity("active").SetActorPortNumber(uint32(aggIdx) + 1).SetActorPortPriority(1).SetLacpduTimeout(0) + } + + if a.ateAggName == LAG1 { + configureOTGISIS(t, lagDev, a, pfx1AdvV4) + configureOTGBGP(t, lagDev, a, pfx1AdvV4, pfx1AdvV6) + } else { + configureOTGISIS(t, lagDev, a, pfx2AdvV4) + configureOTGBGP(t, lagDev, a, pfx2AdvV4, pfx2AdvV6) + } + return pmd100GFRPorts +} + +// configureOTGISIS configure ISIS on ATE +func configureOTGISIS(t *testing.T, dev gosnappi.Device, agg *aggPortData, advV4 *ipAddr) { + t.Helper() + isis := dev.Isis().SetSystemId(agg.ateISISSysID).SetName(agg.ateAggName + ".ISIS") + isis.Basic().SetHostname(isis.Name()) + isis.Advanced().SetAreaAddresses([]string{ateAreaAddress}) + isisInt := isis.Interfaces().Add() + + isisInt = isisInt.SetEthName(dev.Ethernets(). + Items()[0].Name()).SetName(agg.ateAggName + ".ISISInt"). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2).SetMetric(20) + isisInt.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + devIsisRoutes4 := isis.V4Routes().Add().SetName(agg.ateAggName + ".isisnet4").SetLinkMetric(10) + devIsisRoutes4.Addresses().Add(). + SetAddress(advV4.ip).SetPrefix(advV4.prefix).SetCount(1).SetStep(1) + +} + +// configureOTGBGP configure BGP on ATE +func configureOTGBGP(t *testing.T, dev gosnappi.Device, agg *aggPortData, advV4, advV6 *ipAddr) { + t.Helper() + + iDutBgp := dev.Bgp().SetRouterId(agg.ateIPv4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(agg.ateAggName + ".IPv4").Peers().Add().SetName(agg.ateAggName + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(agg.dutIPv4).SetAsNumber(asn).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(false) + + iDutBgp6Peer := iDutBgp.Ipv6Interfaces().Add().SetIpv6Name(agg.ateAggName + ".IPv6").Peers().Add().SetName(agg.ateAggName + ".BGP6.peer") + iDutBgp6Peer.SetPeerAddress(agg.dutIPv6).SetAsNumber(asn).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + iDutBgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(false).SetUnicastIpv6Prefix(true) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(agg.ateAggName + ".BGP4.Route") + if agg.ateAggName != LAG1 { + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(pfx2AdvV4.ip + "1"). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(pfx3AdvV4.ip).SetPrefix(pfx3AdvV4.prefix).SetCount(1) + } else { + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(agg.ateIPv4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + } +} + +// configForwardingViable is to set forwarding viable on DUT ports +func configForwardingViable(t *testing.T, dut *ondatra.DUTDevice, dutPorts []*ondatra.Port, forwardingViable bool) { + for _, port := range dutPorts { + if forwardingViable { + gnmi.Update(t, dut, gnmi.OC().Interface(port.Name()).ForwardingViable().Config(), forwardingViable) + } else { + gnmi.Update(t, dut, gnmi.OC().Interface(port.Name()).ForwardingViable().Config(), forwardingViable) + } + } +} + +// incrementMAC uses a mac string and increments it by the given i +func incrementMAC(mac string, i int) (string, error) { + macAddr, err := net.ParseMAC(mac) + if err != nil { + return "", err + } + convMac := binary.BigEndian.Uint64(append([]byte{0, 0}, macAddr...)) + convMac = convMac + uint64(i) + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, convMac) + if err != nil { + return "", err + } + newMac := net.HardwareAddr(buf.Bytes()[2:8]) + return newMac.String(), nil +} + +func createFlows(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) []gosnappi.Flow { + for _, aggID := range []*aggPortData{agg1, agg2, agg3} { + dutAggMac = append(dutAggMac, gnmi.Get(t, ate.OTG(), gnmi.OTG().Interface(aggID.ateAggName+".Eth").Ipv4Neighbor(aggID.dutIPv4).LinkLayerAddress().State())) + } + f1V4 := configureFlows(t, top, pfx1AdvV4, pfx2AdvV4, "pfx1ToPfx2_3", agg1, []*aggPortData{agg2, agg3}, dutAggMac[0], ipRange[1]) + f2V4 := configureFlows(t, top, pfx1AdvV4, pfx4AdvV4, "pfx1ToPfx4", agg1, []*aggPortData{agg2, agg3}, dutAggMac[0], ipRange[0]) + f3V4 := configureFlows(t, top, pfx2AdvV4, pfx1AdvV4, "pfx2ToPfx1Lag2", agg2, []*aggPortData{agg1}, dutAggMac[1], ipRange[0]) + return []gosnappi.Flow{f1V4, f2V4, f3V4} +} + +// configureFlows configure flows for traffic on ATE +func configureFlows(t *testing.T, top gosnappi.Config, srcV4 *ipAddr, dstV4 *ipAddr, flowName string, srcAgg *aggPortData, + dstAgg []*aggPortData, dutAggMac string, ipRange uint32) gosnappi.Flow { + + t.Helper() + flowV4 := top.Flows().Add().SetName(flowName) + flowV4.Metrics().SetEnable(true) + flowV4.TxRx().Port(). + SetTxName(srcAgg.ateAggName) + + if flowName == "pfx2ToPfx1Lag2" || flowName == "pfx2ToPfx1Lag3" { + flowV4.TxRx().Port(). + SetRxNames([]string{dstAgg[0].ateAggName}) + } else { + flowV4.TxRx().Port(). + SetRxNames([]string{dstAgg[0].ateAggName, dstAgg[1].ateAggName}) + } + flowV4.Size().SetFixed(1500) + flowV4.Rate().SetPps(trafficPPS) + eV4 := flowV4.Packet().Add().Ethernet() + eV4.Src().SetValue(srcAgg.ateAggMAC) + eV4.Dst().SetValue(dutAggMac) + v4 := flowV4.Packet().Add().Ipv4() + v4.Src().Increment().SetStart(srcV4.ip).SetCount(v4Count) + v4.Dst().Increment().SetStart(dstV4.ip).SetCount(ipRange) + return flowV4 +} + +// installGRIBIRoutes configure route using gRIBI client +func installGRIBIRoutes(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, top gosnappi.Config) { + t.Helper() + ctx := context.Background() + gribic := dut.RawAPIs().GRIBI(t) + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(12, 0). + WithRedundancyMode(fluent.ElectedPrimaryClient).WithFIBACK() + client.Start(ctx, t) + gribi.FlushAll(client) + client.StartSending(ctx, t) + gribi.BecomeLeader(t, client) + + tcArgs := &testArgs{ + ctx: ctx, + client: client, + dut: dut, + ate: ate, + top: top, + } + + t.Logf("An IPv4Entry for %s is pointing to ATE LAG2 and Backup NHG to LAG3 via gRIBI", pfx4AdvV4.ip+"/24") + + // Programming AFT entries for backup NHG + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3000).WithNextHopNetworkInstance(niRepairVrf), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3000).AddNextHop(3000, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(1000)).WithIPAddress(agg3.ateIPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(1000)).AddNextHop(uint64(1000), uint64(1)), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF222).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF222+"/32").WithNextHopGroup(1000), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(2000).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF222). + WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(2000).AddNextHop(2000, 1), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroup(2000).WithPrefix(gribiIPv4EntryVRF111+"/32"). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut))) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryVRF111+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + // Programming AFT entries for prefixes Encap in Default VRF + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(1)).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF111). + WithNextHopNetworkInstance(niTeVrf111), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(1)).AddNextHop(uint64(1), uint64(1)), + + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithPrefix(pfx4AdvV4.ip+"/24").WithNextHopGroup(1). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut))) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(pfx4AdvV4.ip+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + // Programming AFT entries for encapped prefixes "203.0.113.1/32" + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(101)).WithIPAddress(agg2.ateIPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithID(uint64(101)).AddNextHop(uint64(101), uint64(1)).WithBackupNHG(3000), + + fluent.IPv4Entry().WithNetworkInstance(niTeVrf111). + WithPrefix(gribiIPv4EntryVRF111+"/32").WithNextHopGroup(101). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)), + ) + + if err := awaitTimeout(tcArgs.ctx, t, tcArgs.client, 5*time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, tcArgs.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryVRF111+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) +} + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +// startTraffic start traffic on ATE +func startTraffic(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, top gosnappi.Config) { + t.Helper() + capturePktsBeforeTraffic(t, dut, dutPortList) + time.Sleep(10 * time.Second) + ate.OTG().StartTraffic(t) + time.Sleep(time.Minute) + ate.OTG().StopTraffic(t) + time.Sleep(time.Second * 40) + otgutils.LogFlowMetrics(t, ate.OTG(), top) + otgutils.LogLAGMetrics(t, ate.OTG(), top) + otgutils.LogPortMetrics(t, ate.OTG(), top) +} + +// capturePktsBeforeTraffic capture the pkts before traffic on DUT Ports +func capturePktsBeforeTraffic(t *testing.T, dut *ondatra.DUTDevice, dutPortList []*ondatra.Port) { + rxPktsBeforeTraffic = map[*ondatra.Port]uint64{} + txPktsBeforeTraffic = map[*ondatra.Port]uint64{} + for _, port := range dutPortList { + rxPktsBeforeTraffic[port] = gnmi.Get(t, dut, gnmi.OC().Interface(port.Name()).Counters().InPkts().State()) + txPktsBeforeTraffic[port] = gnmi.Get(t, dut, gnmi.OC().Interface(port.Name()).Counters().OutPkts().State()) + } +} + +// verifyTrafficFlow verify the each flow on ATE +func verifyTrafficFlow(t *testing.T, ate *ondatra.ATEDevice, flows []gosnappi.Flow, status bool) bool { + if flows[0].Name() == "pfx1ToPfx4" { + rxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flows[0].Name()).Counters().InPkts().State()) + txPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flows[0].Name()).Counters().OutPkts().State()) + lostPkt := txPkts - rxPkts + + if status { + if got := (lostPkt * 100 / txPkts); got >= 51 { + return false + } + } else if got := (lostPkt * 100 / txPkts); got > 0 { + return false + } + } else { + for _, flow := range flows { + rxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().InPkts().State()) + txPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State()) + lostPkt := txPkts - rxPkts + + if got := (lostPkt * 100 / txPkts); got > 0 { + return false + } + } + } + return true +} + +// 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, isisInstance).Isis() + intf := isisPath.Interface(intfName) + query := intf.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, 90*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 +} + +// checkBidirectionalTraffic verify the bidirectional traffic on DUT ports. +func checkBidirectionalTraffic(t *testing.T, dut *ondatra.DUTDevice, portList []*ondatra.Port) error { + + for _, port := range portList { + txPkts := gnmi.Get(t, dut, gnmi.OC().Interface(port.Name()).Counters().OutPkts().State()) + rxPkts := gnmi.Get(t, dut, gnmi.OC().Interface(port.Name()).Counters().InPkts().State()) + if got := (rxPkts - rxPktsBeforeTraffic[port]) / 100; got == 0 { + return fmt.Errorf("No Packet received, LossPct on Port %s: got %d", port.Name(), got) + } + if got := (txPkts - txPktsBeforeTraffic[port]) / 100; got == 0 { + return fmt.Errorf("No Packet transmitted, LossPct on Port %s: got %d", port.Name(), got) + } + } + return nil +} + +// confirmNonViableForwardingTraffic verify the traffic received on DUT +// interfaces and transmitted to ATE-1 +func confirmNonViableForwardingTraffic(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, + atePort []*ondatra.Port, dutPort []*ondatra.Port) error { + + // Ensure no traffic is transmitted out of DUT ports with Forwarding Viable False + for _, port := range atePort { + rxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(port.ID()).Counters().InFrames().State()) + if got := rxPkts / 100; got > 0 { + return fmt.Errorf("Packets are transmiited out of %s: got %d, want 0", port.Name(), got) + } + } + // Ensure that traffic is delivered to ATE-1 port1 + for _, port := range dutPort { + rxPkts := gnmi.Get(t, dut, gnmi.OC().Interface(port.Name()).Counters().InPkts().State()) - rxPktsBeforeTraffic[port] + txPkts := gnmi.Get(t, dut, gnmi.OC().Interface(dutPortList[0].Name()).Counters().OutPkts().State()) - txPktsBeforeTraffic[port] + if got := rxPkts / 100; got == 0 { + return fmt.Errorf("No Packet received on Interface %s: got %d, want packet", port.Name(), got) + } + if got := txPkts / 100; got == 0 { + return fmt.Errorf("No Packet transmitted on Interface %s: got %d, want packet", port.Name(), got) + } + } + return nil +} + +// validateLag3Traffic to ensure traffic Received/Transmitted on DUT LAG_3 +func validateLag3Traffic(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, dutPortList []*ondatra.Port) bool { + result := false + for _, port := range dutPortList { + rxPkts := gnmi.Get(t, dut, gnmi.OC().Interface(port.Name()).Counters().InPkts().State()) - rxPktsBeforeTraffic[port] + txPkts := gnmi.Get(t, dut, gnmi.OC().Interface(port.Name()).Counters().OutPkts().State()) - txPktsBeforeTraffic[port] + if got := rxPkts / 100; got > 0 { + if got := txPkts / 100; got > 0 { + result = true + } + } else { + result = false + } + } + return result +} + +// trafficRXWeights to ensure 50:50 Load Balancing +func trafficRXWeights(t *testing.T, ate *ondatra.ATEDevice, aggNames []string, flow gosnappi.Flow) []uint64 { + t.Helper() + var rxs []uint64 + for _, aggName := range aggNames { + metrics := gnmi.Get(t, ate.OTG(), gnmi.OTG().Lag(aggName).State()) + rxs = append(rxs, (metrics.GetCounters().GetInFrames())) + } + var total uint64 + for _, rx := range rxs { + total += rx + } + for idx, rx := range rxs { + rxs[idx] = (rx * 100) / total + } + return rxs +} diff --git a/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/metadata.textproto b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/metadata.textproto new file mode 100644 index 00000000000..a7ce571869e --- /dev/null +++ b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/metadata.textproto @@ -0,0 +1,55 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "2beaac46-9b7b-49c4-9bde-62ad530aa5c6" +plan_id: "RT-5.7" +description: "Aggregate Not Viable All" +testbed: TESTBED_DUT_ATE_8LINKS + +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + aggregate_atomic_update: true + interface_enabled: true + missing_value_for_defaults: true + missing_isis_interface_afi_safi_enable: true + } +} + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + interface_ref_config_unsupported:true + wecmp_auto_unsupported: true + isis_loopback_required: true + weighted_ecmp_fixed_packet_verification: true + interface_ref_interface_id_format: true + } +} + +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + omit_l2_mtu: true + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + missing_isis_interface_afi_safi_enable: true + isis_require_same_l1_metric_with_l2_metric: true + route_policy_under_afi_unsupported: true + static_protocol_name: "STATIC" + aggregate_atomic_update: true + missing_value_for_defaults: true + max_ecmp_paths: true + explicit_interface_in_default_vrf: false + } +} diff --git a/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/aggregate_forwarding_viable_test.go b/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/aggregate_forwarding_viable_test.go index 9d6e1802c2d..3e6a376eb05 100644 --- a/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/aggregate_forwarding_viable_test.go +++ b/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/aggregate_forwarding_viable_test.go @@ -49,6 +49,7 @@ import ( "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" ) @@ -342,8 +343,8 @@ func (tc *testArgs) configureATE(t *testing.T) { // Adding the rest of the ports to the configuration and to the LAG agg := tc.top.Lags().Add().SetName(ateDst.Name) if tc.lagType == lagTypeSTATIC { - lagId, _ := strconv.Atoi(tc.aggID) - agg.Protocol().SetChoice("static").Static().SetLagId(uint32(lagId)) + lagID, _ := strconv.Atoi(tc.aggID) + agg.Protocol().Static().SetLagId(uint32(lagID)) for i, p := range tc.atePorts[1:] { port := tc.top.Ports().Add().SetName(p.ID()) newMac, err := incrementMAC(ateDst.MAC, i+1) @@ -353,7 +354,6 @@ func (tc *testArgs) configureATE(t *testing.T) { agg.Ports().Add().SetPortName(port.Name()).Ethernet().SetMac(newMac).SetName("LAGRx-" + strconv.Itoa(i)) } } else { - agg.Protocol().SetChoice("lacp") agg.Protocol().Lacp().SetActorKey(1).SetActorSystemPriority(1).SetActorSystemId(ateDst.MAC) for i, p := range tc.atePorts[1:] { port := tc.top.Ports().Add().SetName(p.ID()) @@ -477,7 +477,7 @@ func debugATEFlows(t *testing.T, ate *ondatra.ATEDevice, flow gosnappi.Flow, lp func (tc *testArgs) verifyCounterDiff(t *testing.T, before, after []*oc.Interface_Counters, want []float64) { b := &strings.Builder{} w := tabwriter.NewWriter(b, 0, 0, 1, ' ', 0) - approxOpt := cmpopts.EquateApprox(0 /* frac */, 0.01 /* absolute */) + approxOpt := cmpopts.EquateApprox(0 /* frac */, 0.1 /* absolute */) fmt.Fprint(w, "Interface Counter Deltas\n\n") fmt.Fprint(w, "Name\tInPkts\tInOctets\tOutPkts\tOutOctets\n") allOutPkts := []uint64{} @@ -520,17 +520,18 @@ func (tc *testArgs) testAggregateForwardingFlow(t *testing.T, forwardingViable b t.Log("First port does not forward traffic because it is marked as not viable.") gnmi.Update(t, tc.dut, gnmi.OC().Interface(pName).ForwardingViable().Config(), forwardingViable) } - - v := gnmi.Lookup(t, tc.dut, gnmi.OC().Interface(pName).ForwardingViable().State()) - got, present := v.Val() - t.Logf("First port %s forwarding-viable: got %v, present %v, want %v", pName, got, present, forwardingViable) - switch { - case present && got != forwardingViable: - t.Errorf("First port %s forwarding-viable: got %t, want %t", pName, got, forwardingViable) - case !present && !deviations.MissingValueForDefaults(tc.dut): - t.Errorf("First port %s forwarding-viable value not found", pName) - case !present && deviations.MissingValueForDefaults(tc.dut) && !forwardingViable: - t.Errorf("First port %s forwarding-viable defaults true not equal to %t", pName, forwardingViable) + v, ok := gnmi.Watch(t, tc.dut, gnmi.OC().Interface(pName).ForwardingViable().State(), time.Minute, func(v *ygnmi.Value[bool]) bool { + val, ok := v.Val() + if !ok && !deviations.MissingValueForDefaults(tc.dut) { + return false + } + if !ok && deviations.MissingValueForDefaults(tc.dut) { + return forwardingViable + } + return val == forwardingViable + }).Await(t) + if !ok { + t.Errorf("First port %s forwarding-viable mismatch: got %v, want %v", pName, v, forwardingViable) } i1 := ateSrc.Name @@ -551,10 +552,11 @@ func (tc *testArgs) testAggregateForwardingFlow(t *testing.T, forwardingViable b tc.ate.OTG().PushConfig(t, tc.top) tc.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, tc.ate.OTG(), tc.top, "IPv4") beforeTrafficCounters := tc.getCounters(t, "before") tc.ate.OTG().StartTraffic(t) - time.Sleep(15 * time.Second) + time.Sleep(time.Minute) tc.ate.OTG().StopTraffic(t) otgutils.LogFlowMetrics(t, tc.ate.OTG(), tc.top) diff --git a/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/metadata.textproto b/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/metadata.textproto index 249c72cd7a5..d56d764fd36 100644 --- a/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/metadata.textproto +++ b/feature/interface/aggregate/otg_tests/aggregate_forwarding_viable_test/metadata.textproto @@ -14,6 +14,7 @@ platform_exceptions: { explicit_interface_in_default_vrf: true aggregate_atomic_update: true interface_enabled: true + missing_value_for_defaults: true } } platform_exceptions: { @@ -24,5 +25,7 @@ platform_exceptions: { aggregate_atomic_update: true interface_enabled: true default_network_instance: "default" + missing_value_for_defaults: true } } + diff --git a/feature/interface/aggregate/otg_tests/aggregate_test/README.md b/feature/interface/aggregate/otg_tests/aggregate_test/README.md index ae7f2e62baf..feae819cfd9 100644 --- a/feature/interface/aggregate/otg_tests/aggregate_test/README.md +++ b/feature/interface/aggregate/otg_tests/aggregate_test/README.md @@ -40,8 +40,13 @@ Validate link operational status of Static LAG and LACP. * TODO: /lacp/interfaces/interface/members/member/state/counters/lacp-out-pkts * TODO: /lacp/interfaces/interface/members/member/state/counters/lacp-rx-errors -* TODO: /lacp/interfaces/interface/members/member/state/oper-key -* TODO: /lacp/interfaces/interface/members/member/state/partner-id -* TODO: /lacp/interfaces/interface/members/member/state/system-id -* TODO: /lacp/interfaces/interface/members/member/state/port-num +* /lacp/interfaces/interface/name +* /lacp/interfaces/interface/state/name +* /lacp/interfaces/interface/members/member/interface +* /lacp/interfaces/interface/members/member/state/interface +* /lacp/interfaces/interface/members/member/state/oper-key +* /lacp/interfaces/interface/members/member/state/partner-key +* /lacp/interfaces/interface/members/member/state/partner-id +* /lacp/interfaces/interface/members/member/state/system-id +* /lacp/interfaces/interface/members/member/state/port-num * /interfaces/interface/ethernet/state/aggregate-id diff --git a/feature/interface/aggregate/otg_tests/aggregate_test/aggregate_test.go b/feature/interface/aggregate/otg_tests/aggregate_test/aggregate_test.go index b222d8e0bbf..51f9f345db9 100644 --- a/feature/interface/aggregate/otg_tests/aggregate_test/aggregate_test.go +++ b/feature/interface/aggregate/otg_tests/aggregate_test/aggregate_test.go @@ -21,6 +21,7 @@ import ( "net" "sort" "strconv" + "strings" "testing" "time" @@ -116,7 +117,12 @@ type testCase struct { ate *ondatra.ATEDevice top gosnappi.Config + // dutPorts is the set of ports the DUT -- the first (i.e., dutPorts[0]) + // is not configured in the aggregate interface. dutPorts []*ondatra.Port + // atePorts is the set of ports on the ATE -- the first, as with the DUT + // is not configured in the aggregate interface. + // is not configured in the aggregate interface. atePorts []*ondatra.Port aggID string } @@ -270,7 +276,7 @@ func (tc *testCase) configureATE(t *testing.T) { tc.top.Ports().Add().SetName(p0.ID()) srcDev := tc.top.Devices().Add().SetName(ateSrc.Name) srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p0.ID()) + srcEth.Connection().SetPortName(p0.ID()) srcEth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4").SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6").SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) @@ -278,7 +284,7 @@ func (tc *testCase) configureATE(t *testing.T) { agg := tc.top.Lags().Add().SetName(ateDst.Name) if tc.lagType == lagTypeSTATIC { lagId, _ := strconv.Atoi(tc.aggID) - agg.Protocol().SetChoice("static").Static().SetLagId(uint32(lagId)) + agg.Protocol().Static().SetLagId(uint32(lagId)) for i, p := range tc.atePorts[1:] { port := tc.top.Ports().Add().SetName(p.ID()) newMac, err := incrementMAC(ateDst.MAC, i+1) @@ -288,7 +294,6 @@ func (tc *testCase) configureATE(t *testing.T) { agg.Ports().Add().SetPortName(port.Name()).Ethernet().SetMac(newMac).SetName("LAGRx-" + strconv.Itoa(i)) } } else { - agg.Protocol().SetChoice("lacp") agg.Protocol().Lacp().SetActorKey(1).SetActorSystemPriority(1).SetActorSystemId(ateDst.MAC) for i, p := range tc.atePorts[1:] { port := tc.top.Ports().Add().SetName(p.ID()) @@ -319,7 +324,7 @@ func (tc *testCase) configureATE(t *testing.T) { dstDev := tc.top.Devices().Add().SetName(agg.Name() + ".dev") dstEth := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) - dstEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.LAG_NAME).SetLagName(agg.Name()) + dstEth.Connection().SetLagName(agg.Name()) dstEth.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4").SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) dstEth.Ipv6Addresses().Add().SetName(ateDst.Name + ".IPv6").SetAddress(ateDst.IPv6).SetGateway(dutDst.IPv6).SetPrefix(uint32(ateDst.IPv6Len)) @@ -338,6 +343,68 @@ const ( dynamic = oc.IfIp_NeighborOrigin_DYNAMIC ) +// verifyLACPTelemetry validats that the telemetry matches between the ATE's LACP +// and the device LACP for values that correspond to one another. +func (tc *testCase) verifyLACPTelemetry(t *testing.T) { + // If the test case is validating static LAGs then we do not check + // the LACP telemetry since it isn't populated. + if tc.lagType != oc.IfAggregate_AggregationType_LACP { + return + } + + gotLAG := gnmi.Get(t, tc.dut, gnmi.OC().Lacp().Interface(tc.aggID).State()) + if got := gotLAG.GetName(); got != tc.aggID { + t.Errorf("DUT LAG had incorrect name, got: %s, want: %s", got, tc.aggID) + } + + for i := 1; i < len(tc.dutPorts); i++ { + // The ports in dutPort correspond 1:1 with the ports in atePort. + // port1 is reserved for traffic injection. + dutPort := tc.dutPorts[i] + atePort := tc.atePorts[i] + + // We are validating LACP statistics, so we need to validate each member port. + // The ATE ports are configured with the ID rather than the name. + ateLACP := gnmi.Get(t, tc.ate.OTG(), gnmi.OTG().Lacp().LagMember(atePort.ID()).State()) + dutLACP := gnmi.Get(t, tc.dut, gnmi.OC().Lacp().Interface(tc.aggID).Member(dutPort.Name()).State()) + + if got := dutLACP.GetInterface(); got != dutPort.Name() { + t.Errorf("DUT LAG had incorrect name, got: %s, want: %s", got, tc.aggID) + } + + // We want to check: + // port-num of the ATE matches partner-port-num of the DUT. + // oper-key of the ATE matches partner-key of the DUT. + // partner-id of the ATE matches system-id of the DUT. + // And vice versa so that we know that the telemetry from both + // sides matches. + + if ateLACP.PortNum == nil || dutLACP.PartnerPortNum == nil || *ateLACP.PortNum != *dutLACP.PartnerPortNum { + t.Errorf("DUT LAG %s: ATE port-num (%d) did not match DUT partner-port-num (%d)", tc.aggID, ateLACP.PortNum, dutLACP.PartnerPortNum) + } + + if dutLACP.PortNum == nil || ateLACP.PartnerPortNum == nil || *dutLACP.PortNum != *ateLACP.PartnerPortNum { + t.Errorf("DUT LAG %s: ATE partner-port-num (%d) did not match DUT port-num (%d)", tc.aggID, ateLACP.PartnerPortNum, dutLACP.PortNum) + } + + if ateLACP.OperKey == nil || dutLACP.PartnerKey == nil || *ateLACP.OperKey != *dutLACP.PartnerKey { + t.Errorf("DUT LAG %s: ATE oper-key (%d) did not match DUT partner-key (%d)", tc.aggID, ateLACP.OperKey, dutLACP.PartnerKey) + } + + if dutLACP.OperKey == nil || ateLACP.PartnerKey == nil || *dutLACP.OperKey != *ateLACP.PartnerKey { + t.Errorf("DUT LAG %s: ATE partner-key (%d) did not match DUT oper-key (%d)", tc.aggID, ateLACP.PartnerKey, dutLACP.OperKey) + } + + if ateLACP.PartnerId == nil || dutLACP.SystemId == nil || !strings.EqualFold(*ateLACP.PartnerId, *dutLACP.SystemId) { + t.Errorf("DUT LAG %s: ATE partner-id (%s) did not match DUT system-id (%s)", tc.aggID, *ateLACP.PartnerId, *dutLACP.SystemId) + } + + if dutLACP.PartnerId == nil || ateLACP.SystemId == nil || !strings.EqualFold(*ateLACP.PartnerId, *dutLACP.SystemId) { + t.Errorf("DUT LAG %s: ATE system-id (%s) did not match DUT partner-id (%s)", tc.aggID, *ateLACP.SystemId, *dutLACP.PartnerId) + } + } +} + func (tc *testCase) verifyAggID(t *testing.T, dp *ondatra.Port) { dip := gnmi.OC().Interface(dp.Name()) di := gnmi.Get(t, tc.dut, dip.State()) @@ -544,6 +611,8 @@ func TestNegotiation(t *testing.T) { tc.configureATE(t) t.Run("VerifyATE", tc.verifyATE) + t.Run("VerifyLACPTelemetry", tc.verifyLACPTelemetry) + t.Run("MinLinks", tc.verifyMinLinks) }) diff --git a/feature/interface/aggregate/otg_tests/balancing_test/README.md b/feature/interface/aggregate/otg_tests/balancing_test/README.md index aceda7e25b6..d09453272e8 100644 --- a/feature/interface/aggregate/otg_tests/balancing_test/README.md +++ b/feature/interface/aggregate/otg_tests/balancing_test/README.md @@ -41,3 +41,26 @@ None ## Minimum DUT platform requirement vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths and RPC intended to be covered by this test. + +```yaml +paths: + /interfaces/interface/ethernet/config/aggregate-id: + /interfaces/interface/aggregation/config/lag-type: + /lacp/config/system-priority: + /lacp/interfaces/interface/config/name: + /lacp/interfaces/interface/config/interval: + /lacp/interfaces/interface/config/lacp-mode: + /lacp/interfaces/interface/config/system-id-mac: + /lacp/interfaces/interface/config/system-priority: + +rpcs: + gnmi: + gNMI.Set: + union_replace: false + gNMI.Subscribe: + on_change: false +``` 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 435f53f07f6..3ea546dc932 100644 --- a/feature/interface/aggregate/otg_tests/balancing_test/balancing_test.go +++ b/feature/interface/aggregate/otg_tests/balancing_test/balancing_test.go @@ -76,6 +76,8 @@ const ( opUp = oc.Interface_OperStatus_UP ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag + trafficPps = 10000 + totalPackets = 200000 ) var ( @@ -241,45 +243,45 @@ func (tc *testCase) verifyLAG(t *testing.T) { if tc.lagType == oc.IfAggregate_AggregationType_LACP { t.Logf("Waiting LAG DUT ports to start collecting and distributing") - for _, dp := range tc.dutPorts[1:] { - _, ok := gnmi.WatchAll(t, tc.dut, gnmi.OC().Lacp().InterfaceAny().Member(dp.Name()).Collecting().State(), time.Minute, func(val *ygnmi.Value[bool]) bool { - col, present := val.Val() - return present && col - }).Await(t) - if !ok { - t.Fatalf("DUT LAG port %v is not collecting", dp) + watchD := gnmi.WatchAll[*oc.Lacp_Interface](t, tc.dut, gnmi.OC().Lacp().InterfaceAny().State(), time.Minute, func(val *ygnmi.Value[*oc.Lacp_Interface]) bool { + col, present := val.Val() + if !present || col == nil { + return false } - _, ok = gnmi.WatchAll(t, tc.dut, gnmi.OC().Lacp().InterfaceAny().Member(dp.Name()).Distributing().State(), time.Minute, func(val *ygnmi.Value[bool]) bool { - dist, present := val.Val() - return present && dist - }).Await(t) - if !ok { - t.Fatalf("DUT LAG port %v is not distributing", dp) + for _, dp := range tc.dutPorts[1:] { + m := col.GetMember(dp.Name()) + if !m.GetCollecting() || !m.GetDistributing() { + return false + } } + return true + }) + if _, ok := watchD.Await(t); !ok { + t.Fatalf("DUT LAG is not ready to collect and distribute") } + t.Logf("Waiting LAG OTG ports to start collecting and distributing") - for _, p := range tc.atePorts[1:] { - _, ok := gnmi.Watch(t, tc.ate.OTG(), gnmi.OTG().Lacp().LagMember(p.ID()).Collecting().State(), time.Minute, func(val *ygnmi.Value[bool]) bool { - col, present := val.Val() - t.Logf("collecting for port %v is %v and present is %v", p.ID(), col, present) - return present && col - }).Await(t) - if !ok { - t.Fatalf("OTG LAG port %v is not collecting", p) + watchO := gnmi.Watch[*otgtelemetry.Lacp](t, tc.ate.OTG(), gnmi.OTG().Lacp().State(), 2*time.Minute, func(val *ygnmi.Value[*otgtelemetry.Lacp]) bool { + col, present := val.Val() + if !present || col == nil { + return false } - _, ok = gnmi.Watch(t, tc.ate.OTG(), gnmi.OTG().Lacp().LagMember(p.ID()).Distributing().State(), time.Minute, func(val *ygnmi.Value[bool]) bool { - dist, present := val.Val() - t.Logf("distributing for port %v is %v and present is %v", p.ID(), dist, present) - return present && dist - }).Await(t) - if !ok { - t.Fatalf("OTG LAG port %v is not distributing", p) + for _, ap := range tc.atePorts[1:] { + m := col.GetLagMember(ap.ID()) + if !m.GetCollecting() || !m.GetDistributing() { + return false + } } + return true + }) + if _, ok := watchO.Await(t); !ok { + t.Fatalf("OTG LAG is not ready to collect and distribute") } + otgutils.LogLACPMetrics(t, tc.ate.OTG(), tc.top) } - otgutils.LogLAGMetrics(t, tc.ate.OTG(), tc.top) + otgutils.LogLAGMetrics(t, tc.ate.OTG(), tc.top) } func (tc *testCase) configureDUT(t *testing.T) { @@ -381,7 +383,7 @@ func (tc *testCase) configureATE(t *testing.T) { tc.top.Ports().Add().SetName(p0.ID()) d0 := tc.top.Devices().Add().SetName(ateSrc.Name) srcEth := d0.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p0.ID()) + srcEth.Connection().SetPortName(p0.ID()) srcEth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4").SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6").SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) @@ -417,7 +419,7 @@ func (tc *testCase) configureATE(t *testing.T) { dstDev := tc.top.Devices().Add().SetName(agg.Name() + ".dev") dstEth := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) - dstEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.LAG_NAME).SetLagName(agg.Name()) + dstEth.Connection().SetLagName(agg.Name()) dstEth.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4").SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) dstEth.Ipv6Addresses().Add().SetName(ateDst.Name + ".IPv6").SetAddress(ateDst.IPv6).SetGateway(dutDst.IPv6).SetPrefix(uint32(ateDst.IPv6Len)) @@ -439,7 +441,7 @@ func normalize(xs []uint64) (ys []float64, sum uint64) { return ys, sum } -var approxOpt = cmpopts.EquateApprox(0 /* frac */, 0.01 /* absolute */) +var approxOpt = cmpopts.EquateApprox(0 /* frac */, 0.1 /* absolute */) // portWants converts the nextHop wanted weights to per-port wanted // weights listed in the same order as atePorts. @@ -502,8 +504,11 @@ func (tc *testCase) testFlow(t *testing.T, l3header string) { flow := tc.top.Flows().Add().SetName(l3header) flow.Metrics().SetEnable(true) flow.Size().SetFixed(128) + flow.Rate().SetPps(trafficPps) + flow.Duration().FixedPackets().SetPackets(totalPackets) flow.Packet().Add().Ethernet().Src().SetValue(ateSrc.MAC) + ipType := "IPv4" if l3header == "ipv4" { flow.TxRx().Device().SetTxNames([]string{i1 + ".IPv4"}).SetRxNames([]string{i2 + ".IPv4"}) v4 := flow.Packet().Add().Ipv4() @@ -522,6 +527,7 @@ func (tc *testCase) testFlow(t *testing.T, l3header string) { v6 := flow.Packet().Add().Ipv6() v6.Src().SetValue(ateSrc.IPv6) v6.Dst().SetValue(ateDst.IPv6) + ipType = "IPv6" } if l3header == "ipv6inipv4" { flow.TxRx().Device().SetTxNames([]string{i1 + ".IPv4"}).SetRxNames([]string{i2 + ".IPv4"}) @@ -539,7 +545,7 @@ func (tc *testCase) testFlow(t *testing.T, l3header string) { v6.FlowLabel().SetValues(generateRandomFlowLabelList(250000)) v6.Src().SetValue(ateSrc.IPv6) v6.Dst().SetValue(ateDst.IPv6) - + ipType = "IPv6" } tcp := flow.Packet().Add().Tcp() @@ -548,12 +554,14 @@ func (tc *testCase) testFlow(t *testing.T, l3header string) { tc.ate.OTG().PushConfig(t, tc.top) tc.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, tc.ate.OTG(), tc.top, ipType) + tc.verifyLAG(t) beforeTrafficCounters := tc.getCounters(t, "before") tc.ate.OTG().StartTraffic(t) - time.Sleep(15 * time.Second) + time.Sleep(20 * time.Second) tc.ate.OTG().StopTraffic(t) otgutils.LogPortMetrics(t, tc.ate.OTG(), tc.top) @@ -565,9 +573,32 @@ func (tc *testCase) testFlow(t *testing.T, l3header string) { if pkts == 0 { t.Errorf("Flow sent packets: got %v, want non zero", pkts) } + + if deviations.InterfaceCountersUpdateDelayed(tc.dut) { + batch := gnmi.OCBatch() + for _, port := range tc.dutPorts[1:] { + batch.AddPaths(gnmi.OC().Interface(port.Name()).Counters()) + } + + _, ok := gnmi.Watch(t, tc.dut, batch.State(), time.Second*60, func(v *ygnmi.Value[*oc.Root]) bool { + got, present := v.Val() + if !present { + return false + } + totalPks := uint64(0) + for _, port := range tc.dutPorts[1:] { + totalPks += got.GetInterface(port.Name()).GetCounters().GetOutPkts() - beforeTrafficCounters[port.Name()].GetOutPkts() + } + return totalPks >= pkts + }).Await(t) + + if !ok { + t.Fatalf("Counters did not update in time") + } + } + afterTrafficCounters := tc.getCounters(t, "after") tc.verifyCounterDiff(t, beforeTrafficCounters, afterTrafficCounters) - } func (tc *testCase) getCounters(t *testing.T, when string) map[string]*oc.Interface_Counters { diff --git a/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto b/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto index 68c8eb17c3a..f29298037aa 100644 --- a/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto +++ b/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto @@ -11,6 +11,7 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + interface_counters_update_delayed: true } } platform_exceptions: { diff --git a/feature/interface/holdtime/feature.textproto b/feature/interface/holdtime/feature.textproto index 881dc8f0047..26920b39624 100644 --- a/feature/interface/holdtime/feature.textproto +++ b/feature/interface/holdtime/feature.textproto @@ -11,6 +11,9 @@ # 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" 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 new file mode 100644 index 00000000000..913505b0063 --- /dev/null +++ b/feature/interface/holdtime/otg_tests/holdtime_test/holddown_timers_test.go @@ -0,0 +1,616 @@ +package holddown_times_test + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/openconfig/ondatra/netutil" + + "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/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + lagName = "LAGRx" // OTG LAG NAME + upTimer = 5000 + downTimer = 300 + toleranceMS = 200 // Define the tolerance in milliseconds + +) + +var ( + aggID string + dutPort1Intf *ondatra.Port + ateSrc = attrs.Attributes{ + Name: "ateSrc", + MAC: "02:11:01:00:00:01", + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutDst = attrs.Attributes{ + Desc: "DUT to ATE destination", + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + ateDst = attrs.Attributes{ + Name: "ateDst", + MAC: "02:12:01:00:00:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, aggPorts []*ondatra.Port, aggID string) { + t.Helper() + + agg := dutDst.NewOCInterface(aggID, dut) + agg.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_STATIC + gnmi.Replace(t, dut, gnmi.OC().Interface(aggID).Config(), agg) + + for _, port := range aggPorts { + d := &oc.Root{} + + i := d.GetOrCreateInterface(port.Name()) + i.GetOrCreateEthernet().AggregateId = ygot.String(aggID) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + gnmi.Replace(t, dut, gnmi.OC().Interface(port.Name()).Config(), i) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice, aggID string) { + t.Helper() + fptest.ConfigureDefaultNetworkInstance(t, dut) + dutAggPorts := []*ondatra.Port{ + dut.Port(t, "port1"), + } + configureDUTBundle(t, dut, dutAggPorts, aggID) + +} + +func configureOTG(t *testing.T, + ate *ondatra.ATEDevice, + aggID string) { + t.Helper() + + top := gosnappi.NewConfig() + + ateAggPorts := []*ondatra.Port{ + ate.Port(t, "port1"), + } + configureOTGBundle(t, top, ateAggPorts, aggID) + + t.Log(top.String()) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + + OTGInterfaceUP(t, ate) +} + +func OTGInterfaceUP(t *testing.T, + ate *ondatra.ATEDevice) { + + p1 := ondatra.ATE(t, "ate").Port(t, "port1") + portStateAction := gosnappi.NewControlState() + + // make sure interface is not down + portStateAction.Port().Link().SetPortNames([]string{p1.ID()}).SetState(gosnappi.StatePortLinkState.UP) + ate.OTG().SetControlState(t, portStateAction) +} + +func OTGInterfaceDOWN(t *testing.T, + ate *ondatra.ATEDevice, + dut *ondatra.DUTDevice) time.Time { + + p1 := ondatra.ATE(t, "ate").Port(t, "port1") + portStateAction := gosnappi.NewControlState() + timestamp := gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State()) + + timeObj, err := time.Parse(time.RFC3339Nano, timestamp) + if err != nil { + t.Errorf("Failed to parse time string: %v", timestamp) + return timeObj + } + + // make sure interface is not down + portStateAction.Port().Link().SetPortNames([]string{p1.ID()}).SetState(gosnappi.StatePortLinkState.DOWN) + ate.OTG().SetControlState(t, portStateAction) + + return timeObj +} + +func configureOTGBundle(t *testing.T, + + top gosnappi.Config, + aggPorts []*ondatra.Port, + aggID string) { + t.Helper() + agg := top.Lags().Add().SetName(lagName) + lagID, _ := strconv.Atoi(aggID) + agg.Protocol().Static().SetLagId(uint32(lagID)) + + for i, p := range aggPorts { + port := top.Ports().Add().SetName(p.ID()) + agg.Ports().Add().SetPortName(port.Name()).Ethernet().SetMac(ateSrc.MAC).SetName("LAGRx-" + strconv.Itoa(i)) + } + + dstDev := top.Devices().Add().SetName(agg.Name() + ".dev") + dstEth := dstDev.Ethernets().Add().SetName(lagName + ".Eth").SetMac(ateDst.MAC) + dstEth.Connection().SetLagName(agg.Name()) + dstEth.Ipv4Addresses().Add().SetName(lagName + ".IPv4").SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) +} + +func displaySummaryTable(t *testing.T, + preActionTS, + postActionTS string, + actualDurationInMS, + minToleranceInMS, + maxToleranceInMS int64, + expectedOperStatus, + actualOperStatus string, + pass bool) { + + result := "FAIL" + if pass { + result = "PASS" + } + + // Prepare the strings for output. + expectedDurationStr := "300ms" // Assuming this is a constant value + + minToleranceStr := strconv.Itoa(int(minToleranceInMS)) + "ms" + maxToleranceStr := strconv.Itoa(int(maxToleranceInMS)) + "ms" + actualDurationStr := strconv.Itoa(int(actualDurationInMS)) + "ms" + + // Create a slice of metrics and corresponding values. + metrics := []string{"Pre-action TS", + "Post-action TS", + "Expected Duration", + "Actual Duration", + "Min Tolerance", + "Max Tolerance", + "Expected Oper Status", + "Actual Oper Status", + "Result"} + values := []string{preActionTS, + postActionTS, + expectedDurationStr, + actualDurationStr, + minToleranceStr, + maxToleranceStr, + expectedOperStatus, + actualOperStatus, + result} + + // Find the maximum width for the metrics to align the values. + maxMetricWidth := 0 + for _, metric := range metrics { + if len(metric) > maxMetricWidth { + maxMetricWidth = len(metric) + } + } + + // Create the vertical table. + table := "" + for i, metric := range metrics { + table += fmt.Sprintf("%-*s: %s\n", maxMetricWidth, metric, values[i]) + } + + t.Logf("\n%s", table) +} + +func flapOTGInterface(t *testing.T, + ate *ondatra.ATEDevice, + dut *ondatra.DUTDevice, + actionState string) (time.Time, time.Time, string, string) { + + // Shut down OTG Interface + p1 := ondatra.ATE(t, "ate").Port(t, "port1") + portStateAction := gosnappi.NewControlState() + + var otgStateChangeTsStr string + + // TC2 Step 1 Read timestamp of last oper-status change form DUT port-1 + preStateTSSTR := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).LastChange().State()) + DutLastChangeTS1 := time.Unix(0, int64(preStateTSSTR)).UTC().Format(time.RFC3339Nano) + t.Logf("Step1. DutLastChangeTS1 is: %v", DutLastChangeTS1) + if actionState == "UP" { + portStateAction.Port().Link().SetPortNames([]string{p1.ID()}).SetState(gosnappi.StatePortLinkState.UP) + otgStateChangeTsStr = gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State()) + ate.OTG().SetControlState(t, portStateAction) + } else if actionState == "DOWN" { + // TC2 Step 2 Bring Down OTG Interface + t.Log("RT-5.5.2: Bring Down OTG Interface") + portStateAction.Port().Link().SetPortNames([]string{p1.ID()}).SetState(gosnappi.StatePortLinkState.DOWN) + otgStateChangeTsStr = gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State()) + ate.OTG().SetControlState(t, portStateAction) + + // TC2 Step 3 + t.Log("Step 3 sleeping 500ms") + time.Sleep(500 * time.Millisecond) + } + + // Step 4. Read timestamp of last oper-status change form DUT port-1 (DUT_LAST_CHANGE_TS) + postStateTSSTR := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).LastChange().State()) + DutLastChangeTS2STR := time.Unix(0, int64(postStateTSSTR)).UTC().Format(time.RFC3339Nano) + DutLastChangeOper2 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).OperStatus().State()) + + var expectedStatus oc.E_Interface_OperStatus + if actionState == "UP" { + expectedStatus = oc.Interface_OperStatus_UP + } else if actionState == "DOWN" { + expectedStatus = oc.Interface_OperStatus_DOWN + } + + // Step 5. verify oper-status is DOWN + if DutLastChangeOper2 != expectedStatus { + t.Errorf("Interface %s status got %v, want %v", aggID, DutLastChangeTS2STR, expectedStatus.String()) + } else { + t.Logf("Interface %s status got %v, want %v", aggID, DutLastChangeTS2STR, expectedStatus.String()) + } + + // convert string type change to time.time + otgStateChangeTs, err := time.Parse(time.RFC3339Nano, otgStateChangeTsStr) + if err != nil { + t.Fatalf("failed to parse event timestamp: %v %v", err, otgStateChangeTs) + } + + DutLastChangeTS2, err := time.Parse(time.RFC3339Nano, DutLastChangeTS2STR) + if err != nil { + t.Fatalf("failed to parse event timestamp: %v %v", err, DutLastChangeTS2) + } + + // Step 6. verify oper-status last change time has changed + t.Log("Compare if pre and post timestamps are the same for the last change before and after shut event") + if DutLastChangeTS1 == DutLastChangeTS2STR { + t.Fatalf("Before Trigger Last Change was %v after trigger Last Change was %v", DutLastChangeTS1, DutLastChangeTS2STR) + } else { + t.Logf("Before Trigger Last Change was %v after trigger Last Change was %v", DutLastChangeTS1, DutLastChangeTS2STR) + } + + // convert to time objects + otgStateChangeTs = otgStateChangeTs.UTC() + DutLastChangeTS2 = DutLastChangeTS2.UTC() + + return otgStateChangeTs, DutLastChangeTS2, expectedStatus.String(), DutLastChangeOper2.String() + +} + +// verifyPortsUp asserts that each port on the device is operating. +func verifyPortsStatus(t *testing.T, dut *ondatra.DUTDevice, portState string, waitTime time.Duration) { + t.Helper() + + t.Logf("Checking Oper Status on %s", aggID) + + // Determine the expected status based on the portState argument. + var want oc.E_Interface_OperStatus + if portState == "UP" { + want = oc.Interface_OperStatus_UP + gnmi.Await(t, dut, + gnmi.OC().Interface(aggID).OperStatus().State(), + time.Second*waitTime, + oc.Interface_OperStatus_UP) + } else { + want = oc.Interface_OperStatus_DOWN + gnmi.Await(t, dut, + gnmi.OC().Interface(aggID).OperStatus().State(), + time.Second*waitTime, + oc.Interface_OperStatus_DOWN) + } + + status := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).OperStatus().State()) + + // check the status and log the result. + if status != want { + t.Fatalf("Failed: %s Status: got %v, want %v", aggID, status, want) + } else { + t.Logf("Pass: %s Status: got %v, want %v", aggID, status, want) + } +} + +func TestHoldTimeConfig(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + dutPort1Intf = dut.Port(t, "port1") + t.Run("ConfigureDUT Interfaces", func(t *testing.T) { + // Configure the DUT + aggID = netutil.NextAggregateInterface(t, dut) + t.Log(dutPort1Intf) + configureDUT(t, dut, aggID) + + }) + + t.Run("Configure Hold Timers on DUT", func(t *testing.T) { + // Construct the hold-time config object + holdTimeConfig := &oc.Interface_HoldTime{ + Up: ygot.Uint32(upTimer), + Down: ygot.Uint32(downTimer), + } + + intfPath := gnmi.OC().Interface(dutPort1Intf.Name()) + gnmi.Update(t, dut, intfPath.HoldTime().Config(), holdTimeConfig) + + }) + + t.Run("ConfigureOTG", func(t *testing.T) { + t.Logf("Configure ATE") + configureOTG(t, ate, aggID) + + }) + + t.Run(fmt.Sprintf("Verify Interface State for %s", aggID), func(t *testing.T) { + // Verify Port Status + t.Logf("Verifying port status for %s", aggID) + verifyPortsStatus(t, dut, "UP", 45) + }) + +} + +func TestTC1ValidateTimersConfig(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + holdTimePath := gnmi.OC().Interface(dutPort1Intf.Name()).HoldTime().State() + + holdTimeState := gnmi.Get(t, dut, holdTimePath) + t.Log(holdTimeState) + if *holdTimeState.Up == upTimer && *holdTimeState.Down == downTimer { + t.Logf("Successfully configured times as up timer is %d and down timer"+ + " is %d", *holdTimeState.Up, *holdTimeState.Down) + } else { + t.Errorf("TC Failed: Configured up and down timers dont match what was configured "+ + "expected up %d got %d expected down %d got %d", upTimer, *holdTimeState.Up, + downTimer, *holdTimeState.Down) + } +} + +func TestTC2LongDown(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + var otgStateChangeTs, DutLastChangeTS2 time.Time + var expectedOper, actualOper string + + t.Run(fmt.Sprintf("Shut down OTG interface to cause remote fault on %s", aggID), func(t *testing.T) { + otgStateChangeTs, DutLastChangeTS2, expectedOper, actualOper = flapOTGInterface(t, ate, dut, "DOWN") + if expectedOper != actualOper { + t.Errorf("expectedOper and actualOper do not match: expected %s, got %s", expectedOper, actualOper) + } + }) + + duration := DutLastChangeTS2.Sub(otgStateChangeTs) + durationInMS := duration.Milliseconds() + + // Define the expected delay and tolerance + expectedDelayMS := 300 // Expected delay in milliseconds + minDuration := int64(expectedDelayMS - toleranceMS) + maxDuration := int64(expectedDelayMS + toleranceMS) + + // Check if the actual duration falls within the expected range + pass := durationInMS <= maxDuration + + t.Run(fmt.Sprintf("Calculate fault duration on %s", aggID), func(t *testing.T) { + t.Logf("Shutdown triggered at: %v", otgStateChangeTs) + t.Logf("Last change reported at: %v", DutLastChangeTS2) + t.Logf("Duration between shutdown triggered and last change reported: %v ms", durationInMS) + + if pass { + t.Logf("PASS: Duration is within the expected range; got %d ms", durationInMS) + } else { + t.Errorf("FAIL: Expected duration to be within %d ms to %d ms; got %d ms", minDuration, maxDuration, durationInMS) + } + }) + + t.Run("Bring back UP OTG Interface", func(t *testing.T) { + OTGInterfaceUP(t, ate) + t.Logf("Verifying port status for %s", aggID) + verifyPortsStatus(t, dut, "UP", 45) + }) + + t.Run("Verify test results", func(t *testing.T) { + displaySummaryTable(t, otgStateChangeTs.Format(time.RFC3339Nano), DutLastChangeTS2.Format(time.RFC3339Nano), + durationInMS, minDuration, maxDuration, expectedOper, actualOper, pass) + + }) + +} + +func TestTC3ShortUP(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + t.Run("Start sending Ethernet Remote Fault on OTG", func(t *testing.T) { + + // shutting down OTG interface to emulate the RF + OTGInterfaceDOWN(t, ate, dut) + oper1 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).OperStatus().State()) + change1 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).LastChange().State()) + t.Log(oper1) + t.Log(change1) + + // bring port back up for 4 seconds below the 5000 ms hold up timer + OTGInterfaceUP(t, ate) + // shut the OTG interface back to down state + OTGInterfaceDOWN(t, ate, dut) + oper2 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).OperStatus().State()) + change2 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).LastChange().State()) + + // ensure the LAG interface is still down + verifyPortsStatus(t, dut, "DOWN", 4) + t.Log(oper2) + + change1Time := time.Unix(0, int64(change1)).UTC() + change2Time := time.Unix(0, int64(change2)).UTC() + + // Compare the times and ensure there is no change in the last change + if change1Time.Before(change2Time) || change1Time.After(change2Time) { + t.Errorf("Time 1 %v and Time 2 dont match %v", change1Time, change2Time) + } else if change1Time.Equal(change2Time) { + t.Logf("Time 1 %v and Time 2 the the same which is expected %v", change1Time, change2Time) + } + + // bring OTG port back up + OTGInterfaceUP(t, ate) + // verify interface is up for next test case + verifyPortsStatus(t, dut, "UP", 45) + + }) + +} + +func TestTC4SLongUP(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + t.Run("Start sending Ethernet Remote Fault on OTG", func(t *testing.T) { + + // shutting down OTG interface to emulate the RF + OTGInterfaceDOWN(t, ate, dut) + time.Sleep(1 * time.Second) + change1 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).LastChange().State()) + t.Log(change1) + + // bring port back up for 4 seconds below the 5000 ms hold up timer + OTGInterfaceUP(t, ate) + // ensure the LAG interface is still down + verifyPortsStatus(t, dut, "UP", 30) + + // Collecting time stamp of interface up + change2 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).LastChange().State()) + + change1Time := time.Unix(0, int64(change1)).UTC() + change2Time := time.Unix(0, int64(change2)).UTC() + + // Calculate the difference in time + duration := change2Time.Sub(change1Time) + + // Convert the duration to milliseconds + durationInMS := duration.Milliseconds() + t.Logf("Duration interface %v ms", durationInMS) + + if durationInMS >= upTimer { + t.Logf("PASS: Expected interface up time delay of at least %v and got %v", upTimer, durationInMS) + } else { + t.Fatalf("FAIL: Expected interface up time delay of at least %v and got %v", upTimer, durationInMS) + } + + }) + +} + +func TestTC5ShortDOWN(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + var time1 time.Time + var change1 *oc.Interface + + // Construct the hold-time config object + holdTimeConfig := &oc.Interface_HoldTime{ + Up: ygot.Uint32(upTimer), + Down: ygot.Uint32(2000), + } + + t.Run("Update hold timer configs down", func(t *testing.T) { + intfPath := gnmi.OC().Interface(dutPort1Intf.Name()) + gnmi.Update(t, dut, intfPath.HoldTime().Config(), holdTimeConfig) + + }) + + t.Run("Flap OTG Interfaces", func(t *testing.T) { + + t.Log("Verify Interface State before TC Start") + verifyPortsStatus(t, dut, "UP", 10) + // shutting down OTG interface to emulate the RF + t.Log("Shutdown OTG Interface") + change1 = gnmi.Get(t, dut, gnmi.OC().Interface(aggID).State()) + t.Logf("change1 last change is %v and status is %v", change1.LastChange, change1.AdminStatus) + + time1 = OTGInterfaceDOWN(t, ate, dut) + time.Sleep(200 * time.Millisecond) + t.Log("Bring OTG Interface Back UP") + OTGInterfaceUP(t, ate) + + }) + + t.Run("Verify Short Down Results", func(t *testing.T) { + + // Start building the log message + logMessage := "Interface Status Timeline\n" + + "----------------------------------------------------\n" + + "Event | Time | Oper Status\n" + + "----------------------------------------------------\n" + + "Last-change time 1 | %v | %v\n" + + "Trigger Start Time | %v | -\n" + + "Last-change Re-check | %v | %v\n" + + change2 := gnmi.Get(t, dut, gnmi.OC().Interface(aggID).State()) + + if *change2.LastChange == *change1.LastChange && change2.OperStatus == change1.OperStatus { + time2 := gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State()) + + // Dereference the value and convert to int64 before passing to time.Unix function + change2LastChangeTime := time.Unix(0, int64(*change2.LastChange)).UTC().Format(time.RFC3339Nano) + change1LastChangeTime := time.Unix(0, int64(*change1.LastChange)).UTC().Format(time.RFC3339Nano) + t1 := time1.UTC().Format(time.RFC3339Nano) + t2, err := time.Parse(time.RFC3339Nano, time2) + if err != nil { + t.Errorf("Failed to parse time string: %v", err) + return + } + + timeDiff := t2.Sub(time1).Milliseconds() + + logMessage += fmt.Sprintf("End Time | %v | -\n"+ + "-----------------------------------------------------\n"+ + "Total Elapsed Time: %vms\n", t2, timeDiff) + t.Logf(logMessage, change1LastChangeTime, change1.OperStatus, t1, change2LastChangeTime, change2.OperStatus) + + } else { + // Dereference the value and convert to int64 before passing to time.Unix function + change2LastChangeTime := time.Unix(0, int64(*change2.LastChange)).UTC().Format(time.RFC3339Nano) + change1LastChangeTime := time.Unix(0, int64(*change1.LastChange)).UTC().Format(time.RFC3339Nano) + t1 := time1.UTC().Format(time.RFC3339Nano) + + // Log failure message and the partially built log message without end time + t.Log("Failed due to an unexpected match such as last-change time or interface oper-status") + t.Fatalf(logMessage, change1LastChangeTime, change1.OperStatus, t1, change2LastChangeTime, change2.OperStatus) + } + }) + + t.Run("Verify port status UP", func(t *testing.T) { + t.Log("re-verify that the interface state is still up") + verifyPortsStatus(t, dut, "UP", 10) + + }) +} diff --git a/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto b/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto index 420c75186b1..6d2e250ac73 100644 --- a/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto +++ b/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto @@ -1,6 +1,15 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata +uuid: "4ed6ff3f-b27e-4f46-93b2-8bcbc521d883" plan_id: "RT-5.5" -description: "Interface hold-times" +description: "Interface hold-time" testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: 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 new file mode 100644 index 00000000000..aea9ddcf069 --- /dev/null +++ b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md @@ -0,0 +1,39 @@ +# RT-5.6: Interface Loopback mode + +## Summary + +Ensure Interface mode can be set to loopback mode and can be added as part of static LAG. + +## Procedure + +### TestCase-1: + +* 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”. +* 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”. + +## 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/loopback-mode: + /interfaces/interface/ethernet/config/port-speed: + /interfaces/interface/ethernet/config/duplex-mode: + /interfaces/interface/ethernet/config/aggregate-id: + /interfaces/interface/aggregation/config/lag-type: + /interfaces/interface/aggregation/config/min-links: + + ## Telemetry paths + /interfaces/interface/state/loopback-mode: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: diff --git a/feature/experimental/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 similarity index 90% rename from feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go rename to feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go index 313b9790fec..dcbc768fe6f 100644 --- a/feature/experimental/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 @@ -15,7 +15,6 @@ package interface_loopback_aggregate_test import ( - "context" "fmt" "testing" "time" @@ -24,7 +23,6 @@ import ( "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" @@ -119,7 +117,7 @@ func configureOTG(t *testing.T, otg *otg.OTG) { port1 := config.Ports().Add().SetName("port1") iDut1Dev := config.Devices().Add().SetName(atePort1Attr.Name) iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1Attr.Name + ".Eth").SetMac(atePort1Attr.MAC) - iDut1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port1.Name()) + iDut1Eth.Connection().SetPortName(port1.Name()) t.Logf("Pushing config to ATE and starting protocols...") otg.PushConfig(t, config) otg.StartProtocols(t) @@ -184,7 +182,7 @@ func TestInterfaceLoopbackMode(t *testing.T) { cs := gosnappi.NewControlState() t.Run("Admin down OTG port1", func(t *testing.T) { - cs.Port().Link().SetState(gosnappi.StatePortLinkState.DOWN) + cs.Port().Link().SetPortNames([]string{ate.Port(t, "port1").ID()}).SetState(gosnappi.StatePortLinkState.DOWN) otg.SetControlState(t, cs) }) @@ -232,40 +230,9 @@ func TestInterfaceLoopbackMode(t *testing.T) { t.Run("Configure interface loopback mode FACILITY on DUT AE interface", func(t *testing.T) { if deviations.InterfaceLoopbackModeRawGnmi(dut) { - gpbSetRequest := &gpb.SetRequest{ - Update: []*gpb.Update{{ - Path: &gpb.Path{ - Origin: "openconfig", - Elem: []*gpb.PathElem{ - { - Name: "interfaces", - }, - { - Name: "interface", - Key: map[string]string{ - "name": dut.Port(t, "port1").Name(), - }, - }, - { - Name: "config", - }, - { - Name: "loopback-mode", - }, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte("true"), - }, - }, - }}, - } - gnmiClient := dut.RawAPIs().GNMI(t) - _, err := gnmiClient.Set(context.Background(), gpbSetRequest) - if err != nil { - t.Errorf("Failed to update interface loopback mode") - } + + gnmi.Update(t, dut, gnmi.OC().Interface(dutPort1.Name()).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) diff --git a/feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto similarity index 100% rename from feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto rename to feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto diff --git a/feature/interface/ip/feature.textproto b/feature/interface/ip/feature.textproto index fefb8ec57b8..b03ede8d819 100644 --- a/feature/interface/ip/feature.textproto +++ b/feature/interface/ip/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md index c4f46be1e6d..752c66b9ca7 100644 --- a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md +++ b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md @@ -1,4 +1,4 @@ -# RT-5.6: Disable IPv6 ND Router Arvetisment +# RT-5.9: Disable IPv6 ND Router Arvetisment ## Summary @@ -40,3 +40,24 @@ None ## Minimum DUT Platform Requirement vRX + + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: +## Config paths + /interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/interval: + /interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/enable: + /interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/mode: + ##State paths + /interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/state/interval: + +rpcs: + gnmi: + gNMI.Set: + Replace: +``` \ No newline at end of file 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/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go new file mode 100644 index 00000000000..ac04a98cf85 --- /dev/null +++ b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go @@ -0,0 +1,230 @@ +// 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 disable_ipv6_nd_ra_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/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/ygot/ygot" +) + +// Reserving the testbed and running tests. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + plen6 = 126 + ipv6 = "IPv6" + routerAdvertisementTimeInterval = 5 + frameSize = 512 + pps = 100 + routerAdvertisementDisabled = true +) + +var ( + dutSrc = attrs.Attributes{ + Desc: "dutsrc", + IPv6: "2001:db8::1", + IPv6Len: plen6, + MAC: "02:11:01:00:00:04", + } + + ateSrc = attrs.Attributes{ + Name: "atesrc", + MAC: "02:11:01:00:00:01", + IPv6: "2001:db8::2", + IPv6Len: plen6, + } + + dutDst = attrs.Attributes{ + Desc: "dutdst", + IPv6: "2001:db8::5", + IPv6Len: plen6, + MAC: "02:11:01:00:00:05", + } + ateDst = attrs.Attributes{ + Name: "atedst", + MAC: "02:12:01:00:00:01", + IPv6: "2001:db8::6", + IPv6Len: plen6, + } +) + +// Configures port1 and port2 of the DUT. +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, &dutSrc, dut)) + p2 := dut.Port(t, "port2") + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(p2, &dutDst, 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) + } +} + +// 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) + } + s6 := i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + routerAdvert := s6.GetOrCreateRouterAdvertisement() + if !deviations.Ipv6RouterAdvertisementIntervalUnsupported(dut) { + routerAdvert.SetInterval(routerAdvertisementTimeInterval) + } + if deviations.Ipv6RouterAdvertisementConfigUnsupported(dut) { + routerAdvert.SetSuppress(routerAdvertisementDisabled) + } else { + routerAdvert.SetEnable(false) + } + return i +} + +// Configures OTG interfaces to send and receive ipv6 packets. +func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + topo := gosnappi.NewConfig() + t.Logf("Configuring OTG port1") + srcPort := topo.Ports().Add().SetName("port1") + srcDev := topo.Devices().Add().SetName(ateSrc.Name) + srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) + srcEth.Connection().SetPortName(srcPort.Name()) + srcIpv6 := srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6") + srcIpv6.SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) + t.Logf("Configuring OTG port2") + dstPort := topo.Ports().Add().SetName("port2") + dstDev := topo.Devices().Add().SetName(ateDst.Name) + dstEth := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) + dstEth.Connection().SetPortName(dstPort.Name()) + dstIpv6 := dstEth.Ipv6Addresses().Add().SetName(ateDst.Name + ".IPv6") + dstIpv6.SetAddress(ateDst.IPv6).SetGateway(dutDst.IPv6).SetPrefix(uint32(ateDst.IPv6Len)) + topo.Captures().Add().SetName("raCapture").SetPortNames([]string{dstPort.Name()}).SetFormat(gosnappi.CaptureFormat.PCAP) + t.Logf("OTG configuration completed!") + topo.Flows().Clear().Items() + ate.OTG().PushConfig(t, topo) + time.Sleep(10 * time.Second) + t.Logf("starting protocols... ") + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), topo, "IPv6") + return topo +} + +// Verifies that desired parameters are set with required value on the device. +func verifyRATelemetry(t *testing.T, dut *ondatra.DUTDevice) { + txPort := dut.Port(t, "port1") + if !deviations.Ipv6RouterAdvertisementIntervalUnsupported(dut) { + telemetryTimeIntervalQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Interval().State() + timeIntervalOnTelemetry := gnmi.Get(t, dut, telemetryTimeIntervalQuery) + t.Logf("Required RA time interval = %v, RA Time interval observed on telemetry = %v ", routerAdvertisementTimeInterval, timeIntervalOnTelemetry) + if timeIntervalOnTelemetry != routerAdvertisementTimeInterval { + t.Fatalf("Inconsistent Time interval!\nRequired RA time interval = %v and Configured RA Time Interval = %v are not same!", routerAdvertisementTimeInterval, timeIntervalOnTelemetry) + } + } + + if deviations.Ipv6RouterAdvertisementConfigUnsupported(dut) { + deviceRAConfigQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Suppress().Config() + raConfigOnDevice := gnmi.Get(t, dut, deviceRAConfigQuery) + t.Logf("Router Advertisement State = %v", raConfigOnDevice) + } else { + deviceRAConfigQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Enable().Config() + deviceRAModeQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Enable().Config() + raModeOnDevice := gnmi.Get(t, dut, deviceRAModeQuery) + raConfigOnDevice := gnmi.Get(t, dut, deviceRAConfigQuery) + t.Logf("Router Advertisement mode = %v", raModeOnDevice) + t.Logf("Router Advertisement State = %v", raConfigOnDevice) + } +} + +// Captures traffic statistics and verifies for the loss. +func verifyOTGPacketCaptureForRA(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, ipv6Solicitation bool, waitTime uint8) { + otg := ate.OTG() + otg.StartProtocols(t) + + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) + if ipv6Solicitation { + otgutils.WaitForARP(t, ate.OTG(), config, "IPv6") + } + + time.Sleep(time.Duration(waitTime) * time.Second) + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(config.Ports().Items()[1].Name())) + t.Logf("Config Ports %v", config.Ports().Items()) + f, err := os.CreateTemp("", "pcap") + if err != nil { + t.Fatalf("ERROR: 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) + } + f.Close() + validatePackets(t, f.Name()) +} + +// To detect if the routerAdvertisement packet is found in the captured packets. +func validatePackets(t *testing.T, fileName string) { + t.Logf("Reading pcap file from : %v", fileName) + handle, err := pcap.OpenOffline(fileName) + if err != nil { + t.Logf("No Packets found in the file = %v !", fileName) + return + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + ipv6Layer := packet.Layer(layers.LayerTypeIPv6) + if ipv6Layer != nil { + + icmpv6Layer := packet.Layer(layers.LayerTypeICMPv6) + if icmpv6Layer != nil { + routerAdvert := packet.Layer(layers.LayerTypeICMPv6RouterAdvertisement) + if routerAdvert != nil { + t.Fatalf("Error:Found a router advertisement packet!") + } + } + } + } + t.Logf("No Router advertisement packets found!") +} + +func TestIpv6NDRA(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + configureDUT(t, dut) + otgConfig := configureOTG(t, ate) + t.Run("TestCase-1: No periodical Router Advertisement", func(t *testing.T) { + verifyRATelemetry(t, dut) + verifyOTGPacketCaptureForRA(t, ate, otgConfig, false, 10) + }) + t.Run("TestCase-2: No Router Advertisement in response to Router Solicitation", func(t *testing.T) { + verifyOTGPacketCaptureForRA(t, ate, otgConfig, true, 1) + }) +} diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto index 7c5a2c4a727..9855f84b507 100644 --- a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto +++ b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto @@ -1,7 +1,41 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -plan_id: "RT-5.6" -description: "IPv6 ND RA disable" +uuid: "43776caf-2340-4f35-ab30-8c763feaadae" +plan_id: "RT-5.9" +description: "Disable IPv6 ND Router Arvetisment" testbed: TESTBED_DUT_ATE_2LINKS - +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + ipv6_router_advertisement_config_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + ipv6_router_advertisement_interval_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv6_router_advertisement_interval_unsupported: true + } +} \ No newline at end of file diff --git a/feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/README.md b/feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/README.md new file mode 100644 index 00000000000..f84e63747fe --- /dev/null +++ b/feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/README.md @@ -0,0 +1,39 @@ +# RT-5.11: Suppress IPv6 ND Router Advertisement + +## Summary + +Validate IPv6 ND Router Advertisement (RA) is suppresed. No periodic RA are sent. + +## Procedure +* Connect DUT port-1 to OTG port-1 +* Configure IPv6 address on DUT port-1 +* Enable IPv6 ND RA suppression on DUT port-1 + +### RT-5.11.1: No periodical Router Advertisement are sent + +* Verify over period of 10 seconds that IPv6 ND RA **doesn't** arrives on OTG Port-1 ([rfc4861 section 6.2.1](https://datatracker.ietf.org/doc/html/rfc4861#section-6.2.1)) + +### RT-5.11.2: Router Advertisement response is sent to Router Solicitation + +* Send IPv6 ND Router Solicitation from OTG Port-1 +* Verify over period of 1 seconds that IPv6 ND RA does arrives on OTG Port-1 ([rfc4861 section 6.2.6](https://datatracker.ietf.org/doc/html/rfc4861#section-6.2.6)) + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/interval: + /interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/suppress: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF \ No newline at end of file diff --git a/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/README.md b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/README.md index 8a61ea30d8c..61691865a35 100644 --- a/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/README.md +++ b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/README.md @@ -16,11 +16,14 @@ Configure an IPv6 address which is in link local scope. Verify the link local IP * Send IPv6 traffic from OTG port 1 to DUT port 1, validate DUT port 1 receives the traffic * Subtest #3 - Verify adding and removing global unicast address does not affect link local address - * Add configuration for a global unicast address on DUT port 1 + * Add configuration for a global unicast address on DUT port 1 and DUT port 2 * Validate config and state paths are set + * Send IPv6 traffic from OTG port 1 to OTG port 2, validate OTG port 2 receives the traffic * Remove configuration for the global unitcast address on DUT port 1 * Validate that DUT port 1 link local address is still configured * Send IPv6 traffic from OTG port 1 to DUT port 1, validate DUT port 1 receives the traffic + * Send IPv6 traffic from OTG port 1 to OTG port 2, validate OTG port 2 does not receive the traffic + * Subtest #4 - Verify enable/disable of DUT port 1 does not affect link local address * Disable/enable the port and see if the configured link-local address stays? @@ -43,9 +46,14 @@ Configure an IPv6 address which is in link local scope. Verify the link local IP /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/type ``` -## Protocol/RPC Parameter Coverage - -None +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` ## Required DUT platform diff --git a/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/ipv6_link_local_test.go b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/ipv6_link_local_test.go new file mode 100644 index 00000000000..facd36954b2 --- /dev/null +++ b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/ipv6_link_local_test.go @@ -0,0 +1,352 @@ +// 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 ipv6_link_local_test + +import ( + "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/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + srcDUTGlobalIPv6 = "2001:db8::1" + srcOTGGlobalIPv6 = "2001:db8::2" + dstDUTGlobalIPv6 = "2001:db8::5" + dstOTGGlobalIPv6 = "2001:db8::6" + globalIPv6Len = 126 + linkLocalFlowName = "SrcToDstLinkLocal" + globalUnicastFlowName = "SrcToDstGlobalUnicast" +) + +// We use the same link local IPv6 fe80::1 and fe80::2 on both src and dst pairs to ensure +// that devices will allow link local addresses to be reused across different L2 domains. +var ( + dutSrc = attrs.Attributes{ + Desc: "dutsrc", + IPv6: "fe80::1", + IPv6Len: 64, + } + + ateSrc = attrs.Attributes{ + Name: "atesrc", + MAC: "02:11:01:00:00:01", + IPv6: "fe80::2", + IPv6Len: 64, + } + + dutDst = attrs.Attributes{ + Desc: "dutdst", + IPv6: "fe80::1", + IPv6Len: 64, + } + + ateDst = attrs.Attributes{ + Name: "atedst", + MAC: "02:12:01:00:00:01", + IPv6: "fe80::2", + IPv6Len: 64, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestIPv6LinkLocal(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUTLinkLocalInterface(t, dut) + fptest.ConfigureDefaultNetworkInstance(t, dut) + + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + configureOTGInterface(t, ate, top) + otgSrcToDstFlow(t, top, ateSrc.IPv6, ateDst.IPv6, linkLocalFlowName) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + t.Run("Interface Telemetry", func(t *testing.T) { + verifyInterfaceTelemetry(t, dut) + }) + + t.Run("Link Local Traffic Test", func(t *testing.T) { + verifyLinkLocalTraffic(t, dut, ate, top, linkLocalFlowName) + }) + + t.Run("Configure And Delete Global Unicast IPv6", func(t *testing.T) { + configureDUTGlobalIPv6(t, dut) + otgSrcToDstFlow(t, top, srcOTGGlobalIPv6, dstOTGGlobalIPv6, globalUnicastFlowName) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + t.Run("Global IPv6 Traffic Test", func(t *testing.T) { + verifyGlobalUnicastTraffic(t, dut, ate, top, globalUnicastFlowName) + }) + + p1 := dut.Port(t, "port1") + gnmi.Delete(t, dut, gnmi.OC().Interface(p1.Name()).Subinterface(0).Ipv6().Address(srcDUTGlobalIPv6).Config()) + p2 := dut.Port(t, "port2") + gnmi.Delete(t, dut, gnmi.OC().Interface(p2.Name()).Subinterface(0).Ipv6().Address(dstDUTGlobalIPv6).Config()) + + t.Run("Interface Telemetry", func(t *testing.T) { + verifyInterfaceTelemetry(t, dut) + }) + + otgSrcToDstFlow(t, top, ateSrc.IPv6, ateDst.IPv6, linkLocalFlowName) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + t.Run("Traffic Test", func(t *testing.T) { + verifyLinkLocalTraffic(t, dut, ate, top, linkLocalFlowName) + }) + }) + + t.Run("Disable and Enable Port1", func(t *testing.T) { + p1 := dut.Port(t, "port1") + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), false) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().State(), 30*time.Second, false) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), true) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().State(), 30*time.Second, true) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + t.Run("Interface Telemetry", func(t *testing.T) { + verifyInterfaceTelemetry(t, dut) + }) + t.Run("Traffic Test", func(t *testing.T) { + verifyLinkLocalTraffic(t, dut, ate, top, linkLocalFlowName) + }) + }) +} + +func configureDUTGlobalIPv6(t *testing.T, dut *ondatra.DUTDevice) { + portData := []struct { + port *ondatra.Port + ipAddr string + }{ + { + port: dut.Port(t, "port1"), + ipAddr: srcDUTGlobalIPv6, + }, + { + port: dut.Port(t, "port2"), + ipAddr: dstDUTGlobalIPv6, + }, + } + + for _, pd := range portData { + addr := &oc.Interface_Subinterface_Ipv6_Address{ + Ip: ygot.String(pd.ipAddr), + PrefixLength: ygot.Uint8(globalIPv6Len), + } + gnmi.Update(t, dut, gnmi.OC().Interface(pd.port.Name()).Subinterface(0).Ipv6().Address(pd.ipAddr).Config(), addr) + + if _, ok := gnmi.Watch(t, dut, gnmi.OC().Interface(pd.port.Name()).Subinterface(0).Ipv6().Address(pd.ipAddr).State(), 30*time.Second, func(val *ygnmi.Value[*oc.Interface_Subinterface_Ipv6_Address]) bool { + v, present := val.Val() + return present && v.GetType() == oc.IfIp_Ipv6AddressType_GLOBAL_UNICAST && v.GetPrefixLength() == globalIPv6Len && v.GetIp() == pd.ipAddr + }).Await(t); !ok { + t.Errorf("Couldn't configure Global Unicast IPv6 on port: %s", pd.port.Name()) + } + } +} + +func configureDUTLinkLocalInterface(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + + p1 := dut.Port(t, "port1") + srcIntf := dutSrc.NewOCInterface(p1.Name(), dut) + subInt := srcIntf.GetOrCreateSubinterface(0) + subInt4 := subInt.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + subInt4.Enabled = ygot.Bool(true) + } + subInt.GetOrCreateIpv6().Enabled = ygot.Bool(true) + if deviations.LinkLocalMaskLen(dut) { + dutSrc.IPv6Len = 128 + } + subInt.GetOrCreateIpv6().GetOrCreateAddress(dutSrc.IPv6).SetType(oc.IfIp_Ipv6AddressType_LINK_LOCAL_UNICAST) + subInt.GetOrCreateIpv6().GetOrCreateAddress(dutSrc.IPv6).SetPrefixLength(dutSrc.IPv6Len) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), srcIntf) + p2 := dut.Port(t, "port2") + dstIntf := dutDst.NewOCInterface(p2.Name(), dut) + dstSubInt := dstIntf.GetOrCreateSubinterface(0) + dstSubInt4 := dstSubInt.GetOrCreateIpv4() + dstSubInt.GetOrCreateIpv6().Enabled = ygot.Bool(true) + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + dstSubInt4.Enabled = ygot.Bool(true) + } + if deviations.LinkLocalMaskLen(dut) { + dutDst.IPv6Len = 128 + } + dstSubInt.GetOrCreateIpv6().GetOrCreateAddress(dutDst.IPv6).SetType(oc.IfIp_Ipv6AddressType_LINK_LOCAL_UNICAST) + dstSubInt.GetOrCreateIpv6().GetOrCreateAddress(dutDst.IPv6).SetPrefixLength(dutDst.IPv6Len) + + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dstIntf) + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) +} + +func configureOTGInterface(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) { + t.Helper() + + p1 := ate.Port(t, "port1") + ateSrc.AddToOTG(top, p1, &dutSrc) + + // global IPv6 address config for port1 + dev := top.Devices().Items()[0] + eth := dev.Ethernets().Items()[0] + ip := eth.Ipv6Addresses().Add().SetName(dev.Name() + ".IPv6-1") + ip.SetAddress(srcOTGGlobalIPv6).SetGateway(srcDUTGlobalIPv6).SetPrefix(uint32(globalIPv6Len)) + + p2 := ate.Port(t, "port2") + ateDst.AddToOTG(top, p2, &dutDst) + + // global IPv6 address config for port2 + for _, d := range top.Devices().Items() { + if d.Name() == ateDst.Name { + dev = d + } + } + eth = dev.Ethernets().Items()[0] + ip = eth.Ipv6Addresses().Add().SetName(dev.Name() + ".IPv6-1") + ip.SetAddress(dstOTGGlobalIPv6).SetGateway(dstDUTGlobalIPv6).SetPrefix(uint32(globalIPv6Len)) +} + +func otgSrcToDstFlow(t *testing.T, top gosnappi.Config, srcIPv6, dstIPv6, flowName string) { + top.Flows().Clear() + flow := top.Flows().Add().SetName(flowName) + flow.Metrics().SetEnable(true) + e1 := flow.Packet().Add().Ethernet() + e1.Src().SetValue(ateSrc.MAC) + e1.Dst().SetValue(ateDst.MAC) + flow.TxRx().Device().SetTxNames([]string{ateSrc.Name + ".IPv6"}).SetRxNames([]string{ateDst.Name + ".IPv6"}) + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(srcIPv6) + v6.Dst().SetValue(dstIPv6) +} + +func verifyLinkLocalTraffic(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, top gosnappi.Config, flowName string) { + p1 := dut.Port(t, "port1") + beforeInPkts := gnmi.Get(t, dut, gnmi.OC().Interface(p1.Name()).Counters().InPkts().State()) + ate.OTG().StartTraffic(t) + _, ok := gnmi.Watch(t, dut, gnmi.OC().Interface(p1.Name()).Counters().InPkts().State(), time.Second*30, func(v *ygnmi.Value[uint64]) bool { + gotPkts, present := v.Val() + return present && (gotPkts-beforeInPkts) >= 100 + }).Await(t) + if !ok { + t.Fatal("did not get expected number of packets after starting traffic. want > 100") + } + + ate.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, ate.OTG(), top) + flowMetrics := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flowName).Counters().State()) + otgTxPkts := flowMetrics.GetOutPkts() + otgRxPkts := flowMetrics.GetInPkts() + + if otgTxPkts == 0 { + t.Fatalf("txPackets is 0") + } + if got, want := 100*float32(otgTxPkts-otgRxPkts)/float32(otgTxPkts), float32(99); got < want { + t.Errorf("LossPct for flow %s got %f, want 100", flowName, got) + } + afterInPkts := gnmi.Get(t, dut, gnmi.OC().Interface(p1.Name()).Counters().InPkts().State()) + recvDUTPkts := afterInPkts - beforeInPkts + if got, want := lossPct(otgTxPkts, recvDUTPkts), 1.0; got > want { + t.Errorf("LossPct for flow %s got %f, want less than %f%%", flowName, got, want) + } +} + +func verifyGlobalUnicastTraffic(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, top gosnappi.Config, flowName string) { + ate.OTG().StartTraffic(t) + time.Sleep(15 * time.Second) + ate.OTG().StopTraffic(t) + time.Sleep(15 * time.Second) + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + flowMetrics := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flowName).Counters().State()) + otgTxPkts := flowMetrics.GetOutPkts() + otgRxPkts := flowMetrics.GetInPkts() + + if otgTxPkts == 0 { + t.Fatalf("txPackets is 0") + } + if got, want := lossPct(otgTxPkts, otgRxPkts), float64(1); got > want { + t.Errorf("LossPct for flow %s got %f, want 0", flowName, got) + } +} + +func lossPct(tx, rx uint64) float64 { + if tx > rx { + return 100 * float64(tx-rx) / float64(tx) + } + // When computing loss across DUT and OTG, DUT also counts received + // protocol packets not part of the OTG traffic flow. + return 0 +} + +func verifyInterfaceTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + + for p, attr := range map[string]attrs.Attributes{ + p1.Name(): dutSrc, + p2.Name(): dutDst, + } { + conf := gnmi.Get(t, dut, gnmi.OC().Interface(p).Subinterface(0).Ipv6().Address(attr.IPv6).Config()) + if got, want := conf.GetIp(), attr.IPv6; got != want { + t.Errorf("IP address config-path mismatch for port: %s, got: %s, want: %s", p, got, want) + } + if got, want := conf.GetType(), oc.IfIp_Ipv6AddressType_LINK_LOCAL_UNICAST; got != want { + t.Errorf("IP address type config-path mismatch for port: %s, got: %v, want: %v", p, got, want) + } + if got, want := conf.GetPrefixLength(), attr.IPv6Len; got != want { + t.Errorf("IP address prefix length config-path mismatch for port: %s, got: %d, want: %d", p, got, want) + } + + if got, want := gnmi.Get(t, dut, gnmi.OC().Interface(p).Subinterface(0).Ipv6().Enabled().Config()), true; got != want { + t.Errorf("IPv6 subinterface status config-path mismatch for port: %s, got: %v, want: %v", p, got, want) + } + + state := gnmi.Get(t, dut, gnmi.OC().Interface(p).Subinterface(0).Ipv6().Address(attr.IPv6).State()) + if got, want := state.GetIp(), attr.IPv6; got != want { + t.Errorf("IP address state mismatch for port: %s, got: %s, want: %s", p, got, want) + } + if got, want := state.GetType(), oc.IfIp_Ipv6AddressType_LINK_LOCAL_UNICAST; got != want { + t.Errorf("IP address type state mismatch for port: %s, got: %v, want: %v", p, got, want) + } + if got, want := state.GetPrefixLength(), attr.IPv6Len; got != want { + t.Errorf("IP address prefix length state mismatch for port: %s, got: %d, want: %d", p, got, want) + } + + if got, want := gnmi.Get(t, dut, gnmi.OC().Interface(p).Subinterface(0).Ipv6().Enabled().State()), true; got != want { + t.Errorf("IPv6 subinterface status state mismatch for port: %s, got: %v, want: %v", p, got, want) + } + } +} diff --git a/feature/interface/staticarp/ate_tests/static_arp_test/metadata.textproto b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto similarity index 77% rename from feature/interface/staticarp/ate_tests/static_arp_test/metadata.textproto rename to feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto index 2e8a9b92212..8be2f6d388a 100644 --- a/feature/interface/staticarp/ate_tests/static_arp_test/metadata.textproto +++ b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto @@ -1,34 +1,33 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "8b86a8fd-3d9d-47a4-b607-79a3acd496ee" -plan_id: "TE-1.1" -description: "Static ARP" +uuid: "000a845d-5416-4f47-8fc2-1c85f12a6821" +plan_id: "RT-5.8" +description: "IPv6 Link Local" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { - vendor: CISCO + vendor: ARISTA } deviations: { - ipv4_missing_enabled: true + interface_enabled: true + default_network_instance: "default" } } platform_exceptions: { platform: { - vendor: NOKIA + vendor: CISCO } deviations: { - explicit_port_speed: true - explicit_interface_in_default_vrf: true - interface_enabled: true + link_local_mask_len: true } -} +} # CISCOXR platform_exceptions: { platform: { - vendor: ARISTA + vendor: NOKIA } deviations: { + explicit_interface_in_default_vrf: true interface_enabled: true - default_network_instance: "default" } } diff --git a/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/README.md b/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/README.md new file mode 100644 index 00000000000..b29dfea2418 --- /dev/null +++ b/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/README.md @@ -0,0 +1,34 @@ +# RT-5.10: IPv6 Link Local generated by SLAAC + +## Summary + +Enable IPv6 on interface level so ipv6 address of link-local scope is generated/assigned by SLAAC. Verify the link local IPv6 address generated, exists by checking the Openconfig path + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed + +## Procedure + * Configure DUT port 1 with IPv6 so that link local scope IP address is assigned by SLAAC + * Validate config and state paths are auto-populated + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + ## Config paths + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip: + ## State paths + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/ip: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` + +## Protocol/RPC Parameter Coverage +None + +## Minimum required DUT platform +* FFF - fixed form factor 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/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/ipv6_slaac_link_local_test.go new file mode 100644 index 00000000000..2e0021e617a --- /dev/null +++ b/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/ipv6_slaac_link_local_test.go @@ -0,0 +1,103 @@ +package ipv6_slaac_link_local_test + +import ( + "fmt" + "regexp" + "testing" + "time" + + "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/ygot/ygot" +) + +var ( + ipv6BySLAAC = `fe80::.+/64` + intfDesc = "dutInfSLAAC" + waitForAssigned = time.Minute + reIPv6BySLAAC = regexp.MustCompile(ipv6BySLAAC) +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureDUTLinkLocalInterface(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port) { + t.Helper() + + intf := &oc.Interface{Name: ygot.String(p.Name())} + intf.Description = ygot.String(intfDesc) + intf.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + s := intf.GetOrCreateSubinterface(0) + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s.GetOrCreateIpv4().SetEnabled(true) + } + if deviations.InterfaceEnabled(dut) { + s.GetOrCreateIpv6().SetEnabled(true) + } + s.GetOrCreateIpv6().GetOrCreateAutoconf() + gnmi.Replace(t, dut, gnmi.OC().Interface(p.Name()).Config(), intf) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, intf.GetName(), deviations.DefaultNetworkInstance(dut), 0) + } +} + +func getAllIPv6Addresses(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port) []string { + var allIPv6 []string + deadline := time.Now().Add(waitForAssigned) + for time.Now().Before(deadline) { + time.Sleep(10 * time.Second) + ipv6Addrs := gnmi.LookupAll(t, dut, gnmi.OC().Interface(p.Name()).Subinterface(0).Ipv6().AddressAny().State()) + t.Logf("number of ipv6: %d", len(ipv6Addrs)) + for _, ipv6Addr := range ipv6Addrs { + t.Logf("ipv6Addr: %v", ipv6Addr) + if v6, ok := ipv6Addr.Val(); ok { + allIPv6 = append(allIPv6, fmt.Sprintf("%s/%d", v6.GetIp(), v6.GetPrefixLength())) + t.Logf("allIPv6: %v", allIPv6) + } + } + if hasSLAACGeneratedAddress(allIPv6) { + break + } + } + return allIPv6 +} + +func hasSLAACGeneratedAddress(ipv6Addrs []string) bool { + for _, ipv6Addr := range ipv6Addrs { + if reIPv6BySLAAC.MatchString(ipv6Addr) { + return true + } + } + return false +} + +func TestIpv6LinkLocakGenBySLAAC(t *testing.T) { + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + configureDUTLinkLocalInterface(t, dut, p1) + ipv6 := getAllIPv6Addresses(t, dut, p1) + if deviations.SlaacPrefixLength128(dut) { + ipv6BySLAAC = `fe80::.+/128` + reIPv6BySLAAC = regexp.MustCompile(ipv6BySLAAC) + t.Logf("ipv6BySLAAC: %s, reIPv6BySLAAC: %s", ipv6BySLAAC, reIPv6BySLAAC) + found := false + for _, ipv6Addr := range ipv6 { + if reIPv6BySLAAC.MatchString(ipv6Addr) { + t.Logf("SLAAC generated IPv6 address found, ") + found = true + break + } + } + if !found { + t.Errorf("No SLAAC generated IPv6 address found ") + } + } else { + if !hasSLAACGeneratedAddress(ipv6) { + t.Errorf("No SLAAC generated IPv6 address found , got: %s, want: %s", ipv6, ipv6BySLAAC) + } + } +} diff --git a/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto b/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto new file mode 100644 index 00000000000..7cdc467c77e --- /dev/null +++ b/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto @@ -0,0 +1,45 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "22ba3cd3-0dcb-4aba-9a09-d7180785e92c" +plan_id: "RT-5.10" +description: "IPv6 Link Local generated by SLAAC" +testbed: TESTBED_DUT_ATE_2LINKS +tags: TAGS_TRANSIT +tags: TAGS_DATACENTER_EDGE + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + interface_enabled: true + slaac_prefix_length128: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + explicit_interface_in_default_vrf: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + interface_enabled: true + } +} diff --git a/feature/interface/loopback/ate_tests/loopback_aggregate_test/README.md b/feature/interface/loopback/ate_tests/loopback_aggregate_test/README.md deleted file mode 100644 index 265b245bc15..00000000000 --- a/feature/interface/loopback/ate_tests/loopback_aggregate_test/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# RT-5.6: Interface Loopback mode - -## Summary - -Ensure Interface mode can be set to loopback mode and can be added as part of static LAG. - -## Procedure - -### TestCase-1: - -* Configure DUT port-1 to ATE port-1 -* Admin down ATE port-1 down -* Verify DUT port-1 is down -* Configure “loopback mode” set to “FACILITY” -* Add port-1 as part of Static LAG (lacp mode static(on)) -* Validate that port-1 operational status is “UP” -* Validate that LAG port status is “UP” - -## Config Parameter Coverage - -* /interfaces/interface/config/loopback-mode -* /interfaces/interface/ethernet/config/port-speed -* /interfaces/interface/ethernet/config/duplex-mode -* /interfaces/interface/ethernet/config/aggregate-id -* /interfaces/interface/aggregation/config/lag-type -* /interfaces/interface/aggregation/config/min-links -* /lacp/config/system-priority -* /lacp/interfaces/interface/config/name -* /lacp/interfaces/interface/config/interval -* /lacp/interfaces/interface/config/lacp-mode -* /lacp/interfaces/interface/config/system-id-mac -* /lacp/interfaces/interface/config/system-priority - -## Telemetry Parameter Coverage - -* /interfaces/interface/state/loopback-mode -* /lacp/interfaces/interface/members/member/state/counters/lacp-in-pkts -* /lacp/interfaces/interface/members/member/state/counters/lacp-out-pkts -* /lacp/interfaces/interface/members/member/state/counters/lacp-rx-errors -* /lacp/interfaces/interface/members/member/state/oper-key -* /lacp/interfaces/interface/members/member/state/partner-id -* /lacp/interfaces/interface/members/member/state/system-id -* /lacp/interfaces/interface/members/member/state/port-num - -## Protocol/RPC Parameter Coverage - -None - -## Minimum DUT Platform Requirement - -vRX diff --git a/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/README.md b/feature/interface/my_station_mac/otg_tests/my_station_mac_test/README.md similarity index 72% rename from feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/README.md rename to feature/interface/my_station_mac/otg_tests/my_station_mac_test/README.md index c7a09d22263..9bfce6eaa76 100644 --- a/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/README.md +++ b/feature/interface/my_station_mac/otg_tests/my_station_mac_test/README.md @@ -14,14 +14,16 @@ Ensure my MAC entries installed on the DUT are honored and used for routing. * Remove the MyStationMAC configuration. * Validate that traffic is blackholed. -## Config Parameter Coverage - -* /system/mac-address/config/routing-mac. - -## Telemetry Parameter Coverage - -* /system/mac-address/state/routing-mac. - -## Protocol/RPC Parameter Coverage - -N/A +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /system/mac-address/config/routing-mac: + /system/mac-address/state/routing-mac: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/metadata.textproto b/feature/interface/my_station_mac/otg_tests/my_station_mac_test/metadata.textproto similarity index 100% rename from feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/metadata.textproto rename to feature/interface/my_station_mac/otg_tests/my_station_mac_test/metadata.textproto diff --git a/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go b/feature/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go similarity index 98% rename from feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go rename to feature/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go index 095fde831ae..6b9152f6d10 100644 --- a/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go +++ b/feature/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go @@ -159,7 +159,7 @@ func testTraffic( flow.Metrics().SetEnable(true) eth := flow.Packet().Add().Ethernet() eth.Src().SetValue(ateSrc.MAC) - eth.Dst().SetChoice("value").SetValue(myStationMAC) + eth.Dst().SetValue(myStationMAC) if ipType == "IPv4" { v4 := flow.Packet().Add().Ipv4() v4.Src().SetValue(ateSrc.IPv4) @@ -173,6 +173,9 @@ func testTraffic( ate.OTG().PushConfig(t, top) ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + ate.OTG().StartTraffic(t) time.Sleep(10 * time.Second) ate.OTG().StopTraffic(t) diff --git a/feature/interface/singleton/feature.textproto b/feature/interface/singleton/feature.textproto index 35588043c7e..62fad1c9fc6 100644 --- a/feature/interface/singleton/feature.textproto +++ b/feature/interface/singleton/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/interface/singleton/otg_tests/singleton_test/README.md b/feature/interface/singleton/otg_tests/singleton_test/README.md index 1e20d5b2269..d8d218db6f9 100644 --- a/feature/interface/singleton/otg_tests/singleton_test/README.md +++ b/feature/interface/singleton/otg_tests/singleton_test/README.md @@ -73,6 +73,39 @@ a new testbed configuration with the desired port types. * Ensure inbound and outbound unicast counters are the same * Ensure counters increment at the selected SAMPLE interval +### RT-5.1.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2338] +#### Breakout must be explicitly configured by gNMI client + +* On DUT Port-1 with a QSFP-DD 400GBASE-DR4 transceiver inserted +* Ensure no breakout is configured +* Set Port-1 port-speed to 100G + * /interfaces/interface/ethernet/config/port-speed +* Validate that the DUT does not create breakouts implicitly and does not set the breakout speed + * /components/component/port/breakout-mode/groups/group/config + * /components/component/port/breakout-mode/groups/group/config/index + * /components/component/port/breakout-mode/groups/group/config/breakout-speed +* Validate the port state changes to "DOWN" + * /interfaces/interface/state/oper-status + +### RT-5.1.5 [TODO: https://github.com/openconfig/featureprofiles/issues/2338] +#### Setting port-speed on interface that have breakout configured should not be allowed + +* Configure a breakout on Port-1 to 4x100 Gig + * /components/component/port/breakout-mode/groups/group/config +* Try to set port speed of Port-1 to 100G + * /interfaces/interface/ethernet/config/port-speed +* Validate the port-speed is rejected + * Since a breakout port is not expected to support port-speed, verify the gNMI Set operation is rejected + * /interfaces/interface/ethernet/state/port-speed + +### RT-5.1.6 [TODO: https://github.com/openconfig/featureprofiles/issues/2338] +#### Remove breakout and interface config to delete the interface config + +* Using a single gNMI Replace, remove the DUT port-1 and its breakout config +* Ensure the gNMI Replace is successful and configuration for DUT port-1 including its breakout is removed + * /interfaces/interface/ethernet/state/ + * /components/component/port/breakout-mode/groups/group/state + ## Config Parameter Coverage * /interfaces/interface/config/name @@ -136,3 +169,56 @@ a new testbed configuration with the desired port types. ## Minimum DUT Platform Requirement vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths and RPC intended to be covered by this test. + +```yaml +paths: + /interfaces/interface/ethernet/state/counters/in-mac-pause-frames: + /interfaces/interface/ethernet/state/counters/out-mac-pause-frames: + /interfaces/interface/ethernet/state/mac-address: + /interfaces/interface/state/counters/in-broadcast-pkts: + /interfaces/interface/state/counters/in-discards: + /interfaces/interface/state/counters/in-errors: + /interfaces/interface/state/counters/in-multicast-pkts: + /interfaces/interface/state/counters/in-octets: + /interfaces/interface/state/counters/in-unicast-pkts: + /interfaces/interface/state/counters/in-unknown-protos: + /interfaces/interface/state/counters/out-broadcast-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-octets: + /interfaces/interface/state/counters/out-pkts: + /interfaces/interface/state/counters/out-unicast-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv4/state/mtu: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/mtu: + /interfaces/interface/state/oper-status: + /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/ip: + /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/in-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/out-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/ip: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/in-discarded-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/in-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-discarded-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-pkts: + /interfaces/interface/ethernet/state/aggregate-id: + /interfaces/interface/ethernet/state/port-speed: + /interfaces/interface/state/admin-status: + /interfaces/interface/state/description: + /interfaces/interface/state/type: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-forwarded-pkts: + /interfaces/interface/state/hardware-port: + /interfaces/interface/state/id: + /interfaces/interface/state/counters/in-fcs-errors: + /interfaces/interface/state/counters/carrier-transitions: + +rpcs: + gnmi: + gNMI.Set: + union_replace: false + gNMI.Subscribe: + on_change: false +``` diff --git a/feature/interface/singleton/otg_tests/singleton_test/metadata.textproto b/feature/interface/singleton/otg_tests/singleton_test/metadata.textproto index 76237f19190..926a5a3d560 100644 --- a/feature/interface/singleton/otg_tests/singleton_test/metadata.textproto +++ b/feature/interface/singleton/otg_tests/singleton_test/metadata.textproto @@ -12,6 +12,7 @@ platform_exceptions: { deviations: { ip_neighbor_missing: true ipv4_missing_enabled: true + interface_counters_update_delayed: true } } platform_exceptions: { 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 412c13a1bd1..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,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" otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" @@ -192,7 +193,7 @@ func (tc *testCase) configureATE(t *testing.T) { i1 := tc.top.Devices().Add().SetName(ap1.ID()) eth1 := i1.Ethernets().Add().SetName(ateSrc.Name + ".Eth"). SetMac(ateSrc.MAC).SetMtu(uint32(ateMTU)) - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i1.Name()) + eth1.Connection().SetPortName(i1.Name()) if ateSrc.IPv4 != "" { eth1.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4"). SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4). @@ -207,7 +208,7 @@ func (tc *testCase) configureATE(t *testing.T) { i2 := tc.top.Devices().Add().SetName(ap2.ID()) eth2 := i2.Ethernets().Add().SetName(ateDst.Name + ".Eth"). SetMac(ateDst.MAC).SetMtu(uint32(ateMTU)) - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(i2.Name()) + eth2.Connection().SetPortName(i2.Name()) if ateDst.IPv4 != "" { eth2.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4"). SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4). @@ -221,6 +222,8 @@ func (tc *testCase) configureATE(t *testing.T) { tc.ate.OTG().PushConfig(t, tc.top) tc.ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, tc.ate.OTG(), tc.top, "IPv4") + otgutils.WaitForARP(t, tc.ate.OTG(), tc.top, "IPv6") } const ( @@ -242,7 +245,7 @@ func (tc *testCase) verifyInterfaceDUT( fptest.LogQuery(t, dp.String(), dip.State(), di) di.PopulateDefaults() - if tc.mtu == 1500 { + if tc.mtu == 1500 || tc.mtu == 5000 || tc.mtu == 9236 { // MTU default values are still not populated. di.GetSubinterface(0).GetIpv4().Mtu = ygot.Uint16(tc.mtu) di.GetSubinterface(0).GetIpv6().Mtu = ygot.Uint32(uint32(tc.mtu)) @@ -374,7 +377,7 @@ func inCounters(tic *oc.Interface_Counters) *counters { return &counters{unicast: tic.GetInUnicastPkts(), multicast: tic.GetInMulticastPkts(), broadcast: tic.GetInBroadcastPkts(), - drop: tic.GetInDiscards()} + drop: tic.GetInErrors()} } func outCounters(tic *oc.Interface_Counters) *counters { @@ -428,6 +431,36 @@ func (tc *testCase) testFlow(t *testing.T, packetSize uint16, configIPHeader otg t.Logf("ap1 out-octets %d -> ap2 in-octets %d", aicp1.GetCounters().GetOutOctets(), aicp2.GetCounters().GetInOctets()) } + // Flow counters + otgutils.LogFlowMetrics(t, tc.ate.OTG(), tc.top) + fp := gnmi.Get(t, tc.ate.OTG(), gnmi.OTG().Flow(flow.Name()).State()) + fpc := fp.GetCounters() + + // Pragmatic check on the average in and out packet sizes. IPv4 may + // fragment the packet unless DF bit is set. IPv6 never fragments. + // Under no circumstances should DUT send packets greater than MTU. + + octets := fpc.GetOutOctets() + ateOutPkts := fpc.GetOutPkts() + ateInPkts := fpc.GetInPkts() + + if deviations.InterfaceCountersUpdateDelayed(tc.dut) { + batch := gnmi.OCBatch() + batch.AddPaths( + gnmi.OC().Interface(p1.Name()).Counters(), + gnmi.OC().Interface(p2.Name()).Counters(), + ) + gnmi.Watch(t, tc.dut, batch.State(), time.Second*60, func(v *ygnmi.Value[*oc.Root]) bool { + got, present := v.Val() + if !present { + return false + } + diffP1 := diffCounters(p1InBefore, inCounters(got.GetInterface(p1.Name()).GetCounters())) + diffP2 := diffCounters(p2OutBefore, outCounters(got.GetInterface(p2.Name()).GetCounters())) + return (diffP1.unicast+diffP1.drop >= ateOutPkts) && (diffP2.unicast >= ateInPkts-diffP2.drop) + }).Await(t) + } + // After Traffic Unicast, Multicast, Broadcast Counter p1InAfter := inCounters(gnmi.Get(t, tc.dut, p1Counter.State())) p2OutAfter := outCounters(gnmi.Get(t, tc.dut, p2Counter.State())) @@ -447,18 +480,6 @@ func (tc *testCase) testFlow(t *testing.T, packetSize uint16, configIPHeader otg t.Errorf("Large number of outbound Broadcast packets %d, want <= 100)", p2OutDiff.broadcast) } - // Flow counters - otgutils.LogFlowMetrics(t, tc.ate.OTG(), tc.top) - fp := gnmi.Get(t, tc.ate.OTG(), gnmi.OTG().Flow(flow.Name()).State()) - fpc := fp.GetCounters() - - // Pragmatic check on the average in and out packet sizes. IPv4 may - // fragment the packet unless DF bit is set. IPv6 never fragments. - // Under no circumstances should DUT send packets greater than MTU. - - octets := fpc.GetOutOctets() - ateOutPkts := fpc.GetOutPkts() - ateInPkts := fpc.GetInPkts() if ateOutPkts == 0 { t.Error("Flow did not send any packet") } else if avg := octets / ateOutPkts; avg > uint64(tc.mtu) { diff --git a/feature/interface/staticarp/ate_tests/static_arp_test/README.md b/feature/interface/staticarp/ate_tests/static_arp_test/README.md deleted file mode 100644 index 0e9f37f6864..00000000000 --- a/feature/interface/staticarp/ate_tests/static_arp_test/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# TE-1.1: Static ARP - -## Summary - -Ensure static ARP entries installed on the DUT are honoured. - -## Procedure - -* Configure ATE port-1 connected to DUT port-1, and ATE port-2 connected to - DUT port-2, with the relevant IPv4 and IPv6 addresses. -* Without static ARP entry: - * Configure ATE traffic flow to enable custom egress filter on the last - 15-bits of the destination MAC (starting at bit offset 33 of the - ethernet packet). - * Ensure that traffic can be forwarded between ATE port-1 and ATE port-2 - normally. - * Check that the egress filter picks up the last 15-bit of ATE default MAC - address. -* Add static entry to DUT interfaces to override the ATE MAC address. -* With static ARP entry: - * Configure ATE traffic flow with custom egress filter as before, and - ensure that traffic can be forwarded between ATE port-1 and ATE port-2. - * Check that the egress filter picks up the last 15-bit of the MAC address - set by static ARP. - -Note that ATE 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 ATE. - -## 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 diff --git a/feature/interface/staticarp/ate_tests/static_arp_test/static_arp_test.go b/feature/interface/staticarp/ate_tests/static_arp_test/static_arp_test.go deleted file mode 100644 index ab3fe8843d7..00000000000 --- a/feature/interface/staticarp/ate_tests/static_arp_test/static_arp_test.go +++ /dev/null @@ -1,289 +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 -// -// 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 te_1_1_static_arp_test - -import ( - "fmt" - "testing" - "time" - - "github.com/openconfig/featureprofiles/internal/attrs" - "github.com/openconfig/featureprofiles/internal/deviations" - "github.com/openconfig/featureprofiles/internal/fptest" - "github.com/openconfig/ygot/ygot" - - "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi" - "github.com/openconfig/ondatra/gnmi/oc" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -// Settings for configuring the baseline testbed with the test -// topology. IxNetwork flow requires both source and destination -// networks be configured on the ATE. It is not possible to send -// packets to the ether. -// -// The testbed consists of ate:port1 -> dut:port1 and -// dut:port2 -> ate:port2. The first pair is called the "source" -// pair, and the second the "destination" pair. -// -// - Source: ate:port1 -> dut:port1 subnet 192.0.2.0/30 2001:db8::0/126 -// - Destination: dut:port2 -> ate:port2 subnet 192.0.2.4/30 2001:db8::4/126 -// -// Note that the first (.0, .4) and last (.3, .7) IPv4 addresses are -// reserved from the subnet for broadcast, so a /30 leaves exactly 2 -// usable addresses. This does not apply to IPv6 which allows /127 -// for point to point links, but we use /126 so the numbering is -// consistent with IPv4. -// -// A traffic flow is configured from ate:port1 as the source interface -// and ate:port2 as the destination interface. The traffic should -// flow as expected both when using dynamic or static ARP since the -// Ixia interfaces are promiscuous. However, using custom egress -// filter, we can tell if the static ARP is honored or not. -// -// Synthesized static MAC addresses have the form 02:1a:WW:XX:YY:ZZ -// where WW:XX:YY:ZZ are the four octets of the IPv4 in hex. The 0x02 -// means the MAC address is locally administered. -const ( - plen4 = 30 - plen6 = 126 - - poisonedMAC = "12:34:56:78:7a:69" // 0x7a69 = 31337 - noStaticMAC = "" -) - -var ( - ateSrc = attrs.Attributes{ - Name: "ateSrc", - IPv4: "192.0.2.1", - IPv6: "2001:db8::1", - IPv4Len: plen4, - IPv6Len: plen6, - } - - dutSrc = attrs.Attributes{ - Desc: "DUT to ATE source", - IPv4: "192.0.2.2", - IPv6: "2001:db8::2", - MAC: "02:1a:c0:00:02:02", // 02:1a+192.0.2.2 - IPv4Len: plen4, - IPv6Len: plen6, - } - - dutDst = attrs.Attributes{ - Desc: "DUT to ATE destination", - IPv4: "192.0.2.5", - IPv6: "2001:db8::5", - MAC: "02:1a:c0:00:02:05", // 02:1a+192.0.2.5 - IPv4Len: plen4, - IPv6Len: plen6, - } - - ateDst = attrs.Attributes{ - Name: "dst", - IPv4: "192.0.2.6", - IPv6: "2001:db8::6", - IPv4Len: plen4, - IPv6Len: plen6, - } -) - -// configInterfaceDUT configures the interface on "me" with static ARP -// of peer. Note that peermac is used for static ARP, and not -// peer.MAC. -func configInterfaceDUT(t *testing.T, p *ondatra.Port, me, peer *attrs.Attributes, peermac string, dut *ondatra.DUTDevice) *oc.Interface { - i := &oc.Interface{Name: ygot.String(p.Name())} - i.Description = ygot.String(me.Desc) - i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd - if deviations.InterfaceEnabled(dut) { - i.Enabled = ygot.Bool(true) - } - - if deviations.ExplicitPortSpeed(dut) { - e := i.GetOrCreateEthernet() - e.PortSpeed = fptest.GetIfSpeed(t, p) - } - - if me.MAC != "" { - e := i.GetOrCreateEthernet() - e.MacAddress = ygot.String(me.MAC) - } - - s := i.GetOrCreateSubinterface(0) - s4 := s.GetOrCreateIpv4() - if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { - s4.Enabled = ygot.Bool(true) - } - a := s4.GetOrCreateAddress(me.IPv4) - a.PrefixLength = ygot.Uint8(plen4) - - if peermac != noStaticMAC { - n4 := s4.GetOrCreateNeighbor(peer.IPv4) - n4.LinkLayerAddress = ygot.String(peermac) - } - - s6 := s.GetOrCreateIpv6() - if deviations.InterfaceEnabled(dut) { - s6.Enabled = ygot.Bool(true) - } - s6.GetOrCreateAddress(me.IPv6).PrefixLength = ygot.Uint8(plen6) - - if peermac != noStaticMAC { - n6 := s6.GetOrCreateNeighbor(peer.IPv6) - n6.LinkLayerAddress = ygot.String(peermac) - } - - return i -} - -func configureDUT(t *testing.T, peermac string) { - dut := ondatra.DUT(t, "dut") - d := gnmi.OC() - - p1 := dut.Port(t, "port1") - gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(t, p1, &dutSrc, &ateSrc, peermac, dut)) - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) - } - - p2 := dut.Port(t, "port2") - gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(t, p2, &dutDst, &ateDst, peermac, dut)) - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) - } -} - -func configureATE(t *testing.T) (*ondatra.ATEDevice, *ondatra.ATETopology) { - ate := ondatra.ATE(t, "ate") - top := ate.Topology().New() - - p1 := ate.Port(t, "port1") - i1 := top.AddInterface(ateSrc.Name).WithPort(p1) - i1.IPv4(). - WithAddress(ateSrc.IPv4CIDR()). - WithDefaultGateway(dutSrc.IPv4) - i1.IPv6(). - WithAddress(ateSrc.IPv6CIDR()). - WithDefaultGateway(dutSrc.IPv6) - - p2 := ate.Port(t, "port2") - i2 := top.AddInterface(ateDst.Name).WithPort(p2) - i2.IPv4(). - WithAddress(ateDst.IPv4CIDR()). - WithDefaultGateway(dutDst.IPv4) - i2.IPv6(). - WithAddress(ateDst.IPv6CIDR()). - WithDefaultGateway(dutDst.IPv6) - - return ate, top -} - -func testFlow( - t *testing.T, - want string, - ate *ondatra.ATEDevice, - top *ondatra.ATETopology, - headers ...ondatra.Header, -) { - i1 := top.Interfaces()[ateSrc.Name] - i2 := top.Interfaces()[ateDst.Name] - - // Egress tracking inspects packets from DUT and key the flow - // counters by custom bit offset and width. Width is limited to - // 15-bits. - // - // Ethernet header: - // - Destination MAC (6 octets) - // - Source MAC (6 octets) - // - Optional 802.1q VLAN tag (4 octets) - // - Frame size (2 octets) - flow := ate.Traffic().NewFlow("Flow"). - WithSrcEndpoints(i1). - WithDstEndpoints(i2). - WithHeaders(headers...) - flow.EgressTracking().WithOffset(33).WithWidth(15) - - ate.Traffic().Start(t, flow) - time.Sleep(15 * time.Second) - ate.Traffic().Stop(t) - - flowPath := gnmi.OC().Flow(flow.Name()) - - if got := gnmi.Get(t, ate, flowPath.LossPct().State()); got > 0 { - t.Errorf("LossPct for flow %s got %g, want 0", flow.Name(), got) - } - - etPath := flowPath.EgressTrackingAny() - ets := gnmi.GetAll(t, ate, etPath.State()) - for i, et := range ets { - fptest.LogQuery(t, fmt.Sprintf("ATE flow EgressTracking[%d]", i), etPath.State(), et) - } - - if got := len(ets); got != 1 { - t.Errorf("EgressTracking got %d items, want 1", got) - return - } - - if got := ets[0].GetFilter(); got != want { - t.Errorf("EgressTracking filter got %q, want %q", got, want) - } - - if got := ets[0].GetCounters().GetInPkts(); got < 1000 { - t.Errorf("EgressTracking counter in-pkts got %d, want >= 1000", got) - } -} - -func TestStaticARP(t *testing.T) { - // First configure the DUT with dynamic ARP. - configureDUT(t, noStaticMAC) - ate, top := configureATE(t) - top.Push(t).StartProtocols(t) - - ethHeader := ondatra.NewEthernetHeader() - ipv4Header := ondatra.NewIPv4Header() - ipv6Header := ondatra.NewIPv6Header() - - // Default MAC addresses on Ixia are assigned incrementally as: - // - 00:11:01:00:00:01 - // - 00:12:01:00:00:01 - // etc. - // - // The last 15-bits therefore resolve to "1". - t.Run("NotPoisoned", func(t *testing.T) { - t.Run("IPv4", func(t *testing.T) { - testFlow(t, "1" /* want */, ate, top, ethHeader, ipv4Header) - }) - t.Run("IPv6", func(t *testing.T) { - testFlow(t, "1" /* want */, ate, top, ethHeader, ipv6Header) - }) - }) - - // Reconfigure the DUT with static MAC. - configureDUT(t, poisonedMAC) - - // Poisoned MAC address ends with 7a:69, so 0x7a69 = 31337. - t.Run("Poisoned", func(t *testing.T) { - t.Run("IPv4", func(t *testing.T) { - testFlow(t, "31337" /* want */, ate, top, ethHeader, ipv4Header) - }) - t.Run("IPv6", func(t *testing.T) { - testFlow(t, "31337" /* want */, ate, top, ethHeader, ipv6Header) - }) - }) -} diff --git a/feature/interface/staticarp/feature.textproto b/feature/interface/staticarp/feature.textproto index db7a4240b41..0dfd3be72c5 100644 --- a/feature/interface/staticarp/feature.textproto +++ b/feature/interface/staticarp/feature.textproto @@ -11,6 +11,9 @@ # 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" 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 62fa0970b13..526c4fc8e83 100644 --- a/feature/interface/staticarp/otg_tests/static_arp_test/README.md +++ b/feature/interface/staticarp/otg_tests/static_arp_test/README.md @@ -37,3 +37,13 @@ are the destination MAC addresses of the packets seen by the OTG. * /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 +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: + +``` \ No newline at end of file diff --git a/feature/interface/staticarp/otg_tests/static_arp_test/static_arp_test.go b/feature/interface/staticarp/otg_tests/static_arp_test/static_arp_test.go index eee42c25969..b576c4fd267 100644 --- a/feature/interface/staticarp/otg_tests/static_arp_test/static_arp_test.go +++ b/feature/interface/staticarp/otg_tests/static_arp_test/static_arp_test.go @@ -240,7 +240,6 @@ func testFlow( v6.Src().SetValue(ateSrc.IPv6) v6.Dst().SetValue(ateDst.IPv6) } - flow.Duration().SetChoice("fixed_packets") flow.Duration().FixedPackets().SetPackets(1000) flow.Size().SetFixed(100) eth := flow.EgressPacket().Add().Ethernet() @@ -294,13 +293,12 @@ func testFlow( } func TestStaticARP(t *testing.T) { - // Configure the ATE - ate := ondatra.ATE(t, "ate") - config := configureATE(t) - // Configure the DUT with dynamic ARP. configureDUT(t, noStaticMAC) + // Configure the ATE + ate := ondatra.ATE(t, "ate") + config := configureATE(t) ate.OTG().StartProtocols(t) otgutils.WaitForARP(t, ate.OTG(), config, "IPv4") dstMac := gnmi.Get(t, ate.OTG(), gnmi.OTG().Interface(ateSrc.Name+".Eth").Ipv4Neighbor(dutSrc.IPv4).LinkLayerAddress().State()) diff --git a/feature/isis/auth/feature.textproto b/feature/isis/auth/feature.textproto index 8f1c6148786..0300d1bc3ad 100644 --- a/feature/isis/auth/feature.textproto +++ b/feature/isis/auth/feature.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile id { name: "isis_auth" diff --git a/feature/isis/feature.textproto b/feature/isis/feature.textproto index cef1e4f81f6..aaa62b80e57 100644 --- a/feature/isis/feature.textproto +++ b/feature/isis/feature.textproto @@ -1,3 +1,6 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { name: "isis" version: 3 diff --git a/feature/isis/link-state-database/feature.textproto b/feature/isis/link_state_database/feature.textproto similarity index 98% rename from feature/isis/link-state-database/feature.textproto rename to feature/isis/link_state_database/feature.textproto index 36992236888..bb823bb97d6 100644 --- a/feature/isis/link-state-database/feature.textproto +++ b/feature/isis/link_state_database/feature.textproto @@ -1,5 +1,8 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { - name: "isis_link-state-database" + name: "isis_link_state_database" version: 1 } diff --git a/feature/isis/otg_tests/base_adjacencies_test/README.md b/feature/isis/otg_tests/base_adjacencies_test/README.md new file mode 100644 index 00000000000..1fc8c021715 --- /dev/null +++ b/feature/isis/otg_tests/base_adjacencies_test/README.md @@ -0,0 +1,207 @@ +# RT-2.1: Base IS-IS Process and Adjacencies + +## Summary + +Base IS-IS functionality and adjacency establishment. + +## 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 ] + | | + ``` + +### RT-2.1.1 Basic fields test + +* Configure DUT:port1 for an IS-IS session with ATE:port1. +* Read back the configuration to ensure that all fields are readable and + have been set properly (or correctly have their default value). +* Check that all relevant counters are readable and are 0 since the + adjacency has not yet been established. +* Push ATE configuration for the other end of the adjacency, and wait for + the adjacency to form. +* Check that the various state fields of the adjacency are reported + correctly. +* Check that error counters are still 0 and that packet counters have all + increased. + +### RT-2.1.2 Hello padding test + +* Configure IS-IS between DUT:port1 and ATE:port1 for each possible value + of hello padding (DISABLED, STRICT, etc.) +* Confirm in each case that that adjacency forms and the correct values + are reported back by the device. + +### RT-2.1.3 Authentication test + +* Configure IS-IS between DUT:port1 and ATE:port1 With authentication + disabled, then enabled in TEXT mode, then enabled in MD5 mode. +* Confirm in each case that that adjacency forms and the correct values + are reported back by the device. + +### RT-2.1.4 [TODO: https://github.com/openconfig/featureprofiles/issues/3421] + +* Configuration: + * Configure ISIS for ATE port-1 and DUT port-1. + * Configure both DUT and ATE interfaces as ISIS type point-to-point. +* Verification: + * Verify that ISIS adjacency is coming up. + * Verify the output of streaming telemetry path displaying the interface circuit-type as point-to-point. + +### RT-2.1.5 Routing test + +* Configure ISIS level authentication and hello authentication. +* Ensure that IPv4 and IPv6 prefixes that are advertised as attached + prefixes within each LSP are correctly installed into the DUT + routing table, by ensuring that packets are received to the attached + prefix when forwarded from ATE port-1. +* Ensure that IPv4 and IPv6 prefixes that are advertised as part of an + (emulated) neighboring system are installed into the DUT routing + table, and validate that packets are sent and received to them. +* With a known LSP content, ensure that the telemetry received from the + device for the LSP matches the expected content. + +### RT-2.1.6 [TODO: https://github.com/openconfig/featureprofiles/issues/3422] + +* Baseline Configuration on the DUT: + * Set the hello-interval to a standard value (10 seconds). + * Set the hello-multiplier to its default (3). + * Check that the streaming telemetry values are reported correctly by the DUT. +* Adjusting Hello-Interval configuration on the DUT: + * Change the hello-interval to a different value (15 seconds) in the DUT. + * Verify that IS-IS adjacency is coming up in the DUT. + * Verify that the updated Hello-Interval time is reflected in isis adjacency output in the ATE. + * Verify that the correct streaming telemetry values are reported correctly by the DUT. +* Adjusting Hello-Multiplier configuration on the DUT: + * Change the hello-multiplier to a different value (5) the DUT. + * Verify that IS-IS adjacency is coming up in the DUT. + * Verify that the updated Hello-Multiplier is reflected in isis adjacency output in the ATE. + * Verify that the correct streaming telemetry values are reported correctly by 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 + /network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: + /network-instances/network-instance/protocols/protocol/isis/global/config/net: + /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: + /network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: + + + ## State paths + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/dropped: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/processed: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/received: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/sent: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/dropped: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/processed: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/received: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/retransmit: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/sent: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/dropped: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/processed: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/received: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/retransmit: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/sent: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/dropped: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/processed: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/received: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/retransmit: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/sent: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/state/hello-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/state/hello-multiplier: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/adj-changes: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/adj-number: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/auth-fails: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/auth-type-fails: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/id-field-len-mismatches: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/lan-dis-changes: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/rejected-adj: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: + ###For LSDB - Examples of paths + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/state/lsp-id: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/state/maximum-area-addresses: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/state/pdu-type: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/state/sequence-number: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/state/type: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/area-address/state/address: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/hostname/state/hostname: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-interface-addresses/state/address: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-interface-addresses/state/address: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-te-router-id/state/router-id: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-te-router-id/state/router-id: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* 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/feature/experimental/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go b/feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go similarity index 96% rename from feature/experimental/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go rename to feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go index 40628b4b6d8..db6ac87d66e 100644 --- a/feature/experimental/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go +++ b/feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go @@ -23,11 +23,11 @@ import ( "time" "github.com/open-traffic-generator/snappi/gosnappi" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/check" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/featureprofiles/internal/otgutils" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -65,12 +65,12 @@ func CheckPresence(query ygnmi.SingletonQuery[uint32], missingValueForDefaults b // then configures the ATE as well, waits for the adjacency to form, and checks that numerous // counters and other values now have sensible values. func TestBasic(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() // Only push DUT config - no adjacency established yet if err := ts.PushDUT(context.Background(), t); err != nil { t.Fatalf("Unable to push initial DUT config: %v", err) } - isisRoot := session.ISISPath(ts.DUT) + isisRoot := isissession.ISISPath(ts.DUT) port1ISIS := isisRoot.Interface(ts.DUTPort1.Name()) if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { port1ISIS = isisRoot.Interface(ts.DUTPort1.Name() + ".0") @@ -234,13 +234,13 @@ func TestBasic(t *testing.T) { for _, vd := range []check.Validator{ check.Equal(adj.AdjacencyState().State(), oc.Isis_IsisInterfaceAdjState_UP), check.Equal(adj.SystemId().State(), systemID), - check.UnorderedEqual(adj.AreaAddress().State(), []string{session.ATEAreaAddress, session.DUTAreaAddress}, func(a, b string) bool { return a < b }), + check.UnorderedEqual(adj.AreaAddress().State(), []string{isissession.ATEAreaAddress, isissession.DUTAreaAddress}, func(a, b string) bool { return a < b }), check.EqualOrNil(adj.DisSystemId().State(), "0000.0000.0000"), check.NotEqual(adj.LocalExtendedCircuitId().State(), uint32(0)), check.Equal(adj.MultiTopology().State(), false), check.Equal(adj.NeighborCircuitType().State(), oc.Isis_LevelType_LEVEL_2), check.NotEqual(adj.NeighborExtendedCircuitId().State(), uint32(0)), - check.Equal(adj.NeighborIpv4Address().State(), session.ATEISISAttrs.IPv4), + check.Equal(adj.NeighborIpv4Address().State(), isissession.ATEISISAttrs.IPv4), check.Predicate(adj.NeighborSnpa().State(), "Need a valid MAC address", func(got string) bool { mac, err := net.ParseMAC(got) return mac != nil && err == nil @@ -404,7 +404,7 @@ func TestHelloPadding(t *testing.T) { if tc.skip != "" { t.Skip(tc.skip) } - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() ts.ConfigISIS(func(isis *oc.NetworkInstance_Protocol_Isis) { global := isis.GetOrCreateGlobal() global.HelloPadding = tc.mode @@ -415,7 +415,7 @@ func TestHelloPadding(t *testing.T) { if err != nil { t.Fatalf("No IS-IS adjacency formed: %v", err) } - telemPth := session.ISISPath(ts.DUT).Global() + telemPth := isissession.ISISPath(ts.DUT).Global() var vd check.Validator missingValueForDefaults := deviations.MissingValueForDefaults(ts.DUT) if tc.mode == oc.Isis_HelloPaddingType_STRICT { @@ -444,7 +444,7 @@ func TestAuthentication(t *testing.T) { {name: "disabled", mode: oc.IsisTypes_AUTH_MODE_TEXT, enabled: false}, } { t.Run(tc.name, func(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() ts.ConfigISIS(func(isis *oc.NetworkInstance_Protocol_Isis) { level := isis.GetOrCreateLevel(2) level.Enabled = ygot.Bool(true) @@ -483,7 +483,7 @@ func TestAuthentication(t *testing.T) { // TestTraffic has the ATE advertise some routes and verifies that traffic sent to the DUT is routed // appropriately. func TestTraffic(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() otg := ts.ATE.OTG() targetNetwork := &attrs.Attributes{ Desc: "External network (simulated by ATE)", @@ -533,10 +533,10 @@ func TestTraffic(t *testing.T) { v4Flow.TxRx().Device().SetTxNames([]string{srcIpv4.Name()}).SetRxNames([]string{netv4.Name()}) v4FlowEth := v4Flow.Packet().Add().Ethernet() - v4FlowEth.Src().SetValue(session.ATETrafficAttrs.MAC) + v4FlowEth.Src().SetValue(isissession.ATETrafficAttrs.MAC) v4FlowIp := v4Flow.Packet().Add().Ipv4() - v4FlowIp.Src().SetValue(session.ATETrafficAttrs.IPv4) + v4FlowIp.Src().SetValue(isissession.ATETrafficAttrs.IPv4) v4FlowIp.Dst().SetValue(targetNetwork.IPv4) v4Flow.Metrics().SetEnable(true) @@ -547,10 +547,10 @@ func TestTraffic(t *testing.T) { v6Flow.TxRx().Device().SetTxNames([]string{srcIpv6.Name()}).SetRxNames([]string{netv6.Name()}) v6FlowEth := v6Flow.Packet().Add().Ethernet() - v6FlowEth.Src().SetValue(session.ATETrafficAttrs.MAC) + v6FlowEth.Src().SetValue(isissession.ATETrafficAttrs.MAC) v6FlowIp := v6Flow.Packet().Add().Ipv6() - v6FlowIp.Src().SetValue(session.ATETrafficAttrs.IPv6) + v6FlowIp.Src().SetValue(isissession.ATETrafficAttrs.IPv6) v6FlowIp.Dst().SetValue(targetNetwork.IPv6) // v6Flow.Duration().FixedPackets().SetPackets(100) @@ -563,10 +563,10 @@ func TestTraffic(t *testing.T) { deadFlow.TxRx().Device().SetTxNames([]string{srcIpv4.Name()}).SetRxNames([]string{netv4.Name()}) deadFlowEth := deadFlow.Packet().Add().Ethernet() - deadFlowEth.Src().SetValue(session.ATETrafficAttrs.MAC) + deadFlowEth.Src().SetValue(isissession.ATETrafficAttrs.MAC) deadFlowIp := deadFlow.Packet().Add().Ipv4() - deadFlowIp.Src().SetValue(session.ATETrafficAttrs.IPv4) + deadFlowIp.Src().SetValue(isissession.ATETrafficAttrs.IPv4) deadFlowIp.Dst().SetValue(deadNetwork.IPv4) // deadFlow.Duration().FixedPackets().SetPackets(100) diff --git a/feature/experimental/isis/otg_tests/base_adjacencies_test/metadata.textproto b/feature/isis/otg_tests/base_adjacencies_test/metadata.textproto similarity index 100% rename from feature/experimental/isis/otg_tests/base_adjacencies_test/metadata.textproto rename to feature/isis/otg_tests/base_adjacencies_test/metadata.textproto diff --git a/feature/isis/otg_tests/graceful_restart_helper/README.md b/feature/isis/otg_tests/graceful_restart_helper/README.md new file mode 100644 index 00000000000..9c822775000 --- /dev/null +++ b/feature/isis/otg_tests/graceful_restart_helper/README.md @@ -0,0 +1,252 @@ +# RT-2.15: 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.15.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/graceful_restart_helper/metadata.textproto b/feature/isis/otg_tests/graceful_restart_helper/metadata.textproto new file mode 100644 index 00000000000..4c84b917911 --- /dev/null +++ b/feature/isis/otg_tests/graceful_restart_helper/metadata.textproto @@ -0,0 +1,21 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "4ebdc9b9-8f58-4e33-875b-b5b2a3e0de33" +plan_id: "RT-2.15" +description: "IS-IS Graceful Restart Helper" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + 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 + skip_isis_set_level: true + } +} \ No newline at end of file diff --git a/feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/README.md b/feature/isis/otg_tests/isis_change_lsp_lifetime_test/README.md similarity index 70% rename from feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/README.md rename to feature/isis/otg_tests/isis_change_lsp_lifetime_test/README.md index 02310135271..a7bd7a209cd 100644 --- a/feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/README.md +++ b/feature/isis/otg_tests/isis_change_lsp_lifetime_test/README.md @@ -23,23 +23,18 @@ * Verify that the remaining lifetime of the lsp is remaining lifetime = configured lifetime - time passed since the LSP PDU generation. * Verify that once the new LSP PDU is generated the sequence number and checksum of the new LSP PDU is updated -## Config Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/timers/config/lsp-lifetime-interval - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/timers/state/lsp-lifetime-interval - * levels/level/link-state-database/lsp/state/remaining-lifetime +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/timers/config/lsp-lifetime-interval: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/timers/state/lsp-lifetime-interval: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/state/remaining-lifetime: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` diff --git a/feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go b/feature/isis/otg_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go similarity index 88% rename from feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go rename to feature/isis/otg_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go index e63d27ca5ce..8b731d015be 100644 --- a/feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go +++ b/feature/isis/otg_tests/isis_change_lsp_lifetime_test/isis_change_lsp_lifetime_test.go @@ -19,9 +19,9 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/featureprofiles/internal/otgutils" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -48,17 +48,17 @@ const ( ) // configureISIS configures isis on DUT. -func configureISIS(t *testing.T, ts *session.TestSession) { +func configureISIS(t *testing.T, ts *isissession.TestSession) { t.Helper() d := ts.DUTConf netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) - prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) prot.Enabled = ygot.Bool(true) isis := prot.GetOrCreateIsis() globalIsis := isis.GetOrCreateGlobal() if deviations.ISISInstanceEnabledRequired(ts.DUT) { - globalIsis.Instance = ygot.String(session.ISISName) + globalIsis.Instance = ygot.String(isissession.ISISName) } // Global configs @@ -72,18 +72,18 @@ func configureISIS(t *testing.T, ts *session.TestSession) { } // configureOTG configures isis and traffic on OTG. -func configureOTG(t *testing.T, ts *session.TestSession) { +func configureOTG(t *testing.T, ts *isissession.TestSession) { t.Helper() ts.ATEIntf1.Isis().Advanced().SetLspRefreshRate(60) // netv4 is a simulated network containing the ipv4 addresses specified by targetNetwork netv4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName(v4NetName).SetLinkMetric(10) - netv4.Addresses().Add().SetAddress(v4Route1).SetPrefix(uint32(session.ATEISISAttrs.IPv4Len)) + netv4.Addresses().Add().SetAddress(v4Route1).SetPrefix(uint32(isissession.ATEISISAttrs.IPv4Len)) // netv6 is a simulated network containing the ipv6 addresses specified by targetNetwork netv6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName(v6NetName).SetLinkMetric(10) - netv6.Addresses().Add().SetAddress(v6Route1).SetPrefix(uint32(session.ATEISISAttrs.IPv6Len)) + netv6.Addresses().Add().SetAddress(v6Route1).SetPrefix(uint32(isissession.ATEISISAttrs.IPv6Len)) // We generate traffic entering along port2 and destined for port1 srcIpv4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] @@ -98,11 +98,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v4NetName}) v4Flow.Size().SetFixed(512) v4Flow.Rate().SetPps(100) - v4Flow.Duration().SetChoice("continuous") + v4Flow.Duration().Continuous() e1 := v4Flow.Packet().Add().Ethernet() - e1.Src().SetValue(session.ATEISISAttrs.MAC) + e1.Src().SetValue(isissession.ATEISISAttrs.MAC) v4 := v4Flow.Packet().Add().Ipv4() - v4.Src().SetValue(session.ATEISISAttrs.IPv4) + v4.Src().SetValue(isissession.ATEISISAttrs.IPv4) v4.Dst().Increment().SetStart(v4IP).SetCount(1) t.Log("Configuring v6 traffic flow ") @@ -114,33 +114,35 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v6NetName}) v6Flow.Size().SetFixed(512) v6Flow.Rate().SetPps(100) - v6Flow.Duration().SetChoice("continuous") + v6Flow.Duration().Continuous() e2 := v6Flow.Packet().Add().Ethernet() - e2.Src().SetValue(session.ATEISISAttrs.MAC) + e2.Src().SetValue(isissession.ATEISISAttrs.MAC) v6 := v6Flow.Packet().Add().Ipv6() - v6.Src().SetValue(session.ATEISISAttrs.IPv6) + v6.Src().SetValue(isissession.ATEISISAttrs.IPv6) v6.Dst().Increment().SetStart(v6IP).SetCount(1) } // TestISISChangeLSPLifetime verifies isis lsp telemetry paramters with configured lsp lifetime. func TestISISChangeLSPLifetime(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() configureISIS(t, ts) configureOTG(t, ts) otg := ts.ATE.OTG() - pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) - fptest.LogQuery(t, "Protocol ISIS", session.ProtocolPath(ts.DUT).Config(), pcl) + pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) + fptest.LogQuery(t, "Protocol ISIS", isissession.ProtocolPath(ts.DUT).Config(), pcl) ts.PushAndStart(t) + time.Sleep(time.Minute * 2) - isisPath := session.ISISPath(ts.DUT) + isisPath := isissession.ISISPath(ts.DUT) intfName := ts.DUTPort1.Name() if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { intfName += ".0" } t.Run("Isis telemetry", func(t *testing.T) { + time.Sleep(time.Minute * 2) // Checking adjacency ateSysID, err := ts.AwaitAdjacency() @@ -148,7 +150,7 @@ func TestISISChangeLSPLifetime(t *testing.T) { t.Fatalf("Adjacency state invalid: %v", err) } ateLspID := ateSysID + ".00-00" - dutLspID := session.DUTSysID + ".00-00" + dutLspID := isissession.DUTSysID + ".00-00" // wait for ATE Lsp TLV to be present in DUT _, ok := gnmi.Await(t, ts.DUT, isisPath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(v4Route).Prefix().State(), 2*time.Minute, v4Route).Val() @@ -189,7 +191,7 @@ func TestISISChangeLSPLifetime(t *testing.T) { t.Errorf("FAIL- Expected v6 route not found in isis, got %v, want %v", got, want) } ipv4Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv4Entry(v4Route) - if got, ok := gnmi.Watch(t, ts.DUT, ipv4Path.State(), time.Second*30, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + if got, ok := gnmi.Watch(t, ts.DUT, ipv4Path.State(), time.Minute*2, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { ipv4Entry, present := val.Val() return present && ipv4Entry.GetPrefix() == v4Route }).Await(t); !ok { @@ -197,7 +199,7 @@ func TestISISChangeLSPLifetime(t *testing.T) { } ipv6Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv6Entry(v6Route) - if got, ok := gnmi.Watch(t, ts.DUT, ipv6Path.State(), time.Second*30, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv6Entry]) bool { + if got, ok := gnmi.Watch(t, ts.DUT, ipv6Path.State(), time.Minute*2, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv6Entry]) bool { ipv6Entry, present := val.Val() return present && ipv6Entry.GetPrefix() == v6Route }).Await(t); !ok { diff --git a/feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/metadata.textproto b/feature/isis/otg_tests/isis_change_lsp_lifetime_test/metadata.textproto similarity index 96% rename from feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/metadata.textproto rename to feature/isis/otg_tests/isis_change_lsp_lifetime_test/metadata.textproto index b149d899ec6..d1829e30cd9 100644 --- a/feature/experimental/isis/otg_tests/isis_change_lsp_lifetime_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_change_lsp_lifetime_test/metadata.textproto @@ -32,14 +32,14 @@ platform_exceptions: { 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_instance_enabled_required: true + isis_interface_afi_unsupported: true isis_lsp_lifetime_interval_requires_lsp_refresh_interval: true isis_lsp_metadata_leafs_unsupported: true - isis_interface_afi_unsupported: true } } platform_exceptions: { diff --git a/feature/experimental/isis/otg_tests/isis_drain_test/README.md b/feature/isis/otg_tests/isis_drain_test/README.md similarity index 81% rename from feature/experimental/isis/otg_tests/isis_drain_test/README.md rename to feature/isis/otg_tests/isis_drain_test/README.md index 8537bc0f7f8..67410a03579 100644 --- a/feature/experimental/isis/otg_tests/isis_drain_test/README.md +++ b/feature/isis/otg_tests/isis_drain_test/README.md @@ -1,4 +1,4 @@ -# RT-2.12: IS-IS Drain Test +# RT-2.14: IS-IS Drain Test ## Summary @@ -12,15 +12,13 @@ Ensure that IS-IS metric change can drain traffic from a DUT trunk interface * 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. -## Config Parameter Coverage - -## Telemetry Parameter Coverage - -## Protocol/RPC Parameter Coverage - -* IS-IS - * LSP - * TLV 22 metric field. +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` ## Minimum DUT Platform Requirement diff --git a/feature/experimental/isis/otg_tests/isis_drain_test/isis_drain_test.go b/feature/isis/otg_tests/isis_drain_test/isis_drain_test.go similarity index 87% rename from feature/experimental/isis/otg_tests/isis_drain_test/isis_drain_test.go rename to feature/isis/otg_tests/isis_drain_test/isis_drain_test.go index 25d4d3af853..13ba419a8da 100644 --- a/feature/experimental/isis/otg_tests/isis_drain_test/isis_drain_test.go +++ b/feature/isis/otg_tests/isis_drain_test/isis_drain_test.go @@ -164,44 +164,56 @@ func configDstMemberDUT(dut *ondatra.DUTDevice, i *oc.Interface, p *ondatra.Port func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { // configure port 1 p1 := dut.Port(t, "port1") - p1Name := p1.Name() - i1 := &oc.Interface{Name: ygot.String(p1Name)} + i1 := &oc.Interface{Name: ygot.String(p1.Name())} configSrcDUT(dut, i1, &dutPort1) - gnmi.Replace(t, dut, gnmi.OC().Interface(p1Name).Config(), i1) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), i1) // configure port 2 / trunk 2 p2 := dut.Port(t, "port2") - p2Name := p2.Name() - agg2ID = netutil.NextAggregateInterface(t, dut) agg2 := &oc.Interface{Name: ygot.String(agg2ID)} configDstAggregateDUT(dut, agg2, &dutPort2) - gnmi.Replace(t, dut, gnmi.OC().Interface(agg2ID).Config(), agg2) - - i2 := &oc.Interface{Name: ygot.String(p2Name)} + i2 := &oc.Interface{Name: ygot.String(p2.Name())} configDstMemberDUT(dut, i2, p2, agg2ID) - gnmi.Replace(t, dut, gnmi.OC().Interface(p2Name).Config(), i2) + t.Logf("Adding port: %s to Aggregate: %s", p2.Name(), agg2ID) + switch { + case deviations.AggregateAtomicUpdate(dut): + b := &gnmi.SetBatch{} + gnmi.BatchDelete(b, gnmi.OC().Interface(agg2ID).Aggregation().MinLinks().Config()) + gnmi.BatchDelete(b, gnmi.OC().Interface(p2.Name()).Ethernet().AggregateId().Config()) + gnmi.BatchReplace(b, gnmi.OC().Interface(agg2ID).Config(), agg2) + gnmi.BatchReplace(b, gnmi.OC().Interface(p2.Name()).Config(), i2) + b.Set(t, dut) + default: + gnmi.Replace(t, dut, gnmi.OC().Interface(agg2ID).Config(), agg2) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), i2) + } // configure port 3 / trunk 3 p3 := dut.Port(t, "port3") - p3Name := p3.Name() - agg3ID = netutil.NextAggregateInterface(t, dut) agg3 := &oc.Interface{Name: ygot.String(agg3ID)} configDstAggregateDUT(dut, agg3, &dutPort3) - gnmi.Replace(t, dut, gnmi.OC().Interface(agg3ID).Config(), agg3) - - i3 := &oc.Interface{Name: ygot.String(p3Name)} + i3 := &oc.Interface{Name: ygot.String(p3.Name())} configDstMemberDUT(dut, i3, p3, agg3ID) - gnmi.Replace(t, dut, gnmi.OC().Interface(p3Name).Config(), i3) + t.Logf("Adding port: %s to Aggregate: %s", p3.Name(), agg3ID) + + switch { + case deviations.AggregateAtomicUpdate(dut): + b := &gnmi.SetBatch{} + gnmi.BatchDelete(b, gnmi.OC().Interface(agg3ID).Aggregation().MinLinks().Config()) + gnmi.BatchDelete(b, gnmi.OC().Interface(p3.Name()).Ethernet().AggregateId().Config()) + gnmi.BatchReplace(b, gnmi.OC().Interface(agg3ID).Config(), agg3) + gnmi.BatchReplace(b, gnmi.OC().Interface(p3.Name()).Config(), i3) + b.Set(t, dut) + default: + gnmi.Replace(t, dut, gnmi.OC().Interface(agg3ID).Config(), agg3) + gnmi.Replace(t, dut, gnmi.OC().Interface(p3.Name()).Config(), i3) + } // handle deviations for ports and lags - if deviations.ExplicitPortSpeed(dut) { - for _, port := range dut.Ports() { - fptest.SetPortSpeed(t, port) - } - } + fptest.ConfigureDefaultNetworkInstance(t, dut) if deviations.ExplicitInterfaceInDefaultVRF(dut) { fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) @@ -209,6 +221,12 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { fptest.AssignToNetworkInstance(t, dut, agg3ID, deviations.DefaultNetworkInstance(dut), 0) } + if deviations.ExplicitPortSpeed(dut) { + for _, port := range dut.Ports() { + fptest.SetPortSpeed(t, port) + } + } + // configure ISIS configureISISDUT(t, dut, []string{agg2ID, agg3ID}) } @@ -228,13 +246,15 @@ func configureISISDUT(t *testing.T, dut *ondatra.DUTDevice, intfs []string) { 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) - globalISIS.SetMaxEcmpPaths(2) + lspBit := globalISIS.GetOrCreateLspBit().GetOrCreateOverloadBit() lspBit.SetBit = ygot.Bool(false) isisLevel2 := isis.GetOrCreateLevel(2) isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC - + if deviations.ISISLevelEnabled(dut) { + isisLevel2.Enabled = ygot.Bool(true) + } for _, intfName := range intfs { isisIntf := isis.GetOrCreateInterface(intfName) isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(intfName) @@ -276,7 +296,7 @@ func configureATE(t *testing.T, ate *otg.OTG) gosnappi.Config { // configure port 1 - src i1 := cfg.Devices().Add().SetName(atePort1.Name) i1Eth := i1.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) - i1Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p1.Name()) + i1Eth.Connection().SetPortName(p1.Name()) i1IPv4 := i1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") i1IPv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(plen4) i1IPv6 := i1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") @@ -284,12 +304,12 @@ func configureATE(t *testing.T, ate *otg.OTG) gosnappi.Config { // configure lag2 - dst lag2 := cfg.Lags().Add().SetName("lag2") - lag2.Protocol().SetChoice("static").Static().SetLagId(2) + lag2.Protocol().Static().SetLagId(2) lag2.Ports().Add().SetPortName(p2.Name()).Ethernet().SetMac(lag2MAC).SetName("LAGRx-2") - lag2Dev := cfg.Devices().Add().SetName(lag2.Name()) + lag2Dev := cfg.Devices().Add().SetName(lag2.Name() + ".Dev") lag2Eth := lag2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) - lag2Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetLagName(lag2.Name()) + lag2Eth.Connection().SetLagName(lag2.Name()) lag2IPv4 := lag2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") lag2IPv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(plen4) lag2IPv6 := lag2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") @@ -297,12 +317,12 @@ func configureATE(t *testing.T, ate *otg.OTG) gosnappi.Config { // configure lag3 - dst lag3 := cfg.Lags().Add().SetName("lag3") - lag3.Protocol().SetChoice("static").Static().SetLagId(3) + lag3.Protocol().Static().SetLagId(3) lag3.Ports().Add().SetPortName(p3.Name()).Ethernet().SetMac(lag3MAC).SetName("LAGRx-3") - lag3Dev := cfg.Devices().Add().SetName(lag3.Name()) + lag3Dev := cfg.Devices().Add().SetName(lag3.Name() + ".Dev") lag3Eth := lag3Dev.Ethernets().Add().SetName(atePort3.Name + ".Eth").SetMac(atePort3.MAC) - lag3Eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetLagName(lag3.Name()) + lag3Eth.Connection().SetLagName(lag3.Name()) lag3IPv4 := lag3Eth.Ipv4Addresses().Add().SetName(atePort3.Name + ".IPv4") lag3IPv4.SetAddress(atePort3.IPv4).SetGateway(dutPort3.IPv4).SetPrefix(plen4) lag3IPv6 := lag3Eth.Ipv6Addresses().Add().SetName(atePort3.Name + ".IPv6") @@ -353,6 +373,13 @@ func changeMetric(t *testing.T, dut *ondatra.DUTDevice, intf string, metric uint isisIntfLevelAfiv4.Metric = ygot.Uint32(metric) isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) isisIntfLevelAfiv6.Metric = ygot.Uint32(metric) + if deviations.ISISRequireSameL1MetricWithL2Metric(dut) { + l1 := isis.GetOrCreateInterface(intf).GetOrCreateLevel(1) + l1V4 := l1.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + l1V4.Metric = ygot.Uint32(metric) + l1V6 := l1.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + l1V6.Metric = ygot.Uint32(metric) + } gnmi.Update(t, dut, gnmi.OC().Config(), d) } @@ -372,7 +399,7 @@ func createFlow(t *testing.T, ateTopo gosnappi.Config, name string, dstPorts ... ip.Dst().Increment().SetStart(v4IP).SetCount(50) tcp := flow.Packet().Add().Tcp() - tcp.DstPort().SetChoice("increment").Increment().SetStart(12345).SetCount(200) + tcp.DstPort().Increment().SetStart(12345).SetCount(200) return flow } @@ -451,8 +478,9 @@ func TestDrain(t *testing.T) { dut := ondatra.DUT(t, "dut") ate := ondatra.ATE(t, "ate") otg := ate.OTG() - ateTopo := configureATE(t, otg) + configureDUT(t, dut) + ateTopo := configureATE(t, otg) ecmpFlows := createFlow(t, ateTopo, "ecmp-flow", atePort2.Name+".IPv4", atePort3.Name+".IPv4") lag2Flow := createFlow(t, ateTopo, "trunk2-flow", atePort2.Name+".IPv4") diff --git a/feature/experimental/isis/otg_tests/isis_drain_test/metadata.textproto b/feature/isis/otg_tests/isis_drain_test/metadata.textproto similarity index 68% rename from feature/experimental/isis/otg_tests/isis_drain_test/metadata.textproto rename to feature/isis/otg_tests/isis_drain_test/metadata.textproto index 458ee7e3e66..e704ecde9cb 100644 --- a/feature/experimental/isis/otg_tests/isis_drain_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_drain_test/metadata.textproto @@ -2,7 +2,7 @@ # proto-message: Metadata uuid: "596a9ddc-f112-426f-9f5e-80ecfd94cd2c" -plan_id: "RT-2.12" +plan_id: "RT-2.14" description: "IS-IS Drain Test" testbed: TESTBED_DUT_ATE_4LINKS platform_exceptions: { @@ -32,5 +32,18 @@ platform_exceptions: { deviations: { interface_enabled: true default_network_instance: "default" + aggregate_atomic_update: true + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + missing_isis_interface_afi_safi_enable: true + isis_require_same_l1_metric_with_l2_metric: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true } } 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 new file mode 100644 index 00000000000..23fe1fd5e7b --- /dev/null +++ b/feature/isis/otg_tests/isis_extensions_segment_routing_test/README.md @@ -0,0 +1,96 @@ +# RT-2.15 IS-IS Extensions for Segment Routing + +## Summary + +* This test case provides comprehensive coverage of IS-IS extensions for Segment Routing (SR), including: + * Node SID advertisement in IS-IS TLVs. + * SRGB and SRLB configuration and validation. + * Test coverage for Prefix SIDs and Anycast SIDs. + +## 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: + + ``` + ATE1—DUT1–ATE2 + ``` + +2) Enable SR and MPLS: + * Enable Segment Routing for ISIS on the DUT. + * Enable MPLS forwarding on all interfaces. + * Configure appropriate IGP settings to ensure adjacency formation and prefix exchange between the DUT and ATEs. + +### SR-1.2.1: SRGB and SRLB Configuration. + +* Configure a non-default SRGB on the DUT with a specific lower and upper bound (17000-20000). +* Configure an SRLB on the DUT with a specific lower and upper bound (24000-27000). +* Verify that the DUT allocates and advertises labels for prefixes from its configured SRGB range. +* Verify that the DUT allocates and utilizes labels for adjacencies from its configured SRLB range. + +### SR-1.2.2: Node SID Validation. + +* Configure the DUT to advertise its Node SID to ATE1 and ATE2. +* Advertise prefixe (1) from ATE2 to the DUT +* Send labeled traffic transiting through the DUT (using its node-SID) matching prefix (1). +* Verify that the DUT advertises its Node SID in IS-IS TLVs. +* Verify that ATE2 receives traffic with node-SID label popped. +* Verify that traffic arrives to ATE Port 2. +* Verify that corresponding SID forwarding counters are incremented. + +### SR-1.2.3: Prefix SID Validation. + +* Configure the DUT to advertise two loopback prefixes with Prefix SIDs. +* Verify that the DUT advertises the loopback prefixes with the correct Prefix SIDs. +* Send labeled traffic from ATE1 to the loopback prefixes on the DUT +* 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 +paths: + ## Config paths + /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: + /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/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: + + ## Telemetry + /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/state/enabled: + /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/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 +``` + +## Minimum DUT platform requirement +* FFF - fixed form factor \ No newline at end of file diff --git a/feature/isis/otg_tests/isis_interface_hello_padding_enable_test/README.md b/feature/isis/otg_tests/isis_interface_hello_padding_enable_test/README.md new file mode 100644 index 00000000000..7448bf4d1b8 --- /dev/null +++ b/feature/isis/otg_tests/isis_interface_hello_padding_enable_test/README.md @@ -0,0 +1,92 @@ +# RT-2.6: IS-IS Hello-Padding enabled at interface level + +## Summary + +* Base IS-IS functionality and adjacency establishment. +* Verifies isis adjacency by changing MTU. + +## Procedure + +* Configure IS-IS for ATE port-1 and DUT port-1. +* Configure DUT with global hello-padding enabled. +* Ensure that adjacencies are established with: + * Interface level hello padding is enabled. + * Verify that IPv4 and IPv6 IS-ISIS adjacency comes up fine. + * Verify the output of ST path displaying the status of ISIS hello padding. + * If we change the MTU on either side, then adjacency should not come up. + * Verify that IPv4 and IPv6 prefixes that are advertised by ATE correctly installed into DUTs route and forwarding table. + * TODO-Verify the Hellos are sent with Padding during adjacency turn-up if the padding is enabled adaptively/sometimes. + * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. + +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: + /network-instances/network-instance/protocols/protocol/isis/global/config/net: + /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: + /network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/state/hello-padding: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/hello-padding: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-areas: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` \ No newline at end of file diff --git a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go b/feature/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go similarity index 78% rename from feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go rename to feature/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go index 79fb5557b21..7e21ac55d6a 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go +++ b/feature/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go @@ -21,9 +21,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/featureprofiles/internal/otgutils" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -54,11 +54,11 @@ const ( ) // configureISIS configures isis on DUT. -func configureISIS(t *testing.T, ts *session.TestSession) { +func configureISIS(t *testing.T, ts *isissession.TestSession) { t.Helper() d := ts.DUTConf netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) - prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) prot.Enabled = ygot.Bool(true) isis := prot.GetOrCreateIsis() @@ -74,6 +74,7 @@ func configureISIS(t *testing.T, ts *session.TestSession) { // Level configs. level := isis.GetOrCreateLevel(2) level.LevelNumber = ygot.Uint8(2) + level.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC // Authentication configs. auth := level.GetOrCreateAuthentication() @@ -88,7 +89,6 @@ func configureISIS(t *testing.T, ts *session.TestSession) { intfName += ".0" } intf := isis.GetOrCreateInterface(intfName) - intf.HelloPadding = oc.Isis_HelloPaddingType_ADAPTIVE // Interface timers. isisIntfTimers := intf.GetOrCreateTimers() @@ -101,6 +101,8 @@ func configureISIS(t *testing.T, ts *session.TestSession) { // Interface level configs. isisIntfLevel := intf.GetOrCreateLevel(2) isisIntfLevel.LevelNumber = ygot.Uint8(2) + isisIntfLevel.SetEnabled(true) + isisIntfLevel.Enabled = ygot.Bool(true) isisIntfLevel.GetOrCreateHelloAuthentication().Enabled = ygot.Bool(true) isisIntfLevel.GetHelloAuthentication().AuthPassword = ygot.String(password) isisIntfLevel.GetHelloAuthentication().AuthType = oc.KeychainTypes_AUTH_TYPE_SIMPLE_KEY @@ -114,10 +116,14 @@ func configureISIS(t *testing.T, ts *session.TestSession) { isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Metric = ygot.Uint32(v4Metric) isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Metric = ygot.Uint32(v6Metric) + if deviations.MissingIsisInterfaceAfiSafiEnable(ts.DUT) { + isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = nil + isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = nil + } } // configureOTG configures isis and traffic on OTG. -func configureOTG(t *testing.T, ts *session.TestSession) { +func configureOTG(t *testing.T, ts *isissession.TestSession) { t.Helper() ts.ATEIntf1.Isis().RouterAuth().AreaAuth().SetAuthType("md5").SetMd5(password) @@ -127,11 +133,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { // netv4 is a simulated network containing the ipv4 addresses specified by targetNetwork netv4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName(v4NetName).SetLinkMetric(10) - netv4.Addresses().Add().SetAddress(v4Route1).SetPrefix(uint32(session.ATEISISAttrs.IPv4Len)) + netv4.Addresses().Add().SetAddress(v4Route1).SetPrefix(uint32(isissession.ATEISISAttrs.IPv4Len)) // netv6 is a simulated network containing the ipv6 addresses specified by targetNetwork netv6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName(v6NetName).SetLinkMetric(10) - netv6.Addresses().Add().SetAddress(v6Route1).SetPrefix(uint32(session.ATEISISAttrs.IPv6Len)) + netv6.Addresses().Add().SetAddress(v6Route1).SetPrefix(uint32(isissession.ATEISISAttrs.IPv6Len)) // We generate traffic entering along port2 and destined for port1 srcIpv4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] @@ -146,11 +152,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v4NetName}) v4Flow.Size().SetFixed(512) v4Flow.Rate().SetPps(100) - v4Flow.Duration().SetChoice("continuous") + v4Flow.Duration().Continuous() e1 := v4Flow.Packet().Add().Ethernet() - e1.Src().SetValue(session.ATEISISAttrs.MAC) + e1.Src().SetValue(isissession.ATEISISAttrs.MAC) v4 := v4Flow.Packet().Add().Ipv4() - v4.Src().SetValue(session.ATEISISAttrs.IPv4) + v4.Src().SetValue(isissession.ATEISISAttrs.IPv4) v4.Dst().Increment().SetStart(v4IP).SetCount(1) t.Log("Configuring v6 traffic flow ") @@ -162,31 +168,31 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v6NetName}) v6Flow.Size().SetFixed(512) v6Flow.Rate().SetPps(100) - v6Flow.Duration().SetChoice("continuous") + v6Flow.Duration().Continuous() e2 := v6Flow.Packet().Add().Ethernet() - e2.Src().SetValue(session.ATEISISAttrs.MAC) + e2.Src().SetValue(isissession.ATEISISAttrs.MAC) v6 := v6Flow.Packet().Add().Ipv6() - v6.Src().SetValue(session.ATEISISAttrs.IPv6) + v6.Src().SetValue(isissession.ATEISISAttrs.IPv6) v6.Dst().Increment().SetStart(v6IP).SetCount(1) } // TestIsisInterfaceHelloPaddingEnable verifies adjacency with hello padding enabled. func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { - session.DUTISISAttrs.MTU = 1500 - session.ATEISISAttrs.MTU = 1500 + isissession.DUTISISAttrs.MTU = 1500 + isissession.ATEISISAttrs.MTU = 1500 - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() configureISIS(t, ts) configureOTG(t, ts) otg := ts.ATE.OTG() - pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) - fptest.LogQuery(t, "Protocol ISIS", session.ProtocolPath(ts.DUT).Config(), pcl) + pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) + fptest.LogQuery(t, "Protocol ISIS", isissession.ProtocolPath(ts.DUT).Config(), pcl) ts.PushAndStart(t) - statePath := session.ISISPath(ts.DUT) + statePath := isissession.ISISPath(ts.DUT) intfName := ts.DUTPort1.Name() if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { intfName += ".0" @@ -210,7 +216,7 @@ func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { // Changing MTU at ATE side. for _, d := range ts.ATETop.Devices().Items() { Eth := d.Ethernets().Items()[0] - if Eth.Name() == session.ATEISISAttrs.Name+".Eth" { + if Eth.Name() == isissession.ATEISISAttrs.Name+".Eth" { Eth.SetMtu(uint32(2000)) } } @@ -220,7 +226,7 @@ func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { // Adjacency check. _, found := gnmi.Watch(t, ts.DUT, statePath.Interface(intfName).Level(2).Adjacency(ateSysID).AdjacencyState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { state, present := val.Val() - return present && state == oc.Isis_IsisInterfaceAdjState_DOWN + return present && (state == oc.Isis_IsisInterfaceAdjState_DOWN || state == oc.Isis_IsisInterfaceAdjState_INIT) }).Await(t) if !found { t.Errorf("Isis adjacency is not down on interface %v when MTU is changed", intfName) @@ -229,8 +235,8 @@ func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { // Reverting MTU at ATE side. for _, d := range ts.ATETop.Devices().Items() { Eth := d.Ethernets().Items()[0] - if Eth.Name() == session.ATEISISAttrs.Name+".Eth" { - Eth.SetMtu(uint32(session.ATEISISAttrs.MTU)) + if Eth.Name() == isissession.ATEISISAttrs.Name+".Eth" { + Eth.SetMtu(uint32(isissession.ATEISISAttrs.MTU)) } } otg.PushConfig(t, ts.ATETop) @@ -268,12 +274,14 @@ func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.SystemId().State()); got != ateSysID { t.Errorf("FAIL- Expected neighbor system id not found, got %s, want %s", got, ateSysID) } - want := []string{session.ATEAreaAddress, session.DUTAreaAddress} + want := []string{isissession.ATEAreaAddress, isissession.DUTAreaAddress} if got := gnmi.Get(t, ts.DUT, adjPath.AreaAddress().State()); !cmp.Equal(got, want, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { t.Errorf("FAIL- Expected area address not found, got %s, want %s", got, want) } - if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { - t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + if !deviations.IsisDisSysidUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { + t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + } } if got := gnmi.Get(t, ts.DUT, adjPath.LocalExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected local extended circuit id not found,expected non-zero value, got %d", got) @@ -284,8 +292,8 @@ func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.NeighborCircuitType().State()); got != oc.Isis_LevelType_LEVEL_2 { t.Errorf("FAIL- Expected value for circuit type not found, got %s, want %s", got, oc.Isis_LevelType_LEVEL_2) } - if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != session.ATEISISAttrs.IPv4 { - t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, session.ATEISISAttrs.IPv4) + if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != isissession.ATEISISAttrs.IPv4 { + t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, isissession.ATEISISAttrs.IPv4) } if got := gnmi.Get(t, ts.DUT, adjPath.NeighborExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected neighbor extended circuit id not found,expected non-zero value, got %d", got) @@ -326,8 +334,10 @@ func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + if !deviations.IsisDatabaseOverloadsUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { + t.Errorf("FAIL- Not expecting pre isis config database_overloads value to change, got %d, want %d", got, 0) + } } if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) @@ -347,28 +357,42 @@ func TestIsisInterfaceHelloPaddingEnable(t *testing.T) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ManualAddressDropFromAreas().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero ManualAddressDropFromAreas counter, got %d, want %d", got, 0) + if !deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ManualAddressDropFromAreas().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero ManualAddressDropFromAreas counter, got %d, want %d", got, 0) + } } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().PartChanges().State()); got != 0 { - t.Errorf("FAIL- Not expecting partition changes, got %d, want %d", got, 0) + if !deviations.ISISCounterPartChangesUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().PartChanges().State()); got != 0 { + t.Errorf("FAIL- Not expecting partition changes, got %d, want %d", got, 0) + } } if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SpfRuns().State()); got == 0 { t.Errorf("FAIL- Not expecting spf runs counter to be 0, got %d, want non zero", got) } }) t.Run("Route checks", func(t *testing.T) { - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(v4Route).Prefix().State()); got != v4Route { - t.Errorf("FAIL- Expected v4 route not found in isis, got %v, want %v", got, v4Route) - } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(v6Route).Prefix().State()); got != v6Route { - t.Errorf("FAIL- Expected v6 route not found in isis, got %v, want %v", got, v6Route) - } - if got := gnmi.Get(t, ts.DUT, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv4Entry(v4Route).State()).GetPrefix(); got != v4Route { - t.Errorf("FAIL- Expected v4 route not found in aft, got %v, want %v", got, v4Route) - } - if got := gnmi.Get(t, ts.DUT, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.WithISIS().DUT)).Afts().Ipv6Entry(v6Route).State()).GetPrefix(); got != v6Route { - t.Errorf("FAIL- Expected v6 route not found in aft, got %v, want %v", got, v6Route) + _, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(v4Route).Prefix().State(), 1*time.Minute, v4Route).Val() + if !ok { + t.Errorf("FAIL- Couldn't find v4Route in dut LSP TLV") + } + _, ok = gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(v6Route).Prefix().State(), 1*time.Minute, v6Route).Val() + if !ok { + t.Errorf("FAIL- Couldn't find v6Route in dut LSP TLV") + } + ipv4Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv4Entry(v4Route) + if got, ok := gnmi.Watch(t, ts.DUT, ipv4Path.State(), time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + ipv4Entry, present := val.Val() + return present && ipv4Entry.GetPrefix() == v4Route + }).Await(t); !ok { + t.Errorf("ipv4-entry/state/prefix got %v, want %s", got, v4Route) + } + ipv6Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv6Entry(v6Route) + if got, ok := gnmi.Watch(t, ts.DUT, ipv6Path.State(), time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv6Entry]) bool { + ipv6Entry, present := val.Val() + return present && ipv6Entry.GetPrefix() == v6Route + }).Await(t); !ok { + t.Errorf("ipv6-entry/state/prefix got %v, want %s", got, v6Route) } }) t.Run("Traffic checks", func(t *testing.T) { diff --git a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto b/feature/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto similarity index 91% rename from feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto rename to feature/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto index 911a21e8dd5..3586116e655 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto @@ -25,6 +25,8 @@ platform_exceptions: { deviations: { ipv4_missing_enabled: true isis_interface_level1_disable_required: true + isis_dis_sysid_unsupported: true + isis_database_overloads_unsupported: true } } platform_exceptions: { @@ -32,15 +34,16 @@ platform_exceptions: { 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_instance_enabled_required: true isis_lsp_lifetime_interval_requires_lsp_refresh_interval: true isis_timers_csnp_interval_unsupported: true isis_counter_manual_address_drop_from_areas_unsupported: true isis_counter_part_changes_unsupported: true + isis_interface_afi_unsupported: true } } platform_exceptions: { diff --git a/feature/isis/otg_tests/isis_interface_level_passive_test/README.md b/feature/isis/otg_tests/isis_interface_level_passive_test/README.md new file mode 100644 index 00000000000..b0d7374a153 --- /dev/null +++ b/feature/isis/otg_tests/isis_interface_level_passive_test/README.md @@ -0,0 +1,95 @@ +# RT-2.11: IS-IS Passive is enabled at the area level + +## Summary + +* Verify isis adjacency with passive enabled under level. + +## Topology + +* ATE:port1 <-> port1:DUT:port2 <-> ATE:port2 + +## Procedure + +* Configure IS-IS for ATE port-1 and DUT port-1. +* Configure DUT interface with IS-IS passive configured at area level 2. + * Verify that IS-IS adjacency is not coming up in level-2 area for IPv4 and IPV6 address families. +* Undo the IS-IS passive configuration under level 2 + * Verify that IS-IS adjacency for IPv4 and IPV6 address families are coming up in the level-2 area. + * Verify that IPv4 and IPv6 prefixes that are advertised by ATE are correctly installed into DUTs route and forwarding table. + * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. + * TODO-Verify the output of ST path displaying the interface as passive in ISIS database/adj table + +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: + /network-instances/network-instance/protocols/protocol/isis/global/config/net: + /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: + /network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/state/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-areas: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` \ No newline at end of file diff --git a/feature/experimental/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 similarity index 81% rename from feature/experimental/isis/otg_tests/isis_interface_level_passive_test/isis_interface_level_passive_test.go rename to feature/isis/otg_tests/isis_interface_level_passive_test/isis_interface_level_passive_test.go index 6cc6fcea9ba..eb0dde965b0 100644 --- a/feature/experimental/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 @@ -15,16 +15,19 @@ package isis_interface_level_passive_test import ( + "fmt" "net" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ygot/ygot" @@ -53,11 +56,11 @@ const ( ) // configureISIS configures isis on DUT. -func configureISIS(t *testing.T, ts *session.TestSession) { +func configureISIS(t *testing.T, ts *isissession.TestSession) { t.Helper() d := ts.DUTConf netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) - prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) prot.Enabled = ygot.Bool(true) isis := prot.GetOrCreateIsis() @@ -99,8 +102,8 @@ func configureISIS(t *testing.T, ts *session.TestSession) { // Interface level configs. isisIntfLevel2 := intf.GetOrCreateLevel(2) isisIntfLevel2.LevelNumber = ygot.Uint8(2) + isisIntfLevel2.SetEnabled(true) isisIntfLevel2.Enabled = ygot.Bool(true) - isisIntfLevel2.Passive = ygot.Bool(true) isisIntfLevel2.GetOrCreateHelloAuthentication().Enabled = ygot.Bool(true) isisIntfLevel2.GetHelloAuthentication().AuthPassword = ygot.String(password) @@ -114,7 +117,7 @@ func configureISIS(t *testing.T, ts *session.TestSession) { } // configureOTG configures isis and traffic on OTG. -func configureOTG(t *testing.T, ts *session.TestSession) { +func configureOTG(t *testing.T, ts *isissession.TestSession) { t.Helper() ts.ATEIntf1.Isis().RouterAuth().AreaAuth().SetAuthType("md5").SetMd5(password) ts.ATEIntf1.Isis().RouterAuth().DomainAuth().SetAuthType("md5").SetMd5(password) @@ -123,11 +126,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { // netv4 is a simulated network containing the ipv4 addresses specified by targetNetwork netv4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName(v4NetName).SetLinkMetric(10) - netv4.Addresses().Add().SetAddress(v4Route1).SetPrefix(uint32(session.ATEISISAttrs.IPv4Len)) + netv4.Addresses().Add().SetAddress(v4Route1).SetPrefix(uint32(isissession.ATEISISAttrs.IPv4Len)) // netv6 is a simulated network containing the ipv6 addresses specified by targetNetwork netv6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName(v6NetName).SetLinkMetric(10) - netv6.Addresses().Add().SetAddress(v6Route1).SetPrefix(uint32(session.ATEISISAttrs.IPv6Len)) + netv6.Addresses().Add().SetAddress(v6Route1).SetPrefix(uint32(isissession.ATEISISAttrs.IPv6Len)) // We generate traffic entering along port2 and destined for port1 srcIpv4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] @@ -142,11 +145,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v4NetName}) v4Flow.Size().SetFixed(512) v4Flow.Rate().SetPps(100) - v4Flow.Duration().SetChoice("continuous") + v4Flow.Duration().Continuous() e1 := v4Flow.Packet().Add().Ethernet() - e1.Src().SetValue(session.ATEISISAttrs.MAC) + e1.Src().SetValue(isissession.ATEISISAttrs.MAC) v4 := v4Flow.Packet().Add().Ipv4() - v4.Src().SetValue(session.ATEISISAttrs.IPv4) + v4.Src().SetValue(isissession.ATEISISAttrs.IPv4) v4.Dst().Increment().SetStart(v4IP).SetCount(1) t.Log("Configuring v6 traffic flow ") @@ -158,38 +161,55 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v6NetName}) v6Flow.Size().SetFixed(512) v6Flow.Rate().SetPps(100) - v6Flow.Duration().SetChoice("continuous") + v6Flow.Duration().Continuous() e2 := v6Flow.Packet().Add().Ethernet() - e2.Src().SetValue(session.ATEISISAttrs.MAC) + e2.Src().SetValue(isissession.ATEISISAttrs.MAC) v6 := v6Flow.Packet().Add().Ipv6() - v6.Src().SetValue(session.ATEISISAttrs.IPv6) + v6.Src().SetValue(isissession.ATEISISAttrs.IPv6) v6.Dst().Increment().SetStart(v6IP).SetCount(1) } // TestISISLevelPassive verifies adjacency with passive enabled at interface level hierarchy. func TestISISLevelPassive(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() configureISIS(t, ts) - + dut := ts.DUT configureOTG(t, ts) otg := ts.ATE.OTG() - pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) - fptest.LogQuery(t, "Protocol ISIS", session.ProtocolPath(ts.DUT).Config(), pcl) + pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) + fptest.LogQuery(t, "Protocol ISIS", isissession.ProtocolPath(ts.DUT).Config(), pcl) ts.PushAndStart(t) + time.Sleep(time.Minute * 2) - statePath := session.ISISPath(ts.DUT) + statePath := isissession.ISISPath(ts.DUT) intfName := ts.DUTPort1.Name() if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { intfName += ".0" } t.Run("Isis telemetry", func(t *testing.T) { + time.Sleep(time.Minute * 1) + var isispassiveconfig string t.Run("Passive checks", func(t *testing.T) { - // Passive should be true. - if got := gnmi.Get(t, ts.DUT, statePath.Interface(intfName).Level(2).Passive().State()); got != true { - t.Errorf("FAIL- Expected level 2 passive state not found, got %t, want %t", got, true) + if deviations.IsisInterfaceLevelPassiveUnsupported(ts.DUT) { + switch dut.Vendor() { + case ondatra.CISCO: + isispassiveconfig = fmt.Sprintf("router isis DEFAULT\n interface %s\n passive\n", intfName) + 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) + } + if !deviations.IsisInterfaceLevelPassiveUnsupported(ts.DUT) { + // Passive should be true. + if got := gnmi.Get(t, ts.DUT, statePath.Interface(intfName).Level(2).Passive().State()); got != true { + t.Errorf("FAIL- Expected level 2 passive state not found, got %t, want %t", got, true) + } } + t.Logf("Adjacency state after passive update is %s", statePath.Interface(intfName).Level(2).AdjacencyAny().AdjacencyState().State()) // Adjacency should be down. for _, val := range gnmi.LookupAll(t, ts.DUT, statePath.Interface(intfName).LevelAny().AdjacencyAny().AdjacencyState().State()) { if v, _ := val.Val(); v == oc.Isis_IsisInterfaceAdjState_UP { @@ -197,12 +217,23 @@ func TestISISLevelPassive(t *testing.T) { } } // Updating passive config to false on dut. - gnmi.Update(t, ts.DUT, statePath.Interface(intfName).Level(2).Passive().Config(), false) - time.Sleep(time.Second * 5) - - if got := gnmi.Get(t, ts.DUT, statePath.Interface(intfName).Level(2).Passive().State()); got != false { - t.Errorf("FAIL- Expected level 2 passive state not found, got %t, want %t", got, true) + if deviations.IsisInterfaceLevelPassiveUnsupported(ts.DUT) { + switch dut.Vendor() { + case ondatra.CISCO: + isispassiveconfig = fmt.Sprintf("router isis DEFAULT\n interface %s\n no passive\n", intfName) + 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) } + if !deviations.IsisInterfaceLevelPassiveUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Interface(intfName).Level(2).Passive().State()); got != false { + t.Errorf("FAIL- Expected level 2 passive state not found, got %t, want %t", got, true) + } + } + t.Logf("Adjacency state after passive update is %s", statePath.Interface(intfName).LevelAny().AdjacencyAny().AdjacencyState().State()) // Level 2 adjacency should be up. _, err := ts.AwaitAdjacency() if err != nil { @@ -240,12 +271,14 @@ func TestISISLevelPassive(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.SystemId().State()); got != ateSysID { t.Errorf("FAIL- Expected neighbor system id not found, got %s, want %s", got, ateSysID) } - want := []string{session.ATEAreaAddress, session.DUTAreaAddress} + want := []string{isissession.ATEAreaAddress, isissession.DUTAreaAddress} if got := gnmi.Get(t, ts.DUT, adjPath.AreaAddress().State()); !cmp.Equal(got, want, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { t.Errorf("FAIL- Expected area address not found, got %s, want %s", got, want) } - if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { - t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + if !deviations.IsisDisSysidUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { + t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + } } if got := gnmi.Get(t, ts.DUT, adjPath.LocalExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected local extended circuit id not found,expected non-zero value, got %d", got) @@ -256,8 +289,8 @@ func TestISISLevelPassive(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.NeighborCircuitType().State()); got != oc.Isis_LevelType_LEVEL_2 { t.Errorf("FAIL- Expected value for circuit type not found, got %s, want %s", got, oc.Isis_LevelType_LEVEL_2) } - if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != session.ATEISISAttrs.IPv4 { - t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, session.ATEISISAttrs.IPv4) + if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != isissession.ATEISISAttrs.IPv4 { + t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, isissession.ATEISISAttrs.IPv4) } if got := gnmi.Get(t, ts.DUT, adjPath.NeighborExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected neighbor extended circuit id not found,expected non-zero value, got %d", got) @@ -298,8 +331,10 @@ func TestISISLevelPassive(t *testing.T) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + if !deviations.IsisDatabaseOverloadsUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + } } if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto b/feature/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto similarity index 83% rename from feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto rename to feature/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto index 5e205fa3008..d0a0a9cc54f 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto @@ -1,9 +1,9 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "e43009e1-2f75-4926-88f2-43ae1823249a" -plan_id: "RT-2.7" -description: "IS-IS Passive is enabled at interface level" +uuid: "5d594ad6-9957-40e8-bf59-f75d2b700631" +plan_id: "RT-2.11" +description: "IS-IS Passive is enabled at the area level" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { @@ -25,6 +25,8 @@ platform_exceptions: { deviations: { ipv4_missing_enabled: true isis_interface_level1_disable_required: true + isis_dis_sysid_unsupported: true + isis_database_overloads_unsupported: true } } platform_exceptions: { @@ -32,16 +34,16 @@ platform_exceptions: { 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_instance_enabled_required: true + isis_interface_afi_unsupported: true isis_lsp_lifetime_interval_requires_lsp_refresh_interval: true isis_timers_csnp_interval_unsupported: true isis_counter_manual_address_drop_from_areas_unsupported: true isis_counter_part_changes_unsupported: true - isis_interface_afi_unsupported: true } } platform_exceptions: { diff --git a/feature/isis/otg_tests/isis_interface_passive_test/README.md b/feature/isis/otg_tests/isis_interface_passive_test/README.md new file mode 100644 index 00000000000..bf600ec87b0 --- /dev/null +++ b/feature/isis/otg_tests/isis_interface_passive_test/README.md @@ -0,0 +1,91 @@ +# RT-2.7: IS-IS Passive is enabled at interface level + +## Summary + +* Base IS-IS functionality and adjacency establishment. +* Ensure that IS-IS adjacency is not coming up on the passive interface + +## Procedure + +* TestIsisInterfacePassive + + * Configure IS-IS for ATE port-1 and DUT port-1. + * Configure DUT interface as ISIS passive interface. + * Ensure that IS-IS adjacency is not coming up on the passive interface. + * TODO-Verify the output of ST path displaying the interface as passive in ISIS database/adj table + +# OpenConfig Path and RPC Coverage +```yaml +paths: +# config +/network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: +/network-instances/network-instance/protocols/protocol/isis/global/config/net: +/network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: +/network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: +/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: +/network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: + +# isis telemetry +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/state/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-areas: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes : +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go b/feature/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go similarity index 73% rename from feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go rename to feature/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go index c576f6b239a..725c6fab80a 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go +++ b/feature/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go @@ -21,9 +21,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ygnmi/ygnmi" @@ -43,11 +43,11 @@ const ( ) // configureISIS configures isis configs on ts.DUT. -func configureISIS(t *testing.T, ts *session.TestSession) { +func configureISIS(t *testing.T, ts *isissession.TestSession) { t.Helper() d := ts.DUTConf netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) - prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) prot.Enabled = ygot.Bool(true) isis := prot.GetOrCreateIsis() @@ -58,6 +58,9 @@ func configureISIS(t *testing.T, ts *session.TestSession) { globalIsis.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) globalIsis.LevelCapability = oc.Isis_LevelType_LEVEL_2 globalIsis.AuthenticationCheck = ygot.Bool(true) + if deviations.ISISGlobalAuthenticationNotRequired(ts.DUT) { + globalIsis.AuthenticationCheck = nil + } globalIsis.HelloPadding = oc.Isis_HelloPaddingType_ADAPTIVE // Level configs. @@ -69,6 +72,11 @@ func configureISIS(t *testing.T, ts *session.TestSession) { auth.AuthMode = oc.IsisTypes_AUTH_MODE_MD5 auth.AuthType = oc.KeychainTypes_AUTH_TYPE_SIMPLE_KEY auth.AuthPassword = ygot.String(password) + if deviations.ISISExplicitLevelAuthenticationConfig(ts.DUT) { + auth.DisableCsnp = ygot.Bool(false) + auth.DisableLsp = ygot.Bool(false) + auth.DisablePsnp = ygot.Bool(false) + } // Interface configs. intfName := ts.DUTPort1.Name() @@ -101,7 +109,7 @@ func configureISIS(t *testing.T, ts *session.TestSession) { } // configureOTG configures the interfaces and isis protocol on ATE. -func configureOTG(t *testing.T, ts *session.TestSession) { +func configureOTG(t *testing.T, ts *isissession.TestSession) { t.Helper() ts.ATEIntf1.Isis().RouterAuth().AreaAuth().SetAuthType("md5").SetMd5(password) ts.ATEIntf1.Isis().RouterAuth().DomainAuth().SetAuthType("md5").SetMd5(password) @@ -113,14 +121,14 @@ func configureOTG(t *testing.T, ts *session.TestSession) { // TestIsisInterfacePassive verifies passive isis interface. func TestIsisInterfacePassive(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() // Configure isis on dut. configureISIS(t, ts) configureOTG(t, ts) - pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) - fptest.LogQuery(t, "Protocol ISIS", session.ProtocolPath(ts.DUT).Config(), pcl) + pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) + fptest.LogQuery(t, "Protocol ISIS", isissession.ProtocolPath(ts.DUT).Config(), pcl) ts.PushAndStart(t) @@ -129,7 +137,7 @@ func TestIsisInterfacePassive(t *testing.T) { t.Fatalf("Adjacency state invalid: %v", err) } - statePath := session.ISISPath(ts.DUT) + statePath := isissession.ISISPath(ts.DUT) intfName := ts.DUTPort1.Name() if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { intfName += ".0" @@ -183,29 +191,33 @@ func TestIsisInterfacePassive(t *testing.T) { t.Errorf("FAIL- Expected neighbor system id not found, got %s, want %s", got, ateSysID) } // Checking isis area address. - want := []string{session.ATEAreaAddress, session.DUTAreaAddress} + want := []string{isissession.ATEAreaAddress, isissession.DUTAreaAddress} if got := gnmi.Get(t, ts.DUT, adjPath.AreaAddress().State()); !cmp.Equal(got, want, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { t.Errorf("FAIL- Expected area address not found, got %s, want %s", got, want) } // Checking dis system id. - if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { - t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { + t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + } } // Checking isis local extended circuit id. if got := gnmi.Get(t, ts.DUT, adjPath.LocalExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected local extended circuit id not found,expected non-zero value, got %d", got) } // Checking multitopology. - if got := gnmi.Get(t, ts.DUT, adjPath.MultiTopology().State()); got != false { - t.Errorf("FAIL- Expected value for multi topology not found, got %t, want %t", got, false) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.MultiTopology().State()); got != false { + t.Errorf("FAIL- Expected value for multi topology not found, got %t, want %t", got, false) + } } // Checking neighbor circuit type. if got := gnmi.Get(t, ts.DUT, adjPath.NeighborCircuitType().State()); got != oc.Isis_LevelType_LEVEL_2 { t.Errorf("FAIL- Expected value for circuit type not found, got %s, want %s", got, oc.Isis_LevelType_LEVEL_2) } // Checking neighbor ipv4 address. - if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != session.ATEISISAttrs.IPv4 { - t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, session.ATEISISAttrs.IPv4) + if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != isissession.ATEISISAttrs.IPv4 { + t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, isissession.ATEISISAttrs.IPv4) } // Checking isis neighbor extended circuit id. if got := gnmi.Get(t, ts.DUT, adjPath.NeighborExtendedCircuitId().State()); got == 0 { @@ -240,59 +252,81 @@ func TestIsisInterfacePassive(t *testing.T) { t.Errorf("FAIL- Restart support not present") } // Checking isis restart suppress. - if _, ok := gnmi.Lookup(t, ts.DUT, adjPath.RestartStatus().State()).Val(); !ok { - t.Errorf("FAIL- Restart suppress not present") + if !deviations.MissingValueForDefaults(ts.DUT) { + if _, ok := gnmi.Lookup(t, ts.DUT, adjPath.RestartStatus().State()).Val(); !ok { + t.Errorf("FAIL- Restart suppress not present") + } } }) t.Run("System level counter checks", func(t *testing.T) { // Checking authFail counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthFails().State()); got != 0 { - t.Errorf("FAIL- Not expecting any authentication key failure, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthFails().State()); got != 0 { + t.Errorf("FAIL- Not expecting any authentication key failure, got %d, want %d", got, 0) + } } // Checking authTypeFail counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthTypeFails().State()); got != 0 { - t.Errorf("FAIL- Not expecting any authentication type mismatches, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthTypeFails().State()); got != 0 { + t.Errorf("FAIL- Not expecting any authentication type mismatches, got %d, want %d", got, 0) + } } // Checking corrupted lsps counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { - t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { + t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) + } } // Checking database_overloads counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + } } // Checking execeeded maximum seq number counters"). - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) + } } // Checking IdLenMismatch counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().IdLenMismatch().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero IdLen_Mismatch counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().IdLenMismatch().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero IdLen_Mismatch counter, got %d, want %d", got, 0) + } } // Checking LspErrors counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().LspErrors().State()); got != 0 { - t.Errorf("FAIL- Not expecting any lsp errors, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().LspErrors().State()); got != 0 { + t.Errorf("FAIL- Not expecting any lsp errors, got %d, want %d", got, 0) + } } // Checking MaxAreaAddressMismatches counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().MaxAreaAddressMismatches().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero MaxAreaAddressMismatches counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().MaxAreaAddressMismatches().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero MaxAreaAddressMismatches counter, got %d, want %d", got, 0) + } } // Checking OwnLspPurges counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().OwnLspPurges().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero OwnLspPurges counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().OwnLspPurges().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero OwnLspPurges counter, got %d, want %d", got, 0) + } } // Checking SeqNumSkips counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) + } } // Checking ManualAddressDropFromAreas counters. - if !deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) { + if !(deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) || deviations.MissingValueForDefaults(ts.DUT)) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ManualAddressDropFromAreas().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero ManualAddressDropFromAreas counter, got %d, want %d", got, 0) } } // Checking PartChanges counters. - if !deviations.ISISCounterPartChangesUnsupported(ts.DUT) { + if !(deviations.ISISCounterPartChangesUnsupported(ts.DUT) || deviations.MissingValueForDefaults(ts.DUT)) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().PartChanges().State()); got != 0 { t.Errorf("FAIL- Not expecting partition changes, got %d, want %d", got, 0) } diff --git a/feature/experimental/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto b/feature/isis/otg_tests/isis_interface_passive_test/metadata.textproto similarity index 80% rename from feature/experimental/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto rename to feature/isis/otg_tests/isis_interface_passive_test/metadata.textproto index 50d4c8dd863..75bc2932d0e 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_interface_passive_test/metadata.textproto @@ -1,15 +1,17 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "5d594ad6-9957-40e8-bf59-f75d2b700631" -plan_id: "RT-2.11" -description: "IS-IS Passive is enabled at the area level" -testbed: TESTBED_DUT_ATE_2LINKS +uuid: "e43009e1-2f75-4926-88f2-43ae1823249a" +plan_id: "RT-2.7" +description: "IS-IS Passive is enabled at interface level" +testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { vendor: NOKIA } deviations: { + isis_global_authentication_not_required: true + isis_explicit_level_authentication_config: true isis_interface_level1_disable_required: true missing_isis_interface_afi_safi_enable: true explicit_port_speed: true @@ -32,16 +34,16 @@ platform_exceptions: { 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_instance_enabled_required: true + isis_interface_afi_unsupported: true isis_lsp_lifetime_interval_requires_lsp_refresh_interval: true isis_timers_csnp_interval_unsupported: true isis_counter_manual_address_drop_from_areas_unsupported: true isis_counter_part_changes_unsupported: true - isis_interface_afi_unsupported: true } } platform_exceptions: { diff --git a/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/README.md b/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/README.md new file mode 100644 index 00000000000..1a08deafff6 --- /dev/null +++ b/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/README.md @@ -0,0 +1,93 @@ +# RT-2.9: IS-IS metric style wide enabled + +## Summary + +* Base IS-IS functionality and adjacency establishment. +* Verifies route metric with wide metric enabled on DUT. + +## Procedure + + * Configure IS-IS for ATE port-1 and DUT port-1. + * Enable wide metric style on ATE and DUT. + * Advertise ISIS prefixes from ATE with wide metrics (value > 63). + * Verify that IS-IS adjacency for IPv4 and IPV6 address family is coming up. + * Verify that IPv4 and IPv6 prefixes that are advertised by ATE correctly installed into DUTs route and forwarding table. + * TODO-Verify that the metrics of the IPv4 and IPv6 prefixes is correctly reflected + * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. + + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # isis config + /network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: + /network-instances/network-instance/protocols/protocol/isis/global/config/net: + /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: + /network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: + # isis telemetry + /network-instances/network-instance/protocols/protocol/isis/levels/level/state/metric-style: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-areas: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go b/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go similarity index 72% rename from feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go rename to feature/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go index 233e3a1eaf4..29caeafbae6 100644 --- a/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go +++ b/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go @@ -21,12 +21,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/featureprofiles/internal/otgutils" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" ) @@ -57,11 +58,11 @@ const ( ) // configureISIS configures isis on DUT. -func configureISIS(t *testing.T, ts *session.TestSession) { +func configureISIS(t *testing.T, ts *isissession.TestSession) { t.Helper() d := ts.DUTConf netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) - prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) prot.Enabled = ygot.Bool(true) isis := prot.GetOrCreateIsis() @@ -104,6 +105,8 @@ func configureISIS(t *testing.T, ts *session.TestSession) { // Interface level configs. isisIntfLevel := intf.GetOrCreateLevel(2) isisIntfLevel.LevelNumber = ygot.Uint8(2) + isisIntfLevel.SetEnabled(true) + isisIntfLevel.Enabled = ygot.Bool(true) isisIntfLevel.GetOrCreateHelloAuthentication().Enabled = ygot.Bool(true) isisIntfLevel.GetHelloAuthentication().AuthPassword = ygot.String(password) isisIntfLevel.GetHelloAuthentication().AuthType = oc.KeychainTypes_AUTH_TYPE_SIMPLE_KEY @@ -117,10 +120,14 @@ func configureISIS(t *testing.T, ts *session.TestSession) { isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Metric = ygot.Uint32(dutV4Metric) isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Metric = ygot.Uint32(dutV6Metric) + if deviations.MissingIsisInterfaceAfiSafiEnable(ts.DUT) { + isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = nil + isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = nil + } } // configureOTG configures isis and traffic on OTG. -func configureOTG(t *testing.T, ts *session.TestSession) { +func configureOTG(t *testing.T, ts *isissession.TestSession) { t.Helper() ts.ATEIntf1.Isis().Basic().SetEnableWideMetric(true) @@ -130,11 +137,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { // netv4 is a simulated network containing the ipv4 addresses specified by targetNetwork netv4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName(v4NetName).SetLinkMetric(ateV4Metric) - netv4.Addresses().Add().SetAddress(v4Route).SetPrefix(uint32(session.ATEISISAttrs.IPv4Len)) + netv4.Addresses().Add().SetAddress(v4Route).SetPrefix(uint32(isissession.ATEISISAttrs.IPv4Len)) // netv6 is a simulated network containing the ipv6 addresses specified by targetNetwork netv6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName(v6NetName).SetLinkMetric(ateV6Metric) - netv6.Addresses().Add().SetAddress(v6Route).SetPrefix(uint32(session.ATEISISAttrs.IPv6Len)) + netv6.Addresses().Add().SetAddress(v6Route).SetPrefix(uint32(isissession.ATEISISAttrs.IPv6Len)) // We generate traffic entering along port2 and destined for port1 srcIpv4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] @@ -149,11 +156,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v4NetName}) v4Flow.Size().SetFixed(512) v4Flow.Rate().SetPps(100) - v4Flow.Duration().SetChoice("continuous") + v4Flow.Duration().Continuous() e1 := v4Flow.Packet().Add().Ethernet() - e1.Src().SetValue(session.ATEISISAttrs.MAC) + e1.Src().SetValue(isissession.ATEISISAttrs.MAC) v4 := v4Flow.Packet().Add().Ipv4() - v4.Src().SetValue(session.ATEISISAttrs.IPv4) + v4.Src().SetValue(isissession.ATEISISAttrs.IPv4) v4.Dst().Increment().SetStart(v4IP).SetCount(1) t.Log("Configuring v6 traffic flow ") @@ -165,34 +172,36 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v6NetName}) v6Flow.Size().SetFixed(512) v6Flow.Rate().SetPps(100) - v6Flow.Duration().SetChoice("continuous") + v6Flow.Duration().Continuous() e2 := v6Flow.Packet().Add().Ethernet() - e2.Src().SetValue(session.ATEISISAttrs.MAC) + e2.Src().SetValue(isissession.ATEISISAttrs.MAC) v6 := v6Flow.Packet().Add().Ipv6() - v6.Src().SetValue(session.ATEISISAttrs.IPv6) + v6.Src().SetValue(isissession.ATEISISAttrs.IPv6) v6.Dst().Increment().SetStart(v6IP).SetCount(1) } // TestISISWideMetricEnabled verifies route metric with wide metric enabled on DUT. func TestISISWideMetricEnabled(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() configureISIS(t, ts) configureOTG(t, ts) otg := ts.ATE.OTG() - pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) - fptest.LogQuery(t, "Protocol ISIS", session.ProtocolPath(ts.DUT).Config(), pcl) + pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) + fptest.LogQuery(t, "Protocol ISIS", isissession.ProtocolPath(ts.DUT).Config(), pcl) ts.PushAndStart(t) + time.Sleep(time.Minute * 2) - statePath := session.ISISPath(ts.DUT) + statePath := isissession.ISISPath(ts.DUT) intfName := ts.DUTPort1.Name() if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { intfName += ".0" } t.Run("ISIS telemetry", func(t *testing.T) { + time.Sleep(time.Minute * 2) // Checking adjacency ateSysID, err := ts.AwaitAdjacency() @@ -200,7 +209,7 @@ func TestISISWideMetricEnabled(t *testing.T) { t.Fatalf("Adjacency state invalid: %v", err) } ateLspID := ateSysID + ".00-00" - dutLspID := session.DUTSysID + ".00-00" + dutLspID := isissession.DUTSysID + ".00-00" t.Run("Afi-Safi checks", func(t *testing.T) { if got := gnmi.Get(t, ts.DUT, statePath.Interface(intfName).Level(2).Af(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).AfiName().State()); got != oc.IsisTypes_AFI_TYPE_IPV4 { @@ -228,12 +237,14 @@ func TestISISWideMetricEnabled(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.SystemId().State()); got != ateSysID { t.Errorf("FAIL- Expected neighbor system id not found, got %s, want %s", got, ateSysID) } - want := []string{session.ATEAreaAddress, session.DUTAreaAddress} + want := []string{isissession.ATEAreaAddress, isissession.DUTAreaAddress} if got := gnmi.Get(t, ts.DUT, adjPath.AreaAddress().State()); !cmp.Equal(got, want, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { t.Errorf("FAIL- Expected area address not found, got %s, want %s", got, want) } - if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { - t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + if !deviations.IsisDisSysidUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { + t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + } } if got := gnmi.Get(t, ts.DUT, adjPath.LocalExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected local extended circuit id not found,expected non-zero value, got %d", got) @@ -244,8 +255,8 @@ func TestISISWideMetricEnabled(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.NeighborCircuitType().State()); got != oc.Isis_LevelType_LEVEL_2 { t.Errorf("FAIL- Expected value for circuit type not found, got %s, want %s", got, oc.Isis_LevelType_LEVEL_2) } - if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != session.ATEISISAttrs.IPv4 { - t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, session.ATEISISAttrs.IPv4) + if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != isissession.ATEISISAttrs.IPv4 { + t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, isissession.ATEISISAttrs.IPv4) } if got := gnmi.Get(t, ts.DUT, adjPath.NeighborExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected neighbor extended circuit id not found,expected non-zero value, got %d", got) @@ -286,8 +297,10 @@ func TestISISWideMetricEnabled(t *testing.T) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + if !deviations.IsisDatabaseOverloadsUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { + t.Errorf("FAIL- Not expecting pre isis config database_overloads value to change, got %d, want %d", got, 0) + } } if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) @@ -307,50 +320,64 @@ func TestISISWideMetricEnabled(t *testing.T) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ManualAddressDropFromAreas().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero ManualAddressDropFromAreas counter, got %d, want %d", got, 0) + if !deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ManualAddressDropFromAreas().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero ManualAddressDropFromAreas counter, got %d, want %d", got, 0) + } } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().PartChanges().State()); got != 0 { - t.Errorf("FAIL- Not expecting partition changes, got %d, want %d", got, 0) + if !deviations.ISISCounterPartChangesUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().PartChanges().State()); got != 0 { + t.Errorf("FAIL- Not expecting partition changes, got %d, want %d", got, 0) + } } if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SpfRuns().State()); got == 0 { t.Errorf("FAIL- Not expecting spf runs counter to be 0, got %d, want non zero", got) } }) t.Run("Wide metric checks", func(t *testing.T) { - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).MetricStyle().State()); got != oc.E_Isis_MetricStyle(2) { - t.Errorf("FAIL- Expected metric style not found, got %s, want %s", got, oc.E_Isis_MetricStyle(2)) + if !deviations.ISISMetricStyleTelemetryUnsupported(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).MetricStyle().State()); got != oc.E_Isis_MetricStyle(2) { + t.Errorf("FAIL- Expected metric style not found, got %s, want %s", got, oc.E_Isis_MetricStyle(2)) + } } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(ateV4Route).Prefix().State()); got != ateV4Route { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(ateV4Route).Prefix().State(), 1*time.Minute, ateV4Route).Val(); !ok { t.Errorf("FAIL- Expected ate v4 route not found, got %v, want %v", got, ateV4Route) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(ateV4Route).Metric().State()); got != ateV4Metric { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(ateV4Route).Metric().State(), 1*time.Minute, ateV4Metric).Val(); !ok { t.Errorf("FAIL- Expected metric for ate v4 route not found, got %v, want %v", got, ateV4Metric) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(ateV6Route).Prefix().State()); got != ateV6Route { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(ateV6Route).Prefix().State(), 1*time.Minute, ateV6Route).Val(); !ok { t.Errorf("FAIL- Expected ate v6 route not found, got %v, want %v", got, ateV6Route) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(ateV6Route).Metric().State()); got != ateV6Metric { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(ateLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(ateV6Route).Metric().State(), 1*time.Minute, ateV6Metric).Val(); !ok { t.Errorf("FAIL- Expected metric for ate v6 route not found, got %v, want %v", got, ateV6Metric) } - if got := gnmi.Get(t, ts.DUT, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv4Entry(ateV4Route).State()).GetPrefix(); got != ateV4Route { - t.Errorf("FAIL- Expected ate v4 route not found in aft, got %v, want %v", got, ateV4Route) - } - if got := gnmi.Get(t, ts.DUT, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv6Entry(ateV6Route).State()).GetPrefix(); got != ateV6Route { - t.Errorf("FAIL- Expected ate v6 route not found in aft, got %v, want %v", got, ateV6Route) - } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(dutV4Route).Prefix().State()); got != dutV4Route { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(dutV4Route).Prefix().State(), 1*time.Minute, dutV4Route).Val(); !ok { t.Errorf("FAIL- Expected dut v4 route not found, got %v, want %v", got, dutV4Route) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(dutV4Route).Metric().State()); got != dutV4Metric { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(dutV4Route).Metric().State(), 1*time.Minute, dutV4Metric).Val(); !ok { t.Errorf("FAIL- Expected metric for dut v4 route not found, got %v, want %v", got, dutV4Metric) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(dutV6Route).Prefix().State()); got != dutV6Route { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(dutV6Route).Prefix().State(), 1*time.Minute, dutV6Route).Val(); !ok { t.Errorf("FAIL- Expected dut v6 route not found, got %v, want %v", got, dutV6Route) } - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(dutV6Route).Metric().State()); got != dutV6Metric { + if got, ok := gnmi.Await(t, ts.DUT, statePath.Level(2).Lsp(dutLspID).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).Ipv6Reachability().Prefix(dutV6Route).Metric().State(), 1*time.Minute, dutV6Metric).Val(); !ok { t.Errorf("FAIL- Expected metric for dut v6 route not found, got %v, want %v", got, dutV6Metric) } + ipv4Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv4Entry(ateV4Route) + if got, ok := gnmi.Watch(t, ts.DUT, ipv4Path.State(), time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + ipv4Entry, present := val.Val() + return present && ipv4Entry.GetPrefix() == ateV4Route + }).Await(t); !ok { + t.Errorf("ipv4-entry/state/prefix got %v, want %s", got, ateV4Route) + } + ipv6Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).Afts().Ipv6Entry(ateV6Route) + if got, ok := gnmi.Watch(t, ts.DUT, ipv6Path.State(), time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv6Entry]) bool { + ipv6Entry, present := val.Val() + return present && ipv6Entry.GetPrefix() == ateV6Route + }).Await(t); !ok { + t.Errorf("ipv6-entry/state/prefix got %v, want %s", got, ateV6Route) + } }) t.Run("Traffic checks", func(t *testing.T) { t.Logf("Starting traffic") diff --git a/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/metadata.textproto b/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/metadata.textproto new file mode 100644 index 00000000000..4a83159128d --- /dev/null +++ b/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/metadata.textproto @@ -0,0 +1,58 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "8cab716c-70b4-4730-999d-b997fdf3e37d" +plan_id: "RT-2.9" +description: "IS-IS metric style wide enabled" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + isis_interface_level1_disable_required: true + missing_isis_interface_afi_safi_enable: true + explicit_port_speed: true + explicit_interface_in_default_vrf: true + missing_value_for_defaults: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + isis_interface_level1_disable_required: true + isis_dis_sysid_unsupported: true + isis_database_overloads_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + isis_counter_manual_address_drop_from_areas_unsupported: true + isis_counter_part_changes_unsupported: true + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + isis_metric_style_telemetry_unsupported: true + isis_timers_csnp_interval_unsupported: true + missing_isis_interface_afi_safi_enable: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} +tags: TAGS_AGGREGATION diff --git a/feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/README.md b/feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/README.md new file mode 100644 index 00000000000..49fa92ad9b2 --- /dev/null +++ b/feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/README.md @@ -0,0 +1,94 @@ +# RT-2.8: IS-IS metric style wide not enabled + +## Summary + +* Base IS-IS functionality and adjacency establishment. +* Verifies route metric with wide metric disabled on DUT. + +## Procedure + +* TestISISWideMetricNotEnabled + + * Configure IS-IS for ATE port-1 and DUT port-1. + * Do not configure metric style wide under the area level. + * Enable wide metric style on ATE. + * Advertise ISIS prefixes from ATE with wide metrics (value > 63). + * Verify that IS-IS adjacency for IPv4 and IPV6 address family is coming up. + * Verify that IPv4 and IPv6 prefixes that are advertised by ATE correctly installed into DUTs route and forwarding table. + * TODO-Verify that the metrics of the IPv4 and IPv6 prefixes is 63. + * Ensure that IPv4 and IPv6 prefixes that are advertised as part of an (emulated) neighboring system are installed into the DUT routing table, and validate that packets are sent and received to them. + +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: + /network-instances/network-instance/protocols/protocol/isis/global/config/net: + /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: + /network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/passive: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/levels/level/state/metric-style: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-areas: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: + /network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` \ No newline at end of file diff --git a/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/isis_metric_style_wide_not_enabled_test.go b/feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/isis_metric_style_wide_not_enabled_test.go similarity index 94% rename from feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/isis_metric_style_wide_not_enabled_test.go rename to feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/isis_metric_style_wide_not_enabled_test.go index ef18088647e..b2c1abf2fb9 100644 --- a/feature/experimental/isis/otg_tests/isis_metric_style_wide_not_enabled_test/isis_metric_style_wide_not_enabled_test.go +++ b/feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/isis_metric_style_wide_not_enabled_test.go @@ -21,9 +21,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/featureprofiles/internal/otgutils" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -57,11 +57,11 @@ const ( ) // configureISIS configures isis on DUT. -func configureISIS(t *testing.T, ts *session.TestSession) { +func configureISIS(t *testing.T, ts *isissession.TestSession) { t.Helper() d := ts.DUTConf netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) - prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) prot.Enabled = ygot.Bool(true) isis := prot.GetOrCreateIsis() @@ -119,7 +119,7 @@ func configureISIS(t *testing.T, ts *session.TestSession) { } // configureOTG configures isis and traffic on OTG. -func configureOTG(t *testing.T, ts *session.TestSession) { +func configureOTG(t *testing.T, ts *isissession.TestSession) { t.Helper() ts.ATEIntf1.Isis().Basic().SetEnableWideMetric(true) @@ -129,11 +129,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { // netv4 is a simulated network containing the ipv4 addresses specified by targetNetwork netv4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName(v4NetName).SetLinkMetric(ateV4Metric) - netv4.Addresses().Add().SetAddress(v4Route).SetPrefix(uint32(session.ATEISISAttrs.IPv4Len)) + netv4.Addresses().Add().SetAddress(v4Route).SetPrefix(uint32(isissession.ATEISISAttrs.IPv4Len)) // netv6 is a simulated network containing the ipv6 addresses specified by targetNetwork netv6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName(v6NetName).SetLinkMetric(ateV6Metric) - netv6.Addresses().Add().SetAddress(v6Route).SetPrefix(uint32(session.ATEISISAttrs.IPv6Len)) + netv6.Addresses().Add().SetAddress(v6Route).SetPrefix(uint32(isissession.ATEISISAttrs.IPv6Len)) // We generate traffic entering along port2 and destined for port1 srcIpv4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] @@ -148,11 +148,11 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v4NetName}) v4Flow.Size().SetFixed(512) v4Flow.Rate().SetPps(100) - v4Flow.Duration().SetChoice("continuous") + v4Flow.Duration().Continuous() e1 := v4Flow.Packet().Add().Ethernet() - e1.Src().SetValue(session.ATEISISAttrs.MAC) + e1.Src().SetValue(isissession.ATEISISAttrs.MAC) v4 := v4Flow.Packet().Add().Ipv4() - v4.Src().SetValue(session.ATEISISAttrs.IPv4) + v4.Src().SetValue(isissession.ATEISISAttrs.IPv4) v4.Dst().Increment().SetStart(v4IP).SetCount(1) t.Log("Configuring v6 traffic flow ") @@ -164,28 +164,28 @@ func configureOTG(t *testing.T, ts *session.TestSession) { SetRxNames([]string{v6NetName}) v6Flow.Size().SetFixed(512) v6Flow.Rate().SetPps(100) - v6Flow.Duration().SetChoice("continuous") + v6Flow.Duration().Continuous() e2 := v6Flow.Packet().Add().Ethernet() - e2.Src().SetValue(session.ATEISISAttrs.MAC) + e2.Src().SetValue(isissession.ATEISISAttrs.MAC) v6 := v6Flow.Packet().Add().Ipv6() - v6.Src().SetValue(session.ATEISISAttrs.IPv6) + v6.Src().SetValue(isissession.ATEISISAttrs.IPv6) v6.Dst().Increment().SetStart(v6IP).SetCount(1) } // TestISISWideMetricNotEnabled verifies route metric with wide metric disabled on DUT. func TestISISWideMetricNotEnabled(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() configureISIS(t, ts) configureOTG(t, ts) otg := ts.ATE.OTG() - pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, session.ISISName) - fptest.LogQuery(t, "Protocol ISIS", session.ProtocolPath(ts.DUT).Config(), pcl) + pcl := ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName) + fptest.LogQuery(t, "Protocol ISIS", isissession.ProtocolPath(ts.DUT).Config(), pcl) ts.PushAndStart(t) - statePath := session.ISISPath(ts.DUT) + statePath := isissession.ISISPath(ts.DUT) intfName := ts.DUTPort1.Name() if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { intfName += ".0" @@ -198,7 +198,7 @@ func TestISISWideMetricNotEnabled(t *testing.T) { t.Fatalf("Adjacency state invalid: %v", err) } ateLspID := ateSysID + ".00-00" - dutLspID := session.DUTSysID + ".00-00" + dutLspID := isissession.DUTSysID + ".00-00" t.Run("Afi-Safi checks", func(t *testing.T) { if got := gnmi.Get(t, ts.DUT, statePath.Interface(intfName).Level(2).Af(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).AfiName().State()); got != oc.IsisTypes_AFI_TYPE_IPV4 { @@ -226,7 +226,7 @@ func TestISISWideMetricNotEnabled(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.SystemId().State()); got != ateSysID { t.Errorf("FAIL- Expected neighbor system id not found, got %s, want %s", got, ateSysID) } - want := []string{session.ATEAreaAddress, session.DUTAreaAddress} + want := []string{isissession.ATEAreaAddress, isissession.DUTAreaAddress} if got := gnmi.Get(t, ts.DUT, adjPath.AreaAddress().State()); !cmp.Equal(got, want, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { t.Errorf("FAIL- Expected area address not found, got %s, want %s", got, want) } @@ -242,8 +242,8 @@ func TestISISWideMetricNotEnabled(t *testing.T) { if got := gnmi.Get(t, ts.DUT, adjPath.NeighborCircuitType().State()); got != oc.Isis_LevelType_LEVEL_2 { t.Errorf("FAIL- Expected value for circuit type not found, got %s, want %s", got, oc.Isis_LevelType_LEVEL_2) } - if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != session.ATEISISAttrs.IPv4 { - t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, session.ATEISISAttrs.IPv4) + if got := gnmi.Get(t, ts.DUT, adjPath.NeighborIpv4Address().State()); got != isissession.ATEISISAttrs.IPv4 { + t.Errorf("FAIL- Expected value for ipv4 address not found, got %s, want %s", got, isissession.ATEISISAttrs.IPv4) } if got := gnmi.Get(t, ts.DUT, adjPath.NeighborExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected neighbor extended circuit id not found,expected non-zero value, got %d", got) diff --git a/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/metadata.textproto b/feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/metadata.textproto similarity index 89% rename from feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/metadata.textproto rename to feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/metadata.textproto index 9babe9e9f59..c5d39a9df20 100644 --- a/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_metric_style_wide_not_enabled_test/metadata.textproto @@ -1,10 +1,10 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "8cab716c-70b4-4730-999d-b997fdf3e37d" -plan_id: "RT-2.9" -description: "IS-IS metric style wide enabled" -testbed: TESTBED_DUT_ATE_2LINKS +uuid: "d472b5af-70fa-4259-ae6d-29c921820419" +plan_id: "RT-2.8" +description: "IS-IS metric style wide not enabled" +testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { vendor: NOKIA @@ -32,11 +32,11 @@ platform_exceptions: { 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_instance_enabled_required: true isis_lsp_lifetime_interval_requires_lsp_refresh_interval: true isis_timers_csnp_interval_unsupported: true isis_counter_manual_address_drop_from_areas_unsupported: true diff --git a/feature/experimental/isis/ate_tests/lsp_updates_test/README.md b/feature/isis/otg_tests/lsp_updates_test/README.md similarity index 58% rename from feature/experimental/isis/ate_tests/lsp_updates_test/README.md rename to feature/isis/otg_tests/lsp_updates_test/README.md index d207d7c482a..b1f4c16e5bc 100644 --- a/feature/experimental/isis/ate_tests/lsp_updates_test/README.md +++ b/feature/isis/otg_tests/lsp_updates_test/README.md @@ -20,26 +20,21 @@ Ensure that IS-IS updates reflect parameter changes on DUT. port via configuration, update value in configuration, and ensure that ATE and DUT telemetry reflects the change. -## Config Parameter Coverage - -For prefix: /network-instances/network-instance/protocols/protocol/isis/ - -Parameters: - -* global/lsp-bit/overload-bit/config/set-bit - -## Telemetry Parameter Coverage - -* /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric - -* /network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/state/set-bit - -## Protocol/RPC Parameter Coverage - -* IS-IS - * LSP - * Flags - overload bit (5) - * TLV 22 metric field. +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/config/set-bit: + + ## Telemetry Parameter Coverage + /network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/state/set-bit: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` ## Minimum DUT Platform Requirement diff --git a/feature/experimental/isis/otg_tests/lsp_updates_test/lsp_updates_test.go b/feature/isis/otg_tests/lsp_updates_test/lsp_updates_test.go similarity index 78% rename from feature/experimental/isis/otg_tests/lsp_updates_test/lsp_updates_test.go rename to feature/isis/otg_tests/lsp_updates_test/lsp_updates_test.go index 3b8ab65cbea..ebc89fc9d84 100644 --- a/feature/experimental/isis/otg_tests/lsp_updates_test/lsp_updates_test.go +++ b/feature/isis/otg_tests/lsp_updates_test/lsp_updates_test.go @@ -20,10 +20,10 @@ import ( "testing" "time" - "github.com/openconfig/featureprofiles/feature/experimental/isis/otg_tests/internal/session" "github.com/openconfig/featureprofiles/internal/check" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -37,13 +37,23 @@ func TestMain(m *testing.M) { } func TestOverloadBit(t *testing.T) { - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() + // Only push DUT config - no adjacency established yet + if err := ts.PushDUT(context.Background(), t); err != nil { + t.Fatalf("Unable to push initial DUT config: %v", err) + } + isisPath := isissession.ISISPath(ts.DUT) + overloads := isisPath.Level(2).SystemLevelCounters().DatabaseOverloads() + //Lookup the initial value for 'database-overloads' leaf counter after config is pushed to DUT & before adjacency is formed + getDbOlInitCount := gnmi.Lookup(t, ts.DUT, overloads.State()) + olVal, present := getDbOlInitCount.Val() + if !present { + olVal = uint32(0) + } ts.ATE = ondatra.ATE(t, "ate") otg := ts.ATE.OTG() ts.PushAndStart(t) ts.MustAdjacency(t) - isisPath := session.ISISPath(ts.DUT) - overloads := isisPath.Level(2).SystemLevelCounters().DatabaseOverloads() setBit := isisPath.Global().LspBit().OverloadBit().SetBit() deadline := time.Now().Add(time.Second * 3) checkSetBit := check.Equal(setBit.State(), false) @@ -53,7 +63,7 @@ func TestOverloadBit(t *testing.T) { for _, vd := range []check.Validator{ checkSetBit, - check.EqualOrNil(overloads.State(), uint32(0)), + check.EqualOrNil(overloads.State(), olVal), } { if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { t.Error(err) @@ -61,13 +71,13 @@ func TestOverloadBit(t *testing.T) { } ts.DUTConf. GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)). - GetProtocol(session.PTISIS, session.ISISName). + GetProtocol(isissession.PTISIS, isissession.ISISName). GetIsis(). GetGlobal(). GetOrCreateLspBit(). GetOrCreateOverloadBit().SetBit = ygot.Bool(true) ts.PushDUT(context.Background(), t) - if err := check.Equal[uint32](overloads.State(), 1).AwaitFor(time.Second*15, ts.DUTClient); err != nil { + if err := check.Equal(overloads.State(), uint32(olVal+1)).AwaitFor(time.Second*10, ts.DUTClient); err != nil { t.Error(err) } if err := check.Equal(setBit.State(), true).AwaitFor(time.Second*3, ts.DUTClient); err != nil { @@ -93,7 +103,7 @@ func TestOverloadBit(t *testing.T) { func TestMetric(t *testing.T) { t.Logf("Starting...") - ts := session.MustNew(t).WithISIS() + ts := isissession.MustNew(t).WithISIS() ts.ATE = ondatra.ATE(t, "ate") configuredMetric := uint32(100) otg := ts.ATE.OTG() @@ -101,18 +111,18 @@ func TestMetric(t *testing.T) { if deviations.ExplicitInterfaceInDefaultVRF(ts.DUT) { isisIntfName = ts.DUT.Port(t, "port1").Name() + ".0" } - ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(session.PTISIS, session.ISISName).GetIsis(). + ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(isissession.PTISIS, isissession.ISISName).GetIsis(). GetInterface(isisIntfName). GetOrCreateLevel(2). GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST). Metric = ygot.Uint32(configuredMetric) - ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(session.PTISIS, session.ISISName).GetIsis().GetOrCreateLevel(2). + ts.DUTConf.GetNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)).GetProtocol(isissession.PTISIS, isissession.ISISName).GetIsis().GetOrCreateLevel(2). MetricStyle = oc.E_Isis_MetricStyle(2) ts.PushAndStart(t) ts.MustAdjacency(t) - metric := session.ISISPath(ts.DUT).Interface(isisIntfName).Level(2). + metric := isissession.ISISPath(ts.DUT).Interface(isisIntfName).Level(2). Af(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Metric() if err := check.Equal(metric.State(), uint32(100)).AwaitFor(time.Second*3, ts.DUTClient); err != nil { t.Error(err) diff --git a/feature/experimental/isis/otg_tests/lsp_updates_test/metadata.textproto b/feature/isis/otg_tests/lsp_updates_test/metadata.textproto similarity index 95% rename from feature/experimental/isis/otg_tests/lsp_updates_test/metadata.textproto rename to feature/isis/otg_tests/lsp_updates_test/metadata.textproto index 824cd42db30..f9ac6a76780 100644 --- a/feature/experimental/isis/otg_tests/lsp_updates_test/metadata.textproto +++ b/feature/isis/otg_tests/lsp_updates_test/metadata.textproto @@ -24,7 +24,6 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true - isis_interface_level1_disable_required: true } } platform_exceptions: { diff --git a/feature/isis/otg_tests/static_route_isis_redistribution/README.md b/feature/isis/otg_tests/static_route_isis_redistribution/README.md new file mode 100644 index 00000000000..0d7f4913481 --- /dev/null +++ b/feature/isis/otg_tests/static_route_isis_redistribution/README.md @@ -0,0 +1,388 @@ +# RT-2.12: Static route to IS-IS redistribution + +## Summary + +- Static metric to IS-IS Extended IP Reachability / IPv6 IP Reachability (TLV135 / TLV236) + - IS-IS wide metric set to value of metric of static route (metric propagation) +- Redistribution to IS-IS L2 based on combination of prefix-set and set-tag + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed + +## Procedure + +#### Initial Setup: + +* Connect DUT port-1 and port-2 to ATE port-1 and port-2 respectively + +* 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::4/126``` + * ATE port-2 IPv6 address ```ap2-v6 = 2001:DB8::5/126``` + +* Create an IPv4 network i.e. ```ipv4-network = 192.168.10.0/24``` attached to ATE port-2 + +* Create an IPv6 network i.e. ```ipv6-network = 2024:db8:128:128::/64``` attached to ATE port-2 + +* Configure IPv4 and IPv6 IS-IS L2 adjacency between ATE port-1 and DUT port-1 + * /network-instances/network-instance/protocols/protocol/isis/global/afi-safi + * Set level-capability to ```LEVEL_2``` + * /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability + * Set metric-style to ```WIDE_METRIC``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style + +* On the DUT advertise networks of ```dp2-v4``` i.e. ```192.168.1.4/30``` and ```dp2-v6``` i.e. ```2001:DB8::0/126``` through the IS-IS adjacency between DUT port-1 and ATE port-1 + * Do not configure IS-IS between DUT port-2 and ATE port-2 + * Do not advertise ```ipv4-network 192.168.10.0/24``` or ```ipv6-network 2024:db8:128:128::/64``` + +* Configure an IPv4 static route ```ipv4-route``` on DUT destined to the ```ipv4-network``` i.e. ```192.168.10.0/24``` with the next hop set to the IPv4 address of ATE port-2 ```ap2-v4``` i.e. ```192.168.1.6/30``` + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + * Set the metric of the ```ipv4-route``` to 104 + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric + * Set a tag on the ```ipv4-route``` to 40 + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag + +* Configure an IPv6 static route on DUT destined to the ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` with the next hop set to the IPv6 address of ATE port-2 ```ap2-v6``` i.e. ```2001:DB8::5/126``` + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + * Set the metric of the ```ipv6-route``` to 106 + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric + * Set a tag on the ```ipv6-route``` to 60 + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag + +### RT-2.12.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv4 static route to IS-IS with metric propogation diabled + +* Redistribute ```ipv4-route``` to IS-IS +* Set address-family to ```IPV4``` + * /network-instances/network-instance/table-connections/table-connection/config/address-family +* Configure source protocol to ```STATIC``` + * /network-instances/network-instance/table-connections/table-connection/config/src-protocol +* Configure destination protocol to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/config/dst-protocol +* Configure default import policy to ```ACCEPT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +* Disable metric propogation by setting it to ```true``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +* Verify the address-family is set to ```IPV4``` + * /network-instances/network-instance/table-connections/table-connection/state/address-family +* Verify source protocol is set to ```STATIC``` + * /network-instances/network-instance/table-connections/table-connection/state/src-protocol +* Verify destination protocol is set to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/state/dst-protocol +* Verify default import policy is set to ```ACCEPT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +* Verify disable metric propogation is set to ```true``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with default metric of ```0``` and not ```104``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/metric + +### RT-2.12.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv4 static route to IS-IS with metric propogation enabled +* Enable metric propogation by setting disable-metric-propagation to ```false``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +* Verify disable metric propogation is now ```false``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +* Validate that the ATE receives the redistributed static route ```ipv4-route``` with metric```104``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/metric + +### RT-2.12.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv6 static route to IS-IS with metric propogation diabled + +* Redistribute ```ipv6-route``` to IS-IS +* Set address-family to ```IPV6``` + * /network-instances/network-instance/table-connections/table-connection/config/address-family +* Configure source protocol to ```STATIC``` + * /network-instances/network-instance/table-connections/table-connection/config/src-protocol +* Configure destination protocol to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/config/dst-protocol +* Configure default import policy to ```ACCEPT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +* Disable metric propogation by setting it to ```true``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +* Verify the address-family is set to ```IPV6``` + * /network-instances/network-instance/table-connections/table-connection/state/address-family +* Verify source protocol is set to ```STATIC``` + * /network-instances/network-instance/table-connections/table-connection/state/src-protocol +* Verify destination protocol is set to ```ISIS``` + * /network-instances/network-instance/table-connections/table-connection/state/dst-protocol +* Verify default import policy is set to ```ACCEPT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +* Verify disable metric propogation is set to ```true``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with default metric of ```0``` and not ```106``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/prefix + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/metric + +### RT-2.12.4 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv6 static route to IS-IS with metric propogation enabled + +* Enable metric propogation by setting it to ```false``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation +* Verify disable metric propogation is now ```false``` + * /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation +* Validate that the ATE receives the redistributed static route ```ipv6-route``` with metric ```106``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/prefix + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/metric + +### RT-2.12.5 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv4 and IPv6 static route to IS-IS with default-import-policy set to reject + +* Configure default import policy to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-import-policy +* Verify default import policy is set to ```REJECT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/state/default-import-policy +* Validate that the ATE does not receives the redistributed static route ```ipv4-route``` and ```ipv6-route``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/prefix + +### RT-2.12.6 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv4 static route to IS-IS matching a prefix using a route-policy + +* Configure an IPv4 route-policy definition with the name ```route-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```route-policy-v4``` configure a statement with the name ```statement-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set level to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-level +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set metric to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-metric +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set metric style to ```WIDE_METRIC``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-metric-style-type +* Configure a prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v4``` set the ip-prefix to ```ipv4-network``` i.e. ```192.168.10.0/24``` and masklength to ```exact``` + * /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 +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` set prefix set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set +* Apply routing policy ```route-policy-v4``` for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +* Verify IPv4 route-policy definition is configured with the name ```route-policy-v4``` + * /routing-policy/policy-definitions/policy-definition/state/name +* Verify for routing-policy ```route-policy-v4``` a statement with the name ```statement-v4``` is configured + * /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` policy-result is set to ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result +* verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` level is set to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-level +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` metric is set to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-metric +* verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` metric style is set to ```WIDE_METRIC``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-metric-style-type +* Verify prefix-set with the name ```prefix-set-v4``` and mode ```IPV4``` is configured + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* Verify for prefix-set ```prefix-set-v4``` the ip-prefix is set to ```192.168.10.0/24``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` match options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` prefix-set is set to ```prefix-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set +* Verify routing policy ```route-policy-v4``` is applied as import policy for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/state/import-policy +* Verify that the redistibuted static routes ```ipv4-route``` is being received by the ATE + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix +* Verify that the ATE receives the redistributed static route ```ipv4-route``` with metric of ```1000``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/metric +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` +* Validate that the traffic is received on ATE port-2 + +### RT-2.12.7 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv4 static route to IS-IS matching a tag + +* Configure a tag-set with name ```tag-set-v4``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/name +* Configure tag-set ```tag-set-v4``` with a tag value of ```100``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value + * here we are setting incorrect tag value of 100 to validate that the route is not redistributed +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` configure match-set-tag condition to ```tag-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/tag-set +* For routing-policy ```route-policy-v4``` statement ```statement-v4``` configure match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/match-set-options +* Verify a tag-set with name ```tag-set-v4``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/name +* Verify tag-set ```tag-set-v4``` with a tag value of ```100``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` tag-set is set to ```tag-set-v4``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/tag-set +* Verify for routing-policy ```route-policy-v4``` statement ```statement-v4``` match-set-options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/match-set-options +* Verify that the ATE does not receives the redistributed static route ```ipv4-route``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/metric +* Configure tag-set ```tag-set-v4``` with a tag value of ```40``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value + * here we are setting correct tag value of 40, as defined in initial setup of this test, to validate that the route is now redistributed +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` +* Validate that the traffic is received on ATE port-2 + +### RT-2.12.8 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv6 static route to IS-IS matching a prefix using a route-policy + +* Configure an IPv6 route-policy definition with the name ```route-policy-v6``` + * /routing-policy/policy-definitions/policy-definition/config/name +* For routing-policy ```route-policy-v6``` configure a statement with the name ```statement-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set level to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-level +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set metric to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-metric +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set metric style to ```WIDE_METRIC``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-metric-style-type +* Configure a prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` for the routing policy ```route-policy-v6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* For prefix-set ```prefix-set-v6``` set the ip-prefix to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` and masklength to ```exact``` + * /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 +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` set prefix set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set +* Apply routing policy ```route-policy-v6``` for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/config/import-policy +* Verify for routing-policy ```route-policy-v6``` a statement with the name ```statement-v6``` is configured + * /routing-policy/policy-definitions/policy-definition/statements/statement/state/name +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` policy-result is set to ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result +* verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` level is set to ```2``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-level +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` metric is set to ```1000``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-metric +* verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` metric style is set to ```WIDE_METRIC``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-metric-style-type +* Verify prefix-set with the name ```prefix-set-v6``` and mode ```IPV6``` is configured + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode +* Verify for prefix-set ```prefix-set-v6``` the ip-prefix is set to ```2024:db8:128:128::/64``` and masklength is set to ```exact``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` match options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` prefix-set is set to ```prefix-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set +* Verify routing policy ```route-policy-v6``` is applied as import policy for redistribution to IS-IS + * /network-instances/network-instance/table-connections/table-connection/state/import-policy +* Verify that the redistibuted static routes ``ipv6-route``` is being received by the ATE + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/prefix +* Verify that the ATE receives the redistributed static route ```ipv6-route``` with metric of ```1000``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/metric +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` +* Validate that the traffic is received on ATE port-2 + +### RT-2.12.9 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] +#### Redistribute IPv6 static route to IS-IS matching a tag + +* Configure a tag-set with name ```tag-set-v6``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/name +* Configure tag-set ```tag-set-v6``` with a tag value of ```100``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value + * here we are setting incorrect tag value of 100 to validate that the route is not redistributed +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` configure tag-set to ```tag-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/tag-set +* For routing-policy ```route-policy-v6``` statement ```statement-v6``` configure match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/match-set-options +* Verify a tag-set with name ```tag-set-v6``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/name +* Verify tag-set ```tag-set-v6``` with a tag value of ```100``` is configured + * /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` match-set-tag is set to ```tag-set-v6``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/tag-set +* Verify for routing-policy ```route-policy-v6``` statement ```statement-v6``` match-set-options is set to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/match-set-options +* Verify that the ATE does not receives the redistributed static route ```ipv6-route``` + * /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/pv6-reachability/prefixes/prefix/state/metric +* Configure tag-set ```tag-set-v6``` with a tag value of ```60``` + * /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value + * here we are setting correct tag value of 60, as defined in initial setup of this test, to validate that the route is now redistributed +* Initiate traffic from ATE port-1 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` +* Validate that the traffic is received on ATE port-2 + +## 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/table-connections/table-connection/config: + /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/default-import-policy: + /network-instances/network-instance/table-connections/table-connection/config/import-policy: + /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation: + /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/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/defined-sets/tag-sets/tag-set/config/name: + /routing-policy/defined-sets/tag-sets/tag-set/config/tag-value: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/config/tag-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-level: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-metric: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-metric-style-type: + + ## State Paths ## + /network-instances/network-instance/table-connections/table-connection/state/address-family: + /network-instances/network-instance/table-connections/table-connection/state/default-import-policy: + /network-instances/network-instance/table-connections/table-connection/state/disable-metric-propagation: + /network-instances/network-instance/table-connections/table-connection/state/dst-protocol: + /network-instances/network-instance/table-connections/table-connection/state/import-policy: + /network-instances/network-instance/table-connections/table-connection/state/src-protocol: + /routing-policy/policy-definitions/policy-definition/state/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/state/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result: + /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode: + /routing-policy/defined-sets/prefix-sets/prefix-set/state/name: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set: + /routing-policy/defined-sets/tag-sets/tag-set/state/name: + /routing-policy/defined-sets/tag-sets/tag-set/state/tag-value: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-tag-set/state/tag-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-level: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-metric: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/state/set-metric-style-type: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/prefix: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/metric: + /network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/metric: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` + +## Required DUT platform + +* FFF diff --git a/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto b/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto new file mode 100644 index 00000000000..2e9a824e2c1 --- /dev/null +++ b/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto @@ -0,0 +1,48 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1d0a79c7-7aa8-43a8-b83f-620d40fa1e1a" +plan_id: "RT-2.12" +description: "Static route to IS-IS redistribution" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + missing_isis_interface_afi_safi_enable: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + omit_l2_mtu: true + static_protocol_name: "STATIC" + isis_interface_afi_unsupported: true + isis_instance_enabled_required: true + missing_value_for_defaults: true + skip_isis_set_level: true + skip_setting_disable_metric_propagation: true + ipv6_static_route_with_ipv4_next_hop_requires_static_arp: true + routing_policy_tag_set_embedded: true + same_policy_attached_to_all_afis: true + match_tag_set_condition_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + static_protocol_name: "static" + skip_prefix_set_mode: true + enable_table_connections: 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 new file mode 100644 index 00000000000..9bbc947d8d3 --- /dev/null +++ b/feature/isis/otg_tests/static_route_isis_redistribution/static_route_isis_redistribution_test.go @@ -0,0 +1,600 @@ +// 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 static_route_isis_redistribution_test + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" + "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" + "github.com/openconfig/ygot/ygot" +) + +const ( + lossTolerance = float64(1) + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + v4Route = "192.168.10.0" + v4TrafficStart = "192.168.10.1" + v4RoutePrefix = uint32(24) + v6Route = "2024:db8:128:128::" + v6TrafficStart = "2024:db8:128:128::1" + v6RoutePrefix = uint32(64) + dp2v4Route = "192.168.1.4" + dp2v4Prefix = uint32(30) + dp2v6Route = "2001:DB8::0" + dp2v6Prefix = uint32(126) + v4Flow = "v4Flow" + v6Flow = "v6Flow" + trafficDuration = 30 * time.Second + prefixMatch = "exact" + v4RoutePolicy = "route-policy-v4" + v4Statement = "statement-v4" + v4PrefixSet = "prefix-set-v4" + v6RoutePolicy = "route-policy-v6" + v6Statement = "statement-v6" + v6PrefixSet = "prefix-set-v6" + protoSrc = oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC + protoDst = oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS + dummyV6 = "2001:db8::192:0:2:d" + dummyMAC = "00:1A:11:00:0A:BC" + V4tagValue = 40 + V6tagValue = 60 +) + +var ( + advertisedIPv4 = ipAddr{address: dp2v4Route, prefix: dp2v4Prefix} + advertisedIPv6 = ipAddr{address: dp2v6Route, prefix: dp2v6Prefix} +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type ipAddr struct { + address string + prefix uint32 +} + +type TableConnectionConfig struct { + ImportPolicy []string `json:"import-policy"` + DisableMetricPropagation bool `json:"disable-metric-propagation"` + DstProtocol string `json:"dst-protocol"` + AddressFamily string `json:"address-family"` + SrcProtocol string `json:"src-protocol"` +} + +func getAndVerifyIsisImportPolicy(t *testing.T, + dut *ondatra.DUTDevice, DisableMetricValue bool, + RplName string, addressFamily string) { + + gnmiClient := dut.RawAPIs().GNMI(t) + getResponse, err := gnmiClient.Get(context.Background(), &gpb.GetRequest{ + Path: []*gpb.Path{{ + Elem: []*gpb.PathElem{ + {Name: "network-instances"}, + {Name: "network-instance", Key: map[string]string{"name": "DEFAULT"}}, + {Name: "table-connections"}, + {Name: "table-connection", Key: map[string]string{ + "src-protocol": "STATIC", + "dst-protocol": "ISIS", + "address-family": addressFamily}}, + {Name: "config"}, + }, + }}, + Type: gpb.GetRequest_CONFIG, + Encoding: gpb.Encoding_JSON_IETF, + }) + + if err != nil { + t.Fatalf("failed due to %v", err) + } + t.Log("Received parameters of table connections") + + t.Log("Verify Get outputs ") + for _, notification := range getResponse.Notification { + for _, update := range notification.Update { + if update.Path != nil { + var config TableConnectionConfig + err = json.Unmarshal(update.Val.GetJsonIetfVal(), &config) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + if config.SrcProtocol != "openconfig-policy-types:STATIC" { + t.Fatalf("src-protocol is not set to STATIC as expected") + } + if config.DstProtocol != "openconfig-policy-types:ISIS" { + t.Fatalf("dst-protocol is not set to ISIS as expected") + } + addressFamilyMatchString := fmt.Sprintf("openconfig-types:%s", addressFamily) + if config.AddressFamily != addressFamilyMatchString { + t.Fatalf("address-family is not set to %s as expected", addressFamily) + } + if config.DisableMetricPropagation != DisableMetricValue { + t.Fatalf("disable-metric-propagation is not set to %v as expected", DisableMetricValue) + } + for _, i := range config.ImportPolicy { + if i != RplName { + t.Fatalf("import-policy is not set to %s as expected", RplName) + } + } + t.Logf("Table Connection Details:\n"+ + "SRC PROTO GOT %v WANT STATIC\n"+ + "DST PROTO GOT %v WANT ISIS\n"+ + "ADDRESS FAMILY GOT %v WANT %v\n"+ + "DISABLEMETRICPROPAGATION GOT %v WANT %v\n", config.SrcProtocol, + config.DstProtocol, config.AddressFamily, addressFamily, + config.DisableMetricPropagation, DisableMetricValue) + } + } + } +} + +func isisImportPolicyConfig(t *testing.T, dut *ondatra.DUTDevice, policyName string, + srcProto oc.E_PolicyTypes_INSTALL_PROTOCOL_TYPE, + dstProto oc.E_PolicyTypes_INSTALL_PROTOCOL_TYPE, + addfmly oc.E_Types_ADDRESS_FAMILY, + metricPropagation bool) { + + t.Log("configure redistribution under isis") + + dni := deviations.DefaultNetworkInstance(dut) + + 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) { + state := "enable" + configEnableTbNative(t, dut, state) + } + 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) + } + + batchSet.Set(t, dut) +} + +func configureRoutePolicy(dut *ondatra.DUTDevice, rplName string, statement string, prefixSetCond, tagSetCond bool, + rplType oc.E_RoutingPolicy_PolicyResultType) (*oc.RoutingPolicy, error) { + + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(rplName) + + if prefixSetCond { + // Condition for prefix set configuration + stmt1, err := pdef.AppendNewStatement(v4Statement) + if err != nil { + return nil, err + } + v4Prefix := v4Route + "/" + strconv.FormatUint(uint64(v4RoutePrefix), 10) + pset := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v4PrefixSet) + pset.GetOrCreatePrefix(v4Prefix, prefixMatch) + if !deviations.SkipPrefixSetMode(dut) { + pset.SetMode(oc.PrefixSet_Mode_IPV4) + } + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v4PrefixSet) + stmt1.GetOrCreateActions().SetPolicyResult(rplType) + + stmt2, err := pdef.AppendNewStatement(v6Statement) + if err != nil { + return nil, err + } + v6Prefix := v6Route + "/" + strconv.FormatUint(uint64(v6RoutePrefix), 10) + pset = rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(v6PrefixSet) + pset.GetOrCreatePrefix(v6Prefix, prefixMatch) + if !deviations.SkipPrefixSetMode(dut) { + pset.SetMode(oc.PrefixSet_Mode_IPV6) + } + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(v6PrefixSet) + stmt2.GetOrCreateActions().SetPolicyResult(rplType) + } else if tagSetCond { + // Condition for tag set configuration + stmt1, err := pdef.AppendNewStatement(v4Statement) + if err != nil { + return nil, err + } + v4tagSet := getTagSetName(dut, rplName, v4Statement, "v4") + tagSet1 := rp.GetOrCreateDefinedSets().GetOrCreateTagSet(v4tagSet) + tagSet1.SetTagValue([]oc.RoutingPolicy_DefinedSets_TagSet_TagValue_Union{oc.UnionUint32(V4tagValue)}) + stmt1.GetOrCreateConditions().GetOrCreateMatchTagSet().SetTagSet(v4tagSet) + stmt1.GetOrCreateActions().SetPolicyResult(rplType) + + stmt2, err := pdef.AppendNewStatement(v6Statement) + if err != nil { + return nil, err + } + v6tagSet := getTagSetName(dut, rplName, v6Statement, "v6") + tagSet2 := rp.GetOrCreateDefinedSets().GetOrCreateTagSet(v6tagSet) + tagSet2.SetTagValue([]oc.RoutingPolicy_DefinedSets_TagSet_TagValue_Union{oc.UnionUint32(V6tagValue)}) + stmt2.GetOrCreateConditions().GetOrCreateMatchTagSet().SetTagSet(v6tagSet) + stmt2.GetOrCreateActions().SetPolicyResult(rplType) + } else { + // Create a common statement + stmt, err := pdef.AppendNewStatement(statement) + if err != nil { + return nil, err + } + stmt.GetOrCreateActions().SetPolicyResult(rplType) + } + + return rp, nil +} + +func configureStaticRoute(t *testing.T, + dut *ondatra.DUTDevice, + ipv4Route string, + ipv4Mask string, + tagValueV4 uint32, + metricValueV4 uint32, + ipv6Route string, + ipv6Mask string, + tagValueV6 uint32, + metricValueV6 uint32) { + + staticRoute1 := ipv4Route + "/" + ipv4Mask + staticRoute2 := ipv6Route + "/" + ipv6Mask + + ni := oc.NetworkInstance{Name: ygot.String(deviations.DefaultNetworkInstance(dut))} + static := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + sr := static.GetOrCreateStatic(staticRoute1) + sr.SetTag, _ = sr.To_NetworkInstance_Protocol_Static_SetTag_Union(tagValueV4) + nh := sr.GetOrCreateNextHop("0") + nh.NextHop = oc.UnionString(isissession.ATEISISAttrs.IPv4) + nh.Metric = ygot.Uint32(metricValueV4) + + sr2 := static.GetOrCreateStatic(staticRoute2) + sr2.SetTag, _ = sr.To_NetworkInstance_Protocol_Static_SetTag_Union(tagValueV6) + nh2 := sr2.GetOrCreateNextHop("0") + nh2.NextHop = oc.UnionString(isissession.ATEISISAttrs.IPv6) + nh2.Metric = ygot.Uint32(metricValueV6) + + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol( + oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + deviations.StaticProtocolName(dut)).Config(), + static) +} + +func configureOTGFlows(t *testing.T, top gosnappi.Config, ts *isissession.TestSession) { + t.Helper() + + srcV4 := ts.ATEIntf2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + srcV6 := ts.ATEIntf2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + dst1V4 := ts.ATEIntf1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dst1V6 := ts.ATEIntf1.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + v4F := top.Flows().Add() + v4F.SetName(v4Flow).Metrics().SetEnable(true) + v4F.TxRx().Device().SetTxNames([]string{srcV4.Name()}).SetRxNames([]string{dst1V4.Name()}) + + v4FEth := v4F.Packet().Add().Ethernet() + v4FEth.Src().SetValue(isissession.ATETrafficAttrs.MAC) + + v4FIp := v4F.Packet().Add().Ipv4() + v4FIp.Src().SetValue(srcV4.Address()) + v4FIp.Dst().Increment().SetStart(v4TrafficStart).SetCount(254) + + eth := v4F.EgressPacket().Add().Ethernet() + ethTag := eth.Dst().MetricTags().Add() + ethTag.SetName("MACTrackingv4").SetOffset(36).SetLength(12) + + v6F := top.Flows().Add() + v6F.SetName(v6Flow).Metrics().SetEnable(true) + v6F.TxRx().Device().SetTxNames([]string{srcV6.Name()}).SetRxNames([]string{dst1V6.Name()}) + + v6FEth := v6F.Packet().Add().Ethernet() + v6FEth.Src().SetValue(isissession.ATETrafficAttrs.MAC) + + v6FIP := v6F.Packet().Add().Ipv6() + v6FIP.Src().SetValue(srcV6.Address()) + v6FIP.Dst().Increment().SetStart(v6TrafficStart).SetCount(1) + + eth = v6F.EgressPacket().Add().Ethernet() + ethTag = eth.Dst().MetricTags().Add() + ethTag.SetName("MACTrackingv6").SetOffset(36).SetLength(12) +} + +func advertiseRoutesWithISIS(t *testing.T, ts *isissession.TestSession) { + t.Helper() + + // configure emulated network params + net2v4 := ts.ATEIntf1.Isis().V4Routes().Add().SetName("v4-isisNet-dev1").SetLinkMetric(10) + net2v4.Addresses().Add().SetAddress(advertisedIPv4.address).SetPrefix(advertisedIPv4.prefix) + net2v6 := ts.ATEIntf1.Isis().V6Routes().Add().SetName("v6-isisNet-dev1").SetLinkMetric(10) + net2v6.Addresses().Add().SetAddress(advertisedIPv6.address).SetPrefix(advertisedIPv6.prefix) +} + +func verifyRplConfig(t *testing.T, dut *ondatra.DUTDevice, tagSetName string, tagValue oc.UnionUint32) { + tagSetState := gnmi.Get(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().TagSet(tagSetName).TagValue().State()) + tagNameState := gnmi.Get(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().TagSet(tagSetName).Name().State()) + + setTagValue := []oc.RoutingPolicy_DefinedSets_TagSet_TagValue_Union{tagValue} + + for _, value := range tagSetState { + configuredTagValue := []oc.RoutingPolicy_DefinedSets_TagSet_TagValue_Union{value} + if setTagValue[0] == configuredTagValue[0] { + t.Logf("Passed: setTagValue is %v and configuredTagValue is %v", setTagValue[0], configuredTagValue[0]) + } else { + t.Errorf("Failed: setTagValue is %v and configuredTagValue is %v", setTagValue[0], configuredTagValue[0]) + } + } + t.Logf("verify tag name matches expected") + if tagNameState != tagSetName { + t.Errorf("Failed to get tag-set name got %s wanted %s", tagNameState, tagSetName) + } else { + t.Logf("Passed Found tag-set name got %s wanted %s", tagNameState, tagSetName) + } +} + +func getTagSetName(dut *ondatra.DUTDevice, policyName, stmtName, afStr string) string { + if deviations.RoutingPolicyTagSetEmbedded(dut) { + return fmt.Sprintf("%s %s", policyName, stmtName) + } + return fmt.Sprintf("tag-set-%s", afStr) +} + +func configEnableTbNative(t testing.TB, d *ondatra.DUTDevice, state string) { + t.Helper() + switch d.Vendor() { + case ondatra.NOKIA: + adminEnable, err := json.Marshal(state) + if err != nil { + t.Fatalf("Error with json Marshal: %v", err) + } + + gpbSetRequest := &gpb.SetRequest{ + Prefix: &gpb.Path{ + Origin: "native", + }, + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "network-instance", Key: map[string]string{"name": "DEFAULT"}}, + {Name: "table-connections"}, + {Name: "admin-state"}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: adminEnable, + }, + }, + }, + }, + } + + gnmiClient := d.RawAPIs().GNMI(t) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("Unexpected error updating SRL static-route tag-set: %v", err) + } + default: + t.Fatalf("Unsupported vendor %s for deviation 'EnableTableConnections'", d.Vendor()) + } +} + +func TestStaticToISISRedistribution(t *testing.T) { + var ts *isissession.TestSession + + t.Run("Initial Setup", func(t *testing.T) { + t.Run("Configure ISIS on DUT", func(t *testing.T) { + ts = isissession.MustNew(t).WithISIS() + if err := ts.PushDUT(context.Background(), t); err != nil { + t.Fatalf("Unable to push initial DUT config: %v", err) + } + }) + + t.Run("Configure Static Route on DUT", func(t *testing.T) { + ipv4Mask := strconv.FormatUint(uint64(v4RoutePrefix), 10) + ipv6Mask := strconv.FormatUint(uint64(v6RoutePrefix), 10) + configureStaticRoute(t, ts.DUT, v4Route, ipv4Mask, 40, 104, v6Route, ipv6Mask, 60, 106) + }) + + t.Run("OTG Configuration", func(t *testing.T) { + configureOTGFlows(t, ts.ATETop, ts) + advertiseRoutesWithISIS(t, ts) + ts.PushAndStart(t) + ts.MustAdjacency(t) + + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv4") + otgutils.WaitForARP(t, ts.ATE.OTG(), ts.ATETop, "IPv6") + }) + }) + + cases := []struct { + desc string + policyStmtType oc.E_RoutingPolicy_PolicyResultType + metricPropogation bool + protoAf oc.E_Types_ADDRESS_FAMILY + RplName string + RplStatement string + verifyTrafficStats bool + trafficFlows []string + TagSetCondition bool + PrefixSetCondition bool + }{{ + desc: "RT-2.12.1: Redistribute IPv4 static route to IS-IS with metric propagation disabled", + metricPropogation: false, + protoAf: oc.Types_ADDRESS_FAMILY_IPV4, + RplName: "DEFAULT-POLICY-PASS-ALL-V4", + RplStatement: "PASS-ALL", + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + }, { + desc: "RT-2.12.2: Redistribute IPv6 static route to IS-IS with metric propagation disabled", + metricPropogation: false, + protoAf: oc.Types_ADDRESS_FAMILY_IPV6, + RplName: "DEFAULT-POLICY-PASS-ALL-V6", + RplStatement: "PASS-ALL", + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + }, { + desc: "RT-2.12.3: Redistribute IPv4 static route to IS-IS with metric propagation enabled", + metricPropogation: true, + protoAf: oc.Types_ADDRESS_FAMILY_IPV4, + RplName: "DEFAULT-POLICY-PASS-ALL-V4", + RplStatement: "PASS-ALL", + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + }, { + desc: "RT-2.12.4: Redistribute IPv6 static route to IS-IS with metric propogation enabled", + metricPropogation: true, + protoAf: oc.Types_ADDRESS_FAMILY_IPV6, + RplName: "DEFAULT-POLICY-PASS-ALL-V6", + RplStatement: "PASS-ALL", + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + }, { + desc: "RT-2.12.5: Redistribute IPv4 and IPv6 static route to IS-IS with default-import-policy set to reject", + metricPropogation: false, + protoAf: oc.Types_ADDRESS_FAMILY_IPV4, + RplName: "DEFAULT-POLICY-PASS-ALL-V4", + RplStatement: "PASS-ALL", + policyStmtType: oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE, + }, { + desc: "RT-2.12.6: Redistribute IPv4 static route to IS-IS matching a prefix using a route-policy", + protoAf: oc.Types_ADDRESS_FAMILY_IPV4, + RplName: v4RoutePolicy, + metricPropogation: true, + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + verifyTrafficStats: true, + trafficFlows: []string{v4Flow}, + PrefixSetCondition: true, + }, { + desc: "RT-2.12.7: Redistribute IPv4 static route to IS-IS matching a tag", + protoAf: oc.Types_ADDRESS_FAMILY_IPV4, + RplName: v4RoutePolicy, + metricPropogation: true, + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + verifyTrafficStats: true, + trafficFlows: []string{v4Flow}, + TagSetCondition: true, + }, { + desc: "RT-2.12.8: Redistribute IPv6 static route to IS-IS matching a prefix using a route-policy", + protoAf: oc.Types_ADDRESS_FAMILY_IPV6, + RplName: v6RoutePolicy, + metricPropogation: true, + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + verifyTrafficStats: true, + trafficFlows: []string{v6Flow}, + PrefixSetCondition: true, + }, { + desc: "RT-2.12.9: Redistribute IPv6 static route to IS-IS matching a prefix using a tag", + protoAf: oc.Types_ADDRESS_FAMILY_IPV4, + RplName: v6RoutePolicy, + metricPropogation: true, + policyStmtType: oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE, + verifyTrafficStats: true, + trafficFlows: []string{v4Flow}, + TagSetCondition: true, + }} + + for _, tc := range cases { + if deviations.MatchTagSetConditionUnsupported(ts.DUT) && tc.TagSetCondition { + t.Skipf("Skipping test case %s due to match tag set condition not supported", tc.desc) + } + + dni := deviations.DefaultNetworkInstance(ts.DUT) + + t.Run(tc.desc, func(t *testing.T) { + t.Run(fmt.Sprintf("Configure Policy Type %s", tc.policyStmtType.String()), func(t *testing.T) { + rpl, err := configureRoutePolicy(ts.DUT, tc.RplName, tc.RplStatement, tc.PrefixSetCondition, + tc.TagSetCondition, tc.policyStmtType) + if err != nil { + fmt.Println("Error configuring route policy:", err) + return + } + gnmi.Update(t, ts.DUT, gnmi.OC().RoutingPolicy().Config(), rpl) + }) + + if tc.TagSetCondition { + t.Run("Verify Configuration for RPL TagSet", func(t *testing.T) { + verifyRplConfig(t, ts.DUT, getTagSetName(ts.DUT, tc.RplName, v4Statement, "v4"), oc.UnionUint32(V4tagValue)) + verifyRplConfig(t, ts.DUT, getTagSetName(ts.DUT, tc.RplName, v6Statement, "v6"), oc.UnionUint32(V6tagValue)) + }) + } + + 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) + }) + + 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()) + }) + + if tc.verifyTrafficStats { + t.Run(fmt.Sprintf("Verify traffic for %s", tc.trafficFlows), func(t *testing.T) { + + ts.ATE.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + ts.ATE.OTG().StopTraffic(t) + + for _, flow := range tc.trafficFlows { + loss := otgutils.GetFlowLossPct(t, ts.ATE.OTG(), flow, 20*time.Second) + if loss > lossTolerance { + t.Errorf("Traffic loss too high for flow %s", flow) + } else { + t.Logf("Traffic loss for flow %s is %v", flow, loss) + } + } + }) + } + + t.Run("Verify Route on OTG", func(t *testing.T) { + configuredMetric := uint32(10) + _, ok := gnmi.WatchAll(t, ts.ATE.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().PrefixAny().Metric().State(), time.Minute, func(v *ygnmi.Value[uint32]) bool { + metric, present := v.Val() + if present { + if metric == configuredMetric { + return true + } + } + return false + }).Await(t) + + metricInReceivedLsp := gnmi.GetAll(t, ts.ATE.OTG(), gnmi.OTG().IsisRouter("devIsis").LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().PrefixAny().Metric().State())[0] + if !ok { + t.Fatalf("Metric not matched. Expected %d got %d ", configuredMetric, metricInReceivedLsp) + } + }) + }) + } +} diff --git a/feature/isis/otg_tests/weighted_ecmp_test/README.md b/feature/isis/otg_tests/weighted_ecmp_test/README.md new file mode 100644 index 00000000000..8a5bdf8448a --- /dev/null +++ b/feature/isis/otg_tests/weighted_ecmp_test/README.md @@ -0,0 +1,185 @@ +# RT-2.13: Weighted-ECMP for IS-IS + +## Summary + +This is to ensure that, + +* Implementations can be configured for weighted equal cost multipath (ECMP) + routing for IS-IS neighbors that are one hop away. + +* When WECMP is enabled, traffic destined to an IS-IS route represented by a + multipath set of next-hop interfaces will be unequally distributed across + the interfaces based on their bandwidth. + +## Testbed type + +[TESTBED_DUT_ATE_8LINKS](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_8.testbed) + +## Topolgy + +Each LAG bundle below is made up of 2x100G ports. + +```mermaid +graph LR; +A[ATE1:LAG1] <-- IBGP+IS-IS --> B[LAG1:DUT]; +C[DUT:LAG2] <-- IBGP+IS-IS --> D[LAG1:ATE2]; +E[DUT:LAG3] <-- IBGP+IS-IS --> F[LAG2:ATE2]; +G[DUT:LAG4] <-- IBGP+IS-IS --> H[LAG3:ATE2]; +``` + +## Procedure + +### Test environment setup + +* Configure 1 aggregate interface with 2 100GE ports between DUT and ATE1 +* Configure 3 aggregate interfaces, each with 2 100GE ports between DUT and ATE2. +* Configure IPv4 and IPv6 L2 adjacencies between DUT and ATE aggregate interfaces. + Therefore, DUT will have + * 1xIS-IS adjacency with ATE1 DUT:LAG1<->ATE1:LAG1, + * 3xIS-IS adjacencies between DUT and ATE2 + * DUT:LAG2<->ATE2:LAG1 + * DUT:LAG3<->ATE2:LAG2 + * DUT:LAG4<->ATE2:LAG3 + + * Set ISIS parameters as + * /network-instances/network-instance/protocols/protocol/isis/global/ + * afi-safi/af/config/afi-name: IPV4, IPV6 + * afi-safi/af/config/safi-name: UNICAST + * afi-safi/af/config/enabled: true + * config/level-capability = LEVEL_2 + * /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style = WIDE_METRIC + +* Configure IPv4 and IPv6 IBGP peering between both ATEs and the DUT using + their loopback addresses for both IPv4 and IPv6 address families. + + * /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/config + +* Attach a network with an IPv4 and an IPv6 prefix to ATE2 and have it + advertise these prefixes over its IBGP peering with the DUT. The DUT in turn + should advertise these prefixes over its IBGP peering with ATE1 + + * Please use `IPv4 prefix = 100.0.1.0/24` and `IPv6 prefix = + 2001:db8:64:64::/64` + +* Similarly, attach a different network to ATE1 with IPv4 and IPv6 prefixes + and advertise the same over its IBGP peering with the DUT. + + * Please use `IPv4 prefix = 100.0.2.0/24` and `IPv6 prefix = + 2001:db8:64:65::/64` + +* On the DUT, enable WECMP loadbalancing for multipath IS-IS routes and set + the load-balancing-weight to use LAG bandwidth. + + * /network-instances/network-instance/protocols/protocol/isis/global/config/weighted-ecmp + set to Enabled + + * /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/weighted-ecmp/config/load-balancing-weight + set to Auto + +## RT-2.13.1: Equal distribution of traffic + +* Start 1024 flows from IPv4 addresses in 100.0.2.0/24 to 100.0.1.0/24 + +* Start 1024 flows from IPv6 addresses in 2001:db8:64:65::/64 to + 2001:db8:64:64::/64 + + +### Verification + +* Ensure that the DUT has learnt the routes for prefixes 100.0.1.0/24 and + 2001:db8:64:64::/64 over IBGP. Following paths + + * /network-instances/network-instance/afts/next-hops/next-hop/state/ip-address + +* Ensure that the DUT has learnt routes to the IPv4 and IPv6 loopback + addresses of ATE2. It is expected that these prefixes are reachable via 3 + different Next-Hop addresses corresponding to the LAG1, LAG2 and LAG3 + interfaces on ATE2. + +* It is expected that the IS-IS instance in DUT will equally distribute the + traffic received on DUT:LAG1 over the LAG bundles corresponding to + ATE2:LAG1, ATE2:LAG2 and ATE2:LAG3 when the 3 LAG bundles have the same + bandwidth available. + + * Traffic distribution between DUT:LAG2, DUT:LAG3 and DUT:LAG4 is expected + to be ~33% each of the total traffic received on DUT:LAG1. + + * Check for the following paths + + * /network-instances/network-instance/protocols/protocol/isis/global/state/weighted-ecmp, + should be true + + * /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/weighted-ecmp/state/load-balancing-weight, + should be auto + + * /interfaces/interface/state/counters/out-pkts + + * /interfaces/interface/state/counters/in-pkts + +## RT-2.13.2: Unequal distribution of traffic + +* Stop traffic from RT-9.1 and introduce a failure by disabling one of the + member interfaces in ATE2:LAG1. + +* Restart 1024 flows from IPv4 addresses in 100.0.2.0/24 to 100.0.1.0/24 + +* Restart 1024 flows from IPv6 addresses in 2001:db8:64:65::/64 to + 2001:db8:64:64::/64 + + +### Verification + +* It is expected that the IS-IS instance in DUT will unequally distribute the + traffic received from ATE1:LAG1 over the LAG bundles corresponding to + ATE2:LAG1, ATE2:LAG2 and ATE3:LAG3. + + * Traffic on DUT:LAG2 is expected to be ~20% while traffic on DUT:LAG3 and + DUT:LAG4 is expected to be ~40% each of the total traffic received on + DUT:LAG1. If the traffic is not unequally shared between the DUT LAG + bundles towards ATE2 then this test is a failure. + + * Check for the following paths + + * /interfaces/interface/state/counters/out-pkts + + * /interfaces/interface/state/counters/in-pkts + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + ## Config Paths ## + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/peer-group-name: + /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/afi-safi-name: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/afi-name: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/safi-name: + /network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: + /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: + /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 + /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/conditions/match-prefix-set/config/prefix-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result: + value: ACCEPT_ROUTE + /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: + + ## State Paths ## + /network-instances/network-instance/protocols/protocol/isis/global/state/weighted-ecmp: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/weighted-ecmp/state/load-balancing-weight: + /interfaces/interface/state/counters/out-pkts: + /interfaces/interface/state/counters/in-pkts: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` diff --git a/feature/isis/otg_tests/weighted_ecmp_test/metadata.textproto b/feature/isis/otg_tests/weighted_ecmp_test/metadata.textproto new file mode 100644 index 00000000000..947afd21cf8 --- /dev/null +++ b/feature/isis/otg_tests/weighted_ecmp_test/metadata.textproto @@ -0,0 +1,45 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "2beaac46-9b7b-49c4-9bde-62ad530aa5c4" +plan_id: "RT-2.13" +description: "Weighted-ECMP for IS-IS" +testbed: TESTBED_DUT_ATE_8LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + omit_l2_mtu: true + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + missing_isis_interface_afi_safi_enable: true + isis_require_same_l1_metric_with_l2_metric: true + route_policy_under_afi_unsupported: true + static_protocol_name: "STATIC" + rib_wecmp: true + explicit_port_speed: 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 + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_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 new file mode 100644 index 00000000000..64d778f03a5 --- /dev/null +++ b/feature/isis/otg_tests/weighted_ecmp_test/weighted_ecmp_test.go @@ -0,0 +1,767 @@ +package weighted_ecmp_test + +import ( + "fmt" + "math/rand" + "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/netutil" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PLen = 30 + ipv6PLen = 126 + isisInstance = "DEFAULT" + dutAreaAddress = "49.0001" + ateAreaAddress = "49" + dutSysID = "1920.0000.2001" + asn = 64501 + acceptRoutePolicy = "PERMIT-ALL" + trafficPPS = 50000 // Should be 5000000 + trafficv6PPS = 50000 // Should be 5000000 + srcTrafficV4 = "100.0.2.1" + srcTrafficV6 = "2001:db8:64:65::1" + dstTrafficV4 = "100.0.1.1" + dstTrafficV6 = "2001:db8:64:64::1" + v4Count = 254 + v6Count = 1000 // Should be 10000000 + fixedPackets = 1000000 +) + +type aggPortData struct { + dutIPv4 string + ateIPv4 string + dutIPv6 string + ateIPv6 string + ateAggName string + ateAggMAC string + atePort1MAC string + atePort2MAC string + ateISISSysID string + ateLoopbackV4 string + ateLoopbackV6 string +} + +type ipAddr struct { + ip string + prefix uint32 +} + +var ( + agg1 = &aggPortData{ + dutIPv4: "192.0.2.1", + ateIPv4: "192.0.2.2", + dutIPv6: "2001:db8::1", + ateIPv6: "2001:db8::2", + ateAggName: "lag1", + ateAggMAC: "02:00:01:01:01:01", + atePort1MAC: "02:00:01:01:01:02", + atePort2MAC: "02:00:01:01:01:03", + ateISISSysID: "640000000002", + ateLoopbackV4: "192.0.2.17", + ateLoopbackV6: "2001:db8::17", + } + agg2 = &aggPortData{ + dutIPv4: "192.0.2.5", + ateIPv4: "192.0.2.6", + dutIPv6: "2001:db8::5", + ateIPv6: "2001:db8::6", + ateAggName: "lag2", + ateAggMAC: "02:00:01:01:01:04", + atePort1MAC: "02:00:01:01:01:05", + atePort2MAC: "02:00:01:01:01:06", + ateISISSysID: "640000000003", + ateLoopbackV4: "192.0.2.18", + ateLoopbackV6: "2001:db8::18", + } + agg3 = &aggPortData{ + dutIPv4: "192.0.2.9", + ateIPv4: "192.0.2.10", + dutIPv6: "2001:db8::11", + ateIPv6: "2001:db8::12", + ateAggName: "lag3", + ateAggMAC: "02:00:01:01:01:07", + atePort1MAC: "02:00:01:01:01:08", + atePort2MAC: "02:00:01:01:01:09", + ateISISSysID: "640000000004", + ateLoopbackV4: "192.0.2.18", + ateLoopbackV6: "2001:db8::18", + } + agg4 = &aggPortData{ + dutIPv4: "192.0.2.13", + ateIPv4: "192.0.2.14", + dutIPv6: "2001:db8::15", + ateIPv6: "2001:db8::16", + ateAggName: "lag4", + ateAggMAC: "02:00:01:01:01:10", + atePort1MAC: "02:00:01:01:01:11", + atePort2MAC: "02:00:01:01:01:12", + ateISISSysID: "640000000005", + ateLoopbackV4: "192.0.2.18", + ateLoopbackV6: "2001:db8::18", + } + dutLoopback = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "192.0.2.21", + IPv6: "2001:db8::21", + IPv4Len: 32, + IPv6Len: 128, + } + ate1AdvV4 = &ipAddr{ip: "100.0.2.0", prefix: 24} + ate1AdvV6 = &ipAddr{ip: "2001:db8:64:65::0", prefix: 64} + ate2AdvV4 = &ipAddr{ip: "100.0.1.0", prefix: 24} + ate2AdvV6 = &ipAddr{ip: "2001:db8:64:64::0", prefix: 64} + + equalDistributionWeights = []uint64{33, 33, 33} + unequalDistributionWeights = []uint64{20, 40, 40} + + ecmpTolerance = uint64(1) + + lb string + + vendor ondatra.Vendor + + isisLevel = 2 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} +func TestWeightedECMPForISIS(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + aggIDs := configureDUT(t, dut) + vendor = dut.Vendor() + // Enable weighted ECMP in ISIS and set LoadBalancing to Auto + if !deviations.RibWecmp(dut) { + b := &gnmi.SetBatch{} + // isisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + isisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + gnmi.BatchReplace(b, isisPath.Global().WeightedEcmp().Config(), true) + for _, aggID := range aggIDs { + gnmi.BatchReplace(b, isisPath.Interface(aggID).WeightedEcmp().Config(), &oc.NetworkInstance_Protocol_Isis_Interface_WeightedEcmp{ + LoadBalancingWeight: oc.NetworkInstance_Protocol_Isis_Interface_WeightedEcmp_LoadBalancingWeight_Union(oc.WeightedEcmp_LoadBalancingWeight_auto), + }) + } + b.Set(t, dut) + } + if deviations.WecmpAutoUnsupported(dut) { + 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]) + default: + t.Fatalf("Unsupported vendor %s for deviation 'WecmpAutoUnsupported'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, weight) + } + top := configureATE(t, ate) + flows := configureFlows(t, top, ate1AdvV4, ate1AdvV6, ate2AdvV4, ate2AdvV6) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + VerifyISISTelemetry(t, dut, aggIDs, []*aggPortData{agg1, agg2}) + + for _, agg := range []*aggPortData{agg1, agg2} { + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + gnmi.Await(t, dut, bgpPath.Neighbor(agg.ateLoopbackV4).SessionState().State(), 2*time.Minute, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + gnmi.Await(t, dut, bgpPath.Neighbor(agg.ateLoopbackV6).SessionState().State(), 2*time.Minute, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + } + + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + t.Log("Waiting for BGP v4 prefix to be installed") + got, found := gnmi.Watch(t, dut, statePath.Neighbor(agg2.ateLoopbackV4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes().Installed().State(), 120*time.Second, func(val *ygnmi.Value[uint32]) bool { + prefixCount, ok := val.Val() + return ok && prefixCount == 1 + }).Await(t) + if !found { + t.Fatalf("Installed prefixes v4 mismatch: got %v, want %v", got, 1) + } + + t.Log("Waiting for BGP v6 prefix to be installed") + got, found = gnmi.Watch(t, dut, statePath.Neighbor(agg2.ateLoopbackV6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Prefixes().Installed().State(), 120*time.Second, func(val *ygnmi.Value[uint32]) bool { + prefixCount, ok := val.Val() + return ok && prefixCount == 1 + }).Await(t) + if !found { + t.Fatalf("Installed prefixes v6 mismatch: got %v, want %v", got, 1) + } + + startTraffic(t, ate, top) + time.Sleep(time.Minute) + t.Run("Equal_Distribution_Of_Traffic", func(t *testing.T) { + for _, flow := range flows { + loss := otgutils.GetFlowLossPct(t, ate.OTG(), flow.Name(), 20*time.Second) + if got, want := loss, 0.0; got != want { + t.Errorf("Flow %s loss: got %f, want %f", flow.Name(), got, want) + } + } + time.Sleep(time.Minute) + weights := trafficRXWeights(t, ate, []string{agg2.ateAggName, agg3.ateAggName, agg4.ateAggName}) + for idx, weight := range equalDistributionWeights { + if got, want := weights[idx], weight; got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP Percentage for Aggregate Index: %d: got %d, want %d", idx+1, got, want) + } + } + }) + + // Disable ATE2:Port1 + if deviations.ATEPortLinkStateOperationsUnsupported(ate) { + p3 := dut.Port(t, "port3") + gnmi.Replace(t, dut, gnmi.OC().Interface(p3.Name()).Enabled().Config(), false) + t.Logf("Disable ATE2:Port1: %s, %s", p3.Name(), gnmi.OC().Interface(p3.Name()).OperStatus().State()) + } else { + p3 := ate.Port(t, "port3") // ATE:port3 is ATE2:port1 + psa := gosnappi.NewControlState() + psa.Port().Link().SetPortNames([]string{p3.ID()}).SetState(gosnappi.StatePortLinkState.DOWN) + ate.OTG().SetControlState(t, psa) + time.Sleep(10 * time.Second) + defer func() { + psa := gosnappi.NewControlState() + psa.Port().Link().SetPortNames([]string{p3.ID()}).SetState(gosnappi.StatePortLinkState.UP) + ate.OTG().SetControlState(t, psa) + }() + } + p3 := dut.Port(t, "port3") + gnmi.Await(t, dut, gnmi.OC().Interface(p3.Name()).OperStatus().State(), time.Minute*2, oc.Interface_OperStatus_DOWN) + + if deviations.WecmpAutoUnsupported(dut) { + 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]) + default: + t.Fatalf("Unsupported vendor %s for deviation 'WecmpAutoUnsupported'", dut.Vendor()) + } + helpers.GnmiCLIConfig(t, dut, weight) + } + + top.Flows().Clear() + if deviations.ISISLoopbackRequired(dut) { + flows = configureFlows(t, top, ate1AdvV4, ate1AdvV6, ate2AdvV4, ate2AdvV6) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + VerifyISISTelemetry(t, dut, aggIDs, []*aggPortData{agg1, agg2}) + for _, agg := range []*aggPortData{agg1, agg2} { + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + gnmi.Await(t, dut, bgpPath.Neighbor(agg.ateLoopbackV4).SessionState().State(), 3*time.Minute, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + gnmi.Await(t, dut, bgpPath.Neighbor(agg.ateLoopbackV6).SessionState().State(), 3*time.Minute, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + } + } + + startTraffic(t, ate, top) + time.Sleep(time.Minute) + + t.Run("Unequal_Distribution_Of_Traffic", func(t *testing.T) { + for _, flow := range flows { + loss := otgutils.GetFlowLossPct(t, ate.OTG(), flow.Name(), 20*time.Second) + if got, want := loss, 0.0; got != want { + t.Errorf("Flow %s loss: got %f, want %f", flow.Name(), got, want) + } + } + time.Sleep(time.Minute) + weights := trafficRXWeights(t, ate, []string{agg2.ateAggName, agg3.ateAggName, agg4.ateAggName}) + for idx, weight := range unequalDistributionWeights { + if got, want := weights[idx], weight; got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP Percentage for Aggregate Index: %d: got %d, want %d", idx+1, got, want) + } + } + }) +} + +func trafficRXWeights(t *testing.T, ate *ondatra.ATEDevice, aggNames []string) []uint64 { + t.Helper() + var rxs []uint64 + for _, aggName := range aggNames { + metrics := gnmi.Get(t, ate.OTG(), gnmi.OTG().Lag(aggName).State()) + rxs = append(rxs, metrics.GetCounters().GetInFrames()) + } + var total uint64 + for _, rx := range rxs { + total += rx + } + for idx, rx := range rxs { + rxs[idx] = (rx * 100) / total + } + return rxs +} + +func startTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) { + t.Helper() + ate.OTG().StartTraffic(t) + time.Sleep(time.Minute) + ate.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, ate.OTG(), top) + otgutils.LogLAGMetrics(t, ate.OTG(), top) +} + +func randRange(t *testing.T, start, end uint32, count int) []uint32 { + if count > int(end-start) { + t.Fatal("randRange: count greater than end-start.") + } + rand.New(rand.NewSource(time.Now().UnixNano())) + var result []uint32 + for len(result) < count { + diff := end - start + randomValue := rand.Int31n(int32(diff)) + int32(start) + result = append(result, uint32(randomValue)) + } + return result +} + +func configureFlows(t *testing.T, top gosnappi.Config, srcV4, srcV6, dstV4, dstV6 *ipAddr) []gosnappi.Flow { + t.Helper() + dut := ondatra.DUT(t, "dut") + top.Flows().Clear() + fV4 := top.Flows().Add().SetName("flowV4") + if deviations.WeightedEcmpFixedPacketVerification(dut) { + fV4.Duration().FixedPackets().SetPackets(fixedPackets) + } + fV4.Metrics().SetEnable(true) + fV4.TxRx().Device(). + SetTxNames([]string{agg1.ateAggName + ".IPv4"}). + SetRxNames([]string{agg2.ateAggName + ".IPv4", agg3.ateAggName + ".IPv4", agg4.ateAggName + ".IPv4"}) + fV4.Size().SetFixed(1500) + fV4.Rate().SetPps(trafficPPS) + eV4 := fV4.Packet().Add().Ethernet() + eV4.Src().SetValue(agg1.ateAggMAC) + v4 := fV4.Packet().Add().Ipv4() + v4.Src().Increment().SetStart(srcTrafficV4).SetCount(v4Count) + v4.Dst().Increment().SetStart(dstTrafficV4).SetCount(v4Count) + udp := fV4.Packet().Add().Udp() + udp.SrcPort().SetValues(randRange(t, 34525, 65535, 5000)) + udp.DstPort().SetValues(randRange(t, 49152, 65535, 5000)) + + fV6 := top.Flows().Add().SetName("flowV6") + if deviations.WeightedEcmpFixedPacketVerification(dut) { + fV6.Duration().FixedPackets().SetPackets(fixedPackets) + } + fV6.Metrics().SetEnable(true) + fV6.TxRx().Device(). + SetTxNames([]string{agg1.ateAggName + ".IPv6"}). + SetRxNames([]string{agg2.ateAggName + ".IPv6", agg3.ateAggName + ".IPv6", agg4.ateAggName + ".IPv6"}) + fV6.Size().SetFixed(1500) + fV6.Rate().SetPps(trafficv6PPS) + eV6 := fV6.Packet().Add().Ethernet() + eV6.Src().SetValue(agg1.ateAggMAC) + + v6 := fV6.Packet().Add().Ipv6() + v6.Src().Increment().SetStart(srcTrafficV6).SetCount(v6Count) + v6.Dst().Increment().SetStart(dstTrafficV6).SetCount(v6Count) + udpv6 := fV6.Packet().Add().Udp() + udpv6.SrcPort().SetValues(randRange(t, 35521, 65535, 5000)) + udpv6.DstPort().SetValues(randRange(t, 49152, 65535, 5000)) + + return []gosnappi.Flow{fV4, fV6} +} + +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + t.Helper() + top := gosnappi.NewConfig() + pmd100GFRPorts := []string{} + + for aggIdx, a := range []*aggPortData{agg1, agg2, agg3, agg4} { + p1 := ate.Port(t, fmt.Sprintf("port%d", (aggIdx*2)+1)) + p2 := ate.Port(t, fmt.Sprintf("port%d", (aggIdx*2)+2)) + top.Ports().Add().SetName(p1.ID()) + top.Ports().Add().SetName(p2.ID()) + if p1.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, p1.ID()) + } + if p2.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, p2.ID()) + } + + agg := top.Lags().Add().SetName(a.ateAggName) + agg.Protocol().Static().SetLagId(uint32(aggIdx + 1)) + + lagDev := top.Devices().Add().SetName(agg.Name() + ".Dev") + lagEth := lagDev.Ethernets().Add().SetName(agg.Name() + ".Eth").SetMac(a.ateAggMAC) + lagEth.Connection().SetLagName(agg.Name()) + lagEth.Ipv4Addresses().Add().SetName(agg.Name() + ".IPv4").SetAddress(a.ateIPv4).SetGateway(a.dutIPv4).SetPrefix(ipv4PLen) + lagEth.Ipv6Addresses().Add().SetName(agg.Name() + ".IPv6").SetAddress(a.ateIPv6).SetGateway(a.dutIPv6).SetPrefix(ipv6PLen) + lagDev.Ipv4Loopbacks().Add().SetName(agg.Name() + ".Loopback4").SetEthName(lagEth.Name()).SetAddress(a.ateLoopbackV4) + lagDev.Ipv6Loopbacks().Add().SetName(agg.Name() + ".Loopback6").SetEthName(lagEth.Name()).SetAddress(a.ateLoopbackV6) + + agg.Ports().Add().SetPortName(p1.ID()).Ethernet().SetMac(a.atePort1MAC).SetName(a.ateAggName + ".1") + agg.Ports().Add().SetPortName(p2.ID()).Ethernet().SetMac(a.atePort2MAC).SetName(a.ateAggName + ".2") + + configureOTGISIS(t, lagDev, a) + if aggIdx == 0 { + configureOTGBGP(t, lagDev, a, ate1AdvV4, ate1AdvV6) + } else { + configureOTGBGP(t, lagDev, a, ate2AdvV4, ate2AdvV6) + } + } + + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := top.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + return top +} + +func configureOTGBGP(t *testing.T, dev gosnappi.Device, agg *aggPortData, advV4, advV6 *ipAddr) { + t.Helper() + v4 := dev.Ipv4Loopbacks().Items()[0] + v6 := dev.Ipv6Loopbacks().Items()[0] + + iDutBgp := dev.Bgp().SetRouterId(agg.ateIPv4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(v4.Name()).Peers().Add().SetName(agg.ateAggName + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(dutLoopback.IPv4).SetAsNumber(asn).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(false) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(false) + + iDutBgp6Peer := iDutBgp.Ipv6Interfaces().Add().SetIpv6Name(v6.Name()).Peers().Add().SetName(agg.ateAggName + ".BGP6.peer") + iDutBgp6Peer.SetPeerAddress(dutLoopback.IPv6).SetAsNumber(asn).SetAsType(gosnappi.BgpV6PeerAsType.IBGP) + iDutBgp6Peer.Capability().SetIpv4UnicastAddPath(false).SetIpv6UnicastAddPath(true) + iDutBgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(false).SetUnicastIpv6Prefix(true) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(agg.ateAggName + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(agg.ateLoopbackV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(advV4.ip).SetPrefix(advV4.prefix).SetCount(1) + bgpNeti1Bgp4PeerRoutes.AddPath().SetPathId(1) + + bgpNeti1Bgp6PeerRoutes := iDutBgp6Peer.V6Routes().Add().SetName(agg.ateAggName + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(advV6.ip).SetPrefix(advV6.prefix).SetCount(1) + bgpNeti1Bgp6PeerRoutes.AddPath().SetPathId(1) +} + +func configureOTGISIS(t *testing.T, dev gosnappi.Device, agg *aggPortData) { + t.Helper() + isis := dev.Isis().SetSystemId(agg.ateISISSysID).SetName(agg.ateAggName + ".ISIS") + isis.Basic().SetHostname(isis.Name()) + isis.Advanced().SetAreaAddresses([]string{ateAreaAddress}) + + isisInt := isis.Interfaces().Add(). + SetEthName(dev.Ethernets().Items()[0].Name()).SetName(agg.ateAggName + ".ISISInt"). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2).SetMetric(10) + isisInt.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + // configure ISIS loopback interface and advertise them via ISIS. + isisPort2V4 := dev.Isis().V4Routes().Add().SetName(agg.ateAggName + ".ISISV4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(agg.ateLoopbackV4).SetPrefix(32) + isisPort2V6 := dev.Isis().V6Routes().Add().SetName(agg.ateAggName + ".ISISV6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(agg.ateLoopbackV6).SetPrefix(uint32(128)) + +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) []string { + t.Helper() + fptest.ConfigureDefaultNetworkInstance(t, dut) + + configureDUTLoopback(t, dut) + + var aggIDs []string + for aggIdx, a := range []*aggPortData{agg1, agg2, agg3, agg4} { + b := &gnmi.SetBatch{} + d := &oc.Root{} + + aggID := netutil.NextAggregateInterface(t, dut) + aggIDs = append(aggIDs, aggID) + + agg := d.GetOrCreateInterface(aggID) + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_STATIC + agg.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag + agg.Description = ygot.String(a.ateAggName) + if deviations.InterfaceEnabled(dut) { + agg.Enabled = ygot.Bool(true) + } + s := agg.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + a4 := s4.GetOrCreateAddress(a.dutIPv4) + a4.PrefixLength = ygot.Uint8(ipv4PLen) + + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + a6 := s6.GetOrCreateAddress(a.dutIPv6) + a6.PrefixLength = ygot.Uint8(ipv6PLen) + + gnmi.BatchDelete(b, gnmi.OC().Interface(aggID).Aggregation().MinLinks().Config()) + gnmi.BatchReplace(b, gnmi.OC().Interface(aggID).Config(), agg) + + p1 := dut.Port(t, fmt.Sprintf("port%d", (aggIdx*2)+1)) + p2 := dut.Port(t, fmt.Sprintf("port%d", (aggIdx*2)+2)) + for _, port := range []*ondatra.Port{p1, p2} { + gnmi.BatchDelete(b, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) + + i := d.GetOrCreateInterface(port.Name()) + i.Description = ygot.String(fmt.Sprintf("LAG - Member -%s", port.Name())) + e := i.GetOrCreateEthernet() + e.AggregateId = ygot.String(aggID) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + if port.PMD() == ondatra.PMD100GBASEFR && deviations.ExplicitPortSpeed(dut) { + e.AutoNegotiate = ygot.Bool(false) + e.DuplexMode = oc.Ethernet_DuplexMode_FULL + e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } + + gnmi.BatchReplace(b, gnmi.OC().Interface(port.Name()).Config(), i) + } + b.Set(t, dut) + } + // Wait for LAG interfaces to be UP + for _, aggID := range aggIDs { + gnmi.Await(t, dut, gnmi.OC().Interface(aggID).AdminStatus().State(), 60*time.Second, oc.Interface_AdminStatus_UP) + } + configureRoutingPolicy(t, dut) + configureDUTISIS(t, dut, aggIDs) + configureDUTBGP(t, dut) + return aggIDs +} + +func configureRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(acceptRoutePolicy) + stmt, _ := pdef.AppendNewStatement("20") + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinition(acceptRoutePolicy).Config(), pdef) +} + +func 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()) + 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) + } +} + +func configureDUTISIS(t *testing.T, dut *ondatra.DUTDevice, aggIDs []string) { + t.Helper() + + d := &oc.Root{} + dutConfIsisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + + globalISIS := isis.GetOrCreateGlobal() + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + + lspBit := globalISIS.GetOrCreateLspBit().GetOrCreateOverloadBit() + lspBit.SetBit = ygot.Bool(false) + + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + if deviations.ISISLevelEnabled(dut) { + isisLevel2.Enabled = ygot.Bool(true) + } + if deviations.ISISLoopbackRequired(dut) { + gnmi.Update(t, dut, gnmi.OC().Config(), d) + // add loopback interface to ISIS + aggIDs = append(aggIDs, "Loopback0") + } + // Add other ISIS interfaces + for _, aggID := range aggIDs { + isisIntf := isis.GetOrCreateInterface(aggID) + isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(aggID) + isisIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + isisIntf.InterfaceRef = nil + } + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv4.Metric = ygot.Uint32(10) + isisIntfLevelAfiv4.Enabled = ygot.Bool(true) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv6.Metric = ygot.Uint32(10) + isisIntfLevelAfiv6.Enabled = ygot.Bool(true) + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfiv4.Enabled = nil + isisIntfLevelAfiv6.Enabled = nil + } + } + if deviations.ISISLoopbackRequired(dut) { + gnmi.Update(t, dut, dutConfIsisPath.Config(), prot) + } else { + gnmi.Update(t, dut, gnmi.OC().Config(), d) + } +} + +func configureDUTBGP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutLoopback.IPv4) + global.As = ygot.Uint32(asn) + 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) + pgName := "BGP-PEER-GROUP1" + pg := bgp.GetOrCreatePeerGroup(pgName) + pg.PeerAs = ygot.Uint32(asn) + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pg.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{acceptRoutePolicy}) + rpl.SetImportPolicy([]string{acceptRoutePolicy}) + } else { + af4 := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + rpl := af4.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{acceptRoutePolicy}) + rpl.SetImportPolicy([]string{acceptRoutePolicy}) + + af6 := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + rpl = af6.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{acceptRoutePolicy}) + rpl.SetImportPolicy([]string{acceptRoutePolicy}) + } + + for _, a := range []*aggPortData{agg1, agg2, agg3, agg4} { + bgpNbrV4 := bgp.GetOrCreateNeighbor(a.ateLoopbackV4) + bgpNbrV4.PeerGroup = ygot.String(pgName) + bgpNbrV4.PeerAs = ygot.Uint32(asn) + bgpNbrV4.Enabled = ygot.Bool(true) + bgpNbrV4T := bgpNbrV4.GetOrCreateTransport() + localAddressLeafv4 := dutLoopback.IPv4 + if deviations.ISISLoopbackRequired(dut) { + localAddressLeafv4 = lb + } + bgpNbrV4T.LocalAddress = ygot.String(localAddressLeafv4) + af4 := bgpNbrV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbrV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(false) + + bgpNbrV6 := bgp.GetOrCreateNeighbor(a.ateLoopbackV6) + bgpNbrV6.PeerGroup = ygot.String(pgName) + bgpNbrV6.PeerAs = ygot.Uint32(asn) + bgpNbrV6.Enabled = ygot.Bool(true) + bgpNbrV6T := bgpNbrV6.GetOrCreateTransport() + localAddressLeafv6 := dutLoopback.IPv6 + if deviations.ISISLoopbackRequired(dut) { + localAddressLeafv6 = lb + } + bgpNbrV6T.LocalAddress = ygot.String(localAddressLeafv6) + + af4 = bgpNbrV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(false) + af6 = bgpNbrV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + } + + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config(), niProto) + +} +func VerifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntfs []string, loopBacks []*aggPortData) { + 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 { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", dutIntf) + t.Fatal("No IS-IS adjacencies reported.") + } + } + if deviations.ISISLoopbackRequired(dut) { + // verify loopback has been received via ISIS + t.Log("Starting route check") + for _, loopBack := range loopBacks { + batch := gnmi.OCBatch() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + id := formatID(loopBack.ateISISSysID) + iPv4Query := statePath.Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis().Level(uint8(isisLevel)).Lsp(id).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().Prefix(fmt.Sprintf(loopBack.ateLoopbackV4 + "/32")) + iPv6Query := statePath.Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis().Level(uint8(isisLevel)).Lsp(id).Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_IPV6_REACHABILITY).ExtendedIpv4Reachability().Prefix(fmt.Sprintf(loopBack.ateLoopbackV6 + "/128")) + batch.AddPaths(iPv4Query, iPv6Query) + _, ok := gnmi.Watch(t, dut, batch.State(), 5*time.Minute, func(val *ygnmi.Value[*oc.Root]) bool { + _, present := val.Val() + return present + }).Await(t) + if !ok { + t.Fatalf("ISIS did not receive the route loopback %s", loopBack.ateLoopbackV4) + } + } + } +} + +func formatID(input string) string { + part1 := input[:4] + part2 := input[4:8] + part3 := input[8:12] + + formatted := fmt.Sprintf("%s.%s.%s.00-00", part1, part2, part3) + + return formatted +} diff --git a/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md b/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md index 6e0e134a35d..e40267537ed 100644 --- a/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md +++ b/feature/lldp/ate_tests/core_lldp_tlv_population_test/README.md @@ -17,29 +17,31 @@ Determine LLDP advertisement and reception operates correctly. configuration of lldp/interfaces/interface/config/enabled (TRUE or FALSE) on any interface. -## Config Parameter coverage - -* /lldp/config/enabled -* /lldp/interfaces/interface/config/enabled - -## Telemetry Parameter coverage - -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/system-name -* /lldp/interfaces/interface/state/name -* /lldp/state/chassis-id -* /lldp/state/chassis-id-type -* /lldp/state/system-name - -## Protocol/RPC Parameter coverage - -LLDP: - -* /lldp/config/enabled = true -* /lldp/interfaces/interface/config/enabled = true +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. + +```yaml +paths: + ## Config Paths ## + /lldp/config/enabled: + /lldp/interfaces/interface/config/enabled: + + ## State Paths ## + /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id: + /lldp/interfaces/interface/neighbors/neighbor/state/port-id: + /lldp/interfaces/interface/neighbors/neighbor/state/system-name: + /lldp/interfaces/interface/state/name: + /lldp/state/chassis-id: + /lldp/state/chassis-id-type: + /lldp/state/system-name: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + +``` ## Minimum DUT platform requirement diff --git a/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go b/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go index 61532d41ea1..73498534b12 100644 --- a/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go +++ b/feature/lldp/ate_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go @@ -94,19 +94,21 @@ func TestCoreLLDPTLVPopulation(t *testing.T) { func configureNode(t *testing.T, name string, lldpEnabled bool) (*ondatra.DUTDevice, *oc.Lldp) { node := ondatra.DUT(t, name) p := node.Port(t, portName) - lldp := gnmi.OC().Lldp() + d := &oc.Root{} + lldp := d.GetOrCreateLldp() + llint := lldp.GetOrCreateInterface(p.Name()) - gnmi.Replace(t, node, lldp.Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Enabled().Config(), lldpEnabled) if lldpEnabled { - gnmi.Replace(t, node, lldp.Interface(p.Name()).Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Interface(p.Name()).Config(), llint) } if deviations.InterfaceEnabled(node) { gnmi.Replace(t, node, gnmi.OC().Interface(p.Name()).Enabled().Config(), true) } - return node, gnmi.Get(t, node, lldp.Config()) + return node, gnmi.Get(t, node, gnmi.OC().Lldp().Config()) } // verifyNodeConfig verifies the config by comparing against the telemetry state object. diff --git a/feature/lldp/feature.textproto b/feature/lldp/feature.textproto index 4016a95d96f..50ba028ca87 100644 --- a/feature/lldp/feature.textproto +++ b/feature/lldp/feature.textproto @@ -1,3 +1,6 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { name: "lldp" version: 1 diff --git a/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md b/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md index 6e0e134a35d..e40267537ed 100644 --- a/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md +++ b/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md @@ -17,29 +17,31 @@ Determine LLDP advertisement and reception operates correctly. configuration of lldp/interfaces/interface/config/enabled (TRUE or FALSE) on any interface. -## Config Parameter coverage - -* /lldp/config/enabled -* /lldp/interfaces/interface/config/enabled - -## Telemetry Parameter coverage - -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id -* /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id -* /lldp/interfaces/interface/neighbors/neighbor/state/port-id-subtype -* /lldp/interfaces/interface/neighbors/neighbor/state/system-name -* /lldp/interfaces/interface/state/name -* /lldp/state/chassis-id -* /lldp/state/chassis-id-type -* /lldp/state/system-name - -## Protocol/RPC Parameter coverage - -LLDP: - -* /lldp/config/enabled = true -* /lldp/interfaces/interface/config/enabled = true +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. + +```yaml +paths: + ## Config Paths ## + /lldp/config/enabled: + /lldp/interfaces/interface/config/enabled: + + ## State Paths ## + /lldp/interfaces/interface/neighbors/neighbor/state/chassis-id: + /lldp/interfaces/interface/neighbors/neighbor/state/port-id: + /lldp/interfaces/interface/neighbors/neighbor/state/system-name: + /lldp/interfaces/interface/state/name: + /lldp/state/chassis-id: + /lldp/state/chassis-id-type: + /lldp/state/system-name: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + +``` ## Minimum DUT platform requirement diff --git a/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go b/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go index 3eb1dce5f70..e34e217dcd9 100644 --- a/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go +++ b/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go @@ -138,18 +138,21 @@ func TestLLDPDisabled(t *testing.T) { func configureDUT(t *testing.T, name string, lldpEnabled bool) (*ondatra.DUTDevice, *oc.Lldp) { node := ondatra.DUT(t, name) p := node.Port(t, portName) - lldp := gnmi.OC().Lldp() + d := &oc.Root{} + lldp := d.GetOrCreateLldp() + llint := lldp.GetOrCreateInterface(p.Name()) - gnmi.Replace(t, node, lldp.Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Enabled().Config(), lldpEnabled) if lldpEnabled { - gnmi.Replace(t, node, lldp.Interface(p.Name()).Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Interface(p.Name()).Config(), llint) } + if deviations.InterfaceEnabled(node) { gnmi.Replace(t, node, gnmi.OC().Interface(p.Name()).Enabled().Config(), true) } - return node, gnmi.Get(t, node, lldp.Config()) + return node, gnmi.Get(t, node, gnmi.OC().Lldp().Config()) } func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { @@ -159,7 +162,7 @@ func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { srcPort := config.Ports().Add().SetName(portName) srcDev := config.Devices().Add().SetName(ateSrc.Name) srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(srcPort.Name()) + srcEth.Connection().SetPortName(srcPort.Name()) // LLDP configuration. lldp := config.Lldp().Add() diff --git a/feature/localaggregates/feature.textproto b/feature/localaggregates/feature.textproto index 445dadfead2..dc5c76ea7fa 100644 --- a/feature/localaggregates/feature.textproto +++ b/feature/localaggregates/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/mpls/otg_tests/label_block/README.md b/feature/mpls/otg_tests/label_block/README.md new file mode 100644 index 00000000000..39aeb3511f2 --- /dev/null +++ b/feature/mpls/otg_tests/label_block/README.md @@ -0,0 +1,60 @@ +# MPLS-1.1: MPLS label blocks using ISIS + +## Summary + +Define reserved MPLS label blocks: static and MPLS-SR. + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +Topology: ATE1—DUT1 + +On DUT1 configure: + +* ISIS adjacency between ATE1 and DUT1 +* Enable MPLS-SR for ISIS (`/network-instances/network-instance/protocols/protocol/isis/global/segment-routing/config/enabled`) +* 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) + +Verify: + +* Defined blocks are configured on DUT1. +* DUT1 advertises its SRGB and SRLB to ATE1. + + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # configuration + /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: + /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: + # telemetry + /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: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF diff --git a/feature/mpls/otg_tests/static_bgp_nexthop/README.md b/feature/mpls/otg_tests/static_bgp_nexthop/README.md new file mode 100644 index 00000000000..7ed0a06fe99 --- /dev/null +++ b/feature/mpls/otg_tests/static_bgp_nexthop/README.md @@ -0,0 +1,121 @@ +# MPLS-2.2: MPLS forwarding via static LSP to BGP next-hop. + +## Summary + +Validate static LSP functionality with BGP resolved next-hop. This test verifies that the DUT can forward MPLS traffic based on a static LSP that uses a next-hop resolved via BGP. + +## Testbed type + +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Configuration + +1) Create the topology below: + + ``` + | | ---- | ATE Port 2 | ---- [eBGP peer] + [ ATE Port 1 ] ---- | DUT | | | + | | ---- | 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 + +```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. + +**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" +``` + +### MPLS-2.2.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. + +### MPLS-2.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. + +### MPLS-2.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. + +### 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. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + ## Config paths + /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: + /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/index: + + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` \ No newline at end of file diff --git a/feature/mpls/otg_tests/static_lsp/README.md b/feature/mpls/otg_tests/static_lsp/README.md new file mode 100644 index 00000000000..ba2191e8cd1 --- /dev/null +++ b/feature/mpls/otg_tests/static_lsp/README.md @@ -0,0 +1,42 @@ +# TE-9.2: MPLS based forwarding Static LSP + +## Summary + +Validate static lsp functionality. + +## Procedure + +* Create topology ATE1–DUT1-ATE2 +* Enable MPLS forwarding and create egress static LSP to pop the label and forward to ATE2: +* Match incoming label (1000001) +* Set IP next-hop +* Set egress interface +* Set the action to pop label +* Start 2 traffic flows with specified MPLS tags IPv4-MPLS[1000002]-MPLS[1000001] +* Verify that traffic is received at ATE2 with MPLS label [1000001] removed + +## 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/mpls/lsps/static-lsps/static-lsp/egress/config/next-hop: + /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/push-label: + + ## State paths + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/next-hop: + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/incoming-label: + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/push-label: + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/metric: + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/interface: + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/state/subinterface: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` 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 new file mode 100644 index 00000000000..394f59200a1 --- /dev/null +++ b/feature/mpls/sr/otg_tests/isis_node_sid_forward/README.md @@ -0,0 +1,65 @@ +# SR-1.1: Transit forwarding to Node-SID via ISIS + +## Summary + +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) + +## 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. + +* 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 + +Verify that: + +* DUT advertises both prefixes with node-SID to ATE2. + +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. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # srgb definition + /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/protocols/protocol/isis/global/segment-routing/config/enabled: + /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/config/srgb: + # 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: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` +## Required DUT platform + +* FFF diff --git a/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/README.md b/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/README.md index 277eeb24485..65b5c91b444 100644 --- a/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/README.md +++ b/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/README.md @@ -7,31 +7,128 @@ IPv4 and IPv6 packet sizes are sent over them. ## Procedure -* Configure DUT with routed input and output interfaces with an Ethernet MTU of 9216. +* Test environment setup + * Configure DUT with routed input and output interfaces with an Ethernet MTU of 9216. * Test should be executed with two different interface/connectivity profiles: 1) Standalone -- one input and one output port 2) Bundle with four input members and four output members -* Run traffic flows of the following size over IPv4 and IPv6 between ATE ports. - * 1500 Bytes - * 2000 Bytes - * 4000 Bytes - * 9202 Bytes -* Assert ATE reports packets sent and received count are the same, indicating no fragmentation, and - successful transit. -## Config Parameter coverage + An example OpenConfig configuration pushed to the DUT + + ```yaml + + openconfig-interfaces: + - interface: + name: 'tunnel1_if_name' + config: + name: 'tunnel1_if_name' + tunnel: # configures the tunnel parameters + config: + src: 'tunnel1_outer_ip_src' + dst: 'tunnel1_outer_ip_dst' + ttl: 'tunnel1_outer_ttl' + gre-key: 'tunnel1_outer_gre_key' + ipv4: + config: + mtu: 'tunnel1_inner_mtu' + addresses: + - address: + config: + ip: 'tunnel1_interface_ipv4' # For tunnel decap destination and/or route next-hop + prefix-length: 'tunnel1_interface_ipv4_prefixlen' + ipv6: + config: + mtu: 'tunnel1_inner_mtu' + addresses: + - address: + config: + ip: 'tunnel1_interface_ipv6' # For tunnel decap destination and/or route next-hop + prefix-length: 'tunnel1_interface_ipv6_prefixlen' + -* /interfaces/interface[name=*]/config/mtu: -* /interfaces/interface[name=*]/subinterfaces/subinterface[index=*]/ipv4/config/mtu: -* /interfaces/interface[name=*]/subinterfaces/subinterface[index=*]/ipv6/config/mtu: +* MTU-1.3.1: Test with Physical and Bundle interfaces + * Run traffic flows of the following size over IPv4 and IPv6 between ATE ports. + * 1500 Bytes + * 2000 Bytes + * 4000 Bytes + * 9202 Bytes + * Assert ATE reports packets sent and received count are the same, indicating no fragmentation, and + successful transit. -## Telemetry Parameter coverage +* MTU-1.3.2: Test w/ tunnel interfaces and forwarding plane via the physical and bundled interfaces [TODO: https://github.com/openconfig/featureprofiles/issues/3411] + * Configure DUT with a GRE tunnel interface. + * Tunnel interface uses /32 tunnel destination and loopback interface as the source of the tunnel. + * Ensure MTU configured on this tunnel interface is in context to the MTU on the egress physical/bundle interface. It is acceptable if the implementation doesn't support explicit MTU config on tunnel interfaces. Idea is for the tunnel interface to respect the egress physical/bundle interface MTU config. + * Configure a static route for the tunnel end point destination pointing at an exit interface. + * Pick 2 different /24 IPv4 and /64 IPv6 subnets each to emulate destination of payload traffic to be tunneled. Note: This is for the destination prefixes of the flows generated by ATE:Port1. + * Ensure you have static routes in place for the inner header destination to point at the exit interface (Physical/Bundle) + * Run traffic flows of the following size over IPv4 and IPv6. For each scenario, the MTU configured on the physical/bundle interfaces plus the tunnel interface must be the same. + * 1500 Bytes + * 2000 Bytes + * 4000 Bytes + * 9202 Bytes + * 9202 Bytes + * Assert ATE reports packets sent and received count are the same, indicating no fragmentation, and + successful transit. + * Run the above for GUE tunnel interface -No configuration coverage, validates success by checking flow statistics between ATE ports. +## OpenConfig Path and RPC Coverage -## Protocol/RPC Parameter 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. -N/A +```yaml +paths: + # Config Paths + /interfaces/interface/config/mtu: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/mtu: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/mtu: + # TODO: OpenConfig definition required for Tunnel protocol under interfaces/interfaces/interface/tunnel/ as GRE, IP-IP, GUE etc. + /interfaces/interface/tunnel/config/dst: + /interfaces/interface/tunnel/config/src: + /interfaces/interface/tunnel/ipv4/addresses/address/config/ip: + /interfaces/interface/tunnel/ipv4/addresses/address/config/prefix-length: + /interfaces/interface/tunnel/ipv6/addresses/address/config/ip: + /interfaces/interface/tunnel/ipv6/addresses/address/config/prefix-length: + # State Paths + /interfaces/interface/state/counters/in-pkts: + /interfaces/interface/state/counters/in-octets: + /interfaces/interface/state/counters/out-pkts: + /interfaces/interface/state/counters/out-octets: + /interfaces/interface/state/counters/in-errors: + /interfaces/interface/state/counters/in-unicast-pkts: + /interfaces/interface/state/counters/in-discards: + /interfaces/interface/state/counters/out-errors: + /interfaces/interface/state/counters/out-unicast-pkts: + /interfaces/interface/state/counters/out-discards: + /interfaces/interface/tunnel/ipv4/state/counters/in-pkts: + /interfaces/interface/tunnel/ipv4/state/counters/in-octets: + /interfaces/interface/tunnel/ipv4/state/counters/out-pkts: + /interfaces/interface/tunnel/ipv4/state/counters/out-octets: + /interfaces/interface/tunnel/ipv4/state/counters/in-error-pkts: + /interfaces/interface/tunnel/ipv4/state/counters/in-forwarded-pkts: + /interfaces/interface/tunnel/ipv4/state/counters/in-forwarded-octets: + /interfaces/interface/tunnel/ipv4/state/counters/in-discarded-pkts: + /interfaces/interface/tunnel/ipv4/state/counters/out-error-pkts: + /interfaces/interface/tunnel/ipv4/state/counters/out-forwarded-pkts: + /interfaces/interface/tunnel/ipv4/state/counters/out-forwarded-octets: + /interfaces/interface/tunnel/ipv4/state/counters/out-discarded-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/in-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/in-octets: + /interfaces/interface/tunnel/ipv6/state/counters/out-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/out-octets: + /interfaces/interface/tunnel/ipv6/state/counters/in-error-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/in-forwarded-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/in-forwarded-octets: + /interfaces/interface/tunnel/ipv6/state/counters/in-discarded-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/out-error-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/out-forwarded-pkts: + /interfaces/interface/tunnel/ipv6/state/counters/out-forwarded-octets: + /interfaces/interface/tunnel/ipv6/state/counters/out-discarded-pkts: + +rpcs: + gnmi: + gNMI.Set: +``` ## Minimum DUT platform requirement diff --git a/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/large_ip_packet_transmission_test.go b/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/large_ip_packet_transmission_test.go index e20b0058b2b..99580892350 100644 --- a/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/large_ip_packet_transmission_test.go +++ b/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/large_ip_packet_transmission_test.go @@ -17,9 +17,6 @@ import ( "testing" "time" - otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" - "github.com/openconfig/ygnmi/ygnmi" - "github.com/open-traffic-generator/snappi/gosnappi" "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" @@ -29,7 +26,7 @@ import ( "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ondatra/netutil" - ondatraotg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ondatra/otg" "github.com/openconfig/ygot/ygot" ) @@ -42,7 +39,7 @@ const ( ipv4PrefixLen = 30 ipv6 = "IPv6" ipv6PrefixLen = 126 - mtu = 9_216 + mtu = 9216 trafficRunDuration = 15 * time.Second trafficStopWaitDuration = 10 * time.Second acceptablePacketSizeDelta = 0.5 @@ -51,8 +48,8 @@ const ( ) var ( - dutPort1 = &attrs.Attributes{ - Name: "dutPort1", + dutSrc = &attrs.Attributes{ + Name: "dutSrc", MAC: "00:12:01:01:01:01", IPv4: "192.0.2.1", IPv6: "2001:db8::1", @@ -61,8 +58,8 @@ var ( MTU: mtu, } - dutPort2 = &attrs.Attributes{ - Name: "dutPort2", + dutDst = &attrs.Attributes{ + Name: "dutDst", MAC: "00:12:02:01:01:01", IPv4: "192.0.2.5", IPv6: "2001:db8::5", @@ -71,8 +68,8 @@ var ( MTU: mtu, } - atePort1 = &attrs.Attributes{ - Name: "atePort1", + ateSrc = &attrs.Attributes{ + Name: "ateSrc", MAC: "02:00:01:01:01:01", IPv4: "192.0.2.2", IPv6: "2001:db8::2", @@ -81,8 +78,8 @@ var ( MTU: mtu, } - atePort2 = &attrs.Attributes{ - Name: "atePort2", + ateDst = &attrs.Attributes{ + Name: "ateDst", MAC: "02:00:02:01:01:01", IPv4: "192.0.2.6", IPv6: "2001:db8::6", @@ -92,13 +89,13 @@ var ( } dutPorts = map[string]*attrs.Attributes{ - "port1": dutPort1, - "port2": dutPort2, + "port1": dutSrc, + "port2": dutDst, } atePorts = map[string]*attrs.Attributes{ - "port1": atePort1, - "port2": atePort2, + "port1": ateSrc, + "port2": ateDst, } testCases = []testDefinition{ @@ -130,67 +127,44 @@ var ( } ) -type testData struct { - flowProto string - ate *ondatra.ATEDevice - otg *ondatraotg.OTG - otgConfig gosnappi.Config - srcAtePort *attrs.Attributes - dstAtePort *attrs.Attributes -} - -func (d *testData) waitInterface(t *testing.T) { - otgutils.WaitForARP(t, d.otg, d.otgConfig, d.flowProto) -} - -func (d *testData) waitBundle(t *testing.T) { - time.Sleep(5 * time.Second) - - for _, bundleName := range []string{d.srcAtePort.Name, d.dstAtePort.Name} { - gnmi.Watch( - t, d.otg, gnmi.OTG().Lag(bundleName).OperStatus().State(), - time.Minute, - func(val *ygnmi.Value[otgtelemetry.E_Lag_OperStatus]) bool { - state, present := val.Val() - return present && state.String() == "UP" - }, - ).Await(t) - } -} - type testDefinition struct { name string desc string flowSize uint32 } -type trafficFlowParams struct { - name string - proto string - size uint32 +type testData struct { + flowProto string + otg *otg.OTG + dut *ondatra.DUTDevice + otgConfig gosnappi.Config + dutLAGNames []string +} + +func (d *testData) waitInterface(t *testing.T) { + otgutils.WaitForARP(t, d.otg, d.otgConfig, d.flowProto) } -func createFlow( - srcAtePort, dstAtePort *attrs.Attributes, - flowParams trafficFlowParams, -) gosnappi.Flow { - flow := gosnappi.NewFlow().SetName(flowParams.name) +func createFlow(flowName string, flowSize uint32, ipv string) gosnappi.Flow { + flow := gosnappi.NewFlow().SetName(flowName) flow.Metrics().SetEnable(true) flow.TxRx().Device(). - SetTxNames([]string{fmt.Sprintf("%s.%s", srcAtePort.Name, flowParams.proto)}). - SetRxNames([]string{fmt.Sprintf("%s.%s", dstAtePort.Name, flowParams.proto)}) - flow.Packet().Add().Ethernet() - flow.SetSize(gosnappi.NewFlowSize().SetFixed(flowParams.size)) + SetTxNames([]string{fmt.Sprintf("%s.%s", ateSrc.Name, ipv)}). + SetRxNames([]string{fmt.Sprintf("%s.%s", ateDst.Name, ipv)}) + ethHdr := flow.Packet().Add().Ethernet() + ethHdr.Src().SetValue(ateSrc.MAC) + flow.SetSize(gosnappi.NewFlowSize().SetFixed(flowSize)) - switch flowParams.proto { + switch ipv { case ipv4: v4 := flow.Packet().Add().Ipv4() - v4.Src().SetValue(srcAtePort.IPv4) - v4.Dst().SetValue(dstAtePort.IPv4) + v4.Src().SetValue(ateSrc.IPv4) + v4.Dst().SetValue(ateDst.IPv4) + v4.DontFragment().SetValue(1) case ipv6: v6 := flow.Packet().Add().Ipv6() - v6.Src().SetValue(srcAtePort.IPv6) - v6.Dst().SetValue(dstAtePort.IPv6) + v6.Src().SetValue(ateSrc.IPv6) + v6.Dst().SetValue(ateDst.IPv6) } flow.EgressPacket().Add().Ethernet() @@ -198,31 +172,15 @@ func createFlow( return flow } -func runTest( - t *testing.T, - tt testDefinition, - td testData, - waitF func(t *testing.T), -) { - t.Logf("Name: %s", tt.name) - t.Logf("Description: %s", tt.desc) - - flowParams := createFlow( - td.srcAtePort, - td.dstAtePort, - trafficFlowParams{ - name: tt.name, - proto: td.flowProto, - size: tt.flowSize, - }, - ) +func runTest(t *testing.T, tt testDefinition, td testData, waitF func(t *testing.T)) { + t.Logf("Name: %s, Description: %s", tt.name, tt.desc) + flowParams := createFlow(tt.name, tt.flowSize, td.flowProto) td.otgConfig.Flows().Clear() td.otgConfig.Flows().Append(flowParams) - td.otg.PushConfig(t, td.otgConfig) + time.Sleep(time.Second * 30) td.otg.StartProtocols(t) - waitF(t) td.otg.StartTraffic(t) @@ -231,7 +189,7 @@ func runTest( td.otg.StopTraffic(t) time.Sleep(trafficStopWaitDuration) - otgutils.LogFlowMetrics(t, td.ate.OTG(), td.otgConfig) + otgutils.LogFlowMetrics(t, td.otg, td.otgConfig) flow := gnmi.OTG().Flow(tt.name) flowCounters := flow.Counters() @@ -275,8 +233,7 @@ func runTest( } avgPacketSize := uint32(inOctets / inPkts) - packetSizeDelta := float32(avgPacketSize-tt.flowSize) / - (float32(avgPacketSize+tt.flowSize) / 2) * 100 + packetSizeDelta := float32(avgPacketSize-tt.flowSize) / (float32(avgPacketSize+tt.flowSize) / 2) * 100 if packetSizeDelta > acceptablePacketSizeDelta { t.Errorf( @@ -294,12 +251,10 @@ func configureDUTPort( port *ondatra.Port, portAttrs *attrs.Attributes, ) { - gnmiOCRoot := gnmi.OC() - gnmi.Replace( t, dut, - gnmiOCRoot.Interface(port.Name()).Config(), + gnmi.OC().Interface(port.Name()).Config(), portAttrs.NewOCInterface(port.Name(), dut), ) @@ -308,61 +263,48 @@ func configureDUTPort( } if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance( - t, dut, port.Name(), deviations.DefaultNetworkInstance(dut), subInterfaceIndex, - ) + fptest.AssignToNetworkInstance(t, dut, port.Name(), deviations.DefaultNetworkInstance(dut), subInterfaceIndex) } } func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { for portName, portAttrs := range dutPorts { port := dut.Port(t, portName) - configureDUTPort(t, dut, port, portAttrs) - verifyDUTPort(t, dut, port.Name()) } } func verifyDUTPort(t *testing.T, dut *ondatra.DUTDevice, portName string) { - configuredInterfaceMtu := gnmi.Get(t, dut, gnmi.OC().Interface(portName).Mtu().State()) - configuredIpv4SubInterfaceMtu := gnmi.Get( - t, - dut, - gnmi.OC().Interface(portName).Subinterface(subInterfaceIndex).Ipv4().Mtu().Config(), - ) - configuredIpv6SubInterfaceMtu := gnmi.Get( - t, - dut, - gnmi.OC().Interface(portName).Subinterface(subInterfaceIndex).Ipv6().Mtu().Config(), - ) + switch { + case deviations.OmitL2MTU(dut): + configuredIpv4SubInterfaceMtu := gnmi.Get(t, dut, gnmi.OC().Interface(portName).Subinterface(subInterfaceIndex).Ipv4().Mtu().State()) + configuredIpv6SubInterfaceMtu := gnmi.Get(t, dut, gnmi.OC().Interface(portName).Subinterface(subInterfaceIndex).Ipv6().Mtu().State()) + expectedSuBInterfaceMtu := mtu - expectedInterfaceMtu := mtu - expectedSuBInterfaceMtu := mtu - - if !deviations.OmitL2MTU(dut) { - expectedInterfaceMtu = expectedInterfaceMtu + 14 - } - - if int(configuredInterfaceMtu) != expectedInterfaceMtu { - t.Errorf( - "dut %s configured mtu is incorrect, got: %d, want: %d", - dut.Name(), configuredInterfaceMtu, expectedInterfaceMtu, - ) - } + if int(configuredIpv4SubInterfaceMtu) != expectedSuBInterfaceMtu { + t.Errorf( + "dut %s configured mtu is incorrect, got: %d, want: %d", + dut.Name(), configuredIpv4SubInterfaceMtu, expectedSuBInterfaceMtu, + ) + } - if int(configuredIpv4SubInterfaceMtu) != expectedSuBInterfaceMtu { - t.Errorf( - "dut %s configured mtu is incorrect, got: %d, want: %d", - dut.Name(), configuredIpv4SubInterfaceMtu, expectedSuBInterfaceMtu, - ) - } + if int(configuredIpv6SubInterfaceMtu) != expectedSuBInterfaceMtu { + t.Errorf( + "dut %s configured mtu is incorrect, got: %d, want: %d", + dut.Name(), configuredIpv6SubInterfaceMtu, expectedSuBInterfaceMtu, + ) + } + default: + configuredInterfaceMtu := gnmi.Get(t, dut, gnmi.OC().Interface(portName).Mtu().State()) + expectedInterfaceMtu := mtu + 14 - if int(configuredIpv6SubInterfaceMtu) != expectedSuBInterfaceMtu { - t.Errorf( - "dut %s configured mtu is incorrect, got: %d, want: %d", - dut.Name(), configuredIpv6SubInterfaceMtu, expectedSuBInterfaceMtu, - ) + if int(configuredInterfaceMtu) != expectedInterfaceMtu { + t.Errorf( + "dut %s configured mtu is incorrect, got: %d, want: %d", + dut.Name(), configuredInterfaceMtu, expectedInterfaceMtu, + ) + } } } @@ -371,9 +313,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { for portName, portAttrs := range atePorts { port := ate.Port(t, portName) - dutPort := dutPorts[portName] - portAttrs.AddToOTG(otgConfig, port, dutPort) } @@ -384,21 +324,17 @@ func TestLargeIPPacketTransmission(t *testing.T) { dut := ondatra.DUT(t, "dut") ate := ondatra.ATE(t, "ate") otg := ate.OTG() - configureDUT(t, dut) - otgConfig := configureATE(t, ate) t.Cleanup(func() { + deleteBatch := &gnmi.SetBatch{} if deviations.ExplicitInterfaceInDefaultVRF(dut) { - netInst := &oc.NetworkInstance{ - Name: ygot.String(deviations.DefaultNetworkInstance(dut)), - } + netInst := &oc.NetworkInstance{Name: ygot.String(deviations.DefaultNetworkInstance(dut))} for portName := range dutPorts { - gnmi.Delete( - t, - dut, + gnmi.BatchDelete( + deleteBatch, gnmi.OC(). NetworkInstance(*netInst.Name). Interface(fmt.Sprintf("%s.%d", dut.Port(t, portName).Name(), subInterfaceIndex)). @@ -408,52 +344,45 @@ func TestLargeIPPacketTransmission(t *testing.T) { } for portName := range dutPorts { - gnmi.Delete(t, dut, gnmi.OC().Interface(dut.Port(t, portName).Name()).Mtu().Config()) - gnmi.Delete( - t, - dut, + gnmi.BatchDelete( + deleteBatch, gnmi.OC(). Interface(dut.Port(t, portName).Name()). Subinterface(subInterfaceIndex). Config(), ) + gnmi.BatchDelete(deleteBatch, gnmi.OC().Interface(dut.Port(t, portName).Name()).Mtu().Config()) } + deleteBatch.Set(t, dut) }) for _, tt := range testCases { for _, flowProto := range []string{ipv4, ipv6} { td := testData{ - flowProto: flowProto, - ate: ate, - otg: otg, - otgConfig: otgConfig, - srcAtePort: atePort1, - dstAtePort: atePort2, + flowProto: flowProto, + otg: otg, + otgConfig: otgConfig, } t.Run(fmt.Sprintf("%s-%s", tt.name, flowProto), func(t *testing.T) { - runTest( - t, - tt, - td, - td.waitInterface, - ) + runTest(t, tt, td, td.waitInterface) }) } } } -func configureDUTBundle( - t *testing.T, dut *ondatra.DUTDevice, lag *attrs.Attributes, bundleMembers []*ondatra.Port, -) string { +func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, lag *attrs.Attributes, bundleMembers []*ondatra.Port) string { bundleID := netutil.NextAggregateInterface(t, dut) - - gnmiOCRoot := gnmi.OC() ocRoot := &oc.Root{} - if deviations.AggregateAtomicUpdate(dut) { + deleteBatch := &gnmi.SetBatch{} + gnmi.BatchDelete(deleteBatch, gnmi.OC().Interface(bundleID).Aggregation().MinLinks().Config()) + for _, port := range bundleMembers { + gnmi.BatchDelete(deleteBatch, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) + } + deleteBatch.Set(t, dut) bundle := ocRoot.GetOrCreateInterface(bundleID) - bundle.GetOrCreateAggregation() + bundle.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_STATIC bundle.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag for _, port := range bundleMembers { @@ -470,29 +399,11 @@ func configureDUTBundle( } } - gnmi.Update( - t, - dut, - gnmiOCRoot.Config(), - ocRoot, - ) - } - - lacp := &oc.Lacp_Interface{ - Name: ygot.String(bundleID), - LacpMode: oc.Lacp_LacpActivityType_UNSET, + gnmi.Update(t, dut, gnmi.OC().Config(), ocRoot) } - lacpPath := gnmiOCRoot.Lacp().Interface(bundleID) - gnmi.Replace(t, dut, lacpPath.Config(), lacp) - agg := &oc.Interface{ - Name: ygot.String(bundleID), - Mtu: ygot.Uint16(mtu), - Type: oc.IETFInterfaces_InterfaceType_ieee8023adLag, - } - if !deviations.OmitL2MTU(dut) { - agg.Mtu = ygot.Uint16(mtu + 14) - } + agg := ocRoot.GetOrCreateInterface(bundleID) + agg.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag agg.Description = ygot.String(fmt.Sprintf("dutLag-%s", bundleID)) if deviations.InterfaceEnabled(dut) { agg.Enabled = ygot.Bool(true) @@ -500,7 +411,6 @@ func configureDUTBundle( subInterface := agg.GetOrCreateSubinterface(subInterfaceIndex) v4SubInterface := subInterface.GetOrCreateIpv4() - v4SubInterface.SetMtu(mtu) if deviations.InterfaceEnabled(dut) { v4SubInterface.Enabled = ygot.Bool(true) } @@ -508,7 +418,6 @@ func configureDUTBundle( v4Address.PrefixLength = ygot.Uint8(ipv4PrefixLen) v6SubInterface := subInterface.GetOrCreateIpv6() - v6SubInterface.SetMtu(mtu) if deviations.InterfaceEnabled(dut) { v6SubInterface.Enabled = ygot.Bool(true) } @@ -518,34 +427,37 @@ func configureDUTBundle( intfAgg := agg.GetOrCreateAggregation() intfAgg.LagType = oc.IfAggregate_AggregationType_STATIC - aggPath := gnmiOCRoot.Interface(bundleID) + switch { + case deviations.OmitL2MTU(dut): + v4SubInterface.SetMtu(mtu) + v6SubInterface.SetMtu(mtu) + default: + agg.Mtu = ygot.Uint16(mtu + 14) + } + + aggPath := gnmi.OC().Interface(bundleID) gnmi.Replace(t, dut, aggPath.Config(), agg) if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance( - t, dut, bundleID, deviations.DefaultNetworkInstance(dut), subInterfaceIndex, - ) + fptest.AssignToNetworkInstance(t, dut, bundleID, deviations.DefaultNetworkInstance(dut), subInterfaceIndex) } - // if we didnt setup the ports in the lag before - if !deviations.AggregateAtomicUpdate(dut) { - for _, port := range bundleMembers { - intf := &oc.Interface{Name: ygot.String(port.Name())} - intf.GetOrCreateEthernet().AggregateId = ygot.String(bundleID) - intf.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + for _, port := range bundleMembers { + intf := &oc.Interface{Name: ygot.String(port.Name())} + intf.GetOrCreateEthernet().AggregateId = ygot.String(bundleID) + intf.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd - if deviations.InterfaceEnabled(dut) { - intf.Enabled = ygot.Bool(true) - } + if deviations.InterfaceEnabled(dut) { + intf.Enabled = ygot.Bool(true) + } - if deviations.ExplicitPortSpeed(dut) { - fptest.SetPortSpeed(t, port) - } + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, port) + } - intfPath := gnmiOCRoot.Interface(port.Name()) + intfPath := gnmi.OC().Interface(port.Name()) - gnmi.Replace(t, dut, intfPath.Config(), intf) - } + gnmi.Replace(t, dut, intfPath.Config(), intf) } verifyDUTPort(t, dut, *agg.Name) @@ -558,21 +470,8 @@ func configureATEBundles( bundleMemberCount int, ) gosnappi.Config { otgConfig := gosnappi.NewConfig() - - otgConfig = configureATEBundle( - otgConfig, - atePort1, - dutPort1, - allAtePorts[0:bundleMemberCount], - 1, - ) - otgConfig = configureATEBundle( - otgConfig, - atePort2, - dutPort2, - allAtePorts[bundleMemberCount:2*bundleMemberCount], - 2, - ) + configureATEBundle(otgConfig, ateSrc, dutSrc, allAtePorts[0:bundleMemberCount], 1) + configureATEBundle(otgConfig, ateDst, dutDst, allAtePorts[bundleMemberCount:2*bundleMemberCount], 2) portNames := make([]string, len(allAtePorts)) for idx, port := range allAtePorts { @@ -582,10 +481,7 @@ func configureATEBundles( // note that it seems max in otg containers is 9000 so bundle tests > 1500 bytes will fail, // for whatever reason individual ports work just fine > 1500 bytes though! also, physical gear // seems to work just fine as well, so we'll set this to the max we can for kne tests. - layer1 := otgConfig.Layer1().Add(). - SetName("layerOne"). - SetPortNames(portNames). - SetMtu(9000) + layer1 := otgConfig.Layer1().Add().SetName("layerOne").SetPortNames(portNames).SetMtu(9000) // set the l1 speed for the otg config based on speed setting in testbed, fallthrough case is // do nothing which defaults to 10g @@ -610,7 +506,7 @@ func configureATEBundle( dutLag *attrs.Attributes, bundleMembers []*ondatra.Port, bundleID uint32, -) gosnappi.Config { +) { agg := otgConfig.Lags().Add().SetName(ateLag.Name) agg.Protocol().Static().SetLagId(bundleID) @@ -643,8 +539,6 @@ func configureATEBundle( SetAddress(ateLag.IPv6). SetGateway(dutLag.IPv6). SetPrefix(ipv6PrefixLen) - - return otgConfig } // sortPorts sorts the ports by the testbed port ID. @@ -662,7 +556,6 @@ func TestLargeIPPacketTransmissionBundle(t *testing.T) { allDutPorts := sortPorts(dut.Ports()) allAtePorts := sortPorts(ate.Ports()) - if len(allDutPorts) < 2 { t.Fatalf("testbed requires at least two dut ports, but only has %d", len(allDutPorts)) } @@ -683,16 +576,14 @@ func TestLargeIPPacketTransmissionBundle(t *testing.T) { allDutBundleMembers = append(allDutBundleMembers, lagOneDutBundleMembers...) allDutBundleMembers = append(allDutBundleMembers, lagTwoDutBundleMembers...) - lagOne := configureDUTBundle(t, dut, dutPort1, lagOneDutBundleMembers) - lagTwo := configureDUTBundle(t, dut, dutPort2, lagTwoDutBundleMembers) + lagOne := configureDUTBundle(t, dut, dutSrc, lagOneDutBundleMembers) + lagTwo := configureDUTBundle(t, dut, dutDst, lagTwoDutBundleMembers) otgConfig := configureATEBundles(allAtePorts, bundleMemberCount) t.Cleanup(func() { if deviations.ExplicitInterfaceInDefaultVRF(dut) { - netInst := &oc.NetworkInstance{ - Name: ygot.String(deviations.DefaultNetworkInstance(dut)), - } + netInst := &oc.NetworkInstance{Name: ygot.String(deviations.DefaultNetworkInstance(dut))} for _, lag := range []string{lagOne, lagTwo} { gnmi.Delete( @@ -708,32 +599,22 @@ func TestLargeIPPacketTransmissionBundle(t *testing.T) { for _, port := range allDutBundleMembers { gnmi.Delete(t, dut, gnmi.OC().Interface(port.Name()).Mtu().Config()) - gnmi.Delete( - t, - dut, - gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config(), - ) + gnmi.Delete(t, dut, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) } }) for _, tt := range testCases { for _, flowProto := range []string{ipv4, ipv6} { td := testData{ - flowProto: flowProto, - ate: ate, - otg: otg, - otgConfig: otgConfig, - srcAtePort: atePort1, - dstAtePort: atePort2, + flowProto: flowProto, + otg: otg, + dut: dut, + otgConfig: otgConfig, + dutLAGNames: []string{lagOne, lagTwo}, } t.Run(fmt.Sprintf("%s-%s", tt.name, flowProto), func(t *testing.T) { - runTest( - t, - tt, - td, - td.waitBundle, - ) + runTest(t, tt, td, td.waitInterface) }) } } diff --git a/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/metadata.textproto b/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/metadata.textproto index 475fa7e79a0..400605a46de 100644 --- a/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/metadata.textproto +++ b/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission/metadata.textproto @@ -14,5 +14,17 @@ platform_exceptions: { explicit_interface_in_default_vrf: true aggregate_atomic_update: true interface_enabled: true + omit_l2_mtu: true } } +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + aggregate_atomic_update: true + interface_enabled: true + default_network_instance: "default" + } +} \ No newline at end of file diff --git a/feature/mtu/otg_tests/pmtu_handing/README.md b/feature/mtu/otg_tests/pmtu_handing/README.md new file mode 100644 index 00000000000..0a09af0543a --- /dev/null +++ b/feature/mtu/otg_tests/pmtu_handing/README.md @@ -0,0 +1,83 @@ +# MTU-1.5: Path MTU handing + +## Summary + +This tests ensures that DUT generates ICMP "Fragmentation Needed and Don't Fragment was Set" for packets exceeding egress interface MTU. + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Test environment setup + + ``` + | | | | + [ ATE Port 1 ] ---- | DUT | ---- | ATE Port 2 | + | | | | + ``` + + +### Configuration + +* Configure DUT with routed ports on DUT. +* Configure Ethernet MTU of 9216 on DUT port 1 and Ethernet MTU of 1514 on DUT port 2. +* Configure static routes on DUT for IPV4-DST and IPV6-DST to ATE Port 2 + + +### MTU-1.5.1 IPv4 Path MTU + +Run traffic flows to IPV4-DST with the sizes at 50% linerate for 30 seconds: +- 2000 Bytes +- 4000 Bytes +- 9000 Bytes + +Verify: +* Ensure that ATE Port-1 receives ICMP type-3, code-4 for packet of every flow sent. +* DUT pipeline counters report fragment packet discards. +* Verify the amount of traffic forwarded and dropped to the control-plane and compare to the amount of packets sent. Default rate-limiting of fragment + traffic is permitted. +* Verify low CPU (<20%) utilization on control plane. + +### MTU-1.5.2 IPv6 Path MTU + +Run traffic flows to IPV6-DST with the sizes at 50% linerate for 30 seconds: +- 2000 Bytes +- 4000 Bytes +- 9000 Bytes + +* Ensure that ATE Port-1 receives ICMPv6 type-2 code-0 for packet of every flow sent. +* Verify that DUT pipeline counters report fragment packet discards. +* Verify the amount of traffic forwarded and dropped to the control-plane and compare to the amount of packets sent. Default rate-limiting of fragment + traffic is permitted. +* Verify low CPU (<20%) utilization on control plane. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # tunnel interfaces + /interfaces/interface/config/mtu: + # telemetry + /components/component/integrated-circuit/pipeline-counters/drop/state/packet-processing-aggregate: + platform_type: [ "INTEGRATED_CIRCUIT" ] + /components/component/integrated-circuit/pipeline-counters/drop/lookup-block/state/fragment-total-drops: + platform_type: [ "INTEGRATED_CIRCUIT" ] + + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +The device may support some vendor proprietary leafs to count MTU exceeded packets which are dropped due to control plane policing rules in the `components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor` tree. +Implementation should add code with a switch statement to expose these counters, if they exist. + +## Required DUT platform + +* FFF diff --git a/feature/networkinstance/feature.textproto b/feature/networkinstance/feature.textproto index 98cef284592..10e9b7da93c 100644 --- a/feature/networkinstance/feature.textproto +++ b/feature/networkinstance/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/networkinstance/otg_tests/defaults_test/README.md b/feature/networkinstance/otg_tests/defaults_test/README.md index c6882500284..a5ac736175e 100644 --- a/feature/networkinstance/otg_tests/defaults_test/README.md +++ b/feature/networkinstance/otg_tests/defaults_test/README.md @@ -1,3 +1,26 @@ # OC-1.2: Default Address Families TODO(robshakir): fill in test plan from code already written. + +## Description + +This test verifies that the IPv4 and IPv6 address families are enabled within a network instance by default. + +## Test Procedure + +* Configure an ATE with port1 connected to DUT port1, and port2 connected to DUT port2. +* Configure the DUT to have: + * these interfaces within the `DEFAULT` network instance and validate that traffic can be forwarded between ATE port1 and ATE port2. + * these interfaces within a non-default `L3VRF` and validate that traffic can be forwarded between ATE port1 and ATE port2. + +## 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: +rpcs: + gnmi: + gNMI.Subscribe: +``` + diff --git a/feature/networkinstance/otg_tests/defaults_test/defaults_test.go b/feature/networkinstance/otg_tests/defaults_test/defaults_test.go index 9c1aa24c511..0e7355e012e 100644 --- a/feature/networkinstance/otg_tests/defaults_test/defaults_test.go +++ b/feature/networkinstance/otg_tests/defaults_test/defaults_test.go @@ -18,6 +18,7 @@ package ni_address_families_test import ( "fmt" + "slices" "testing" "time" @@ -103,6 +104,7 @@ var ( IPv6Len: 64, MAC: "02:00:02:01:01:01", } + kneDeviceModelList = []string{"ncptx", "ceos", "srlinux", "xrd"} ) // TestDefaultAddressFamilies verifies that both IPv4 and IPv6 are enabled by default without a need for additional @@ -181,6 +183,14 @@ func TestDefaultAddressFamilies(t *testing.T) { otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + // https://github.com/openconfig/featureprofiles/issues/3410 + // Below code will be removed once ixia issue is fixed. + if slices.Contains(kneDeviceModelList, dut.Model()) { + ate.OTG().StartTraffic(t) + time.Sleep(15 * time.Second) + ate.OTG().StopTraffic(t) + } + ate.OTG().StartTraffic(t) time.Sleep(15 * time.Second) ate.OTG().StopTraffic(t) diff --git a/feature/experimental/p4rt/README.md b/feature/p4rt/README.md similarity index 90% rename from feature/experimental/p4rt/README.md rename to feature/p4rt/README.md index f673b15bac5..3acc900e9fc 100644 --- a/feature/experimental/p4rt/README.md +++ b/feature/p4rt/README.md @@ -51,6 +51,10 @@ This document specifies the requirements for p4rt test implementation. implementation already exists in `p4rtutils` library: `p4rtutils.P4RTNodesByPort()`. -7. If P4RT Node Names cannot be resolved by walking the Components tree, use - deviation flag `--deviation_explicit_p4rt_node_component` and pass the node - names through args `--arg_p4rt_node_name_1`, `--arg_p4rt_node_name_2`. +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/p4rt/otg_tests/base_p4rt/README.md b/feature/p4rt/otg_tests/base_p4rt/README.md similarity index 73% rename from feature/experimental/p4rt/otg_tests/base_p4rt/README.md rename to feature/p4rt/otg_tests/base_p4rt/README.md index e4ca8068fb1..e690e261459 100644 --- a/feature/experimental/p4rt/otg_tests/base_p4rt/README.md +++ b/feature/p4rt/otg_tests/base_p4rt/README.md @@ -24,19 +24,16 @@ Validate that the P4RT server can accept basic configuration and Read/Write RPCs * Repeat the same steps for another FAP and verify the Table entries. - -## Config Parameter Coverage - -* /components/component/integrated-circuit/config/node-id -* /interfaces/interface/config/id - - -## Telemetry Parameter coverage - -No new telemetry covered. - - -## Protocol/RPC Parameter coverage - -No new Protocol/RPC covered. +## OpenConfig Path and RPC Coverage +```yaml +paths: + /components/component/integrated-circuit/config/node-id: + platform_type: ["INTEGRATED_CIRCUIT"] + /interfaces/interface/config/id: +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/p4rt/otg_tests/base_p4rt/base_p4rt_test.go b/feature/p4rt/otg_tests/base_p4rt/base_p4rt_test.go similarity index 79% rename from feature/experimental/p4rt/otg_tests/base_p4rt/base_p4rt_test.go rename to feature/p4rt/otg_tests/base_p4rt/base_p4rt_test.go index 7c59fc4b38a..bd246fc9d1d 100644 --- a/feature/experimental/p4rt/otg_tests/base_p4rt/base_p4rt_test.go +++ b/feature/p4rt/otg_tests/base_p4rt/base_p4rt_test.go @@ -277,16 +277,12 @@ func setupP4RTClient(ctx context.Context, args *testArgs) error { // Function to compare and check if the expected table is present in RPC ReadResponse func verifyReadReceiveMatch(t *testing.T, expected_table *p4_v1.Update, received_entry *p4_v1.ReadResponse) error { - matches := 0 for _, table := range received_entry.Entities { if cmp.Equal(table, expected_table.Entity, protocmp.Transform(), protocmp.IgnoreFields(&p4_v1.TableEntry{}, "meter_config", "counter_data")) { - matches++ + return nil } } - if matches == 0 { - return errors.New("no matches found") - } - return nil + return fmt.Errorf("no matches found: \ngot %+v, \nwant: %+v", received_entry, expected_table) } // TestP4rtConnect connects to the P4Runtime server over grpc @@ -357,10 +353,45 @@ func TestP4rtConnect(t *testing.T) { { Type: p4_v1.Update_INSERT, IsIpv4: 0x1, + TTL: 0x0, + TTLMask: 0xFF, + Priority: 1, + }, + { + Type: p4_v1.Update_INSERT, + IsIpv4: 0x1, + TTL: 0x1, + 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, TTL: 0x1, TTLMask: 0xFF, Priority: 1, }, + { + Type: p4_v1.Update_INSERT, + IsIpv6: 0x1, + TTL: 0x2, + TTLMask: 0xFF, + Priority: 1, + }, }), Atomicity: p4_v1.WriteRequest_CONTINUE_ON_ERROR, }) @@ -372,7 +403,6 @@ func TestP4rtConnect(t *testing.T) { } } - nomatch := 0 // To count no matches for Table entries // Receive read response for index, client := range clients { rStream, rErr := client.Read(&p4_v1.ReadRequest{ @@ -407,7 +437,6 @@ func TestP4rtConnect(t *testing.T) { expected_entity := expected_update[0] if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { t.Errorf("Table entry for GDP %s", err) - nomatch += 1 } // Construct expected table for LLDP to match with received table entry @@ -422,10 +451,9 @@ func TestP4rtConnect(t *testing.T) { expected_entity = expected_update[0] if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { t.Errorf("Table entry for LLDP %s", err) - nomatch += 1 } - // Construct expected table for traceroute to match with received table entry + // Construct expected table for traceroute v4 & TTL = 1 to match with received table entry expected_update = p4rtutils.ACLWbbIngressTableEntryGet([]*p4rtutils.ACLWbbIngressTableEntryInfo{ { Type: p4_v1.Update_INSERT, @@ -437,11 +465,81 @@ func TestP4rtConnect(t *testing.T) { }) expected_entity = expected_update[0] if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { - t.Errorf("Table entry for traceroute %s", err) - nomatch += 1 + t.Errorf("TableEntry for traceroute v4 & ttl=1: %s", err) } - } - if nomatch > 0 { - t.Fatalf("Table entry matches failed") + + // 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{ + { + Type: p4_v1.Update_INSERT, + IsIpv6: 0x1, + TTL: 0x1, + 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=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/experimental/p4rt/otg_tests/base_p4rt/metadata.textproto b/feature/p4rt/otg_tests/base_p4rt/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/base_p4rt/metadata.textproto rename to feature/p4rt/otg_tests/base_p4rt/metadata.textproto diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md b/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md similarity index 86% rename from feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md rename to feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md index a5bffb0cdd9..8c5f6f83043 100644 --- a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md @@ -14,13 +14,14 @@ Verify that GDP packets are punted with correct metadata. * Verify that the packet has the ingress_singleton_port metadata set and it corresponds to the interface ID of the port that the packet was received on. -## Config Parameter coverage - -No new configuration covered. - -## Telemetry Parameter coverage - -No new telemetry covered. +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/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 similarity index 100% rename from feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go rename to feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto b/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto rename to feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto diff --git a/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/README.md b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/README.md new file mode 100644 index 00000000000..d96635af3e4 --- /dev/null +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/README.md @@ -0,0 +1,57 @@ +# P4RT-3.21: Google Discovery Protocol: PacketOut with LAG + +## Summary + +Verify that GDP packets can be sent by the controller. + +## Testbed Type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed + +## Procedure + +* Configure two lag bundles between ATE and DUT with one member port in each of the LAG. + A[ATE:LAG1] <---> B[LAG1:DUT]; + C[ATE:LAG2] <---> D[LAG2:DUT]; +* Enable the P4RT server on the device. +* Connect two P4RT clients in a master/secondary configuration. +* Configure the forwarding pipeline and install the P4RT table entry required for GDP. +* Send a GDP packet from the master with egress_singleton_port set to one of the connected interfaces. +* Verify that the GDP packet is received on the ATE port connected to the indicated interface. +* Repeat sending the packet in the same way but from the secondary connection. +* Verify that the packet is not received on the ATE. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config parameter coverage + /interfaces/interface/ethernet/config/port-speed: + /interfaces/interface/ethernet/config/duplex-mode: + /interfaces/interface/ethernet/config/aggregate-id: + /interfaces/interface/aggregation/config/lag-type: + /network-instances/network-instance/vlans/vlan/state/vlan-id: + + ## Telemetry parameter coverage + /lacp/interfaces/interface/members/member/state/counters/lacp-in-pkts: + /lacp/interfaces/interface/members/member/state/counters/lacp-out-pkts: + /lacp/interfaces/interface/members/member/state/counters/lacp-rx-errors: + /lacp/interfaces/interface/name: + /lacp/interfaces/interface/state/name: + /lacp/interfaces/interface/members/member/interface: + /lacp/interfaces/interface/members/member/state/interface: + + + ## Protocol/RPC Parameter Coverage +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: + gNMI.Get: +``` + +## Required DUT platform + +* FFF 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 new file mode 100644 index 00000000000..4f26092b92d --- /dev/null +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/google_discovery_protocol_packetout_lag_test.go @@ -0,0 +1,563 @@ +// 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 google_discovery_protocol_packetout_lag_test + +import ( + "context" + "errors" + "flag" + "fmt" + "net" + "sort" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/cisco-open/go-p4/utils" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + "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" + p4v1pb "github.com/p4lang/p4runtime/go/p4/v1" +) + +const ( + ipv4PLen = 30 + packetCount = 300 +) + +var ( + p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + streamName = "p4rt" + gdpInLayers layers.EthernetType = 0x6007 + deviceID = uint64(1) + portID = uint32(10) + electionID = uint64(100) + vlanID = uint16(4000) + pktOutDstMAC = "02:F6:65:64:00:08" +) + +type aggPortData struct { + dutIPv4 string + ateIPv4 string + ateAggName string + ateAggMAC string + atePortMAC string + aggPortID uint32 + hasVlan bool +} + +var ( + agg1 = &aggPortData{ + dutIPv4: "192.0.2.1", + ateIPv4: "192.0.2.2", + ateAggName: "lag1", + ateAggMAC: "02:00:01:01:01:01", + atePortMAC: "02:00:01:01:01:02", + aggPortID: 10, + hasVlan: true, + } + agg2 = &aggPortData{ + dutIPv4: "192.0.2.5", + ateIPv4: "192.0.2.6", + ateAggName: "lag2", + ateAggMAC: "02:00:01:01:01:04", + atePortMAC: "02:00:01:01:01:05", + aggPortID: 11, + hasVlan: false, + } +) + +type PacketIO interface { + GetTableEntry(delete bool) []*p4rtutils.ACLWbbIngressTableEntryInfo + GetPacketOut(portID uint32) []*p4v1pb.PacketOut +} + +type testArgs struct { + ctx context.Context + leader *p4rt_client.P4RTClient + follower *p4rt_client.P4RTClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + packetIO PacketIO +} + +// programmTableEntry programs or deletes p4rt table entry based on delete flag. +func programmTableEntry(ctx context.Context, t *testing.T, client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool) error { + t.Helper() + err := client.Write(&p4v1pb.WriteRequest{ + DeviceId: deviceID, + ElectionId: &p4v1pb.Uint128{High: uint64(0), Low: electionID}, + Updates: p4rtutils.ACLWbbIngressTableEntryGet( + packetIO.GetTableEntry(delete), + ), + Atomicity: p4v1pb.WriteRequest_CONTINUE_ON_ERROR, + }) + if err != nil { + return err + } + return nil +} + +// sendPackets sends out packets via PacketOut message in StreamChannel. +func sendPackets(t *testing.T, client *p4rt_client.P4RTClient, packets []*p4v1pb.PacketOut, packetCount int) { + count := packetCount / len(packets) + for _, packet := range packets { + for i := 0; i < count; i++ { + if err := client.StreamChannelSendMsg( + &streamName, &p4v1pb.StreamMessageRequest{ + Update: &p4v1pb.StreamMessageRequest_Packet{ + Packet: packet, + }, + }); err != nil { + t.Errorf("There is error seen in Packet Out. %v, %s", err, err) + } + } + } +} + +// testPacketOut sends out PacketOut with GDP payload on p4rt leader or +// follower client, then verify DUT interface statistics +func testPacketOut(ctx context.Context, t *testing.T, args *testArgs) { + leader := args.leader + follower := args.follower + + // Insert wbb acl entry on the DUT + if err := programmTableEntry(ctx, t, leader, args.packetIO, false); err != nil { + t.Fatalf("There is error when programming entry") + } + // Delete wbb acl entry on the device + defer programmTableEntry(ctx, t, leader, args.packetIO, true) + + packetOutTests := []struct { + desc string + client *p4rt_client.P4RTClient + expectPass bool + }{{ + desc: "PacketOut from Primary Controller", + client: leader, + expectPass: true, + }, { + desc: "PacketOut from Secondary Controller", + client: follower, + expectPass: false, + }} + + for _, test := range packetOutTests { + t.Run(test.desc, func(t *testing.T) { + // Check initial packet counters + port1 := sortPorts(args.ate.Ports())[0].ID() + counter0 := gnmi.Get(t, args.ate.OTG(), gnmi.OTG().Port(port1).Counters().InFrames().State()) + packets := args.packetIO.GetPacketOut(portID) + sendPackets(t, test.client, packets, packetCount) + + // Wait for ate stats to be populated + time.Sleep(4 * time.Minute) + otgutils.LogFlowMetrics(t, args.ate.OTG(), args.top) + otgutils.LogPortMetrics(t, args.ate.OTG(), args.top) + // Check packet counters after packet out + counter1 := gnmi.Get(t, args.ate.OTG(), gnmi.OTG().Port(port1).Counters().InFrames().State()) + // Verify InPkts stats to check P4RT stream + t.Logf("Received %v packets on ATE port %s", counter1-counter0, port1) + + if test.expectPass { + if counter1-counter0 < uint64(packetCount*0.95) { + t.Fatalf("Not all the packets are received.") + } + } else { + if counter1-counter0 > uint64(packetCount*0.10) { + t.Fatalf("Unexpected packets are received.") + } + } + }) + } +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// sortPorts sorts the ports by the testbed port ID. +func sortPorts(ports []*ondatra.Port) []*ondatra.Port { + sort.Slice(ports, func(i, j int) bool { + idi, idj := ports[i].ID(), ports[j].ID() + li, lj := len(idi), len(idj) + if li == lj { + return idi < idj + } + return li < lj // "port2" < "port10" + }) + return ports +} + +// configureDUT configures agg1 and agg2 on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) []string { + t.Helper() + fptest.ConfigureDefaultNetworkInstance(t, dut) + var aggIDs []string + for aggIdx, a := range []*aggPortData{agg1, agg2} { + b := &gnmi.SetBatch{} + d := &oc.Root{} + + aggID := netutil.NextAggregateInterface(t, dut) + aggIDs = append(aggIDs, aggID) + + agg := d.GetOrCreateInterface(aggID) + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_STATIC + agg.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag + agg.Description = ygot.String(a.ateAggName) + if deviations.InterfaceEnabled(dut) { + agg.Enabled = ygot.Bool(true) + } + s := agg.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + a4 := s4.GetOrCreateAddress(a.dutIPv4) + a4.PrefixLength = ygot.Uint8(ipv4PLen) + + gnmi.BatchDelete(b, gnmi.OC().Interface(aggID).Aggregation().MinLinks().Config()) + gnmi.BatchReplace(b, gnmi.OC().Interface(aggID).Config(), agg) + + p1 := dut.Port(t, fmt.Sprintf("port%d", (aggIdx*1)+1)) + // p2 := dut.Port(t, fmt.Sprintf("port%d", (aggIdx*2)+2)) + for _, port := range []*ondatra.Port{p1} { + gnmi.BatchDelete(b, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) + i := d.GetOrCreateInterface(port.Name()) + // i := &oc.Interface{Name: ygot.String(p1.Name()), Id: ygot.Uint32(a.aggPortID)} + // i := d.Interface{Name: ygot.String(port.Name()), Id: ygot.Uint32(a.aggPortID)} + i.Id = ygot.Uint32(a.aggPortID) + i.Description = ygot.String(fmt.Sprintf("LAG - Member -%s", port.Name())) + e := i.GetOrCreateEthernet() + e.AggregateId = ygot.String(aggID) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + if a.hasVlan && deviations.P4RTGdpRequiresDot1QSubinterface(dut) { + s1 := i.GetOrCreateSubinterface(1) + s1.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(vlanID) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(10) + i.GetOrCreateAggregation().GetOrCreateSwitchedVlan().SetNativeVlan(10) + } + } + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + gnmi.BatchReplace(b, gnmi.OC().Interface(port.Name()).Config(), i) + } + + b.Set(t, dut) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + for _, aggID := range aggIDs { + fptest.AssignToNetworkInstance(t, dut, aggID, deviations.DefaultNetworkInstance(dut), 0) + } + } + // Wait for LAG interfaces to be UP + for _, aggID := range aggIDs { + gnmi.Await(t, dut, gnmi.OC().Interface(aggID).AdminStatus().State(), 60*time.Second, oc.Interface_AdminStatus_UP) + } + gnmi.Replace(t, dut, gnmi.OC().System().MacAddress().RoutingMac().Config(), pktOutDstMAC) + return aggIDs +} + +// configureATE configures agg1 and agg2 on the ATE. +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + t.Helper() + top := gosnappi.NewConfig() + + for aggIdx, a := range []*aggPortData{agg1, agg2} { + p1 := ate.Port(t, fmt.Sprintf("port%d", (aggIdx*1)+1)) + // p2 := ate.Port(t, fmt.Sprintf("port%d", (aggIdx*2)+2)) + top.Ports().Add().SetName(p1.ID()) + agg := top.Lags().Add().SetName(a.ateAggName) + agg.Protocol().Static().SetLagId(uint32(aggIdx + 1)) + + lagDev := top.Devices().Add().SetName(agg.Name() + ".Dev") + lagEth := lagDev.Ethernets().Add().SetName(agg.Name() + ".Eth").SetMac(a.ateAggMAC) + lagEth.Connection().SetLagName(agg.Name()) + lagEth.Ipv4Addresses().Add().SetName(agg.Name() + ".IPv4").SetAddress(a.ateIPv4).SetGateway(a.dutIPv4).SetPrefix(ipv4PLen) + agg.Ports().Add().SetPortName(p1.ID()).Ethernet().SetMac(a.atePortMAC).SetName(a.ateAggName + ".1") + // agg.Ports().Add().SetPortName(p2.ID()).Ethernet().SetMac(a.atePort2MAC).SetName(a.ateAggName + ".2") + } + return top +} + +// configureDeviceIDs configures p4rt device-id on the DUT. +func configureDeviceID(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice) { + nodes := p4rtutils.P4RTNodesByPort(t, dut) + p4rtNode, ok := nodes["port1"] + if !ok { + t.Fatal("Couldn't find P4RT Node for port: port1") + } + t.Logf("Configuring P4RT Node: %s", p4rtNode) + c := oc.Component{} + c.Name = ygot.String(p4rtNode) + c.IntegratedCircuit = &oc.Component_IntegratedCircuit{} + c.IntegratedCircuit.NodeId = ygot.Uint64(deviceID) + gnmi.Replace(t, dut, gnmi.OC().Component(p4rtNode).Config(), &c) +} + +// setupP4RTClient sends client arbitration message for both leader and follower clients, +// then sends setforwordingpipelineconfig with leader client. +func setupP4RTClient(ctx context.Context, args *testArgs) error { + // Setup p4rt-client stream parameters + streamParameter := p4rt_client.P4RTStreamParameters{ + Name: streamName, + DeviceId: deviceID, + ElectionIdH: uint64(0), + ElectionIdL: electionID, + } + + // Send ClientArbitration message on both p4rt leader and follower clients. + clients := []*p4rt_client.P4RTClient{args.leader, args.follower} + for index, client := range clients { + if client != nil { + client.StreamChannelCreate(&streamParameter) + if err := client.StreamChannelSendMsg(&streamName, &p4v1pb.StreamMessageRequest{ + Update: &p4v1pb.StreamMessageRequest_Arbitration{ + Arbitration: &p4v1pb.MasterArbitrationUpdate{ + DeviceId: streamParameter.DeviceId, + ElectionId: &p4v1pb.Uint128{ + High: streamParameter.ElectionIdH, + Low: streamParameter.ElectionIdL - uint64(index), + }, + }, + }, + }); err != nil { + return fmt.Errorf("errors seen when sending ClientArbitration message: %v", err) + } + if _, _, arbErr := client.StreamChannelGetArbitrationResp(&streamName, 1); arbErr != nil { + if err := p4rtutils.StreamTermErr(client.StreamTermErr); err != nil { + return err + } + return fmt.Errorf("errors seen in ClientArbitration response: %v", arbErr) + } + } + } + + // Load p4info file. + p4Info, err := utils.P4InfoLoad(p4InfoFile) + if err != nil { + return errors.New("errors seen when loading p4info file") + } + + // Send SetForwardingPipelineConfig for p4rt leader client. + if err := args.leader.SetForwardingPipelineConfig(&p4v1pb.SetForwardingPipelineConfigRequest{ + DeviceId: deviceID, + ElectionId: &p4v1pb.Uint128{High: uint64(0), Low: electionID}, + Action: p4v1pb.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, + Config: &p4v1pb.ForwardingPipelineConfig{ + P4Info: p4Info, + Cookie: &p4v1pb.ForwardingPipelineConfig_Cookie{ + Cookie: 159, + }, + }, + }); err != nil { + return errors.New("errors seen when sending SetForwardingPipelineConfig") + } + return nil +} + +// getGDPParameter returns GDP related parameters for testPacketOut testcase. +func getGDPParameter(t *testing.T) PacketIO { + mac, err := net.ParseMAC(pktOutDstMAC) + if err != nil { + t.Fatalf("Could not parse MAC: %v", err) + } + return &GDPPacketIO{ + IngressPort: fmt.Sprint(portID), + DstMAC: mac, + } +} + +func TestPacketOut(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ctx := context.Background() + + configureDUT(t, dut) + + // Configure the ATE + ate := ondatra.ATE(t, "ate") + top := configureATE(t, ate) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + + // Configure P4RT device-id and port-id on the DUT + configureDeviceID(ctx, t, dut) + + 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 = getGDPParameter(t) + testPacketOut(ctx, t, args) +} + +type GDPPacketIO struct { + PacketIO + IngressPort string + DstMAC net.HardwareAddr +} + +// packetGDPRequestGet generates PacketOut payload for GDP packets. +func packetGDPRequestGet(vlan bool) []byte { + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + pktEth := &layers.Ethernet{ + SrcMAC: net.HardwareAddr{0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA}, + // GDP MAC is 00:0A:DA:F0:F0:F0 + DstMAC: net.HardwareAddr{0x00, 0x0A, 0xDA, 0xF0, 0xF0, 0xF0}, + EthernetType: gdpInLayers, + } + + payload := []byte{} + payLoadLen := 64 + for i := 0; i < payLoadLen; i++ { + payload = append(payload, byte(i)) + } + if vlan { + pktEth.EthernetType = layers.EthernetTypeDot1Q + d1q := &layers.Dot1Q{ + VLANIdentifier: vlanID, + Type: gdpInLayers, + } + gopacket.SerializeLayers(buf, opts, + pktEth, d1q, gopacket.Payload(payload), + ) + } else { + gopacket.SerializeLayers(buf, opts, + pktEth, gopacket.Payload(payload), + ) + } + return buf.Bytes() +} + +func ipPacketToATEPort1(dstMAC net.HardwareAddr) []byte { + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + eth := &layers.Ethernet{ + SrcMAC: net.HardwareAddr{0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA}, + // GDP MAC is 00:0A:DA:F0:F0:F0 + DstMAC: dstMAC, + EthernetType: layers.EthernetTypeIPv4, + } + ip := &layers.IPv4{ + SrcIP: net.ParseIP(agg2.ateIPv4), + DstIP: net.ParseIP(agg1.ateIPv4), + TTL: 2, + Version: 4, + Protocol: layers.IPProtocolIPv4, + } + tcp := &layers.TCP{ + SrcPort: 10000, + DstPort: 20000, + Seq: 11050, + } + + // Required for checksum computation. + tcp.SetNetworkLayerForChecksum(ip) + + payload := []byte{} + payLoadLen := 64 + for i := 0; i < payLoadLen; i++ { + payload = append(payload, byte(i)) + } + gopacket.SerializeLayers(buf, opts, + eth, ip, tcp, gopacket.Payload(payload), + ) + return buf.Bytes() +} + +// GetTableEntry creates wbb acl entry related to GDP. +func (gdp *GDPPacketIO) GetTableEntry(delete bool) []*p4rtutils.ACLWbbIngressTableEntryInfo { + actionType := p4v1pb.Update_INSERT + if delete { + actionType = p4v1pb.Update_DELETE + } + return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ + Type: actionType, + EtherType: 0x6007, + EtherTypeMask: 0xFFFF, + Priority: 1, + }} +} + +// GetPacketOut generates PacketOut message with payload as GDP. +func (gdp *GDPPacketIO) GetPacketOut(portID uint32) []*p4v1pb.PacketOut { + gdpWithVlan := &p4v1pb.PacketOut{ + Payload: packetGDPRequestGet(true), + Metadata: []*p4v1pb.PacketMetadata{ + { + MetadataId: uint32(1), // "egress_port" + Value: []byte(fmt.Sprint(portID)), + }, + }, + } + gdpWithoutVlan := &p4v1pb.PacketOut{ + Payload: packetGDPRequestGet(false), + Metadata: []*p4v1pb.PacketMetadata{ + { + MetadataId: uint32(1), // "egress_port" + Value: []byte(fmt.Sprint(portID)), + }, + }, + } + + nonGDP := &p4v1pb.PacketOut{ + Payload: ipPacketToATEPort1(gdp.DstMAC), + Metadata: []*p4v1pb.PacketMetadata{ + { + MetadataId: uint32(2), // "submit_to_ingress" + Value: []byte{1}, + }, + }, + } + return []*p4v1pb.PacketOut{gdpWithoutVlan, gdpWithVlan, nonGDP} +} diff --git a/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/metadata.textproto b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/metadata.textproto new file mode 100644 index 00000000000..4a20b5c1048 --- /dev/null +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/metadata.textproto @@ -0,0 +1,44 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "14861016-4ac8-43a8-8535-3f2c034e0e03" +plan_id: "P4RT-3.21" +description: "Google Discovery Protocol: PacketOut with LAG" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + p4rt_gdp_requires_dot1q_subinterface: true + no_mix_of_tagged_and_untagged_subinterfaces: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + } +} + diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md similarity index 87% rename from feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md rename to feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md index 8d6fd5b5870..d1ba03f1f5b 100644 --- a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md @@ -15,15 +15,14 @@ Verify that GDP packets can be sent by the controller. * Repeat sending the packet in the same way but from the secondary connection. * Verify that the packet is not received on the ATE. - - -## Config Parameter coverage - -No new configuration covered. - -## Telemetry Parameter coverage - -No new telemetry covered. +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/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 similarity index 100% rename from feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go rename to feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto rename to feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto diff --git a/feature/experimental/p4rt/otg_tests/lldp_packetin_test/README.md b/feature/p4rt/otg_tests/lldp_packetin_test/README.md similarity index 85% rename from feature/experimental/p4rt/otg_tests/lldp_packetin_test/README.md rename to feature/p4rt/otg_tests/lldp_packetin_test/README.md index 4af42c8c091..e3398a89860 100644 --- a/feature/experimental/p4rt/otg_tests/lldp_packetin_test/README.md +++ b/feature/p4rt/otg_tests/lldp_packetin_test/README.md @@ -13,15 +13,14 @@ Verify that LLDP packets are punted with correct metadata. * Send an LLDP packet from the ATE and verify that it is received by the client. * Verify that the packet has the ingress_singleton_port metadata set and it corresponds to the interface ID of the port that the packet was received on. - - -## Config Parameter coverage - -No new configuration covered. - -## Telemetry Parameter coverage - -No new telemetry covered. +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go b/feature/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go similarity index 100% rename from feature/experimental/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go rename to feature/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go diff --git a/feature/experimental/p4rt/otg_tests/lldp_packetin_test/metadata.textproto b/feature/p4rt/otg_tests/lldp_packetin_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/lldp_packetin_test/metadata.textproto rename to feature/p4rt/otg_tests/lldp_packetin_test/metadata.textproto diff --git a/feature/experimental/p4rt/otg_tests/lldp_packetout_test/README.md b/feature/p4rt/otg_tests/lldp_packetout_test/README.md similarity index 80% rename from feature/experimental/p4rt/otg_tests/lldp_packetout_test/README.md rename to feature/p4rt/otg_tests/lldp_packetout_test/README.md index ec9b4d9d73a..97616953d12 100644 --- a/feature/experimental/p4rt/otg_tests/lldp_packetout_test/README.md +++ b/feature/p4rt/otg_tests/lldp_packetout_test/README.md @@ -13,17 +13,13 @@ Verify that LLDP packets can be sent by the controller. * Send an LLDP packet from the client with egress_singleton_port set to one of the connected interfaces. * Verify that the LLDP packet is received on the ATE port connected to the indicated interface. - - - -## Config Parameter coverage - -No new configuration covered. - -## Telemetry Parameter coverage - -No new telemetry covered. - -## Minimum DUT platform requirement +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` vRX if the vendor implementation supports FIB-ACK simulation, otherwise FFF. \ No newline at end of file diff --git a/feature/experimental/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go b/feature/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go similarity index 100% rename from feature/experimental/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go rename to feature/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go diff --git a/feature/experimental/p4rt/otg_tests/lldp_packetout_test/metadata.textproto b/feature/p4rt/otg_tests/lldp_packetout_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/lldp_packetout_test/metadata.textproto rename to feature/p4rt/otg_tests/lldp_packetout_test/metadata.textproto diff --git a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md index c9ad228379e..4719f77f44b 100644 --- a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md +++ b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md @@ -23,13 +23,16 @@ Ensure that data plane traffic is not interrupted by P4RT daemon failure. test tables only configure the control plane traffic. Instead, this test configures the data plane using gRIBI. -## Protocol/RPC Parameter Coverage - -* gRIBI - * ModifyRequest - * GetRequest +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Telemetry Parameter Coverage * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix/ * /interfaces/interface/state/id -* /interfaces/interface/state/name \ No newline at end of file +* /interfaces/interface/state/name diff --git a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/metadata.textproto b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/metadata.textproto index 53b71895f77..d9063936e7a 100644 --- a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/metadata.textproto +++ b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/metadata.textproto @@ -1,4 +1,4 @@ -# proto-file: third_party/openconfig/featureprofiles/proto/metadata.proto +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata uuid: "78f99d29-c7db-4e6d-8284-614154efc0d1" diff --git a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go index 1950762495f..2638b2aee7d 100644 --- a/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go +++ b/feature/p4rt/otg_tests/p4rt_daemon_failure_test/p4rt_daemon_failure_test.go @@ -15,7 +15,6 @@ package p4rt_daemon_failure_test import ( - "context" "fmt" "testing" "time" @@ -24,6 +23,7 @@ import ( "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" "github.com/openconfig/featureprofiles/internal/gribi" "github.com/openconfig/featureprofiles/internal/p4rtutils" "github.com/openconfig/gribigo/fluent" @@ -34,7 +34,6 @@ import ( "github.com/openconfig/ygot/ygot" gpb "github.com/openconfig/gnmi/proto/gnmi" - syspb "github.com/openconfig/gnoi/system" ) func TestMain(m *testing.M) { @@ -80,13 +79,6 @@ var ( IPv4: "192.0.2.6", IPv4Len: ipv4PrefixLen, } - - p4rtDaemons = map[ondatra.Vendor]string{ - ondatra.ARISTA: "P4Runtime", - ondatra.CISCO: "emsd", - ondatra.JUNIPER: "p4-switch", - ondatra.NOKIA: "sr_p4rt_server", - } ) // configInterfaceDUT returns the OC Interface config for a given port. @@ -192,18 +184,6 @@ func startTraffic(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) gos return flow } -// pidByName uses telemetry to find out the PID of a process -func pidByName(t *testing.T, dut *ondatra.DUTDevice, process string) (uint64, error) { - t.Helper() - ps := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - for _, p := range ps { - if p.GetName() == process { - return p.GetPid(), nil - } - } - return 0, fmt.Errorf("could not find PID for process: %s", process) -} - func installRoutes(t *testing.T, dut *ondatra.DUTDevice) error { t.Helper() @@ -278,12 +258,10 @@ func subscribeOnChangeInterfaceID(t *testing.T, dut *ondatra.DUTDevice) *gnmi.Wa return watchID } -func subscribeOnChangeInterfaceName(t *testing.T, dut *ondatra.DUTDevice) *gnmi.Watcher[string] { +func subscribeOnChangeInterfaceName(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port) *gnmi.Watcher[string] { t.Helper() - p1 := dut.Port(t, "port1") - - interfaceNamePath := gnmi.OC().Interface(p1.Name()).Name().State() + interfaceNamePath := gnmi.OC().Interface(p.Name()).Name().State() t.Logf("TRY: subscribe ON_CHANGE to %s", interfaceNamePath) watchName := gnmi.Watch(t, @@ -292,7 +270,7 @@ func subscribeOnChangeInterfaceName(t *testing.T, dut *ondatra.DUTDevice) *gnmi. time.Minute, func(val *ygnmi.Value[string]) bool { iname, present := val.Val() - return present && iname == p1.Name() + return present && iname == p.Name() }) return watchName @@ -301,18 +279,14 @@ func subscribeOnChangeInterfaceName(t *testing.T, dut *ondatra.DUTDevice) *gnmi. func TestP4RTDaemonFailure(t *testing.T) { dut := ondatra.DUT(t, "dut") - p4rtD, ok := p4rtDaemons[dut.Vendor()] - if !ok { - t.Fatalf("Please add support for vendor %v in var p4rtDaemons", dut.Vendor()) - } - t.Logf("Configure DUT") configureDUT(t, dut) // Verify subscribe ON_CHANGE is supported using a commonly supported OC path. - watchName, ok := subscribeOnChangeInterfaceName(t, dut).Await(t) + p1 := dut.Port(t, "port1") + watchName, ok := subscribeOnChangeInterfaceName(t, dut, p1).Await(t) if !ok { - t.Fatalf("FAIL: /interfaces/interface[name=%q]/state/name got:%v want:%q", dut.Port(t, "port1").Name(), watchName, dut.Port(t, "port1").Name()) + t.Fatalf("/interfaces/interface[name=%q]/state/name got:%v want:%q", p1.Name(), watchName, p1.Name()) } // Subscribe ON_CHANGE to '/interfaces/interface/state/id'. @@ -335,23 +309,7 @@ func TestP4RTDaemonFailure(t *testing.T) { flow := startTraffic(t, ate, top) - pID, err := pidByName(t, dut, p4rtD) - if err != nil { - t.Fatal(err) - } - - c := dut.RawAPIs().GNOI(t) - req := &syspb.KillProcessRequest{ - Name: p4rtD, - Pid: uint32(pID), - Signal: syspb.KillProcessRequest_SIGNAL_TERM, - Restart: true, - } - resp, err := c.System().KillProcess(context.Background(), req) - t.Logf("Got kill process response: %v", resp) - if err != nil { - t.Fatalf("FAIL: to execute gNOI.KillProcess, error received: %v", err) - } + gnoi.KillProcess(t, dut, gnoi.P4RT, gnoi.SigTerm, true, true) // let traffic keep running for another 10 seconds. time.Sleep(10 * time.Second) @@ -359,12 +317,15 @@ func TestP4RTDaemonFailure(t *testing.T) { t.Logf("Stop traffic") ate.OTG().StopTraffic(t) - // Verify interfaceID did not change since the last time we read it. - changedID, notOk := watchID.Await(t) - if notOk { - t.Errorf("FAIL: DUT changed /interfaces/interface/state/id during p4rt process restart. want:%q got:%q", dutPort1.ID, changedID.String()) + // Skip check for CISCO devices that use the same process for P4RT & gNMI. + if dut.Vendor() != ondatra.CISCO && dut.Vendor() != ondatra.NOKIA { + // Verify interfaceID did not change since the last time we read it. + changedID, notOk := watchID.Await(t) + if notOk { + t.Errorf("DUT changed /interfaces/interface/state/id during p4rt process restart. want: %q got: %q", dutPort1.ID, changedID.String()) + } + t.Logf("OK: no change detected in /interfaces/interface/state/id want:%q got:%q", dutPort1.ID, changedID.String()) } - t.Logf("OK: no change detected in /interfaces/interface/state/id want:%q got:%q", dutPort1.ID, changedID.String()) recvMetric := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).State()) txPackets := float32(recvMetric.GetCounters().GetOutPkts()) diff --git a/feature/experimental/p4rt/otg_tests/performance_test/README.md b/feature/p4rt/otg_tests/performance_test/README.md similarity index 73% rename from feature/experimental/p4rt/otg_tests/performance_test/README.md rename to feature/p4rt/otg_tests/performance_test/README.md index 470f6c467be..fb7d6b31e32 100644 --- a/feature/experimental/p4rt/otg_tests/performance_test/README.md +++ b/feature/p4rt/otg_tests/performance_test/README.md @@ -15,21 +15,18 @@ Verify that both Packetin and Packetout traffic is handled by the P4RT server at * Setup packetout packets for GDP, LLDP and traceroute from the P4RT client. * Start both packetin and packetout traffic at the same rate simultaneously. * Verify no packetloss for both directions of traffic. -* Verify the metadata ID and the value for all three traffic types on the P4RT client for packetin. - - -## Config Parameter coverage - -* /components/component/integrated-circuit/config/node-id -* /interfaces/interface/config/id - - -## Telemetry Parameter coverage - -No new telemetry covered. - - -## Protocol/RPC Parameter coverage - -No new Protocol/RPC covered. +* Verify the metadata ID and the value for all three traffic types on the P4RT client for packetin. + +## OpenConfig Path and RPC Coverage +```yaml +paths: + /components/component/integrated-circuit/config/node-id: + platform_type: ["INTEGRATED_CIRCUIT"] + /interfaces/interface/config/id: +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/p4rt/otg_tests/performance_test/metadata.textproto b/feature/p4rt/otg_tests/performance_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/performance_test/metadata.textproto rename to feature/p4rt/otg_tests/performance_test/metadata.textproto diff --git a/feature/experimental/p4rt/otg_tests/performance_test/performance_test.go b/feature/p4rt/otg_tests/performance_test/performance_test.go similarity index 100% rename from feature/experimental/p4rt/otg_tests/performance_test/performance_test.go rename to feature/p4rt/otg_tests/performance_test/performance_test.go diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_test/README.md b/feature/p4rt/otg_tests/traceroute_packetin_test/README.md similarity index 73% rename from feature/experimental/p4rt/otg_tests/traceroute_packetin_test/README.md rename to feature/p4rt/otg_tests/traceroute_packetin_test/README.md index 8a69af7d868..0aee47016e4 100644 --- a/feature/experimental/p4rt/otg_tests/traceroute_packetin_test/README.md +++ b/feature/p4rt/otg_tests/traceroute_packetin_test/README.md @@ -16,19 +16,15 @@ Verify that Traceroute packets are punted with correct metadata. * 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. - -## Config Parameter coverage - -* /components/component/integrated-circuit/config/node-id -* /interfaces/interface/config/id - - -## Telemetry Parameter coverage - -No new telemetry covered. - - -## Protocol/RPC Parameter coverage - -No new Protocol/RPC covered. - +## OpenConfig Path and RPC Coverage +```yaml +paths: + /components/component/integrated-circuit/config/node-id: + platform_type: ["INTEGRATED_CIRCUIT"] + /interfaces/interface/config/id: +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_test/metadata.textproto b/feature/p4rt/otg_tests/traceroute_packetin_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/traceroute_packetin_test/metadata.textproto rename to feature/p4rt/otg_tests/traceroute_packetin_test/metadata.textproto diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go b/feature/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go similarity index 100% rename from feature/experimental/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go rename to feature/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go b/feature/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go similarity index 100% rename from feature/experimental/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go rename to feature/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go diff --git a/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md new file mode 100644 index 00000000000..ec980072fd7 --- /dev/null +++ b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md @@ -0,0 +1,255 @@ +# P4RT-5.3: Traceroute: PacketIn With VRF Selection + +## Summary + +Test FRR behaviors with VRF selection scenarios. + +## Topology + +ATE port-1 <------> port-1 DUT +DUT port-2 <------> port-2 ATE +DUT port-3 <------> port-3 ATE +DUT port-4 <------> port-4 ATE +DUT port-5 <------> port-5 ATE +DUT port-6 <------> port-6 ATE +DUT port-7 <------> port-7 ATE +DUT port-8 <------> port-8 ATE + +## Baseline setup + +* Setup equivalent to [TE-17.1 vrf_policy_driven_te](https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/vrf_policy_driven_te/README.md), including GRibi programming. + +* Install a BGP route resolved by ISIS in default VRF to route traffic out of DUT port-8 for 203.0.113.0. + +* Enable the P4RT server on the device. + +* Connect a P4RT client and configure the forwarding pipeline. Install P4RT table entries required for traceroute. + These are located in [p4rt_utils.go] (https://github.com/openconfig/featureprofiles/blob/main/internal/p4rtutils/p4rtutils.go) + p4rtutils.ACLWbbIngressTableEntryGet(packetIO.GetTableEntry(delete, isIPv4)) + + +## Procedure + +At the start of each of the following scenarios, ensure: + +* All ports are up and baseline is reset as above. + +Unless otherwise specified, all the tests below should use traffic with +`dscp_encap_a_1` referenced int he VRF selection policy. + +### Test-1 + +Tests that traceroute with TTL=1 for a packet that would match the VRF +selection policy for encap has target_egress_port set based on the +encap VRF. + +* Send packets to DUT port-1 with outer packet header TTL=1. The outer v4 header has the destination + addresses 138.0.11.8. verify that packets with TTL=1 are received by the client. + +* Verify that the punted packets have both ingress_port and target_egress_port metadata set. + +The distribution of packets should have target_egress_port set with port2 1.56% of +the time, port3 4.68%, port4 18.75% and port6 75%. + +### Test-2 + +Tests that traceroute with TTL=1 for a packet that would match the VRF +selection policy for default has target_egress_port set based on the +default VRF. + +* Send packets to DUT port-1 with packet TTL=1. The v4 header has the destination + address 203.0.113.0. Verify that packets with TTL=1 are received by the client. +* Verify that the packets have both ingress_port and target_egress_port metadata set. +target_egress_port should be dut port 8. + + +### Test-3 + +Tests that a packet punted due to TTL=1 for a packet that would +otherwise hit a transit VRF has target_egress_port set based on that +transit VRF. + +* Send 4in4 (IP protocol 4) and 6in4 (IP protocol 41) packets to DUT port-1 where + * The outer v4 header has the destination address 203.0.113.1. + * The outer v4 header has the source address ipv4_outer_src_111. + * The outer v4 header has DSCP value has `dscp_encap_no_match` and `dscp_encap_match` + * The outer v4 header has TTL=1 +* Verify that the punted packets have both ingress_port and + target_egress_port metadata set. target_egress_port should be set + to on DUT port-2, port-3, and port-4 per the heirarchical weight. + +### Test-4 (TBD) + +Tests that traceroute respects transit FRR. + +### Test-5 (TBD) + +Tests that traceroute respects transit FRR when the backup is also unviable. + +### Test-6 + +Tests that traceroute respects decap rules. + +1. Using gRIBI to install the following entries in the `DECAP_TE_VRF`: + + ``` + IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#3001 (DEFAULT VRF) -> { + {NH#3001, DEFAULT VRF, weight:1} + } + NH#3001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + } + + ``` + +2. Apply vrf selection policy `vrf_selection_policy_w` to DUT port-1. + +3. Send the following 6in4 and 4in4 flows to DUT port-1: + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_no_match` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_no_match` + * proto: `4` + * outer TTL: `1` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_no_match` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_no_match` + * proto: `41` + * outer TTL: `1` + ``` + +4. Verify that all punted packets: + * Have ingress_port and target_egress_port metadata set + * target_egress_port is set to DUT port-8 per the hierarchical weight. + +6. Change the subnet mask from /24 and repeat the test for the masks /32, /22, and /28 and verify again that the packets are punted correctly. + + +### Test-7 (TBD) + +Encap failure cases (TBD on confirmation) + +### Test-8 (TBD) + +Tests that traceroute for a packet with a route lookup miss has an unset target_egress_port. + +### Test-9, decap the encap + +1. Using gRIBI to install the following entries in the `DECAP_TE_VRF`: + + ``` + IPv4Entry {192.51.100.1/24 (DECAP_TE_VRF)} -> NHG#3001 (DEFAULT VRF) -> { + {NH#3001, DEFAULT VRF, weight:1} + } + NH#3001 -> { + decapsulate_header: OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 + } + ``` + +2. Apply vrf selection policy `vrf_selection_policy_w` to DUT port-1. + +3. Send the following packets to DUT port-1: + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_a_1` + * outer_src: `ipv4_outer_src_222` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_a_1` + * proto: `4` + * outer TTL: '1' + ``` + + ``` + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_a_1` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_a_1` + * proto: `41` + * outer TTL: `1` + ``` + +4. We should expect that all punted packets: + * Have ingress_port and target_egress_port metadata set + * target_egress_port is set to DUT port-2, port-3, port-4 and port-6 per the hierarchical weight. + +5. Send the following packets to DUT port -1 + + ``` + * inner_src: `ipv4_inner_src` + * inner_dst: `ipv4_inner_encap_match` + * dscp: `dscp_encap_b_1` + * outer_src: `ipv4_outer_src_111` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_b_1` + * proto: `4` + + * inner_src: `ipv6_inner_src` + * inner_dst: `ipv6_inner_encap_match` + * dscp: `dscp_encap_b_1` + * outer_src: `ipv4_outer_src_222` + * outer_dst: `ipv4_outer_decap_match` + * dscp: `dscp_encap_b_1` + * proto: `41` + ``` + +6. Verify that all punted packets: + * Have ingress_port and target_egress_port metadata set + * target_egress_port is set to DUT port-2, port-3, port-4 and port-6 per the hierarchical weight. + +## Config Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## Telemetry Parameter Coverage + +* network-instances/network-instance/name +* network-instances/network-instance/policy-forwarding/policies/policy/policy-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/protocol +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/dscp-set +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/source-address +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance +* network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` + +## Config parameter coverage diff --git a/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto new file mode 100644 index 00000000000..b9ebd5de7e4 --- /dev/null +++ b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto @@ -0,0 +1,24 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "5a5491b5-9900-4a05-b289-43feb1ceb915" +plan_id: "P4RT-5.3" +description: "Traceroute: PacketIn With VRF Selection" +testbed: TESTBED_DUT_ATE_8LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + gnoi_subcomponent_path: true + deprecated_vlan_id: true + interface_enabled: true + static_protocol_name: "STATIC" + default_network_instance: "default" + gribi_mac_override_static_arp_static_route: true + missing_isis_interface_afi_safi_enable: true + isis_interface_afi_unsupported: true + isis_instance_enabled_required: true + ate_port_link_state_operations_unsupported: true + } +} diff --git a/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go new file mode 100644 index 00000000000..d8e5c3e9739 --- /dev/null +++ b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go @@ -0,0 +1,275 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package to test P4RT with traceroute traffic of IPV4 and IPV6 with TTL/HopLimit as 0&1. +// go test -v . -testbed /root/ondatra/featureprofiles/topologies/atedut_2.testbed -binding /root/ondatra/featureprofiles/topologies/atedut_2.binding -outputs_dir logs + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ygnmi/ygnmi" + pb "github.com/p4lang/p4runtime/go/p4/v1" +) + +type PacketIO interface { + GetTableEntry(delete bool, isIPv4 bool) []*p4rtutils.ACLWbbIngressTableEntryInfo + GetPacketTemplate() *PacketIOPacket + GetTrafficFlow(ate *ondatra.ATEDevice, dstMac string, isIpv4 bool, + TTL uint8, frameSize uint32, frameRate uint64, dstIP string, flowValues *flowArgs) gosnappi.Flow + GetIngressPort() string +} + +type PacketIOPacket struct { + TTL *uint8 + SrcMAC, DstMAC *string + EthernetType *uint32 + HopLimit *uint8 +} + +// programmTableEntry programs or deletes p4rt table entry based on delete flag. +func programmTableEntry(client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool, isIPv4 bool) error { + err := client.Write(&pb.WriteRequest{ + DeviceId: deviceID, + ElectionId: &pb.Uint128{High: uint64(0), Low: electionID}, + Updates: p4rtutils.ACLWbbIngressTableEntryGet( + packetIO.GetTableEntry(delete, isIPv4), + ), + Atomicity: pb.WriteRequest_CONTINUE_ON_ERROR, + }) + return err +} + +// decodePacket decodes L2 header in the packet and returns source and destination MAC and ethernet type. +func decodePacket(t *testing.T, packetData []byte) (string, string, layers.EthernetType) { + t.Helper() + packet := gopacket.NewPacket(packetData, layers.LayerTypeEthernet, gopacket.Default) + etherHeader := packet.Layer(layers.LayerTypeEthernet) + if etherHeader != nil { + header, decoded := etherHeader.(*layers.Ethernet) + if decoded { + return header.SrcMAC.String(), header.DstMAC.String(), header.EthernetType + } + } + return "", "", layers.EthernetType(0) +} + +// decodePacket decodes L2 header in the packet and returns TTL. packetData[14:0] to remove first 14 bytes of Ethernet header. +func decodePacket4(t *testing.T, packetData []byte) uint8 { + t.Helper() + packet := gopacket.NewPacket(packetData[14:], layers.LayerTypeIPv4, gopacket.Default) + if IPv4 := packet.Layer(layers.LayerTypeIPv4); IPv4 != nil { + ipv4, _ := IPv4.(*layers.IPv4) + IPv4 := ipv4.TTL + return IPv4 + } + return 7 +} + +// decodePacket decodes IPV6 L2 header in the packet and returns HopLimit. packetData[14:] to remove first 14 bytes of Ethernet header. +func decodePacket6(t *testing.T, packetData []byte) uint8 { + t.Helper() + packet := gopacket.NewPacket(packetData[14:], layers.LayerTypeIPv6, gopacket.Default) + if IPv6 := packet.Layer(layers.LayerTypeIPv6); IPv6 != nil { + ipv6, _ := IPv6.(*layers.IPv6) + IPv6 := ipv6.HopLimit + return IPv6 + } + return 7 +} + +// testTraffic sends traffic flow for duration seconds and returns the +// number of packets sent out. +func testTraffic(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, flows []gosnappi.Flow, srcEndPoint gosnappi.Port, duration int, cs gosnappi.ControlState) int { + t.Helper() + top.Flows().Clear() + for _, flow := range flows { + flow.TxRx().Port().SetTxName(srcEndPoint.Name()).SetRxName(srcEndPoint.Name()) + flow.Metrics().SetEnable(true) + top.Flows().Append(flow) + } + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + time.Sleep(30 * time.Second) + ate.OTG().StartTraffic(t) + time.Sleep(time.Duration(duration) * time.Second) + ate.OTG().StopTraffic(t) + + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + ate.OTG().SetControlState(t, cs) + + outPkts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().FlowAny().Counters().OutPkts().State()) + total := 0 + for _, count := range outPkts { + total += int(count) + } + return total +} + +// testPacketIn programs p4rt table entry and sends traffic related to Traceroute, +// then validates packetin message metadata and payload. +func testPacketIn(ctx context.Context, t *testing.T, args *testArgs, isIPv4 bool, cs gosnappi.ControlState, flowValues []*flowArgs, EgressPortMap map[string]bool) []float64 { + leader := args.leader + if isIPv4 { + // Insert p4rtutils acl entry on the DUT + if err := programmTableEntry(leader, args.packetIO, false, isIPv4); err != nil { + t.Fatalf("There is error when programming entry") + } + // Delete p4rtutils acl entry on the device + defer programmTableEntry(leader, args.packetIO, true, isIPv4) + } else { + // Insert p4rtutils acl entry on the DUT + if err := programmTableEntry(leader, args.packetIO, false, false); err != nil { + t.Fatalf("There is error when programming entry") + } + // Delete p4rtutils acl entry on the device + defer programmTableEntry(leader, args.packetIO, true, false) + } + streamChan := args.leader.StreamChannelGet(&streamName) + qSize := 12000 + streamChan.SetArbQSize(qSize) + qSizeRead := streamChan.GetArbQSize() + if qSize != qSizeRead { + t.Errorf("Stream '%s' expecting Arbitration qSize(%d) Got (%d)", + streamName, qSize, qSizeRead) + } + + streamChan.SetPacketQSize(qSize) + qSizeRead = streamChan.GetPacketQSize() + if qSize != qSizeRead { + t.Errorf("Stream '%s' expecting Packet qSize(%d) Got (%d)", + streamName, qSize, qSizeRead) + } + + // Send Traceroute traffic from ATE + srcEndPoint := ateInterface(t, args.top, "port1") + llAddress, found := gnmi.Watch(t, args.ate.OTG(), gnmi.OTG().Interface("atePort1"+".Eth").Ipv4Neighbor(portsIPv4["dut:port1"]).LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + return val.IsPresent() + }).Await(t) + if !found { + t.Fatalf("Could not get the LinkLayerAddress %s", llAddress) + } + dstMac, _ := llAddress.Val() + var flow []gosnappi.Flow + for _, flowValue := range flowValues { + flow = append(flow, args.packetIO.GetTrafficFlow(args.ate, dstMac, isIPv4, 1, 300, 50, ipv4InnerDst, flowValue)) + } + pktOut := testTraffic(t, args.top, args.ate, flow, srcEndPoint, 60, cs) + var countPkts = map[string]int{"11": 0, "12": 0, "13": 0, "14": 0, "15": 0, "16": 0, "17": 0} + + packetInTests := []struct { + desc string + client *p4rt_client.P4RTClient + wantPkts int + }{{ + desc: "PacketIn to Primary Controller", + client: leader, + wantPkts: pktOut, + }} + + t.Log("TTL/HopLimit 1") + for _, test := range packetInTests { + t.Run(test.desc, func(t *testing.T) { + // Extract packets from PacketIn message sent to p4rt client + _, packets, err := test.client.StreamChannelGetPackets(&streamName, uint64(test.wantPkts), 30*time.Second) + if err != nil { + t.Errorf("Unexpected error on fetchPackets: %v", err) + } + + if test.wantPkts == 0 { + return + } + + gotPkts := 0 + t.Logf("Start to decode packet and compare with expected packets.") + wantPacket := args.packetIO.GetPacketTemplate() + + for _, packet := range packets { + if packet != nil { + srcMAC, _, etherType := decodePacket(t, packet.Pkt.GetPayload()) + if etherType != layers.EthernetTypeIPv4 && etherType != layers.EthernetTypeIPv6 { + continue + } + if !strings.EqualFold(srcMAC, tracerouteSrcMAC) { + continue + } + if wantPacket.TTL != nil { + // TTL/HopLimit comparison for IPV4 & IPV6 + if isIPv4 { + captureTTL := decodePacket4(t, packet.Pkt.GetPayload()) + if captureTTL != TTL1 { + t.Fatalf("Packet in PacketIn message is not matching wanted packet=IPV4 TTL1") + } + + } else { + captureHopLimit := decodePacket6(t, packet.Pkt.GetPayload()) + if captureHopLimit != HopLimit1 { + t.Fatalf("Packet in PacketIn message is not matching wanted packet=IPV6 HopLimit1") + } + } + } + + // Metadata comparision + if metaData := packet.Pkt.GetMetadata(); metaData != nil { + if got := metaData[0].GetMetadataId(); got == MetadataIngressPort { + if gotPortID := string(metaData[0].GetValue()); gotPortID != args.packetIO.GetIngressPort() { + t.Fatalf("Ingress Port Id mismatch: want %s, got %s", args.packetIO.GetIngressPort(), gotPortID) + } + } else { + t.Fatalf("Metadata ingress port mismatch: want %d, got %d", MetadataIngressPort, got) + } + if got := metaData[1].GetMetadataId(); got == MetadataEgressPort { + countPkts[string(metaData[1].GetValue())]++ + if gotPortID := string(metaData[1].GetValue()); !EgressPortMap[gotPortID] { + t.Fatalf("Egress Port Id mismatch: got %s", gotPortID) + } + } else { + t.Fatalf("Metadata egress port mismatch: want %d, got %d", MetadataEgressPort, got) + } + } else { + t.Fatalf("Packet missing metadata information.") + } + gotPkts++ + } + } + if got, want := gotPkts, test.wantPkts; got != want { + t.Errorf("Number of PacketIn, got: %d, want: %d", got, want) + } + }) + } + loadBalancePercent := []float64{float64(countPkts["11"]) / float64(pktOut), float64(countPkts["12"]) / float64(pktOut), + float64(countPkts["13"]) / float64(pktOut), float64(countPkts["14"]) / float64(pktOut), float64(countPkts["15"]) / float64(pktOut), + float64(countPkts["16"]) / float64(pktOut), float64(countPkts["17"]) / float64(pktOut)} + + return loadBalancePercent +} + +func ateInterface(t *testing.T, topo gosnappi.Config, portID string) gosnappi.Port { + for _, p := range topo.Ports().Items() { + if p.Name() == portID { + return p + } + } + return nil +} diff --git a/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go new file mode 100644 index 00000000000..d3e831d795a --- /dev/null +++ b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go @@ -0,0 +1,264 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package to test P4RT with traceroute traffic of IPV4 and IPV6 with TTL/HopLimit as 0&1. +// go test -v . -testbed /root/ondatra/featureprofiles/topologies/atedut_2.testbed -binding /root/ondatra/featureprofiles/topologies/atedut_2.binding -outputs_dir logs + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/cisco-open/go-p4/utils" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" + pb "github.com/p4lang/p4runtime/go/p4/v1" +) + +// configureDeviceIDs configures p4rt device-id on the DUT. +func configureDeviceID(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice) { + nodes := p4rtutils.P4RTNodesByPort(t, dut) + p4rtNode, ok := nodes["port1"] + if !ok { + t.Fatal("Couldn't find P4RT Node for port: port1") + } + t.Logf("Configuring P4RT Node: %s", p4rtNode) + c := oc.Component{} + c.Name = ygot.String(p4rtNode) + c.IntegratedCircuit = &oc.Component_IntegratedCircuit{} + c.IntegratedCircuit.NodeId = ygot.Uint64(deviceID) + gnmi.Replace(t, dut, gnmi.OC().Component(p4rtNode).Config(), &c) +} + +// setupP4RTClient sends client arbitration message for both leader and follower clients, +// then sends setforwordingpipelineconfig with leader client. +func setupP4RTClient(ctx context.Context, args *testArgs) error { + // Setup p4rt-client stream parameters + streamParameter := p4rt_client.P4RTStreamParameters{ + Name: streamName, + DeviceId: deviceID, + ElectionIdH: uint64(0), + ElectionIdL: electionID, + } + + // Send ClientArbitration message on both p4rt leader and follower clients. + clients := []*p4rt_client.P4RTClient{args.leader, args.follower} + for index, client := range clients { + if client != nil { + client.StreamChannelCreate(&streamParameter) + if err := client.StreamChannelSendMsg(&streamName, &pb.StreamMessageRequest{ + Update: &pb.StreamMessageRequest_Arbitration{ + Arbitration: &pb.MasterArbitrationUpdate{ + DeviceId: streamParameter.DeviceId, + ElectionId: &pb.Uint128{ + High: streamParameter.ElectionIdH, + Low: streamParameter.ElectionIdL - uint64(index), + }, + }, + }, + }); err != nil { + return fmt.Errorf("errors seen when sending ClientArbitration message: %v", err) + } + if _, _, arbErr := client.StreamChannelGetArbitrationResp(&streamName, 1); arbErr != nil { + if err := p4rtutils.StreamTermErr(client.StreamTermErr); err != nil { + return err + } + return fmt.Errorf("errors seen in ClientArbitration response: %v", arbErr) + } + } + } + + // Load p4info file. + p4Info, err := utils.P4InfoLoad(p4InfoFile) + if err != nil { + return errors.New("errors seen when loading p4info file") + } + + // Send SetForwardingPipelineConfig for p4rt leader client. + if err := args.leader.SetForwardingPipelineConfig(&pb.SetForwardingPipelineConfigRequest{ + DeviceId: deviceID, + ElectionId: &pb.Uint128{High: uint64(0), Low: electionID}, + Action: pb.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, + Config: &pb.ForwardingPipelineConfig{ + P4Info: p4Info, + Cookie: &pb.ForwardingPipelineConfig_Cookie{ + Cookie: 159, + }, + }, + }); err != nil { + return errors.New("errors seen when sending SetForwardingPipelineConfig") + } + return nil +} + +// getTracerouteParameter returns Traceroute related parameters for testPacketIn testcase. +func getTracerouteParameter(t *testing.T) PacketIO { + return &TraceroutePacketIO{ + PacketIOPacket: PacketIOPacket{ + TTL: &TTL1, + HopLimit: &HopLimit1, + }, + IngressPort: fmt.Sprint(portID), + EgressPort: fmt.Sprint(portID + 7), + } +} + +// testPacket setup p4RT client, table entry and send traffic and returns the +// percentage of packets sent out of each egress port +func testPacket(t *testing.T, args *testArgs, cs gosnappi.ControlState, flowValues []*flowArgs, EgressPortMap map[string]bool) []float64 { + dut := ondatra.DUT(t, "dut") + ctx := context.Background() + ate := ondatra.ATE(t, "ate") + top := args.otgConfig + top.Flows().Clear() + + configureDeviceID(ctx, t, dut) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + time.Sleep(30 * time.Second) + + args = &testArgs{ + ctx: ctx, + leader: args.leader, + follower: args.follower, + dut: dut, + ate: ate, + top: top, + } + + if err := setupP4RTClient(ctx, args); err != nil { + t.Fatalf("Could not setup p4rt client: %v", err) + } + args.packetIO = getTracerouteParameter(t) + return testPacketIn(ctx, t, args, true, cs, flowValues, EgressPortMap) +} + +type TraceroutePacketIO struct { + PacketIOPacket + IngressPort string + EgressPort string +} + +// GetTableEntry creates p4rtutils acl entry which is used to get the configured p4rt trap rules. +// A packet is sent to controller based on the trap rules +func (traceroute *TraceroutePacketIO) GetTableEntry(delete bool, IsIpv4 bool) []*p4rtutils.ACLWbbIngressTableEntryInfo { + if IsIpv4 { + actionType := pb.Update_INSERT + if delete { + actionType = pb.Update_DELETE + } + return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ + Type: actionType, + IsIpv4: 0x1, + TTL: 0x1, + TTLMask: 0xFF, + Priority: 1, + }, + { + Type: actionType, + IsIpv4: 0x1, + TTL: 0x0, + TTLMask: 0xFF, + Priority: 1, + }} + } + actionType := pb.Update_INSERT + if delete { + actionType = pb.Update_DELETE + } + return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ + Type: actionType, + IsIpv6: 0x1, + TTL: 0x1, + TTLMask: 0xFF, + Priority: 1, + }, + { + Type: actionType, + IsIpv6: 0x1, + TTL: 0x0, + TTLMask: 0xFF, + Priority: 1, + }} +} + +// GetPacketTemplate returns expected packets in PacketIn. +func (traceroute *TraceroutePacketIO) GetPacketTemplate() *PacketIOPacket { + return &traceroute.PacketIOPacket +} + +func (traceroute *TraceroutePacketIO) GetTrafficFlow(ate *ondatra.ATEDevice, dstMac string, isIpv4 bool, + TTL uint8, frameSize uint32, frameRate uint64, dstIP string, flowValues *flowArgs) gosnappi.Flow { + + flow := gosnappi.NewFlow() + flow.SetName(flowValues.flowName) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(tracerouteSrcMAC) + ethHeader.Dst().SetValue(dstMac) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(flowValues.outHdrSrcIP) + ipHeader.Dst().SetValue(flowValues.outHdrDstIP) + ipHeader.TimeToLive().SetValue(uint32(TTL)) + if len(flowValues.outHdrDscp) != 0 { + ipHeader.Priority().Dscp().Phb().SetValues(flowValues.outHdrDscp) + } + if flowValues.udp { + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + + if flowValues.proto != 0 { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Protocol().SetValue(flowValues.proto) + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + innerIPHdr.TimeToLive().SetValue(uint32(TTL)) + } else { + if flowValues.isInnHdrV4 { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } else { + innerIpv6Hdr := flow.Packet().Add().Ipv6() + innerIpv6Hdr.Src().SetValue(flowValues.InnHdrSrcIPv6) + innerIpv6Hdr.Dst().SetValue(flowValues.InnHdrDstIPv6) + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + } + + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(uint64(frameRate)) + return flow +} + +// GetIngressPort return expected ingress port info in Packetin. +func (traceroute *TraceroutePacketIO) GetIngressPort() string { + return traceroute.IngressPort +} diff --git a/feature/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 new file mode 100644 index 00000000000..1d757dab977 --- /dev/null +++ b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go @@ -0,0 +1,1141 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "flag" + "fmt" + "sort" + "strconv" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + baseScenario "github.com/openconfig/featureprofiles/internal/encapfrr" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +// Settings for configuring the baseline testbed with the test +// topology. +// +// ATE port-1 <------> port-1 DUT +// DUT port-2 <------> port-2 ATE +// DUT port-3 <------> port-3 ATE +// DUT port-4 <------> port-4 ATE +// DUT port-5 <------> port-5 ATE +// DUT port-6 <------> port-6 ATE +// DUT port-7 <------> port-7 ATE +// DUT port-8 <------> port-8 ATE + +const ( + plenIPv4 = 30 + plenIPv6 = 126 + dscpEncapA1 = 10 + dscpEncapB1 = 20 + dscpEncapNoMatch = 30 + ipv4OuterSrc111Addr = "198.51.100.111" + ipv4OuterSrc222Addr = "198.51.100.222" + ipv4OuterSrcAddr = "198.100.200.123" + ipv4OuterDst111 = "192.51.100.64" + ipv4OuterDst111WithMask = "192.51.100.0/24" + ipv4InnerDst = "138.0.11.8" + noMatchEncapDest = "20.0.0.1" + maskLen24 = "24" + maskLen126 = "124" + maskLen32 = "32" + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niEncapTeVrfB = "ENCAP_TE_VRF_B" + niTeVrf111 = "TE_VRF_111" + niTeVrf222 = "TE_VRF_222" + niRepairVrf = "REPAIR_VRF" + niTransitVRF = "TRANSIT_VRF" + tolerance = 0.02 + encapFlow = "encapFlow" + gribiIPv4EntryVRF1111 = "203.0.113.1" + gribiIPv4EntryVRF1112 = "203.0.113.2" + gribiIPv4EntryEncapVRF = "138.0.11.0" + gribiIPv6EntryEncapVRF = "2001:db8::138:0:11:0" + ipv6InnerDst = "2001:db8::138:0:11:8" + ipv6InnerDstNoEncap = "2001:db8::20:0:0:1" + + dutAreaAddress = "49.0001" + dutSysID = "1920.0000.2001" + otgSysID1 = "640000000001" + isisInstance = "DEFAULT" + otgIsisPort8LoopV4 = "203.0.113.10" + otgIsisPort8LoopV6 = "2001:db8::203:0:113:10" + dutAS = 65501 + peerGrpName1 = "BGP-PEER-GROUP1" + ipOverIPProtocol = 4 +) + +var ( + p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + streamName = "p4rt" + tracerouteSrcMAC = "00:01:00:02:00:03" + deviceID = uint64(1) + portID = uint32(10) + electionID = uint64(100) + MetadataIngressPort = uint32(1) + MetadataEgressPort = uint32(2) + TTL1 = uint8(1) + HopLimit1 = uint8(1) + + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + MAC: "02:00:01:01:01:01", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + MAC: "02:00:01:01:01:02", + IPv6: "2001:db8::192:0:2:6", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv6: "2001:db8::192:0:2:9", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort3 = attrs.Attributes{ + Name: "atePort3", + IPv4: "192.0.2.10", + MAC: "02:00:01:01:01:03", + IPv6: "2001:db8::192:0:2:a", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv6: "2001:db8::192:0:2:d", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort4 = attrs.Attributes{ + Name: "atePort4", + IPv4: "192.0.2.14", + MAC: "02:00:01:01:01:04", + IPv6: "2001:db8::192:0:2:e", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort5 = attrs.Attributes{ + Desc: "dutPort5", + IPv4: "192.0.2.17", + IPv6: "2001:db8::192:0:2:11", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort5 = attrs.Attributes{ + Name: "atePort5", + IPv4: "192.0.2.18", + MAC: "02:00:01:01:01:05", + IPv6: "2001:db8::192:0:2:12", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort6 = attrs.Attributes{ + Desc: "dutPort6", + IPv4: "192.0.2.21", + IPv6: "2001:db8::192:0:2:15", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort6 = attrs.Attributes{ + Name: "atePort6", + IPv4: "192.0.2.22", + MAC: "02:00:01:01:01:06", + IPv6: "2001:db8::192:0:2:16", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort7 = attrs.Attributes{ + Desc: "dutPort7", + IPv4: "192.0.2.25", + IPv6: "2001:db8::192:0:2:19", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort7 = attrs.Attributes{ + Name: "atePort7", + IPv4: "192.0.2.26", + MAC: "02:00:01:01:01:07", + IPv6: "2001:db8::192:0:2:1a", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort8 = attrs.Attributes{ + Desc: "dutPort8", + IPv4: "192.0.2.29", + IPv6: "2001:db8::192:0:2:1d", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort8 = attrs.Attributes{ + Name: "atePort8", + IPv4: "192.0.2.30", + MAC: "02:00:01:01:01:08", + IPv6: "2001:db8::192:0:2:1e", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + // loopbackIntfName string + // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 + // Below code will be uncommented once ixia issue is fixed. + // tolerance = 0.2 + + portsMap = map[string]attrs.Attributes{ + "dutPort1": dutPort1, + "atePort1": atePort1, + "dutPort2": dutPort2, + "atePort2": atePort2, + "dutPort3": dutPort3, + "atePort3": atePort3, + "dutPort4": dutPort4, + "atePort4": atePort4, + "dutPort5": dutPort5, + "atePort5": atePort5, + "dutPort6": dutPort6, + "atePort6": atePort6, + "dutPort7": dutPort7, + "atePort7": atePort7, + "dutPort8": dutPort8, + "atePort8": atePort8, + } + portsIPv4 = map[string]string{ + "dut:port1": "192.0.2.1", + "ate:port1": "192.0.2.2", + + "dut:port2": "192.0.2.5", + "ate:port2": "192.0.2.6", + + "dut:port3": "192.0.2.9", + "ate:port3": "192.0.2.10", + + "dut:port4": "192.0.2.13", + "ate:port4": "192.0.2.14", + + "dut:port5": "192.0.2.17", + "ate:port5": "192.0.2.18", + + "dut:port6": "192.0.2.21", + "ate:port6": "192.0.2.22", + + "dut:port7": "192.0.2.25", + "ate:port7": "192.0.2.26", + + "dut:port8": "192.0.2.29", + "ate:port8": "192.0.2.30", + } + portsIPv6 = map[string]string{ + "dut:port1": "2001:db8::192:0:2:1", + "ate:port1": "2001:db8::192:0:2:2", + + "dut:port2": "2001:db8::192:0:2:5", + "ate:port2": "2001:db8::192:0:2:6", + + "dut:port3": "2001:db8::192:0:2:9", + "ate:port3": "2001:db8::192:0:2:a", + + "dut:port4": "2001:db8::192:0:2:d", + "ate:port4": "2001:db8::192:0:2:e", + + "dut:port5": "2001:db8::192:0:2:11", + "ate:port5": "2001:db8::192:0:2:12", + + "dut:port6": "2001:db8::192:0:2:15", + "ate:port6": "2001:db8::192:0:2:16", + + "dut:port7": "2001:db8::192:0:2:19", + "ate:port7": "2001:db8::192:0:2:1a", + + "dut:port8": "2001:db8::192:0:2:1d", + "ate:port8": "2001:db8::192:0:2:1e", + } + otgPortDevices []gosnappi.Device + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.11", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + loopbackIntfName string + atePortNamelist []string +) + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +type testArgs struct { + ctx context.Context + client *fluent.GRIBIClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgConfig gosnappi.Config + top gosnappi.Config + electionID gribi.Uint128 + otg *otg.OTG + leader *p4rt_client.P4RTClient + follower *p4rt_client.P4RTClient + packetIO PacketIO +} + +type flowArgs struct { + flowName string + outHdrSrcIP, outHdrDstIP string + InnHdrSrcIP, InnHdrDstIP string + InnHdrSrcIPv6, InnHdrDstIPv6 string + udp, isInnHdrV4 bool + outHdrDscp []uint32 + proto uint32 +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func sortPorts(ports []*ondatra.Port) []*ondatra.Port { + sort.Slice(ports, func(i, j int) bool { + idi, idj := ports[i].ID(), ports[j].ID() + li, lj := len(idi), len(idj) + if li == lj { + return idi < idj + } + return li < lj // "port2" < "port10" + }) + return ports +} + +// dutInterface builds a DUT interface ygot struct for a given port +// according to portsIPv4. Returns nil if the port has no IP address +// mapping. +func dutInterface(p *ondatra.Port, dut *ondatra.DUTDevice, portIDx uint32) *oc.Interface { + id := fmt.Sprintf("%s:%s", p.Device().ID(), p.ID()) + i := &oc.Interface{ + Name: ygot.String(p.Name()), + Description: ygot.String(p.String()), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Id: ygot.Uint32(portIDx), + } + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + if p.PMD() == ondatra.PMD100GBASEFR { + e := i.GetOrCreateEthernet() + e.AutoNegotiate = ygot.Bool(false) + e.DuplexMode = oc.Ethernet_DuplexMode_FULL + e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } + + ipv4, ok := portsIPv4[id] + if !ok { + return nil + } + ipv6, ok := portsIPv6[id] + if !ok { + return nil + } + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + + a := s4.GetOrCreateAddress(ipv4) + a.PrefixLength = ygot.Uint8(plenIPv4) + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + a6 := s6.GetOrCreateAddress(ipv6) + a6.PrefixLength = ygot.Uint8(plenIPv6) + + return i +} + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice, dutPortList []*ondatra.Port) { + dc := gnmi.OC() + for idx, dp := range dutPortList { + portIDx := portID + uint32(idx) + if i := dutInterface(dp, dut, portIDx); i != nil { + gnmi.Replace(t, dut, dc.Interface(dp.Name()).Config(), i) + } else { + t.Fatalf("No address found for port %v", dp) + } + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + for _, dp := range dut.Ports() { + fptest.AssignToNetworkInstance(t, dut, dp.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + } + if deviations.ExplicitPortSpeed(dut) { + for _, dp := range dut.Ports() { + fptest.SetPortSpeed(t, dp) + } + } + + loopbackIntfName = netutil.LoopbackInterface(t, dut, 0) + lo0 := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + loop1 := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, dc.Interface(loopbackIntfName).Config(), loop1) + } else { + v4, ok := ipv4Addrs[0].Val() + if ok { + dutlo0Attrs.IPv4 = v4.GetIp() + } + v6, ok := ipv6Addrs[0].Val() + if ok { + dutlo0Attrs.IPv6 = v6.GetIp() + } + t.Logf("Got DUT IPv4 loopback address: %v", dutlo0Attrs.IPv4) + t.Logf("Got DUT IPv6 loopback address: %v", dutlo0Attrs.IPv6) + } +} + +// configureAdditionalGribiAft configures additional AFT entries for Gribi. +func configureAdditionalGribiAft(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3000).WithNextHopNetworkInstance(niRepairVrf), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3000).AddNextHop(3000, 1), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1000), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(1001), + + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(101). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList := []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112} + for ip := range defaultVRFIPList { + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + // Programming AFT entries for prefixes in ENCAP_TE_VRF_A + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(200).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(200).AddNextHop(200, 1), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + args.client.Modify().AddEntry(t, + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(102).AddNextHop(101, 3).AddNextHop(102, 1).WithBackupNHG(200), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv6Operation(gribiIPv6EntryEncapVRF+"/124"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + +} + +// configureGribiRoute configures Gribi route for prefix +func configureGribiRoute(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs, prefWithMask string) { + t.Helper() + // Using gRIBI, install an IPv4Entry for the prefix 192.51.100.1/24 that points to a + // NextHopGroup that contains a single NextHop that specifies decapsulating the IPv4 + // header and specifies the DEFAULT network instance.This IPv4Entry should be installed + // into the DECAP_TE_VRF. + + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3001).WithDecapsulateHeader(fluent.IPinIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3001).AddNextHop(3001, 1), + fluent.IPv4Entry().WithNetworkInstance(niDecapTeVrf). + WithPrefix(prefWithMask).WithNextHopGroup(3001). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv4Operation(prefWithMask).WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) +} + +// configureISIS configures ISIS on the DUT. +func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName, dutAreaAddress, dutSysID string) { + t.Helper() + d := &oc.Root{} + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + + lspBit := globalISIS.GetOrCreateLspBit().GetOrCreateOverloadBit() + lspBit.SetBit = ygot.Bool(false) + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + + isisIntf := isis.GetOrCreateInterface(intfName) + isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(intfName) + isisIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + + if deviations.InterfaceRefConfigUnsupported(dut) { + isisIntf.InterfaceRef = nil + } + + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Enabled = ygot.Bool(true) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Metric = ygot.Uint32(20) + isisIntfLevelAfiv6.Metric = ygot.Uint32(20) + + isisIntfLevelAfiv6.Enabled = ygot.Bool(true) + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfiv4.Enabled = nil + isisIntfLevelAfiv6.Enabled = nil + } + gnmi.Update(t, dut, gnmi.OC().Config(), d) +} + +// bgpCreateNbr creates BGP neighbor configuration +func bgpCreateNbr(localAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutlo0Attrs.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) + pg1.PeerAs = ygot.Uint32(localAs) + + bgpNbr := bgp.GetOrCreateNeighbor(otgIsisPort8LoopV4) + bgpNbr.PeerGroup = ygot.String(peerGrpName1) + bgpNbr.PeerAs = ygot.Uint32(localAs) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbrT := bgpNbr.GetOrCreateTransport() + bgpNbrT.LocalAddress = ygot.String(dutlo0Attrs.IPv4) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + + return niProto +} + +// verifyISISTelemetry verifies ISIS telemetry. +func verifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntf string) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + dutIntf = dutIntf + ".0" + } + nbrPath := statePath.Interface(dutIntf) + query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", dutIntf) + t.Fatal("No IS-IS adjacencies reported.") + } +} + +// verifyBgpTelemetry verifies BGP telemetry. +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + nbrPath := bgpPath.Neighbor(otgIsisPort8LoopV4) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", otgIsisPort8LoopV4, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", otgIsisPort8LoopV4, state, want) + } +} + +// configureOTG configures the topology of the ATE. +func configureOTG(t testing.TB, otg *otg.OTG, atePorts []*ondatra.Port) gosnappi.Config { + t.Helper() + t.Logf("configureOTG") + config := gosnappi.NewConfig() + pmd100GFRPorts := []string{} + for i, ap := range atePorts { + if ap.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, ap.ID()) + } + // DUT and ATE ports are connected by the same names. + dutid := fmt.Sprintf("dut:%s", ap.ID()) + ateid := fmt.Sprintf("ate:%s", ap.ID()) + + port := config.Ports().Add().SetName(ap.ID()) + atePortNamelist = append(atePortNamelist, port.Name()) + portName := fmt.Sprintf("atePort%s", strconv.Itoa(i+1)) + dev := config.Devices().Add().SetName(portName) + macAddress := portsMap[portName].MAC + eth := dev.Ethernets().Add().SetName(portName + ".Eth").SetMac(macAddress) + eth.Connection().SetPortName(port.Name()) + eth.Ipv4Addresses().Add().SetName(portName + ".IPv4"). + SetAddress(portsIPv4[ateid]).SetGateway(portsIPv4[dutid]). + SetPrefix(plenIPv4) + eth.Ipv6Addresses().Add().SetName(portName + ".IPv6"). + SetAddress(portsIPv6[ateid]).SetGateway(portsIPv6[dutid]). + SetPrefix(plenIPv6) + + otgPortDevices = append(otgPortDevices, dev) + if i == 7 { + iDut8LoopV4 := dev.Ipv4Loopbacks().Add().SetName("Port8LoopV4").SetEthName(eth.Name()) + iDut8LoopV4.SetAddress(otgIsisPort8LoopV4) + iDut8LoopV6 := dev.Ipv6Loopbacks().Add().SetName("Port8LoopV6").SetEthName(eth.Name()) + iDut8LoopV6.SetAddress(otgIsisPort8LoopV6) + isisDut := dev.Isis().SetName("ISIS1").SetSystemId(otgSysID1) + isisDut.Basic().SetIpv4TeRouterId(portsIPv4[ateid]).SetHostname(isisDut.Name()).SetLearnedLspFilter(true) + isisDut.Interfaces().Add().SetEthName(dev.Ethernets().Items()[0].Name()). + SetName("devIsisInt1"). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + // Advertise OTG Port8 loopback address via ISIS. + isisPort2V4 := dev.Isis().V4Routes().Add().SetName("ISISPort8V4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(otgIsisPort8LoopV4).SetPrefix(32) + isisPort2V6 := dev.Isis().V6Routes().Add().SetName("ISISPort8V6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(otgIsisPort8LoopV6).SetPrefix(uint32(128)) + iDutBgp := dev.Bgp().SetRouterId(otgIsisPort8LoopV4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(iDut8LoopV4.Name()).Peers().Add().SetName(ap.ID() + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(dutlo0Attrs.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.Capability().SetIpv4Unicast(true).SetIpv6Unicast(true) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(port.Name() + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(otgIsisPort8LoopV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32). + SetCount(1).SetStep(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(noMatchEncapDest).SetPrefix(32). + SetCount(1).SetStep(1) + + bgpNeti1Bgp6PeerRoutes := iDutBgp4Peer.V6Routes().Add().SetName(atePort8.Name + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(otgIsisPort8LoopV6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDst).SetPrefix(128). + SetCount(1).SetStep(1) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDstNoEncap).SetPrefix(128). + SetCount(1).SetStep(1) + + } + + } + config.Captures().Add().SetName("packetCapture"). + SetPortNames([]string{atePortNamelist[1], atePortNamelist[2], atePortNamelist[3], atePortNamelist[4], + atePortNamelist[5], atePortNamelist[6], atePortNamelist[7]}). + SetFormat(gosnappi.CaptureFormat.PCAP) + + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := config.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + pb, _ := config.Marshal().ToProto() + t.Log(pb.GetCaptures()) + return config +} + +func startCapture(t *testing.T, args *testArgs, capturePortList []string) gosnappi.ControlState { + t.Helper() + args.otgConfig.Captures().Clear() + args.otgConfig.Captures().Add().SetName("packetCapture"). + SetPortNames(capturePortList). + SetFormat(gosnappi.CaptureFormat.PCAP) + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + args.otg.SetControlState(t, cs) + return cs +} + +func verifyPortStatus(t *testing.T, args *testArgs, portList []string, portStatus bool) { + wantStatus := oc.Interface_OperStatus_UP + if !portStatus { + wantStatus = oc.Interface_OperStatus_DOWN + } + for _, port := range portList { + p := args.dut.Port(t, port) + t.Log("Check for port status") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), 1*time.Minute, wantStatus) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if operStatus != wantStatus { + t.Errorf("Get(DUT %v oper status): got %v, want %v", port, operStatus, wantStatus) + } + } +} + +func validateTrafficDistribution(t *testing.T, ate *ondatra.ATEDevice, wantWeights []float64, gotWeights []float64) { + t.Log("Verify packet load balancing as per the programmed weight") + t.Log("got ratio:", gotWeights) + t.Log("want ratio:", wantWeights) + if diff := cmp.Diff(wantWeights, gotWeights, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } +} + +// testGribiMatchNoSourceNoProtocolMacthDSCP is to test based on packet which doesn't match source IP and protocol +// but match DSCP value +// Test-1 +func testGribiMatchNoSourceNoProtocolMacthDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": true, "16": false, "17": false} + LoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrcAddr, outHdrDstIP: ipv4InnerDst, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTunnelTrafficMatchDefaultTerm is to test Tunnel traffic match default term +// Test-2 +func testTunnelTrafficMatchDefaultTerm(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": false, "12": false, "13": false, "14": false, "15": false, "16": false, "17": true} + LoadBalancePercent := []float64{0, 0, 0, 0, 0, 0, 1} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrcAddr, outHdrDstIP: noMatchEncapDest, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: noMatchEncapDest, isInnHdrV4: true, udp: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testGribiDecapMatchSrcProtoDSCP is to test Gribi decap match src proto DSCP +// Test-3 +func testGribiDecapMatchSrcProtoDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": false, "16": false, "17": false} + LoadBalancePercent := []float64{0.0625, 0.1875, 0.75, 0, 0, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: gribiIPv4EntryVRF1111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTunnelTrafficNoDecap is to test Tunnel traffic no decap +// Test-6 +func testTunnelTrafficNoDecap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": false, "12": false, "13": false, "14": false, "15": false, "16": false, "17": true} + LoadBalancePercent := []float64{0, 0, 0, 0, 0, 0, 1} + + cases := []struct { + desc string + prefixWithMask string + }{{ + desc: "Mask Length 24", + prefixWithMask: "192.51.100.0/24", + }, { + desc: "Mask Length 32", + prefixWithMask: "192.51.100.64/32", + }, { + desc: "Mask Length 28", + prefixWithMask: "192.51.100.64/28", + }, { + desc: "Mask Length 22", + prefixWithMask: "192.51.100.0/22", + }} + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + t.Run("Program gRIBi route for Prefix "+tc.prefixWithMask, func(t *testing.T) { + configureGribiRoute(ctx, t, dut, args, tc.prefixWithMask) + }) + t.Run("Create ip-in-ip and ipv6-in-ip flows, send traffic and verify decap functionality", + func(t *testing.T) { + // Send both 6in4 and 4in4 packets. Verify that the packets have their outer + // v4 header stripped and are forwarded according to the route in the DEFAULT + // VRF that matches the inner IP address. + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) + }) + }) + } +} + +// testTunnelTrafficDecapEncap is to test Tunnel traffic decap encap +// Test-9 +func testTunnelTrafficDecapEncap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + configureGribiRoute(ctx, t, dut, args, ipv4OuterDst111WithMask) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": true, "16": false, "17": false} + LoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc222Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) + + LoadBalancePercent = []float64{0.0468, 0.1406, 0.5625, 0, 0.25, 0, 0} + flow = []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc222Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState = startCapture(t, args, baseCapturePortList) + gotWeights = testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTraceRoute is to test Test FRR behaviors with encapsulation scenarios +func TestTraceRoute(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + gribic := dut.RawAPIs().GRIBI(t) + top := gosnappi.NewConfig() + dutPorts := sortPorts(dut.Ports())[0:8] + atePorts := sortPorts(ate.Ports())[0:8] + + t.Log("Configure Default Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.BackupNHGRequiresVrfWithDecap(dut) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + pf := ni.GetOrCreatePolicyForwarding() + fp1 := pf.GetOrCreatePolicy("match-ipip") + fp1.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + fp1.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipOverIPProtocol) + fp1.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(deviations.DefaultNetworkInstance(dut)) + p1 := dut.Port(t, "port1") + intf := pf.GetOrCreateInterface(p1.Name()) + intf.ApplyVrfSelectionPolicy = ygot.String("match-ipip") + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), pf) + } + + t.Run("Configure Interface on DUT", func(t *testing.T) { + configureDUT(t, dut, dutPorts) + }) + + t.Log("Apply vrf selection policy_c to DUT port-1") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyC) + + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + // staticARPWithMagicUniversalIP(t, dut) + baseScenario.StaticARPWithMagicUniversalIP(t, dut) + } + + t.Log("Install BGP route resolved by ISIS.") + t.Log("Configure ISIS on DUT") + configureISIS(t, dut, dut.Port(t, "port8").Name(), dutAreaAddress, dutSysID) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + var otgConfig gosnappi.Config + t.Run("Configure OTG", func(t *testing.T) { + otgConfig = configureOTG(t, otg, atePorts) + }) + + // Connect gRIBI client to DUT referred to as gRIBI - using PRESERVE persistence and + // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) + client.Start(ctx, t) + + client.StartSending(ctx, t) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for clientA: %v", err) + } + eID := gribi.BecomeLeader(t, client) + + leader := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := leader.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + t.Fatalf("Could not initialize p4rt client: %v", err) + } + + follower := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := follower.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + t.Fatalf("Could not initialize p4rt client: %v", err) + } + args := &testArgs{ + ctx: ctx, + client: client, + dut: dut, + ate: ate, + otgConfig: otgConfig, + top: top, + electionID: eID, + otg: otg, + leader: leader, + follower: follower, + } + t.Log("Configure gRIBI routes") + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(client); err != nil { + t.Fatal(err) + } + + t.Log("Verify whether the ports are in up state") + portList := []string{"port2", "port3", "port4", "port5", "port6", "port7", "port8"} + verifyPortStatus(t, args, portList, true) + + t.Log("Verify ISIS telemetry") + verifyISISTelemetry(t, dut, dut.Port(t, "port8").Name()) + t.Log("Verify BGP telemetry") + verifyBgpTelemetry(t, dut) + + t.Run("Test-1: Match on DSCP, no Source and no Protocol", func(t *testing.T) { + testGribiMatchNoSourceNoProtocolMacthDSCP(ctx, t, dut, args) + }) + + t.Log("Delete vrf selection policy C and Apply vrf selectioin policy W") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyW) + + t.Run("Test-2: Match on default term and send to default VRF", func(t *testing.T) { + testTunnelTrafficMatchDefaultTerm(ctx, t, dut, args) + }) + t.Run("Test-3: Match on source, protocol and DSCP, VRF_DECAP hit -> VRF_ENCAP_A miss -> DEFAULT", func(t *testing.T) { + testGribiDecapMatchSrcProtoDSCP(ctx, t, dut, args) + }) + // Below test case will implement later + /* + t.Run("Test-4: Tests that traceroute respects transit FRR", func(t *testing.T) { + + }) + t.Run("Test-5: Tests that traceroute respects transit FRR when the backup is also unviable.", func(t *testing.T) { + + })*/ + t.Run("Test-6: Tunneled traffic with no decap", func(t *testing.T) { + testTunnelTrafficNoDecap(ctx, t, dut, args) + }) + // Below test case will implement later + /* + t.Run("Test-7: Encap failure cases (TBD on confirmation)", func(t *testing.T) { + + }) + t.Run("Test-8: Tests that traceroute for a packet with a route lookup miss has an unset target_egress_port.", func(t *testing.T) { + + })*/ + t.Run("Test-9: Decap then encap", func(t *testing.T) { + testTunnelTrafficDecapEncap(ctx, t, dut, args) + }) +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/README.md b/feature/p4rt/otg_tests/traceroute_packetout_test/README.md similarity index 77% rename from feature/experimental/p4rt/otg_tests/traceroute_packetout_test/README.md rename to feature/p4rt/otg_tests/traceroute_packetout_test/README.md index 36f0890ad29..409e605ef8e 100644 --- a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/README.md +++ b/feature/p4rt/otg_tests/traceroute_packetout_test/README.md @@ -50,13 +50,23 @@ setting must not be interpreted as the actual egress port id. * Validate: * Traffic received over the appropriate ATE port. - - -## Protocol/RPC Parameter Coverage - -* No new configuration covered. - - -## Telemetry Parameter Coverage - -* No new telemetry covered. +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +```yaml +paths: + # config paths + /interfaces/interface/config/id: + /components/component/integrated-circuit/config/node-id: + platform_type: ["INTEGRATED_CIRCUIT"] + # state paths + /interfaces/interface/state/id: + /components/component/integrated-circuit/state/node-id: + platform_type: ["INTEGRATED_CIRCUIT"] + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/metadata.textproto b/feature/p4rt/otg_tests/traceroute_packetout_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/otg_tests/traceroute_packetout_test/metadata.textproto rename to feature/p4rt/otg_tests/traceroute_packetout_test/metadata.textproto diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/packetout_test.go b/feature/p4rt/otg_tests/traceroute_packetout_test/packetout_test.go similarity index 100% rename from feature/experimental/p4rt/otg_tests/traceroute_packetout_test/packetout_test.go rename to feature/p4rt/otg_tests/traceroute_packetout_test/packetout_test.go diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go b/feature/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go similarity index 93% rename from feature/experimental/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go rename to feature/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go index 4a1b5daa721..74b7b98b949 100644 --- a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go +++ b/feature/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go @@ -32,6 +32,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/otgutils" "github.com/openconfig/featureprofiles/internal/p4rtutils" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" @@ -41,13 +42,14 @@ import ( ) const ( - ipv4PrefixLen = 30 - ipv6PrefixLen = 126 - deviceID = uint64(100) - ingressPortId = uint32(2100) - egressPortId = ingressPortId + 1 - electionId = uint64(100) - dstMAC = "00:1A:11:00:00:01" + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + deviceID = uint64(100) + ingressDeviceID = uint64(101) + ingressPortId = uint32(2100) + egressPortId = ingressPortId + 1 + electionId = uint64(100) + dstMAC = "00:1A:11:00:00:01" ) var ( @@ -142,16 +144,29 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { // configureDeviceIDs configures p4rt device-id on the DUT. func configureDeviceID(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice) { nodes := p4rtutils.P4RTNodesByPort(t, dut) - p4rtNode, ok := nodes["port1"] + p4rtNode, ok := nodes["port2"] if !ok { - t.Fatal("Couldn't find P4RT Node for port: port1") + t.Fatal("Couldn't find P4RT Node for port: port2") } t.Logf("Configuring P4RT Node: %s", p4rtNode) + ingressP4RtNode, ok := nodes["port1"] + if !ok { + t.Fatal("Couldn't find P4RT Node for port: port1") + } + t.Logf("Configuring P4RT Node: %s", ingressP4RtNode) + c := oc.Component{} c.Name = ygot.String(p4rtNode) c.IntegratedCircuit = &oc.Component_IntegratedCircuit{} c.IntegratedCircuit.NodeId = ygot.Uint64(deviceID) gnmi.Replace(t, dut, gnmi.OC().Component(p4rtNode).Config(), &c) + if p4rtNode != ingressP4RtNode { + c := oc.Component{} + c.Name = ygot.String(ingressP4RtNode) + c.IntegratedCircuit = &oc.Component_IntegratedCircuit{} + c.IntegratedCircuit.NodeId = ygot.Uint64(ingressDeviceID) + gnmi.Replace(t, dut, gnmi.OC().Component(ingressP4RtNode).Config(), &c) + } } func setupP4RTClient(ctx context.Context, client *p4rt_client.P4RTClient) error { @@ -224,6 +239,8 @@ func TestPacketOut(t *testing.T) { otg := ate.OTG() otg.PushConfig(t, top) otg.StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") configureDeviceID(ctx, t, dut) diff --git a/feature/experimental/p4rt/tests/metadata_validation_test/README.md b/feature/p4rt/tests/metadata_validation_test/README.md similarity index 81% rename from feature/experimental/p4rt/tests/metadata_validation_test/README.md rename to feature/p4rt/tests/metadata_validation_test/README.md index 8ad072ab172..8a311ea7d05 100644 --- a/feature/experimental/p4rt/tests/metadata_validation_test/README.md +++ b/feature/p4rt/tests/metadata_validation_test/README.md @@ -23,3 +23,16 @@ Validate the P4RT server handles Metadata set in Table Entry correctly. ## Notes * [P4RT Proto](https://github.com/p4lang/p4runtime/blob/main/proto/p4/v1/p4runtime.proto) + +## OpenConfig Path and RPC Coverage +```yaml +paths: + /components/component/integrated-circuit/config/node-id: + platform_type: ["INTEGRATED_CIRCUIT"] + /interfaces/interface/config/id: +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/p4rt/tests/metadata_validation_test/metadata.textproto b/feature/p4rt/tests/metadata_validation_test/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/tests/metadata_validation_test/metadata.textproto rename to feature/p4rt/tests/metadata_validation_test/metadata.textproto diff --git a/feature/experimental/p4rt/tests/metadata_validation_test/metadata_validation_test.go b/feature/p4rt/tests/metadata_validation_test/metadata_validation_test.go similarity index 100% rename from feature/experimental/p4rt/tests/metadata_validation_test/metadata_validation_test.go rename to feature/p4rt/tests/metadata_validation_test/metadata_validation_test.go diff --git a/feature/experimental/p4rt/tests/p4rt_election/README.md b/feature/p4rt/tests/p4rt_election/README.md similarity index 96% rename from feature/experimental/p4rt/tests/p4rt_election/README.md rename to feature/p4rt/tests/p4rt_election/README.md index 5b77cea3ccf..9226182e9e4 100644 --- a/feature/experimental/p4rt/tests/p4rt_election/README.md +++ b/feature/p4rt/tests/p4rt_election/README.md @@ -106,3 +106,16 @@ Validate the P4RT server handles primary election and failover. writes for clients with `election_id=9` & `election_id=10`. * TODO: Enable P4RT on an additional FAP and verify that the same set of scenarios work independently of the first FAP + +## OpenConfig Path and RPC Coverage +```yaml +paths: + /components/component/integrated-circuit/config/node-id: + platform_type: ["INTEGRATED_CIRCUIT"] + /interfaces/interface/config/id: +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/p4rt/tests/p4rt_election/metadata.textproto b/feature/p4rt/tests/p4rt_election/metadata.textproto similarity index 100% rename from feature/experimental/p4rt/tests/p4rt_election/metadata.textproto rename to feature/p4rt/tests/p4rt_election/metadata.textproto diff --git a/feature/experimental/p4rt/tests/p4rt_election/p4rt_election_test.go b/feature/p4rt/tests/p4rt_election/p4rt_election_test.go similarity index 100% rename from feature/experimental/p4rt/tests/p4rt_election/p4rt_election_test.go rename to feature/p4rt/tests/p4rt_election/p4rt_election_test.go diff --git a/feature/p4rt/wbb.p4info.pb.txt b/feature/p4rt/wbb.p4info.pb.txt new file mode 100644 index 00000000000..d5ddec0d53a --- /dev/null +++ b/feature/p4rt/wbb.p4info.pb.txt @@ -0,0 +1,173 @@ +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 && outer_vlan_id::mask == 0\n ) ||\n // LLDP\n (\n ether_type == 0x88cc &&\n is_ipv4::mask == 0 && is_ipv6::mask == 0 && ttl::mask == 0 &&\n outer_vlan_id::mask == 0\n ) ||\n // ND\n (\n // TODO(b/188575259) remove optional match for VLAN ID once VLAN ID is\n // completely removed from ND flows.\n (( outer_vlan_id::mask == 0xfff && outer_vlan_id == 0x0FA0) ||\n outer_vlan_id::mask == 0);\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 + } + match_fields { + id: 5 + name: "outer_vlan_id" + annotations: "@sai_field(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)" + bitwidth: 12 + 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/platform/controllercard/feature.textproto b/feature/platform/controllercard/feature.textproto index c406648800d..d8b1bda06cb 100644 --- a/feature/platform/controllercard/feature.textproto +++ b/feature/platform/controllercard/feature.textproto @@ -1,3 +1,6 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { name: "platform_controllercard" version: 1 diff --git a/feature/platform/controllercard/tests/port/README.md b/feature/platform/controllercard/tests/port/README.md new file mode 100644 index 00000000000..5ee88e7e7a8 --- /dev/null +++ b/feature/platform/controllercard/tests/port/README.md @@ -0,0 +1,65 @@ +# gNMI-1.22: Controller card port attributes + +## Summary + +Validate PORT components attached to a CONTROLLER_CARD are modeled with the +expected OC paths. The operational use case is: + +1. As an automated network repair tool, I want to ensure at least one + interface between redundant CONTROLLER_CARD components is fully + operational. Before performing a power off or reboot of a CONTROLLER_CARD + or a network device wired to a CONTROLLER_CARD PORT, I want to use the OC + component tree to trace the subject PORT to it's associated redundant PORT. + +## Testbed type + +* [Single DUT](https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed) + +## Testbed prerequisites + +There are no DUT configuration pre-requisites for this test. The DUT must +contain the following component types: + +* At least one `CONTROLLER_CARD` component +* Each CONTROLLER_CARD must contain at least one `PORT` + +## Procedure + +* gNMI-1.22.1: Validate component PORT attributes attached to a CONTROLLER_CARD + * gNMI Subscribe to the /components and /interfaces tree using ONCE option. + * Verify each PORT present on a CONTROLLER_CARD has the following paths set: + * Search the components to to find components of type PORT with parent = CONTROLLER_CARD + * /components/component/state/parent = the appropriate component of type CONTROLLER_CARD + * Search the /interfaces/interface/state/hardware-port values to find the expected /components/component/name for the physical port on the CONTROLLER_CARD + * For each of these interfaces, verify /interfaces/interface/state/management = TRUE + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths and RPC intended to be covered by this test. + +```yaml +paths: + ## State Paths ## + /components/component/state/name: + platform_type: [ + "CONTROLLER_CARD", + "PORT" + ] + /components/component/state/type: + platform_type: [ + "CONTROLLER_CARD", + "PORT" + ] + /interfaces/interface/state/name: + /interfaces/interface/state/management: + /interfaces/interface/state/hardware-port: + +rpcs: + gnmi: + gNMI.Subscribe: + ONCE: true +``` + +## Minimum DUT platform requirement + +MFF - Modular form factor is specified to ensure coverage for redundant `CONTROLLER_CARD` platform_types. diff --git a/feature/platform/controllercard/tests/port/metadata.textproto b/feature/platform/controllercard/tests/port/metadata.textproto new file mode 100644 index 00000000000..74408b28d30 --- /dev/null +++ b/feature/platform/controllercard/tests/port/metadata.textproto @@ -0,0 +1,12 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +plan_id: "gNMI-1.22" +description: "Controller card port attributes" +uuid: "89333073-196f-4de3-9cd4-5c6f11f64905" + +testbed: TESTBED_DUT + +tags: TAGS_AGGREGATION +tags: TAGS_TRANSIT +tags: TAGS_DATACENTER_EDGE diff --git a/feature/platform/fabric/feature.textproto b/feature/platform/fabric/feature.textproto index b0ec826b2db..b86f5634e1f 100644 --- a/feature/platform/fabric/feature.textproto +++ b/feature/platform/fabric/feature.textproto @@ -1,3 +1,6 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { name: "platform_fabric" version: 1 diff --git a/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/README.md b/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/README.md index 65dfe9ab455..9b02357681f 100644 --- a/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/README.md +++ b/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/README.md @@ -2,14 +2,7 @@ ## Summary WBB is required to support gNMI Subscribe with SAMPLE or ON_CHANGE mode for various counters. -This test if to verify that DUT supports gNMI Subscribe with sample mode, updating -the available backplane capacity counters correctly while forwarding traffic - -* Get backplace capacity before any traffic is sent - -* Send some traffic and wait for the sample to be collected - -* Send more traffic and wait for the sample to be collected +This test if to verify that DUT supports gNMI Subscribe with ON_CHANGE for backplane-facing-capacity ## Testbed type @@ -17,7 +10,7 @@ the available backplane capacity counters correctly while forwarding traffic ## Procedure -### gNMI-1.18.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2322] +### gNMI-1.18.1 * Connect DUT port-1 and 2 to ATE port-1 and 2 respectively @@ -25,40 +18,23 @@ the available backplane capacity counters correctly while forwarding traffic * Configure IPv4/IPv6 addresses on the interfaces -* Using gNMI subscribe with "SAMPLE" mode - - * Run the test twice, once with a SAMPLE interval of 10 Seconds and once again - with a SAMPLE interval of 15 seconds for the below telemetry path +* Using gNMI subscribe with "ON_CHANGE" mode * /components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity - * Initiate traffic: - - * Initiate traffic as per below threshold: - - Port number | Interface1(line rate %) - -------------- | ----------------------- - Port1 | 20% - Port2 | 20% - - * Validate that we are receiving consumed capacity metrics at the selected SAMPLE interval + * consumed-capacity is the sum of the admin-up front panel interface speeds connected to the integrated-circuit - * Increase the traffic as per below threshold +* Ensure that we receieve the initial consumed-capacity metric - Port number | Interface1(line rate %) - -------------- | ----------------------- - Port1 | 70% - Port2 | 70% +* Set port-2 enabled=false - * Validate we are now receiving increased consumed capacity metrics at the selected SAMPLE interval + * Validate that we recieve a changed consumed-capacity metric of a higher value -### gNMI-1.18.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2323] +* Set port-2 enable=true -* Connect DUT port-1 and 2 to ATE port-1 and 2 respectively - - * For MFF DUT ports 1 and 2 SHOULD be on different linecards + * Validate that we recieve a changed consumed-capacity metric matching the initial value -* Configure IPv4/IPv6 addresses on the interfaces +### gNMI-1.18.2 * Use gNMI subscribe with "ON_CHANGE" mode for the below telemetry paths @@ -66,39 +42,84 @@ the available backplane capacity counters correctly while forwarding traffic * /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity * /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct -* Disable one of the available FABRIC component +* Make sure we recieve the initial metric for each of the telemetry paths + +* Disable two FABRIC components * set /components/component/{fabric}/config/power-admin-state to POWER_DISABLED -* Validate that we recieve changed metrics of a lower value for each of the telemetry paths +* Validate that we recieve changed metric of a lower value for the below telemetry paths -* Enable the FABRIC component that was disabled in the previous step + * /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity + * /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct + +* Ensure that the metric for the below telemetry path should not have changed hence no updated metric should be received - * Set /components/component/{fabric|linecard|controller-card}/config/power-admin-state to POWER_ENABLED + * /components/component/integrated-circuit/backplane-facing-capacity/state/total -* Validate that we recieve changed metrics of a higher value for each of the telemetry paths +* Enable the FABRIC components that were disabled previously -## Config parameter coverage + * Set /components/component/{fabric}/config/power-admin-state to POWER_ENABLED -* /interfaces/interface/config/enabled -* /interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled -* /interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled -* /components/component/{fabric}/config/power-admin-state +* Validate that we recieve changed metric matching the initial metric for the below of the telemetry paths -## Telemetry parameter coverage (gNMI subscribe with sample every 10 seconds) + * /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity + * /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct -* /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct -* /components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity -* /components/component/integrated-circuit/backplane-facing-capacity/state/total -* /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity +* Ensure that the metric for the below telemetry path should not have changed hence no updated metric should be received -## Protocol/RPC Parameter Coverage + * /components/component/integrated-circuit/backplane-facing-capacity/state/total -* gNMI - * Set - * Update - * Subscribe +## OpenConfig Path and RPC Coverage + +This example yaml 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. +```yaml +paths: + ## Config parameter coverage + /interfaces/interface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled: + /components/component/fabric/config/power-admin-state: + platform_type: ["FABRIC"] + + ## Telemetry parameter coverage + /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct: + platform_type: ["INTEGRATED_CIRCUIT"] + /components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity: + platform_type: [ "INTEGRATED_CIRCUIT" ] + /components/component/integrated-circuit/backplane-facing-capacity/state/total: + platform_type: [ "INTEGRATED_CIRCUIT" ] + /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity: + platform_type: [ "INTEGRATED_CIRCUIT" ] + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: + Mode: [ "ON_CHANGE", "SAMPLE" ] +``` ## Required DUT platform * MFF + +## 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: + /interfaces/interface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled: + /components/component/fabric/config/power-admin-state: + + ## State paths: N/A + +rpcs: + gnmi: + gNMI.Set: + Replace: +``` \ No newline at end of file diff --git a/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/metadata.textproto b/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/metadata.textproto new file mode 100644 index 00000000000..099686c7f73 --- /dev/null +++ b/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/metadata.textproto @@ -0,0 +1,37 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ef804c64-84dd-432d-ab38-630e9c82b42d" +plan_id: "gNMI-1.18" +description: "gNMI subscribe with sample mode for backplane capacity counters" +testbed: TESTBED_DUT +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + explicit_port_speed: true + explicit_interface_in_default_vrf: true + qos_queue_requires_id: true + missing_value_for_defaults: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + missing_value_for_defaults: true + } +} + diff --git a/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/sampled_backplane_capacity_counters_test.go b/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/sampled_backplane_capacity_counters_test.go new file mode 100644 index 00000000000..651bb61d582 --- /dev/null +++ b/feature/platform/fabric/otg_tests/sampled_backplane_capacity_counters_test/sampled_backplane_capacity_counters_test.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 sampled_backplane_capacity_counters_test + +import ( + "fmt" + "regexp" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/components" + "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/ygnmi/ygnmi" +) + +// Topology: +// ATE port 1 +// | +// DUT--------ATE port 3 +// | +// ATE port 2 + +var ( + icPattern = map[ondatra.Vendor]string{ + ondatra.ARISTA: "^SwitchChip", + ondatra.CISCO: "^[0-9]/[0-9]/CPU[0-9]-NPU[0-9]", + ondatra.JUNIPER: "NPU[0-9]$", + ondatra.NOKIA: "^SwitchChip", + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestSampledBackplaneCapacityCounters(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + ics := components.FindComponentsByType(t, dut, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_INTEGRATED_CIRCUIT) + if len(ics) == 0 { + t.Fatalf("Get IntegratedCircuit card list for %q: got 0, want > 0", dut.Model()) + } + t.Logf("IntegratedCircuit components count: %d", len(ics)) + + subscribeTimeout := 30 * time.Second + + for _, ic := range ics { + if !isCompNameExpected(t, ic, dut.Vendor()) { + continue + } + + t.Run(fmt.Sprintf("Backplane:%s", ic), func(t *testing.T) { + if deviations.BackplaneFacingCapacityUnsupported(dut) { + t.Skipf("Skipping check for BackplanceFacingCapacity due to deviation BackplaneFacingCapacityUnsupported") + } + + for _, sampleInterval := range []time.Duration{10 * time.Second, 15 * time.Second} { + minWant := int(subscribeTimeout/sampleInterval) - 1 + consumedCapacities := gnmi.Collect(t, gnmiOptsForSample(t, dut, sampleInterval), gnmi.OC().Component(ic).IntegratedCircuit().BackplaneFacingCapacity().ConsumedCapacity().State(), subscribeTimeout).Await(t) + if len(consumedCapacities) < minWant { + t.Errorf("ConsumedCapacities: got %d, want >= %d", len(consumedCapacities), minWant) + } + } + }) + } +} + +func isCompNameExpected(t *testing.T, name string, vendor ondatra.Vendor) bool { + t.Helper() + + regexpPattern, ok := icPattern[vendor] + if !ok { + return false + } + r, err := regexp.Compile(regexpPattern) + if err != nil { + t.Fatalf("Cannot compile regular expression: %v", err) + } + return r.MatchString(name) +} + +func gnmiOptsForSample(t *testing.T, dut *ondatra.DUTDevice, interval time.Duration) *gnmi.Opts { + return dut.GNMIOpts().WithYGNMIOpts( + ygnmi.WithSubscriptionMode(gpb.SubscriptionMode_SAMPLE), + ygnmi.WithSampleInterval(interval), + ) +} + +func gnmiOptsForOnChange(t *testing.T, dut *ondatra.DUTDevice) *gnmi.Opts { + return dut.GNMIOpts().WithYGNMIOpts(ygnmi.WithSubscriptionMode(gpb.SubscriptionMode_ON_CHANGE)) +} + +func TestOnChangeBackplaneCapacityCounters(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + ics := components.FindComponentsByType(t, dut, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_INTEGRATED_CIRCUIT) + if len(ics) == 0 { + t.Fatalf("Get IntegratedCircuit card list for %q: got 0, want > 0", dut.Model()) + } + t.Logf("IntegratedCircuit components count: %d", len(ics)) + + fabrics := components.FindComponentsByType(t, dut, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FABRIC) + t.Logf("fabrics are %v", fabrics) + removable_fabrics := make([]string, 0) + for _, f := range fabrics { + compMtyVal, compMtyPresent := gnmi.Lookup(t, dut, gnmi.OC().Component(f).Empty().State()).Val() + if compMtyPresent && compMtyVal { + continue + } + if gnmi.Get(t, dut, gnmi.OC().Component(f).Removable().State()) { + removable_fabrics = append(removable_fabrics, f) + } + } + t.Logf("removable_fabrics are %v", removable_fabrics) + fabrics = removable_fabrics + if len(fabrics) == 0 { + t.Skipf("Get Fabric card list for %q: got 0, want > 0", dut.Model()) + } + t.Logf("Fabric components count: %d", len(fabrics)) + + ts1, tocs1, apct1 := getBackplaneCapacityCounters(t, dut, ics) + + fc := (len(fabrics) / 2) + 1 + for _, f := range fabrics[:fc] { + empty, ok := gnmi.Lookup(t, dut, gnmi.OC().Component(f).Empty().State()).Val() + if ok && empty { + t.Logf("Fabric Component %s is empty, hence skipping", f) + continue + } + gnmi.Replace(t, dut, gnmi.OC().Component(f).Fabric().PowerAdminState().Config(), oc.Platform_ComponentPowerType_POWER_DISABLED) + gnmi.Await(t, dut, gnmi.OC().Component(f).Fabric().PowerAdminState().State(), time.Minute, oc.Platform_ComponentPowerType_POWER_DISABLED) + } + t.Logf("Waiting for 90s after power disable...") + time.Sleep(90 * time.Second) + ts2, tocs2, apct2 := getBackplaneCapacityCounters(t, dut, ics) + + for _, f := range fabrics[:fc] { + empty, ok := gnmi.Lookup(t, dut, gnmi.OC().Component(f).Empty().State()).Val() + if ok && empty { + t.Logf("Fabric Component %s is empty, hence skipping", f) + continue + } + gnmi.Replace(t, dut, gnmi.OC().Component(f).Fabric().PowerAdminState().Config(), oc.Platform_ComponentPowerType_POWER_ENABLED) + if deviations.MissingValueForDefaults(dut) { + time.Sleep(time.Minute) + } else { + if power, ok := gnmi.Await(t, dut, gnmi.OC().Component(f).Fabric().PowerAdminState().State(), time.Minute, oc.Platform_ComponentPowerType_POWER_ENABLED).Val(); !ok { + t.Errorf("Component %s, power-admin-state got: %v, want: %v", f, power, oc.Platform_ComponentPowerType_POWER_ENABLED) + } + } + if oper, ok := gnmi.Await(t, dut, gnmi.OC().Component(f).OperStatus().State(), 2*time.Minute, oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE).Val(); !ok { + t.Errorf("Component %s oper-status after POWER_ENABLED, got: %v, want: %v", f, oper, oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE) + } + } + t.Logf("Waiting for 90s after power enable...") + time.Sleep(90 * time.Second) + ts3, tocs3, apct3 := getBackplaneCapacityCounters(t, dut, ics) + + for _, ic := range ics { + if !isCompNameExpected(t, ic, dut.Vendor()) { + continue + } + + t.Run(fmt.Sprintf("Backplane:CountersCheck:%s", ic), func(t *testing.T) { + if deviations.BackplaneFacingCapacityUnsupported(dut) { + t.Skipf("Skipping check for BackplanceFacingCapacity due to deviation BackplaneFacingCapacityUnsupported") + } + + v1, ok1 := ts1[ic] + v2, ok2 := ts2[ic] + v3, ok3 := ts3[ic] + switch { + case !ok1 || !ok2 || !ok3: + t.Errorf("BackplaneFacingCapacity Total not present: ok1 %t, ok2 %t, ok3 %t", ok1, ok2, ok3) + case v1 != v2 || v1 != v3: + t.Errorf("BackplaneFacingCapacity Total are not valid: v1 %d, v2 %d, v3 %d", v1, v2, v3) + } + + v1, ok1 = tocs1[ic] + v2, ok2 = tocs2[ic] + v3, ok3 = tocs3[ic] + switch { + case !ok1 || !ok2 || !ok3: + t.Errorf("BackplaneFacingCapacity TotalOperationalCapacity not present: ok1 %t, ok2 %t, ok3 %t", ok1, ok2, ok3) + case v1 <= v2 || v1 != v3: + t.Errorf("BackplaneFacingCapacity TotalOperationalCapacity are not valid: v1 %d, v2 %d, v3 %d", v1, v2, v3) + } + + v1, ok1 = apct1[ic] + v2, ok2 = apct2[ic] + v3, ok3 = apct3[ic] + switch { + case !ok1 || !ok2 || !ok3: + t.Errorf("BackplaneFacingCapacity AvailablePct not present: ok1 %t, ok2 %t, ok3 %t", ok1, ok2, ok3) + case v1 != 0 && (v1 <= v2 || v1 != v3): + t.Errorf("BackplaneFacingCapacity AvailablePct are not valid: v1 %d, v2 %d, v3 %d", v1, v2, v3) + } + }) + } +} + +func getBackplaneCapacityCounters(t *testing.T, dut *ondatra.DUTDevice, ics []string) (map[string]uint64, map[string]uint64, map[string]uint64) { + subscribeTimeout := 30 * time.Second + + totals := make(map[string]uint64) + totalOperationalCapacities := make(map[string]uint64) + availablePcts := make(map[string]uint64) + for _, ic := range ics { + if !isCompNameExpected(t, ic, dut.Vendor()) { + continue + } + + t.Run(fmt.Sprintf("Backplane:%s", ic), func(t *testing.T) { + if deviations.BackplaneFacingCapacityUnsupported(dut) { + t.Skipf("Skipping check for BackplanceFacingCapacity due to deviation BackplaneFacingCapacityUnsupported") + } + + ts, ok := gnmi.Watch(t, gnmiOptsForOnChange(t, dut), gnmi.OC().Component(ic).IntegratedCircuit().BackplaneFacingCapacity().Total().State(), subscribeTimeout, func(v *ygnmi.Value[uint64]) bool { + return v.IsPresent() + }).Await(t) + if ok { + v, _ := ts.Val() + totals[ic] = v + } + + tocs, ok := gnmi.Watch(t, gnmiOptsForOnChange(t, dut), gnmi.OC().Component(ic).IntegratedCircuit().BackplaneFacingCapacity().TotalOperationalCapacity().State(), subscribeTimeout, func(v *ygnmi.Value[uint64]) bool { + return v.IsPresent() + }).Await(t) + if ok { + v, _ := tocs.Val() + totalOperationalCapacities[ic] = v + } + + apcts, ok := gnmi.Watch(t, gnmiOptsForOnChange(t, dut), gnmi.OC().Component(ic).IntegratedCircuit().BackplaneFacingCapacity().AvailablePct().State(), subscribeTimeout, func(v *ygnmi.Value[uint16]) bool { + return v.IsPresent() + }).Await(t) + if ok { + v, _ := apcts.Val() + availablePcts[ic] = uint64(v) + } + }) + } + + return totals, totalOperationalCapacities, availablePcts +} diff --git a/feature/platform/healthz/tests/status/README.md b/feature/platform/healthz/tests/status/README.md new file mode 100644 index 00000000000..961564d350c --- /dev/null +++ b/feature/platform/healthz/tests/status/README.md @@ -0,0 +1,103 @@ +# Health-1.2: Healthz component status paths + +## Summary + +Validate healthz status paths exist for select OC component types. There +are two operational use cases for this test. + +1. As a network operator, I want to know if a device is healthy. If the + device is unhealthy, I may choose to execute a mitigation or repair action. + The choice of action is influenced by which component(s) are not healthy. +2. As a SDN controller, I want to know if a device is ready to be programmed + for traffic forwarding. + +## Testbed type + +* [Single DUT](https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed) + +## Testbed prerequisites + +There are no DUT configuration pre-requisites for this test. The DUT must +contain the following component types: + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + "POWER_SUPPLY", + "INTEGRATED_CIRCUIT" + +The DUT should have a HEALTHY state for all the above components. + +## Procedure + +* Healthz-1.2.1: Validate healthz status + * gNMI Subscribe to the /components tree using ON_CHANGE option. + * Validate `/components/component/healthz/state/status` returns `HEALTHY` + for each of the following component types: + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + "POWER_SUPPLY", + "INTEGRATED_CIRCUIT" + * Validate the following paths return a valid value: + * /components/component/healthz/state/last-unhealthy + * /components/component/healthz/state/unhealthy-count + +* Healthz-1.2.3: Reboot DUT and validate status converges to healthy + * Use gnoi.System.Reboot to reboot the DUT + * Repeatedly attempt to open a gNMI subscribe request to the DUT + * Upon success, subscribe to the /components tree using ON_CHANGE option. + * Validate `/components/component/healthz/state/status` exists for each of + the following component types: + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + "POWER_SUPPLY", + "INTEGRATED_CIRCUIT" + * Validate status transitions to `HEALTHY` within a timeout of 15 minutes + from the reboot start time. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths and RPC intended to be covered by this test. + +```yaml +paths: + ## State Paths ## + /components/component/healthz/state/status: + platform_type: [ + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + "POWER_SUPPLY", + "INTEGRATED_CIRCUIT" + ] + /components/component/healthz/state/last-unhealthy: + platform_type: [ + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + "POWER_SUPPLY", + "INTEGRATED_CIRCUIT" + ] + /components/component/healthz/state/unhealthy-count: + platform_type: [ + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + "POWER_SUPPLY", + "INTEGRATED_CIRCUIT" + ] + +rpcs: + gnmi: + gNMI.Subscribe: + ON_CHANGE: true + + gnoi: + system.System.Reboot: + +``` + +## Minimum DUT platform requirement + +MFF - Modular form factor is specified to ensure coverage for `CONTROLLER_CARD` and `FABRIC` platform_types. diff --git a/feature/platform/healthz/tests/status/metadata.textproto b/feature/platform/healthz/tests/status/metadata.textproto new file mode 100644 index 00000000000..76248c3fb88 --- /dev/null +++ b/feature/platform/healthz/tests/status/metadata.textproto @@ -0,0 +1,12 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +plan_id: "Health-1.2" +description: "Healthz component status paths" +uuid: "6935156f-d42f-4bb0-9926-79235859c029" + +testbed: TESTBED_DUT + +tags: TAGS_AGGREGATION +tags: TAGS_TRANSIT +tags: TAGS_DATACENTER_EDGE diff --git a/feature/platform/integrated_circuit/otg_tests/utilization/README.md b/feature/platform/integrated_circuit/otg_tests/utilization/README.md deleted file mode 100644 index 0590372951e..00000000000 --- a/feature/platform/integrated_circuit/otg_tests/utilization/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# IC-1: Integrated Circuit Utilization and Thresholds - -## Summary - -Test resource utilization and threshold for `INTEGRATED_CIRCUIT` (IC) components. - -## Procedure - -* IC-1.1 - * Get IC component names - * Verify resource utilization and threshold leaves exist for component - type `INTEGRATED_CIRCUIT` -* IC-1.2 - * Configure `used-threshold-upper` at 10% for resource IC - * Configure `used-threshold-upper-clear` at 5% for resource IC - * Verify state for the above leaves equals the configured values -* IC-1.4 - * Configure ISIS and BGP on DUT and ATE - * subscribe using SAMPLE mode to `used` leaf. - * Advertise 100 IPv4 and IPv6 routes (200 total) from ATE to DUT - * Verify `used` leaf increases in value -* IC-1.5 - * subscribe ON_CHANGE to `max-limit` from DUT - * subscribe ON_CHANGE to `used-threshold-upper-exceeded` - * Add more IPv4 and IPv6 routes than `max-limit` * `used-threshold-upper` - * Verify `used-threshold-upper-exceeded` upper is true -* IC-1.6 - * Reduce ATE route advertisement to 100 IPv4 and IPv6 routes (200 total) - * Verify `used-threshold-upper-exceeded` upper is true - -## Config Parameter Coverage - -/components/component/chassis/utilization/resources/resource/config/name -/system/utilization/resources/resource/config/name/used-threshold-upper -/system/utilization/resources/resource/config/name/used-threshold-upper-clear -/system/utilization/resources/resource/ - -## Telemetry Parameter Coverage - -/system/utilization/resources/resource/state/active-component-list -/components/component/chassis/utilization/resources/resource/state/committed -/components/component/chassis/utilization/resources/resource/state/free -/components/component/chassis/utilization/resources/resource/state/high-watermark -/components/component/chassis/utilization/resources/resource/state/last-high-watermark -/components/component/chassis/utilization/resources/resource/state/max-limit -/components/component/chassis/utilization/resources/resource/state/name -/components/component/chassis/utilization/resources/resource/state/used -/components/component/chassis/utilization/resources/resource/state/used-threshold-upper -/components/component/chassis/utilization/resources/resource/state/used-threshold-upper-clear - -## Protocol/RPC Parameter Coverage - -None - -## Required DUT platform - -This test should run on both - -* MFF - A modular form factor device containing LINECARDs, FABRIC and redundant CONTROLLER_CARD components -* FFF - fixed form factor diff --git a/feature/platform/integrated_circuit/otg_tests/utilization_test/README.md b/feature/platform/integrated_circuit/otg_tests/utilization_test/README.md new file mode 100644 index 00000000000..42c2fa101f5 --- /dev/null +++ b/feature/platform/integrated_circuit/otg_tests/utilization_test/README.md @@ -0,0 +1,59 @@ +# gNMI-1.21: Integrated Circuit Hardware Resource Utilization Test + +## Summary + +Test `used-threshold-upper` configuration and telemetry for hardware resources. + +## Procedure + +* Connect ATE port-1 to DUT port-1, and ATE port-2 to DUT port-2. + +* Establish BGP session between ATE Port1 --- DUT Port1. + +* Get initial utilization percentages (free/(used+free) * 100) for the FIB + resource in the system. + +* Configure DUT used-threshold-upper to 60% and used-threshold-upper-clear to + 50%. + + * The configuration must be done at the + [system level](https://openconfig.net/projects/models/schemadocs/yangdoc/openconfig-system.html#system-utilization-resources-resource-config) + such that the percentages are reflected in all components using the + resource. + +* Inject unique BGP routes such that FIB utilization increases by at-least 1% + (250000 routes should increase utilization by at-least 1% for + Arista/Cisco/Juniper/Nokia). + +* Get utilization percentages again and validate increase in utilization. + +* Teardown BGP session such that routes are removed from FIB. + +* Get utilization percentages again and validate decrease in utilization. + +## OpenConfig Path and RPC Coverage + +This example yaml 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. +```yaml +paths: + ## Config parameter coverage + /system/utilization/resources/resource/config/name: + /system/utilization/resources/resource/config/used-threshold-upper: + /system/utilization/resources/resource/config/used-threshold-upper-clear: + + ## Telemetry parameter coverage + /system/utilization/resources/resource/state/name: + /system/utilization/resources/resource/state/used-threshold-upper: + /system/utilization/resources/resource/state/used-threshold-upper-clear: + /components/component/integrated-circuit/utilization/resources/resource/state/name: + platform_type: ["INTEGRATED_CIRCUIT"] + /components/component/integrated-circuit/utilization/resources/resource/state/used: + platform_type: ["INTEGRATED_CIRCUIT"] + /components/component/integrated-circuit/utilization/resources/resource/state/free: + platform_type: ["INTEGRATED_CIRCUIT"] +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: + Mode: [ "ON_CHANGE", "SAMPLE" ] +``` diff --git a/feature/experimental/bgp/ate_tests/bgp_always_compare_med/metadata.textproto b/feature/platform/integrated_circuit/otg_tests/utilization_test/metadata.textproto similarity index 59% rename from feature/experimental/bgp/ate_tests/bgp_always_compare_med/metadata.textproto rename to feature/platform/integrated_circuit/otg_tests/utilization_test/metadata.textproto index a2fb22a415a..45d28a4dcdf 100644 --- a/feature/experimental/bgp/ate_tests/bgp_always_compare_med/metadata.textproto +++ b/feature/platform/integrated_circuit/otg_tests/utilization_test/metadata.textproto @@ -1,29 +1,29 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "4e512503-5fec-4dcf-87f4-f510dede1cd0" -plan_id: "RT-1.12" -description: "BGP always compare MED" -testbed: TESTBED_DUT_ATE_4LINKS +uuid: "9ff3be0d-84aa-4513-8a6c-bfe497a8ae9e" +plan_id: "gNMI-1.21" +description: "Integrated Circuit Hardware Resource Utilization Test" +testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { - vendor: NOKIA + vendor: ARISTA } deviations: { + route_policy_under_afi_unsupported: true interface_enabled: true - explicit_interface_in_default_vrf: true + default_network_instance: "default" + mismatched_hardware_resource_name_in_component: true + missing_hardware_resource_telemetry_before_config: true } } platform_exceptions: { platform: { - vendor: ARISTA + vendor: NOKIA } deviations: { - omit_l2_mtu: true - route_policy_under_afi_unsupported: true + explicit_interface_in_default_vrf: true interface_enabled: true - default_network_instance: "default" - bgp_set_med_requires_equal_ospf_set_metric: true } } -tags: TAGS_AGGREGATION +tags: TAGS_TRANSIT diff --git a/feature/platform/integrated_circuit/otg_tests/utilization_test/utilization_test.go b/feature/platform/integrated_circuit/otg_tests/utilization_test/utilization_test.go new file mode 100644 index 00000000000..db1719b5c3f --- /dev/null +++ b/feature/platform/integrated_circuit/otg_tests/utilization_test/utilization_test.go @@ -0,0 +1,339 @@ +// 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 utilization_test + +import ( + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "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/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 ( + dutAS = 64500 + ateAS = 64501 + bgpV6PeerGroup = "BGP-PEER-GROUP-V6" + bgpRoutePolicyName = "BGP-ROUTE-POLICY-ALLOW" + numBGPRoutes = 250000 + bgpAdvertisedRouteStart = "2001:DB8:2::1" + usedThresholdUpper = uint8(60) + usedThresholdUpperClear = uint8(50) +) + +var ( + fibResource = map[ondatra.Vendor]string{ + ondatra.ARISTA: "Routing/Resource6", + ondatra.NOKIA: "ip-lpm-routes", + } + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: 30, + IPv6Len: 126, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + MAC: "02:00:01:01:01:01", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: 30, + IPv6Len: 126, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: 30, + IPv6Len: 126, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + MAC: "02:00:02:01:01:01", + IPv6: "2001:db8::192:0:2:6", + IPv4Len: 30, + IPv6Len: 126, + } +) + +type utilization struct { + used uint64 + free uint64 + upperThreshold uint8 + upperThresholdClear uint8 +} + +func (u *utilization) percent() uint8 { + if u.used == 0 && u.free == 0 { + return 0 + } + return uint8(u.used * 100 / (u.used + u.free)) +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestResourceUtilization(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + otgV6Peer, otgPort1, otgConfig := configureOTG(t, otg) + + verifyBgpTelemetry(t, dut) + gnmi.Replace(t, dut, gnmi.OC().System().Utilization().Resource(fibResource[dut.Vendor()]).Config(), &oc.System_Utilization_Resource{ + Name: ygot.String(fibResource[dut.Vendor()]), + UsedThresholdUpper: ygot.Uint8(usedThresholdUpper), + UsedThresholdUpperClear: ygot.Uint8(usedThresholdUpperClear), + }) + comps := components.FindActiveComponentsByType(t, dut, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_INTEGRATED_CIRCUIT) + beforeUtzs := componentUtilizations(t, dut, comps) + if len(beforeUtzs) != len(comps) { + t.Fatalf("Couldn't retrieve Utilization information for all Active Components") + } + + injectBGPRoutes(t, otg, otgV6Peer, otgPort1, otgConfig) + + afterUtzs := componentUtilizations(t, dut, comps) + if len(afterUtzs) != len(comps) { + t.Fatalf("Couldn't retrieve Utilization information for all Active Components") + } + + t.Run("Utilization after BGP route installation", func(t *testing.T) { + for _, c := range comps { + t.Run(c, func(t *testing.T) { + if beforeUtzs[c].percent() >= afterUtzs[c].percent() { + t.Errorf("Utilization Percent didn't increase for component: %s", c) + } + t.Logf("Before Utilization: %d, After Utilization: %d", beforeUtzs[c].percent(), afterUtzs[c].percent()) + }) + } + }) + + clearBGPRoutes(t, otg, otgV6Peer, otgConfig) + + afterClearUtzs := componentUtilizations(t, dut, comps) + if len(afterClearUtzs) != len(comps) { + t.Fatalf("Couldn't retrieve Utilization information for all Active Components") + } + + t.Run("Utilization after BGP route clear", func(t *testing.T) { + for _, c := range comps { + t.Run(c, func(t *testing.T) { + if afterClearUtzs[c].percent() >= afterUtzs[c].percent() { + t.Errorf("Utilization Percent didn't decrease for component: %s", c) + } + t.Logf("Before Utilization: %d, After Utilization: %d", afterUtzs[c].percent(), afterClearUtzs[c].percent()) + }) + } + }) +} + +func componentUtilizations(t *testing.T, dut *ondatra.DUTDevice, comps []string) map[string]*utilization { + t.Helper() + resName := fibResource[dut.Vendor()] + if deviations.MismatchedHardwareResourceNameInComponent(dut) { + resName += "/-" + } + utzs := map[string]*utilization{} + for _, c := range comps { + comp := gnmi.Get(t, dut, gnmi.OC().Component(c).State()) + res := comp.GetIntegratedCircuit().GetUtilization().GetResource(resName) + utzs[c] = &utilization{ + used: res.GetUsed(), + free: res.GetFree(), + upperThreshold: res.GetUsedThresholdUpper(), + upperThresholdClear: res.GetUsedThresholdUpperClear(), + } + } + return utzs +} + +// configureDUT configures port1-2 on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := gnmi.OC() + + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, "port1")) + fptest.SetPortSpeed(t, dut.Port(t, "port2")) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + configureAcceptRoutePolicy(t, dut) + configureBGPDUT(t, dut) +} + +func configureAcceptRoutePolicy(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pd := rp.GetOrCreatePolicyDefinition(bgpRoutePolicyName) + st, err := pd.AppendNewStatement("id-1") + if err != nil { + t.Fatal(err) + } + st.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) +} + +func configureBGPDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + 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(dutAS) + g.RouterId = ygot.String(dutPort1.IPv4) + g.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + + pg := bgp.GetOrCreatePeerGroup(bgpV6PeerGroup) + pg.PeerAs = ygot.Uint32(ateAS) + pg.PeerGroupName = ygot.String(bgpV6PeerGroup) + pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pg.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{bgpRoutePolicyName}) + rpl.SetImportPolicy([]string{bgpRoutePolicyName}) + } else { + pg1af4 := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + pg1af4.Enabled = ygot.Bool(true) + pg1rpl4 := pg1af4.GetOrCreateApplyPolicy() + pg1rpl4.SetExportPolicy([]string{bgpRoutePolicyName}) + pg1rpl4.SetImportPolicy([]string{bgpRoutePolicyName}) + } + + bgpNbr := bgp.GetOrCreateNeighbor(atePort1.IPv6) + bgpNbr.PeerAs = ygot.Uint32(ateAS) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbr.PeerGroup = ygot.String(bgpV6PeerGroup) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config(), niProto) +} + +func configureOTG(t *testing.T, otg *otg.OTG) (gosnappi.BgpV6Peer, gosnappi.DeviceIpv6, gosnappi.Config) { + t.Helper() + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + + iDut1Bgp := iDut1Dev.Bgp().SetRouterId(iDut1Ipv4.Address()) + iDut1Bgp6Peer := iDut1Bgp.Ipv6Interfaces().Add().SetIpv6Name(iDut1Ipv6.Name()).Peers().Add().SetName(atePort1.Name + ".BGP6.peer") + iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + iDut1Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + t.Logf("Pushing config to ATE and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + + otgutils.WaitForARP(t, otg, config, "IPv4") + + return iDut1Bgp6Peer, iDut1Ipv6, config +} + +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + var nbrIP = []string{atePort1.IPv6} + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + for _, nbr := range nbrIP { + nbrPath := bgpPath.Neighbor(nbr) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", nbr, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", nbr, state, want) + } + } +} + +func injectBGPRoutes(t *testing.T, otg *otg.OTG, bgpPeer gosnappi.BgpV6Peer, otgPort1 gosnappi.DeviceIpv6, otgConfig gosnappi.Config) { + t.Helper() + peerRoutes := bgpPeer.V6Routes().Add().SetName(atePort1.Name + ".BGP6.Route") + peerRoutes.SetNextHopIpv6Address(otgPort1.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + peerRoutes.Addresses().Add(). + SetAddress(bgpAdvertisedRouteStart). + SetPrefix(128). + SetCount(numBGPRoutes).SetStep(2) + peerRoutes.Advanced().SetIncludeLocalPreference(false) + + otg.PushConfig(t, otgConfig) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(time.Minute) +} + +func clearBGPRoutes(t *testing.T, otg *otg.OTG, bgpPeer gosnappi.BgpV6Peer, otgConfig gosnappi.Config) { + bgpPeer.V6Routes().Clear() + otg.PushConfig(t, otgConfig) + time.Sleep(30 * time.Second) + otg.StopProtocols(t) + time.Sleep(time.Minute) +} diff --git a/feature/platform/tests/breakout_configuration/README.md b/feature/platform/tests/breakout_configuration/README.md new file mode 100644 index 00000000000..1cccdf903c4 --- /dev/null +++ b/feature/platform/tests/breakout_configuration/README.md @@ -0,0 +1,41 @@ +# PLT-1.1: Interface breakout Test + +## Summary + +Validate Interface breakout configuration. + +## Procedure + + +* This test is carried out for different breakout types +* Connect DUT with ATE to all interfaces in the breakout port +* Configure each interface with test IP addressing +* Verify correct interface state and speed reported +* Verify that DUT responds to ARP/ICMP on all tested 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: + /components/component/port/breakout-mode/groups/group/index: + platform_type: [ "PORT" ] + /components/component/port/breakout-mode/groups/group/config/index: + platform_type: [ "PORT" ] + /components/component/port/breakout-mode/groups/group/config/num-breakouts: + platform_type: [ "PORT" ] + /components/component/port/breakout-mode/groups/group/config/breakout-speed: + platform_type: [ "PORT" ] + /components/component/port/breakout-mode/groups/group/config/num-physical-channels: + platform_type: [ "PORT" ] +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` + +## Minimum DUT Platform Requirement + +* Breakout types - 4x100G, 2x100G and 4x10G diff --git a/feature/platform/tests/optics_power_and_bias_current_test/metadata.textproto b/feature/platform/tests/optics_power_and_bias_current_test/metadata.textproto index 4d62560e540..1b52681fdb6 100644 --- a/feature/platform/tests/optics_power_and_bias_current_test/metadata.textproto +++ b/feature/platform/tests/optics_power_and_bias_current_test/metadata.textproto @@ -14,14 +14,7 @@ platform_exceptions: { transceiver_thresholds_unsupported: true } } -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - transceiver_thresholds_unsupported: true - } -} + platform_exceptions: { platform: { vendor: ARISTA diff --git a/feature/platform/tests/optics_power_and_bias_current_test/optics_power_and_bias_current_test.go b/feature/platform/tests/optics_power_and_bias_current_test/optics_power_and_bias_current_test.go index 5b7b37bd762..e55884240d5 100644 --- a/feature/platform/tests/optics_power_and_bias_current_test/optics_power_and_bias_current_test.go +++ b/feature/platform/tests/optics_power_and_bias_current_test/optics_power_and_bias_current_test.go @@ -35,6 +35,7 @@ import ( const ( ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd transceiverType = oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_TRANSCEIVER + sensorType = oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_SENSOR sleepDuration = time.Minute minOpticsPower = -40.0 maxOpticsPower = 10.0 @@ -102,9 +103,27 @@ func TestOpticsPowerBiasCurrent(t *testing.T) { t.Errorf("Get biasCurrents list for %q: got 0, want > 0", transceiver) } - v := gnmi.Lookup(t, dut, component.Temperature().Instant().State()) - if _, ok := v.Val(); !ok { - t.Errorf("Transceiver %s: Temperature instant is not defined", transceiver) + subcomponents := gnmi.LookupAll[*oc.Component_Subcomponent](t, dut, gnmi.OC().Component(transceiver).SubcomponentAny().State()) + sensorComponentChecked := false + for _, s := range subcomponents { + subc, ok := s.Val() + if ok { + sensorComponent := gnmi.Get[*oc.Component](t, dut, gnmi.OC().Component(subc.GetName()).State()) + if sensorComponent.GetType() == sensorType { + scomponent := gnmi.OC().Component(sensorComponent.GetName()) + sensorComponentChecked = true + v := gnmi.Lookup(t, dut, scomponent.Temperature().Instant().State()) + if _, ok := v.Val(); !ok { + t.Errorf("Sensor %s: Temperature instant is not defined", sensorComponent.GetName()) + } + } + } + } + if len(subcomponents) == 0 || sensorComponentChecked == false { + v := gnmi.Lookup(t, dut, component.Temperature().Instant().State()) + if _, ok := v.Val(); !ok { + t.Errorf("Transceiver %s: Temperature instant is not defined", transceiver) + } } if deviations.TransceiverThresholdsUnsupported(dut) { diff --git a/feature/experimental/platform/tests/optics_thresholds_test/README.md b/feature/platform/tests/optics_thresholds_test/README.md similarity index 51% rename from feature/experimental/platform/tests/optics_thresholds_test/README.md rename to feature/platform/tests/optics_thresholds_test/README.md index 52f7febdd03..9126d3eb914 100644 --- a/feature/experimental/platform/tests/optics_thresholds_test/README.md +++ b/feature/platform/tests/optics_thresholds_test/README.md @@ -1,4 +1,4 @@ -# gNMI-1.14: Telemetry: Optics Thresholds +# gNMI-1.20: Telemetry: Optics Thresholds ## Summary @@ -8,7 +8,7 @@ Validate optics high and low thresholds for input power, output power, temperatu * Connect at least one optical ethernet interface to ATE. * Check all the transceivers with inslalled optcs. -* Validate that the following optics threshold telemetry paths exist for each optics. +* Validate that the optics threshold telemetry paths exist for each optics. * Output power thresholds: * /components/component/Ethernet/properties/property/laser-tx-power-low-alarm-threshold/state/value * /components/component/Ethernet/properties/property/laser-tx-power-high-alarm-threshold/state/value @@ -30,32 +30,17 @@ Validate optics high and low thresholds for input power, output power, temperatu * /components/component/Ethernet/properties/property/laser-bias-current-low-warn-threshold/state/value * /components/component/Ethernet/properties/property/laser-bias-current-high-warn-threshold/state/value - -## Config Parameter coverage +## OpenConfig Path and RPC Coverage -* None +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 +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` -## Telemetry Parameter coverage - * Output power thresholds: - * /components/component/Ethernet/properties/property/laser-tx-power-low-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-tx-power-high-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-tx-power-low-warn-threshold/state/value - * /components/component/Ethernet/properties/property/laser-tx-power-high-warn-threshold/state/value - * Input power threshold: - * /components/component/Ethernet/properties/property/laser-rx-power-low-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-rx-power-high-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-rx-power-low-warn-threshold/state/value - * /components/component/Ethernet/properties/property/laser-rx-power-high-warn-threshold/state/value - * Optics temperature threshold: - * /components/component/Ethernet/properties/property/laser-temperature-low-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-temperature-high-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-temperature-low-warn-threshold/state/value - * /components/component/Ethernet/properties/property/laser-temperature-high-warn-threshold/state/value - * Optics bias-current threshold: - * /components/component/Ethernet/properties/property/laser-bias-current-low-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-bias-current-high-alarm-threshold/state/value - * /components/component/Ethernet/properties/property/laser-bias-current-low-warn-threshold/state/value - * /components/component/Ethernet/properties/property/laser-bias-current-high-warn-threshold/state/value - ## Notes: * The model for optics threshold paths is not finalized. We may need to update those paths after the model is finalized. diff --git a/feature/experimental/platform/tests/optics_thresholds_test/metadata.textproto b/feature/platform/tests/optics_thresholds_test/metadata.textproto similarity index 91% rename from feature/experimental/platform/tests/optics_thresholds_test/metadata.textproto rename to feature/platform/tests/optics_thresholds_test/metadata.textproto index dfe4a99874a..5b0a770cc61 100644 --- a/feature/experimental/platform/tests/optics_thresholds_test/metadata.textproto +++ b/feature/platform/tests/optics_thresholds_test/metadata.textproto @@ -2,6 +2,6 @@ # proto-message: Metadata uuid: "c1cb3545-2704-4f1a-80d3-150840df5f4a" -plan_id: "gNMI-1.14" +plan_id: "gNMI-1.20" description: "Telemetry: Optics Thresholds" testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/experimental/platform/tests/optics_thresholds_test/optics_thresholds_test.go b/feature/platform/tests/optics_thresholds_test/optics_thresholds_test.go similarity index 100% rename from feature/experimental/platform/tests/optics_thresholds_test/optics_thresholds_test.go rename to feature/platform/tests/optics_thresholds_test/optics_thresholds_test.go diff --git a/feature/platform/tests/power_admin_down_up_test/README.md b/feature/platform/tests/power_admin_down_up_test/README.md index 229f9c52ed7..9ecf373081a 100644 --- a/feature/platform/tests/power_admin_down_up_test/README.md +++ b/feature/platform/tests/power_admin_down_up_test/README.md @@ -21,11 +21,27 @@ ControllerCard. to POWER_ENABLED. * Verify /components/component/state/oper-status returns to ACTIVE. -## Config Parameter coverage +## Minumum DUT platform requirement +vRX -* /components/component/{fabric|linecard|controller-card}/config/power-admin-state +## Config Parameter coverage + * /components/component/{fabric|linecard|controller-card}/config/power-admin-state ## Telemetry Parameter coverage + * /components/component/state/oper-status + * /components/component/{fabric|linecard|controller-card}/state/power-admin-state + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths and RPC intended to be covered by this test. + +```yaml +paths: +/components/component/name: +/components/component/state/name: -* /components/component/state/oper-status -* /components/component/{fabric|linecard|controller-card}/state/power-admin-state +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/platform/tests/power_admin_down_up_test/metadata.textproto b/feature/platform/tests/power_admin_down_up_test/metadata.textproto index ae49957c730..58ef621af8e 100644 --- a/feature/platform/tests/power_admin_down_up_test/metadata.textproto +++ b/feature/platform/tests/power_admin_down_up_test/metadata.textproto @@ -30,3 +30,12 @@ platform_exceptions: { skip_controller_card_power_admin: true } } +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + power_disable_enable_leaf_ref_validation: true + } +} + diff --git a/feature/platform/tests/power_admin_down_up_test/power_admin_down_up_test.go b/feature/platform/tests/power_admin_down_up_test/power_admin_down_up_test.go index d536abd7f5c..9a0f3ff5e63 100644 --- a/feature/platform/tests/power_admin_down_up_test/power_admin_down_up_test.go +++ b/feature/platform/tests/power_admin_down_up_test/power_admin_down_up_test.go @@ -14,6 +14,7 @@ import ( "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" ) func TestMain(m *testing.M) { @@ -106,7 +107,7 @@ func TestControllerCardPowerAdmin(t *testing.T) { t.Skipf("ControllerCard Component %s is already INACTIVE, hence skipping", c) } - powerDownUp(t, dut, c, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CONTROLLER_CARD, 3*time.Minute) + powerDownUp(t, dut, c, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CONTROLLER_CARD, 5*time.Minute) }) } if primary != "" { @@ -132,7 +133,11 @@ func powerDownUp(t *testing.T, dut *ondatra.DUTDevice, name string, cType oc.E_P default: t.Fatalf("Unknown component type: %s", cType.String()) } - + if deviations.PowerDisableEnableLeafRefValidation(dut) { + gnmi.Update(t, dut, c.Config(), &oc.Component{ + Name: ygot.String(name), + }) + } start := time.Now() t.Logf("Starting %s POWER_DISABLE", name) gnmi.Replace(t, dut, config, oc.Platform_ComponentPowerType_POWER_DISABLED) diff --git a/feature/platform/tests/telemetry_inventory_test/README.md b/feature/platform/tests/telemetry_inventory_test/README.md index bbdadf87d62..8fc1a679831 100644 --- a/feature/platform/tests/telemetry_inventory_test/README.md +++ b/feature/platform/tests/telemetry_inventory_test/README.md @@ -6,7 +6,7 @@ Validate Telemetry for each FRU within chassis. ## Procedure -For each of the following component types (linecard, chassis, fan, controller +For each of the following component types (linecard, chassis, fan, fan_tray, controller card, power supply, disk, flash, NPU, transceiver, fabric card), validate: * Presence of component within gNMI telemetry. @@ -19,30 +19,75 @@ card, power supply, disk, flash, NPU, transceiver, fabric card), validate: * TODO: /components/component/linecard/config -## Telemetry Parameter coverage - -* /components/component[name=]/state/temperature/instant -* /components/component/storage -* TODO: /components/component/software-module -* TODO: /components/component/software-module/state/module-type -* /components/component/state/description -* /components/component/state/firmware-version -* /components/component/state/hardware-version -* /components/component/state/id -* /components/component/state/mfg-date -* /components/component/state/mfg-name -* /components/component/state/name -* /components/component/state/oper-status -* /components/component/state/parent -* /components/component/state/part-no -* /components/component/state/serial-no -* /components/component/state/software-version -* /components/component/state/type -* /components/component/state/temperature/alarm-status -* /components/component/state/temperature/instant -* /components/component/state/temperature/max -* /components/component/state/temperature/max-time -* /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct -* /components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity -* /components/component/integrated-circuit/backplane-facing-capacity/state/total -* /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity +## OpenConfig Path and RPC Coverage + +TODO: + /components/component/storage + /components/component/software-module + /components/component/software-module/state/module-type + /components/component/state/mfg-date + /components/component/state/software-version + +```yaml +paths: + /components/component/state/description: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "FABRIC", "FAN", "FAN_TRAY", "LINECARD", "POWER_SUPPLY"] + /components/component/state/firmware-version: + platform_type: ["TRANSCEIVER"] + /components/component/state/hardware-version: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "FABRIC", "LINECARD", "POWER_SUPPLY", "TRANSCEIVER"] + /components/component/state/id: + platform_type: ["CONTROLLER_CARD", "FABRIC", "FAN", "FAN_TRAY", "INTEGRATED_CIRCUIT", "LINECARD", "POWER_SUPPLY", "SENSOR"] + /components/component/state/install-component: + platform_type: ["FABRIC", "FAN", "FAN_TRAY", "FRU", "CONTROLLER_CARD", "LINECARD", "POWER_SUPPLY", "TRANSCEIVER"] + /components/component/state/install-position: + platform_type: ["FABRIC", "FAN", "FAN_TRAY", "FRU", "CONTROLLER_CARD", "LINECARD", "POWER_SUPPLY", "TRANSCEIVER"] + /components/component/state/location: + platform_type: ["FABRIC", "FAN", "FAN_TRAY", "FRU", "CONTROLLER_CARD", "LINECARD", "POWER_SUPPLY", "TRANSCEIVER"] + /components/component/state/mfg-name: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "FABRIC", "LINECARD", "POWER_SUPPLY", "TRANSCEIVER"] + /components/component/state/model-name: + platform_type: ["CHASSIS"] + /components/component/state/name: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "CPU", "FABRIC", "FAN", "FAN_TRAY", "INTEGRATED_CIRCUIT", "LINECARD", "POWER_SUPPLY", "SENSOR", "STORAGE", "TRANSCEIVER"] + /components/component/state/oper-status: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "CPU", "FABRIC", "FAN", "FAN_TRAY", "INTEGRATED_CIRCUIT", "LINECARD", "POWER_SUPPLY", "STORAGE", "TRANSCEIVER"] + /components/component/state/parent: + platform_type: ["CONTROLLER_CARD", "FABRIC", "FAN", "FAN_TRAY", "LINECARD", "POWER_SUPPLY"] + /components/component/state/part-no: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "CPU", "FABRIC", "FAN", "FAN_TRAY", "LINECARD", "POWER_SUPPLY", "STORAGE", "TRANSCEIVER"] + /components/component/state/serial-no: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "CPU", "FABRIC", "FAN", "FAN_TRAY", "LINECARD", "POWER_SUPPLY", "STORAGE", "TRANSCEIVER"] + /components/component/state/type: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "CPU", "FABRIC", "FAN", "FAN_TRAY", "INTEGRATED_CIRCUIT", "LINECARD", "POWER_SUPPLY", "SENSOR", "STORAGE", "TRANSCEIVER"] + /components/component/state/temperature/alarm-status: + platform_type: ["SENSOR"] + /components/component/state/temperature/instant: + platform_type: ["SENSOR"] + /components/component/state/temperature/max: + platform_type: ["SENSOR"] + /components/component/state/temperature/max-time: + platform_type: ["SENSOR"] + /components/component/subcomponents/subcomponent/name: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "CPU", "FABRIC", "FAN", "FAN_TRAY", "INTEGRATED_CIRCUIT", "LINECARD", "POWER_SUPPLY", "SENSOR", "STORAGE", "TRANSCEIVER"] + /components/component/subcomponents/subcomponent/state/name: + platform_type: ["CHASSIS", "CONTROLLER_CARD", "CPU", "FABRIC", "FAN", "FAN_TRAY", "INTEGRATED_CIRCUIT", "LINECARD", "POWER_SUPPLY", "SENSOR", "STORAGE", "TRANSCEIVER"] + /components/component/integrated-circuit/backplane-facing-capacity/state/available-pct: + platform_type: ["INTEGRATED_CIRCUIT"] + /components/component/integrated-circuit/backplane-facing-capacity/state/consumed-capacity: + platform_type: ["INTEGRATED_CIRCUIT"] + /components/component/integrated-circuit/backplane-facing-capacity/state/total: + platform_type: ["INTEGRATED_CIRCUIT"] + /components/component/integrated-circuit/backplane-facing-capacity/state/total-operational-capacity: + platform_type: ["INTEGRATED_CIRCUIT"] + /components/component/controller-card/config/power-admin-state: + platform_type: ["CONTROLLER_CARD"] + /components/component/fabric/config/power-admin-state: + platform_type: ["FABRIC"] + /components/component/linecard/config/power-admin-state: + platform_type: ["LINECARD"] + +rpcs: + gnmi: + gNMI.Get: +``` diff --git a/feature/platform/tests/telemetry_inventory_test/metadata.textproto b/feature/platform/tests/telemetry_inventory_test/metadata.textproto index b8c5d5b234a..74c89711589 100644 --- a/feature/platform/tests/telemetry_inventory_test/metadata.textproto +++ b/feature/platform/tests/telemetry_inventory_test/metadata.textproto @@ -5,14 +5,34 @@ uuid: "44fed7d9-4c79-4952-8b83-fbd5f7138ae9" plan_id: "gNMI-1.4" description: "Telemetry: Inventory" testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + install_position_and_install_component_unsupported: true + model_name_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + install_position_and_install_component_unsupported: true + model_name_unsupported: true + } +} platform_exceptions: { platform: { vendor: JUNIPER } deviations: { - switch_chip_id_unsupported: true backplane_facing_capacity_unsupported: true + install_position_and_install_component_unsupported: true + model_name_unsupported: true storage_component_unsupported: true + switch_chip_id_unsupported: true } } platform_exceptions: { @@ -20,7 +40,8 @@ platform_exceptions: { vendor: NOKIA } deviations: { - backplane_facing_capacity_unsupported: true + install_position_and_install_component_unsupported: true + model_name_unsupported: true + skip_controller_card_power_admin: true } } - diff --git a/feature/platform/tests/telemetry_inventory_test/telemetry_inventory_test.go b/feature/platform/tests/telemetry_inventory_test/telemetry_inventory_test.go index 7cb2a4c6a2e..d29c7c73452 100644 --- a/feature/platform/tests/telemetry_inventory_test/telemetry_inventory_test.go +++ b/feature/platform/tests/telemetry_inventory_test/telemetry_inventory_test.go @@ -34,6 +34,7 @@ var componentType = map[string]oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT{ "Fabric": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FABRIC, "Linecard": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_LINECARD, "Fan": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FAN, + "Fan Tray": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FAN_TRAY, "PowerSupply": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_POWER_SUPPLY, "Supervisor": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CONTROLLER_CARD, "SwitchChip": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_INTEGRATED_CIRCUIT, @@ -43,25 +44,60 @@ var componentType = map[string]oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT{ "Storage": oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_STORAGE, } +// validInstallComponentTypes indicates for each component type, which types of +// install-component it can have (i.e., what types of components can it be installed into). +var validInstallComponentTypes = map[oc.Component_Type_Union]map[oc.Component_Type_Union]bool{ + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CONTROLLER_CARD: { + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CHASSIS: true, + }, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FABRIC: { + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CHASSIS: true, + }, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FAN: { + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CHASSIS: true, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FAN_TRAY: true, + }, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FAN_TRAY: { + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CHASSIS: true, + }, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_LINECARD: { + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CHASSIS: true, + }, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_POWER_SUPPLY: { + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CHASSIS: true, + // Sometimes the parent is the power tray, which has type FRU. + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_FRU: true, + }, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_TRANSCEIVER: { + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CHASSIS: true, + oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_LINECARD: true, + }, +} + // use this map to cache related components used in subtests to run the test faster. var componentsByType map[string][]*oc.Component // Define a superset of the checklist for each component type properties struct { - descriptionValidation bool - idValidation bool - nameValidation bool - partNoValidation bool - serialNoValidation bool - mfgNameValidation bool - mfgDateValidation bool - swVerValidation bool - hwVerValidation bool - fwVerValidation bool - rrValidation bool - operStatus oc.E_PlatformTypes_COMPONENT_OPER_STATUS - parentValidation bool - pType oc.Component_Type_Union + descriptionValidation bool + idValidation bool + installPositionAndComponentValidation bool + nameValidation bool + partNoValidation bool + serialNoValidation bool + mfgNameValidation bool + mfgDateValidation bool + // If modelNameValidation is being used, the /components/component/state/model-name + // of the chassis component must be equal to the ondatra hardware_model name + // of its device. + modelNameValidation bool + swVerValidation bool + hwVerValidation bool + fwVerValidation bool + rrValidation bool + operStatus oc.E_PlatformTypes_COMPONENT_OPER_STATUS + parentValidation bool + pType oc.Component_Type_Union } func TestMain(m *testing.M) { @@ -80,6 +116,7 @@ func TestMain(m *testing.M) { // - Fabric card // - FabricChip // - Fan +// - Fan Tray // - Supervisor or Controller // - Validate telemetry components/component/state/software-version. // - SwitchChip @@ -88,11 +125,14 @@ func TestMain(m *testing.M) { // - integrated-circuit/backplane-facing-capacity/state/consumed-capacity // - integrated-circuit/backplane-facing-capacity/state/total // - integrated-circuit/backplane-facing-capacity/state/total-operational-capacity +// - components/component/subcomponents/subcomponent/name +// - components/component/subcomponents/subcomponent/state/name // - Transceiver // - Storage // - Validate telemetry /components/component/storage exists. // - TempSensor // - Validate telemetry /components/component/state/temperature/instant exists. +// - Validate telemetry /components/component/state/model-name for Chassis. // // Topology: // @@ -133,6 +173,7 @@ func TestHardwareCards(t *testing.T) { serialNoValidation: true, mfgNameValidation: true, mfgDateValidation: false, + modelNameValidation: true, hwVerValidation: true, fwVerValidation: false, rrValidation: false, @@ -143,19 +184,20 @@ func TestHardwareCards(t *testing.T) { }, { desc: "Fabric", cardFields: properties{ - descriptionValidation: true, - idValidation: true, - nameValidation: true, - partNoValidation: true, - serialNoValidation: true, - mfgNameValidation: true, - mfgDateValidation: false, - hwVerValidation: true, - fwVerValidation: false, - rrValidation: false, - operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, - parentValidation: true, - pType: componentType["Fabric"], + descriptionValidation: true, + idValidation: true, + installPositionAndComponentValidation: true, + nameValidation: true, + partNoValidation: true, + serialNoValidation: true, + mfgNameValidation: true, + mfgDateValidation: false, + hwVerValidation: true, + fwVerValidation: false, + rrValidation: false, + operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, + parentValidation: true, + pType: componentType["Fabric"], }, }, { desc: "Fan", @@ -171,78 +213,99 @@ func TestHardwareCards(t *testing.T) { fwVerValidation: false, rrValidation: false, operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, - parentValidation: false, + parentValidation: true, pType: componentType["Fan"], }, }, { - desc: "Linecard", + desc: "Fan Tray", cardFields: properties{ descriptionValidation: true, - idValidation: true, + idValidation: false, nameValidation: true, partNoValidation: true, serialNoValidation: true, - mfgNameValidation: true, + mfgNameValidation: false, mfgDateValidation: false, - hwVerValidation: true, + hwVerValidation: false, fwVerValidation: false, rrValidation: false, operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, parentValidation: true, - pType: oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_LINECARD, + pType: componentType["Fan Tray"], + }, + }, { + desc: "Linecard", + cardFields: properties{ + descriptionValidation: true, + idValidation: true, + installPositionAndComponentValidation: true, + nameValidation: true, + partNoValidation: true, + serialNoValidation: true, + mfgNameValidation: true, + mfgDateValidation: false, + hwVerValidation: true, + fwVerValidation: false, + rrValidation: false, + operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, + parentValidation: true, + pType: oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_LINECARD, }, }, { desc: "PowerSupply", cardFields: properties{ - descriptionValidation: true, - idValidation: true, - nameValidation: true, - partNoValidation: true, - serialNoValidation: true, - mfgNameValidation: true, - mfgDateValidation: false, - hwVerValidation: true, - fwVerValidation: false, - rrValidation: false, - operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, - parentValidation: true, - pType: componentType["PowerSupply"], + descriptionValidation: true, + idValidation: true, + installPositionAndComponentValidation: true, + nameValidation: true, + partNoValidation: true, + serialNoValidation: true, + mfgNameValidation: true, + mfgDateValidation: false, + hwVerValidation: true, + fwVerValidation: false, + rrValidation: false, + operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, + parentValidation: true, + pType: componentType["PowerSupply"], }, }, { desc: "Supervisor", cardFields: properties{ - descriptionValidation: true, - idValidation: true, - nameValidation: true, - partNoValidation: true, - serialNoValidation: true, - mfgNameValidation: true, - mfgDateValidation: false, - swVerValidation: false, - hwVerValidation: true, - fwVerValidation: false, - rrValidation: true, - operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, - parentValidation: true, - pType: componentType["Supervisor"], + descriptionValidation: true, + idValidation: true, + installPositionAndComponentValidation: true, + nameValidation: true, + partNoValidation: true, + serialNoValidation: true, + mfgNameValidation: true, + mfgDateValidation: false, + swVerValidation: false, + hwVerValidation: true, + fwVerValidation: false, + rrValidation: true, + operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, + parentValidation: true, + pType: componentType["Supervisor"], }, }, { desc: "Transceiver", cardFields: properties{ - descriptionValidation: false, - idValidation: false, - nameValidation: true, - partNoValidation: true, - serialNoValidation: true, - mfgNameValidation: true, - mfgDateValidation: false, - swVerValidation: false, - hwVerValidation: true, - fwVerValidation: true, - rrValidation: false, - operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_UNSET, - parentValidation: false, - pType: componentType["Transceiver"], + descriptionValidation: false, + idValidation: false, + installPositionAndComponentValidation: true, + nameValidation: true, + partNoValidation: true, + serialNoValidation: true, + mfgNameValidation: true, + mfgDateValidation: false, + swVerValidation: false, + hwVerValidation: true, + fwVerValidation: true, + rrValidation: false, + operStatus: oc.PlatformTypes_COMPONENT_OPER_STATUS_UNSET, + parentValidation: false, + pType: componentType["Transceiver"], }, }, { desc: "Cpu", @@ -293,6 +356,10 @@ func TestHardwareCards(t *testing.T) { t.Skip("Skip Linecard Telemetry check for fixed form factor devices.") } else if tc.desc == "Supervisor" && *args.NumControllerCards <= 0 { t.Skip("Skip Supervisor Telemetry check for fixed form factor devices.") + } else if tc.desc == "Fan Tray" && *args.NumFanTrays == 0 { + t.Skip("Skip Fan Tray Telemetry check for fixed form factor devices.") + } else if tc.desc == "Fan" && *args.NumFans == 0 { + t.Skip("Skip Fan Telemetry check for fixed form factor devices.") } cards := components[tc.desc] t.Logf("%s components count: %d", tc.desc, len(cards)) @@ -534,6 +601,26 @@ func TestControllerCardEmpty(t *testing.T) { } } +// validateSubcomponentsExistAsComponents checks that if the given component has subcomponents, that +// those subcomponents exist as components on the device (i.e. the leafref is valid). +func validateSubcomponentsExistAsComponents(c *oc.Component, components []*oc.Component, t *testing.T, dut *ondatra.DUTDevice) { + cName := c.GetName() + subcomponentsValue := gnmi.Lookup(t, dut, gnmi.OC().Component(cName).SubcomponentMap().State()) + subcomponents, ok := subcomponentsValue.Val() + if !ok { + // Not all components have subcomponents + // If the component doesn't have subcomponent, skip the check and return early + return + } + for _, subc := range subcomponents { + subcName := subc.GetName() + subComponent := gnmi.Lookup(t, dut, gnmi.OC().Component(subcName).State()) + if !subComponent.IsPresent() { + t.Errorf("Subcomponent %s does not exist as a component on the device", subcName) + } + } +} + func ValidateComponentState(t *testing.T, dut *ondatra.DUTDevice, cards []*oc.Component, p properties) { var validCards []*oc.Component switch p.pType { @@ -558,6 +645,7 @@ func ValidateComponentState(t *testing.T, dut *ondatra.DUTDevice, cards []*oc.Co } cName := card.GetName() t.Run(cName, func(t *testing.T) { + validateSubcomponentsExistAsComponents(card, validCards, t, dut) if p.descriptionValidation { t.Logf("Component %s Description: %s", cName, card.GetDescription()) if card.GetDescription() == "" { @@ -582,6 +670,14 @@ func ValidateComponentState(t *testing.T, dut *ondatra.DUTDevice, cards []*oc.Co } } + if p.installPositionAndComponentValidation && !deviations.InstallPositionAndInstallComponentUnsupported(dut) { + // If the component has a location and is removable, then it needs to have install-component + // and install-position. + if card.GetLocation() != "" && card.GetRemovable() { + testInstallComponentAndInstallPosition(t, card, validCards) + } + } + if p.nameValidation { name := card.GetName() t.Logf("Component %s Name: %s", cName, name) @@ -745,8 +841,9 @@ func ValidateComponentState(t *testing.T, dut *ondatra.DUTDevice, cards []*oc.Co t.Errorf("Component %s Parent: Chassis component NOT found in the hierarchy tree of component", cName) break } - parentType := gnmi.Get(t, dut, gnmi.OC().Component(parent).Type().State()) - if parentType == componentType["Chassis"] { + pLoookup := gnmi.Lookup(t, dut, gnmi.OC().Component(parent).Type().State()) + parentType, present := pLoookup.Val() + if present && parentType == componentType["Chassis"] { t.Logf("Component %s Parent: Found chassis component in the hierarchy tree of component", cName) break } @@ -758,6 +855,14 @@ func ValidateComponentState(t *testing.T, dut *ondatra.DUTDevice, cards []*oc.Co } } + if p.modelNameValidation { + if deviations.ModelNameUnsupported(dut) { + t.Logf("Telemetry path /components/component/state/model-name is not supported due to deviation ModelNameUnsupported. Skipping model name validation.") + } else if card.GetModelName() != dut.Model() { + t.Errorf("Component %s ModelName: got %s, want %s (dut's hardware model)", cName, card.GetModelName(), dut.Model()) + } + } + if p.pType != nil { ptype := card.GetType() t.Logf("Component %s Type: %v", cName, ptype) @@ -769,6 +874,38 @@ func ValidateComponentState(t *testing.T, dut *ondatra.DUTDevice, cards []*oc.Co } } +func testInstallComponentAndInstallPosition(t *testing.T, c *oc.Component, components []*oc.Component) { + icName := c.GetInstallComponent() + ip := c.GetInstallPosition() + hasInstallComponentAndPosition(t, c, icName, ip) + validateInstallComponent(t, icName, components, c) +} + +func validateInstallComponent(t *testing.T, icName string, components []*oc.Component, c *oc.Component) { + compMap := compNameMap(t, ondatra.DUT(t, "dut")) + ic, ok := compMap[icName] + if !ok { + t.Errorf("Component %s's install-component %s is not in component tree", icName, c.GetName()) + return + } + validTypes := validInstallComponentTypes[c.GetType()] + icType := ic.GetType() + if !validTypes[icType] { + t.Errorf("Component %s's install-component %s is not a supported parent type (%s)", c.GetName(), icName, icType) + } +} + +func hasInstallComponentAndPosition(t *testing.T, c *oc.Component, icName string, ip string) { + if icName == "" { + t.Errorf("Component %s is missing install-component", c.GetName()) + return + } + if ip == "" { + t.Errorf("Component %s is missing install-position", c.GetName()) + return + } +} + func TestStorage(t *testing.T) { // TODO: Add Storage test case here once supported. t.Skipf("Telemetry path /components/component/storage is not supported.") @@ -803,14 +940,19 @@ func TestHeatsinkTempSensor(t *testing.T) { t.Skipf("/components/component[name=]/state/temperature/instant is not supported.") } -func TestInterfaceComponentHierarchy(t *testing.T) { - dut := ondatra.DUT(t, "dut") - - // Map of component Name to corresponding Component OC object. +// Creates a map of component Name to corresponding Component OC object. +func compNameMap(t *testing.T, dut *ondatra.DUTDevice) map[string]*oc.Component { compMap := make(map[string]*oc.Component) for _, c := range gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) { compMap[c.GetName()] = c } + return compMap +} + +func TestInterfaceComponentHierarchy(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + compMap := compNameMap(t, dut) // Map of populated Transceivers to a random integer. transceivers := make(map[string]int) @@ -883,3 +1025,57 @@ func TestInterfaceComponentHierarchy(t *testing.T) { t.Fatalf("Couldn't find chassis for %q", dut.Model()) } } + +func TestDefaultPowerAdminState(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + fabrics := []*oc.Component{} + linecards := []*oc.Component{} + supervisors := []*oc.Component{} + + components := gnmi.GetAll(t, dut, gnmi.OC().ComponentAny().State()) + for compName := range componentType { + for _, c := range components { + if c.GetType() == nil || c.GetType() != componentType[compName] { + continue + } + switch compName { + case "Fabric": + fabrics = append(fabrics, c) + case "Linecard": + linecards = append(linecards, c) + case "Supervisor": + supervisors = append(supervisors, c) + } + } + } + + t.Logf("Fabrics: %v", fabrics) + t.Logf("Linecards: %v", linecards) + t.Logf("Supervisors: %v", supervisors) + + if len(fabrics) != 0 { + pas := gnmi.Get(t, dut, gnmi.OC().Component(fabrics[0].GetName()).Fabric().PowerAdminState().State()) + t.Logf("Component %s PowerAdminState: %v", fabrics[0].GetName(), pas) + if pas == oc.Platform_ComponentPowerType_UNSET { + t.Errorf("Component %s PowerAdminState is unset", fabrics[0].GetName()) + } + } + + if len(linecards) != 0 { + pas := gnmi.Get(t, dut, gnmi.OC().Component(linecards[0].GetName()).Linecard().PowerAdminState().State()) + t.Logf("Component %s PowerAdminState: %v", linecards[0].GetName(), pas) + if pas == oc.Platform_ComponentPowerType_UNSET { + t.Errorf("Component %s PowerAdminState is unset", linecards[0].GetName()) + } + } + if !deviations.SkipControllerCardPowerAdmin(dut) { + if len(supervisors) != 0 { + pas := gnmi.Get(t, dut, gnmi.OC().Component(supervisors[0].GetName()).ControllerCard().PowerAdminState().State()) + t.Logf("Component %s PowerAdminState: %v", supervisors[0].GetName(), pas) + if pas == oc.Platform_ComponentPowerType_UNSET { + t.Errorf("Component %s PowerAdminState is unset", supervisors[0].GetName()) + } + } + } +} diff --git a/feature/platform/transceiver/ZR_pre-fec_ber_test/README.md b/feature/platform/transceiver/ZR_pre-fec_ber_test/README.md deleted file mode 100644 index 968b57ae70f..00000000000 --- a/feature/platform/transceiver/ZR_pre-fec_ber_test/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# TRANSCEIVER-2: Telemetry: 400ZR Optics Pre-FEC(Forward Error Correction) BER(Bit Error Rate) - -## Summary - -Validate 400ZR optics module reports pre-FEC bit error rate performance data. - -## 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 target TX output power - example -10 dBm - * Both transceivers are tuned to a valid centre frequency - example 193.1 THz -* With the link ZR link established as explained above, verify that the - following ZR transceiver telemetry paths exist and are streamed for both - the ZR optics - * /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 - -**Note:** For min, max, and avg values, 10 second sampling is preferred. If - 10 seconds is not supported, the sampling interval used must be - communicated. - - -* Verify that the optics pre-FEC BER is updated after the interface flaps. - - * Enable a pair of ZR interfaces on the DUT as explained above. - * Verify the ZR optics pre FEC BER PMs are in the normal range. - * Disable or shut down the interface on the DUT. - * Re-enable the interfaces on the DUT. - * Verify the ZR optics pre FEC PM is updated to the value in the normal - range again. Typical expected value should be less than 1.2E-2 - -## Config Parameter coverage - -* /components/component/oc-transceiver:transceiver/oc-transceiver/config/enabled - -## Telemetry Parameter coverage - - * /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 diff --git a/feature/platform/transceiver/zr_esnr_and_cd_test/README.MD b/feature/platform/transceiver/tests/zr_cd_test/README.md similarity index 62% rename from feature/platform/transceiver/zr_esnr_and_cd_test/README.MD rename to feature/platform/transceiver/tests/zr_cd_test/README.md index cbf4a6e25d7..7895a20d051 100644 --- a/feature/platform/transceiver/zr_esnr_and_cd_test/README.MD +++ b/feature/platform/transceiver/tests/zr_cd_test/README.md @@ -1,14 +1,13 @@ -# TRANSCEIVER-1: Telemetry: 400ZR Electrical Signal to Noise Ratio(eSNR) and Chromatic Dispersion(CD) telemetry values streaming +# TRANSCEIVER-1: Telemetry: 400ZR Chromatic Dispersion(CD) telemetry values streaming ## Summary -Validate 400ZR optics module reports accurate eSNR and CD telemetry values. - -eSNR is defined as the electrical Signal to Noise ratio at the decision -sampling point in dB +Validate 400ZR optics module reports accurate CD telemetry values. Chromatic Dispersion is frequency dependent change in signal phase velocity due -to fiber measured in ps/nm +to fiber measured in ps/nm + +The test must be repeated for each supported operational-mode or as agreed between the vendor and customer. ## Procedure @@ -25,10 +24,6 @@ to fiber measured in ps/nm * With the ZR link is established as explained above, verify that the following ZR transceiver telemetry paths exist and are streamed for both the ZR optics - * /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 * /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 @@ -38,7 +33,7 @@ to fiber measured in ps/nm stream any invalid string values like "nil" or "-inf" until valid values are available for streaming. -* eSNR and CD streamed values must always be of type Decimal64. +* CD streamed values must always be of type Decimal64. When link interfaces are in down state 0 must be reported as a valid value. @@ -47,34 +42,30 @@ to fiber measured in ps/nm communicated. -* Verify that the optics eSNR and CD is updated after the interface flaps. +* Verify that the optics CD is updated after the interface flaps. * Enable a pair of ZR interfaces on the DUT as explained above. - * Verify the ZR optics eSNR and CD telemetry values are in the normal range. + * Verify the ZR optics CD telemetry values are in the normal range. * Disable or shut down the interface on the DUT. * Verify with interfaces in down state both optics are streaming Decimal64 0 - value for both eSNR and CD. + value for CD. * Re-enable the interfaces on the DUT. - * Verify the ZR optics eSNR and CD telemetry values are updated to the + * Verify the ZR optics CD telemetry values are updated to the value in the normal range again. - * Typical expected value range for eSNR is 13.5 to - 18 dB +/-0.1 dB. * Typical CD expected value range is 0 to 2400 ps/nm. -* Verify that the optics eSNR and CD is updated after a fiber cut. +* Verify that the optics CD is updated after a fiber cut. * Enable a pair of ZR interfaces on the DUT as explained above. - * Verify the ZR optics eSNR and CD telemetry values are in the normal + * Verify the ZR optics CD telemetry values are in the normal range. * Simulate a fiber cut using the optical switch that sits in-between the DUT ports. * Verify with link in down state due to fiber cut both optics are streaming - Decimal64 0 value for both eSNR and CD. + Decimal64 0 value for CD. * Re-enable the optical switch connection to clear the fiber cut fault. - * Verify the ZR optics eSNR and CD telemetry values are updated to the value in the normal + * Verify the ZR optics CD telemetry values are updated to the value in the normal range again. - * Typical expected value range for eSNR is 13.5 to - 18 dB +/-0.1 dB. * Typical CD expected value range is 0 to 2400 ps/nm. ## Config Parameter coverage @@ -83,11 +74,16 @@ to fiber measured in ps/nm ## Telemetry Parameter coverage -* /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 * /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 \ No newline at end of file +* /platform/components/component/optical-channel/state/chromatic-dispersion/max + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/platform/transceiver/tests/zr_cd_test/metadata.textproto b/feature/platform/transceiver/tests/zr_cd_test/metadata.textproto new file mode 100644 index 00000000000..0cbcbfbaae7 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_cd_test/metadata.textproto @@ -0,0 +1,15 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "6c7ec460-10be-4c71-81f5-888f76ef241b" +plan_id: "TRANSCEIVER-1" +description: "Telemetry: 400ZR Chromatic Dispersion(CD) telemetry values streaming" +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + } +} \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_cd_test/zr_cd_test.go b/feature/platform/transceiver/tests/zr_cd_test/zr_cd_test.go new file mode 100644 index 00000000000..0d6e421d099 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_cd_test/zr_cd_test.go @@ -0,0 +1,160 @@ +package zr_cd_test + +import ( + "flag" + "testing" + "time" + + "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" +) + +const ( + samplingInterval = 10 * time.Second + minCDValue = -200 + maxCDValue = 2400 + inActiveCDValue = 0.0 + timeout = 10 * time.Minute + flapInterval = 30 * time.Second +) + +type portState int + +const ( + disabled portState = iota + enabled +) + +var ( + frequencies = []uint64{191400000, 196100000} // 400ZR OIF wavelength range + targetOutputPowers = []float64{-13, -9} // 400ZR OIF Tx power range + operationalModeFlag = flag.Int("operational_mode", 1, "vendor-specific operational-mode for the channel") + operationalMode uint16 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func verifyCDValue(t *testing.T, dut1 *ondatra.DUTDevice, pStream *samplestream.SampleStream[float64], sensorName string, status portState) float64 { + CDSampleNexts := pStream.Nexts(2) + CDSample := CDSampleNexts[1] + t.Logf("CDSampleNexts %v", CDSampleNexts) + if CDSample == nil { + t.Fatalf("CD telemetry %s was not streamed in the most recent subscription interval", sensorName) + } + CDVal, ok := CDSample.Val() + if !ok { + t.Fatalf("CD %q telemetry is not present", CDSample) + } + // Check CD return value of correct type + switch { + case status == disabled: + if CDVal != inActiveCDValue { + t.Fatalf("The inactive CD is %v, expected %v", CDVal, inActiveCDValue) + } + case status == enabled: + if CDVal < minCDValue || CDVal > maxCDValue { + t.Fatalf("The variable CD is %v, expected range (%v, %v)", CDVal, minCDValue, maxCDValue) + } + default: + t.Fatalf("Invalid status %v", status) + } + // Get current time + now := time.Now() + // Format the time string + formattedTime := now.Format("2006-01-02 15:04:05") + t.Logf("%s Device %v CD %s value at status %v: %v", formattedTime, dut1.Name(), sensorName, status, CDVal) + + return CDVal +} + +// TODO: Avg and Instant value checks are not available. Need to align their sample streaming windows. +func verifyAllCDValues(t *testing.T, dut1 *ondatra.DUTDevice, p1StreamInstant, p1StreamMax, p1StreamMin, p1StreamAvg *samplestream.SampleStream[float64], status portState) { + verifyCDValue(t, dut1, p1StreamInstant, "Instant", status) + verifyCDValue(t, dut1, p1StreamMax, "Max", status) + verifyCDValue(t, dut1, p1StreamMin, "Min", status) + verifyCDValue(t, dut1, p1StreamAvg, "Avg", status) + + // if CDAvg >= CDMin && CDAvg <= CDMax { + // t.Logf("The average is between the maximum and minimum values, Avg:%v Max:%v Min:%v", CDAvg, CDMax, CDMin) + // } else { + // t.Fatalf("The average is NOT between the maximum and minimum values, Avg:%v Max:%v Min:%v", CDAvg, CDMax, CDMin) + // } + + // if CDInstant >= CDMin && CDInstant <= CDMax { + // t.Logf("The instant is between the maximum and minimum values, Instant:%v Max:%v Min:%v", CDInstant, CDMax, CDMin) + // } else { + // t.Fatalf("The instant is NOT between the maximum and minimum values, Instant:%v Max:%v Min:%v", CDInstant, CDMax, CDMin) + // } +} + +func TestCDValue(t *testing.T) { + dut := ondatra.DUT(t, "dut") + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + fptest.ConfigureDefaultNetworkInstance(t, dut) + + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + cfgplugins.InterfaceConfig(t, dut, dp1) + cfgplugins.InterfaceConfig(t, dut, dp2) + + tr1 := gnmi.Get(t, dut, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + tr2 := gnmi.Get(t, dut, gnmi.OC().Interface(dp2.Name()).Transceiver().State()) + och1 := gnmi.Get(t, dut, gnmi.OC().Component(tr1).Transceiver().Channel(0).AssociatedOpticalChannel().State()) + och2 := gnmi.Get(t, dut, gnmi.OC().Component(tr2).Transceiver().Channel(0).AssociatedOpticalChannel().State()) + component1 := gnmi.OC().Component(och1) + for _, frequency := range frequencies { + for _, targetOutputPower := range targetOutputPowers { + cfgplugins.ConfigOpticalChannel(t, dut, och1, frequency, targetOutputPower, operationalMode) + cfgplugins.ConfigOpticalChannel(t, dut, och2, frequency, targetOutputPower, operationalMode) + + // 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) + + p1StreamInstant := samplestream.New(t, dut, component1.OpticalChannel().ChromaticDispersion().Instant().State(), samplingInterval) + p1StreamMin := samplestream.New(t, dut, component1.OpticalChannel().ChromaticDispersion().Min().State(), samplingInterval) + p1StreamMax := samplestream.New(t, dut, component1.OpticalChannel().ChromaticDispersion().Max().State(), samplingInterval) + p1StreamAvg := samplestream.New(t, dut, component1.OpticalChannel().ChromaticDispersion().Avg().State(), samplingInterval) + + defer p1StreamInstant.Close() + defer p1StreamMin.Close() + defer p1StreamMax.Close() + defer p1StreamAvg.Close() + + verifyAllCDValues(t, dut, p1StreamInstant, p1StreamMax, p1StreamMin, p1StreamAvg, enabled) + + // Disable interface. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), false) + } + // Wait for channels to be down. + gnmi.Await(t, dut, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + gnmi.Await(t, dut, gnmi.OC().Interface(dp2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + t.Logf("Interfaces are down: %v, %v", dp1.Name(), dp2.Name()) + verifyAllCDValues(t, dut, p1StreamInstant, p1StreamMax, p1StreamMin, p1StreamAvg, enabled) + + time.Sleep(flapInterval) + + // Enable interface. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), 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) + t.Logf("Interfaces are up: %v, %v", dp1.Name(), dp2.Name()) + verifyAllCDValues(t, dut, p1StreamInstant, p1StreamMax, p1StreamMin, p1StreamAvg, enabled) + + } + } +} diff --git a/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md new file mode 100644 index 00000000000..49966402fd2 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md @@ -0,0 +1,48 @@ +# TRANSCEIVER-10: Telemetry: 400ZR Optics FEC(Forward Error Correction) Uncorrectable Frames Streaming. + +## Summary + +Validate 400ZR optics module reports uncorrectable FEC frames count. + +This observable represents the number of uncorrectable FEC frames, +measured as RS(544,514) equivalent frames, in a short interval. +This is a post-FEC decoder error metric. + +## 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 target TX output power + example -10 dBm + * Both transceivers are tuned to a valid centre frequency + example 193.1 THz +* With the ZR link established as explained above, verify that the + following ZR transceiver telemetry path exist and is streamed for both + the ZR optics. + * /terminal-device/logical-channels/channel/otn/state/fec-uncorrectable-blocks +* Verify that the reported data should be of type yang:counter64. +* When the modules or the devices are still in a boot stage, they must not + stream any invalid string values like "nil" or "-inf". +* Toggle the interface state using /interfaces/interface/config/enabled and + verify relevant FEC uncorrectable frame count is streamed. If there are no + errors a value of 0 should be streamed for no FEC uncorrectable frames. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config Parameter coverage + /interfaces/interface/config/enabled: + /components/component/transceiver/config/enabled: + platform_type: ["OPTICAL_CHANNEL"] + # Telemetry Parameter coverage + /terminal-device/logical-channels/channel/otn/state/fec-uncorrectable-blocks: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/metadata.textproto b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/metadata.textproto new file mode 100644 index 00000000000..f2bd6231241 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/metadata.textproto @@ -0,0 +1,27 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "6f9059c8-e49c-4ff6-8bd2-f474cd4fcfce" +plan_id: "TRANSCEIVER-10" +description: "Telemetry: 400ZR Optics FEC(Forward Error Correction) Uncorrectable Frames Streaming." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + otn_channel_trib_unsupported: true + eth_channel_ingress_parameters_unsupported: true + eth_channel_assignment_cisco_numbering: true + } +} diff --git a/feature/platform/transceiver/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 new file mode 100644 index 00000000000..8dd5f140adf --- /dev/null +++ b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/zr_fec_uncorrectable_frames_test.go @@ -0,0 +1,111 @@ +// 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 zr_fec_uncorrectable_frames_test + +import ( + "fmt" + "reflect" + "testing" + "time" + + "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/ygot/ygot" +) + +const ( + sampleInterval = 10 * time.Second + intUpdateTime = 2 * time.Minute + otnIndexBase = uint32(4000) + ethIndexBase = uint32(40000) +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func validateFecUncorrectableBlocks(t *testing.T, stream *samplestream.SampleStream[uint64]) { + fecStream := stream.Next() + if fecStream == nil { + t.Fatalf("Fec Uncorrectable Blocks was not streamed in the most recent subscription interval") + } + fec, ok := fecStream.Val() + if !ok { + t.Fatalf("Error capturing streaming Fec value") + } + if reflect.TypeOf(fec).Kind() != reflect.Uint64 { + t.Fatalf("fec value is not type uint64") + } + if fec != 0 { + t.Fatalf("Got FecUncorrectableBlocks got %d, want 0", fec) + } +} + +func TestZrUncorrectableFrames(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + var ( + trs = make(map[string]string) + ochs = make(map[string]string) + otnIndexes = make(map[string]uint32) + ethIndexes = make(map[string]uint32) + ) + + ports := []string{"port1", "port2"} + + for i, port := range ports { + dp := dut.Port(t, port) + cfgplugins.InterfaceConfig(t, dut, dp) + trs[dp.Name()] = gnmi.Get(t, dut, gnmi.OC().Interface(dp.Name()).Transceiver().State()) + ochs[dp.Name()] = gnmi.Get(t, dut, gnmi.OC().Component(trs[dp.Name()]).Transceiver().Channel(0).AssociatedOpticalChannel().State()) + otnIndexes[dp.Name()] = otnIndexBase + uint32(i) + ethIndexes[dp.Name()] = ethIndexBase + uint32(i) + cfgplugins.ConfigOTNChannel(t, dut, ochs[dp.Name()], otnIndexes[dp.Name()], ethIndexes[dp.Name()]) + cfgplugins.ConfigETHChannel(t, dut, dp.Name(), trs[dp.Name()], otnIndexes[dp.Name()], ethIndexes[dp.Name()]) + } + + for _, port := range ports { + t.Run(fmt.Sprintf("Port:%s", port), func(t *testing.T) { + dp := dut.Port(t, port) + 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) + + // Toggle interface enabled + d := &oc.Root{} + i := d.GetOrCreateInterface(dp.Name()) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + + // Disable interface + i.Enabled = ygot.Bool(false) + gnmi.Replace(t, dut, gnmi.OC().Interface(dp.Name()).Config(), i) + // Wait for the cooling-off period + gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_DOWN) + + // Enable interface + i.Enabled = ygot.Bool(true) + gnmi.Replace(t, dut, gnmi.OC().Interface(dp.Name()).Config(), i) + // 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) + }) + } +} diff --git a/feature/platform/transceiver/zr_firmware_version_test/README.md b/feature/platform/transceiver/tests/zr_firmware_version_test/README.md similarity index 90% rename from feature/platform/transceiver/zr_firmware_version_test/README.md rename to feature/platform/transceiver/tests/zr_firmware_version_test/README.md index 3f0a074f0ec..283a3a83c14 100644 --- a/feature/platform/transceiver/zr_firmware_version_test/README.md +++ b/feature/platform/transceiver/tests/zr_firmware_version_test/README.md @@ -28,3 +28,12 @@ Validate 400ZR optics module reports correct firmware version. ## Telemetry Parameter coverage * /platform/components/component/state/firmware-version + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/platform/transceiver/tests/zr_firmware_version_test/metadata.textproto b/feature/platform/transceiver/tests/zr_firmware_version_test/metadata.textproto new file mode 100644 index 00000000000..0e66e1c9c2e --- /dev/null +++ b/feature/platform/transceiver/tests/zr_firmware_version_test/metadata.textproto @@ -0,0 +1,17 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "0f3bb528-28f4-4284-8d2f-de105be09241" +plan_id: "TRANSCEIVER-3" +description: "Telemetry: 400ZR Optics firmware version streaming" +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + } +} \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_firmware_version_test/zr_firmware_version_test.go b/feature/platform/transceiver/tests/zr_firmware_version_test/zr_firmware_version_test.go new file mode 100644 index 00000000000..a5b9f9de78f --- /dev/null +++ b/feature/platform/transceiver/tests/zr_firmware_version_test/zr_firmware_version_test.go @@ -0,0 +1,127 @@ +// 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 zr_firmware_version_test + +import ( + "reflect" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/components" + "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/ygot/ygot" +) + +const ( + targetOutputPower = -10 + frequency = 193100000 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Topology: dut:port1 <--> port2:dut + +func configInterface(t *testing.T, dut1 *ondatra.DUTDevice, dp *ondatra.Port, enable bool) { + d := &oc.Root{} + i := d.GetOrCreateInterface(dp.Name()) + i.Enabled = ygot.Bool(enable) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + gnmi.Replace(t, dut1, gnmi.OC().Interface(dp.Name()).Config(), i) + componentName := components.OpticalChannelComponentFromPort(t, dut1, dp) + // Set config container leaf for optical channel + component := gnmi.OC().Component(componentName) + gnmi.Replace(t, dut1, component.Config(), &oc.Component{ + Name: ygot.String(componentName), + }) + gnmi.Replace(t, dut1, component.OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(targetOutputPower), + Frequency: ygot.Uint64(frequency), + }) +} + +func verifyFirmwareVersionValue(t *testing.T, dut1 *ondatra.DUTDevice, pStream *samplestream.SampleStream[string]) { + firmwareVersionSample := pStream.Next() + if firmwareVersionSample == nil { + t.Fatalf("Firmware telemetry %v was not streamed in the most recent subscription interval", firmwareVersionSample) + } + firmwareVersionVal, ok := firmwareVersionSample.Val() + if !ok { + t.Fatalf("Firmware version %q telemetry is not present", firmwareVersionSample) + } + // Check firmware version return value of correct type + if reflect.TypeOf(firmwareVersionVal).Kind() != reflect.String { + t.Fatalf("Return value is not type string") + } +} + +func TestZRFirmwareVersionState(t *testing.T) { + dut1 := ondatra.DUT(t, "dut") + dp1 := dut1.Port(t, "port1") + dp2 := dut1.Port(t, "port2") + t.Logf("dut1: %v", dut1) + t.Logf("dut1 dp1 name: %v", dp1.Name()) + 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) + transceiverName := gnmi.Get(t, dut1, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + // Check if TRANSCEIVER is of type 400ZR + if dp1.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s Transceiver is not 400ZR its of type: %v", transceiverName, dp1.PMD()) + } + component1 := gnmi.OC().Component(transceiverName) + + p1Stream := samplestream.New(t, dut1, component1.FirmwareVersion().State(), 10*time.Second) + + verifyFirmwareVersionValue(t, dut1, p1Stream) + + p1Stream.Close() +} + +func TestZRFirmwareVersionStateInterfaceFlap(t *testing.T) { + dut1 := ondatra.DUT(t, "dut") + dp1 := dut1.Port(t, "port1") + dp2 := dut1.Port(t, "port2") + t.Logf("dut1: %v", dut1) + t.Logf("dut1 dp1 name: %v", dp1.Name()) + cfgplugins.InterfaceConfig(t, dut1, dp1) + cfgplugins.InterfaceConfig(t, dut1, dp2) + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + transceiverName := gnmi.Get(t, dut1, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + // Check if TRANSCEIVER is of type 400ZR + if dp1.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s Transceiver is not 400ZR its of type: %v", transceiverName, dp1.PMD()) + } + // Disable interface + configInterface(t, dut1, dp1, false) + component1 := gnmi.OC().Component(transceiverName) + + p1Stream := samplestream.New(t, dut1, component1.FirmwareVersion().State(), 10*time.Second) + + // Wait 60 sec cooling-off period + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), 2*time.Minute, oc.Interface_OperStatus_DOWN) + verifyFirmwareVersionValue(t, dut1, p1Stream) + + // Enable interface + configInterface(t, dut1, dp1, true) + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), 2*time.Minute, oc.Interface_OperStatus_UP) + verifyFirmwareVersionValue(t, dut1, p1Stream) +} diff --git a/feature/platform/transceiver/zr_input_output_power_test/README.MD b/feature/platform/transceiver/tests/zr_input_output_power_test/README.md similarity index 69% rename from feature/platform/transceiver/zr_input_output_power_test/README.MD rename to feature/platform/transceiver/tests/zr_input_output_power_test/README.md index 5cd87b500b5..db1563cfaca 100644 --- a/feature/platform/transceiver/zr_input_output_power_test/README.MD +++ b/feature/platform/transceiver/tests/zr_input_output_power_test/README.md @@ -17,6 +17,7 @@ power. * This is the total TX output power * Is mapped to component/optical-channel/ full path shown below +The test must be repeated for each supported operational-mode or as agreed between the vendor and customer. ## TRANSCEIVER-4.1 @@ -53,8 +54,8 @@ power. are available for streaming. * RX Input and TX output power values must always be of type decimal64. - When link interfaces are in down state 0 must be reported as a valid - value. + When link interfaces are in down state RX Input power of -40 dbm must be + reported as a valid value. **Note:** For min, max, and avg values, 10 second sampling is preferred. If 10 seconds is not supported, the sampling interval used must be @@ -76,7 +77,7 @@ power. * Verify the ZR optics RX input and TX output power telemetry values are updated to the value in the normal range again. * Typical min/max value range for RX Signal Power -14 to 0 dbm. - * Typical min/max value range for TX Output Power -13 to -9 dbm. + * Typical min/max value range for TX Output Power -10 to -6 dbm. ## TRANSCEIVER-4.4 @@ -95,23 +96,43 @@ power. * Verify the ZR optics RX input and TX output power telemetry values are updated to the value in the normal range again. * Typical min/max value range for RX Signal Power -14 to 0 dbm. - * Typical min/max value range for TX Output Power -13 to -9 dbm. - -## Config Parameter coverage - -* /components/component/transceiver/config/enabled - -## Telemetry Parameter coverage - -* /components/component/optical-channel/state/input-power/instant -* /components/component/optical-channel/state/input-power/avg -* /components/component/optical-channel/state/input-power/min -* /components/component/optical-channel/state/input-power/max -* /components/component/optical-channel/state/output-power/instant -* /components/component/optical-channel/state/output-power/avg -* /components/component/optical-channel/state/output-power/min -* /components/component/optical-channel/state/output-power/max -* /components/component/transceiver/physical-channel/channel/state/input-power/instant -* /components/component/transceiver/physical-channel/channel/state/input-power/min -* /components/component/transceiver/physical-channel/channel/state/input-power/max -* /components/component/transceiver/physical-channel/channel/state/input-power/avg \ No newline at end of file + * Typical min/max value range for TX Output Power -10 to -6 dbm. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config Parameter coverage + /interfaces/interface/config/enabled: + # Telemetry Parameter coverage + /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/transceiver/physical-channels/channel/state/input-power/instant: + 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/transceiver/physical-channels/channel/state/input-power/avg: + platform_type: [ "TRANSCEIVER" ] + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_input_output_power_test/metadata.textproto b/feature/platform/transceiver/tests/zr_input_output_power_test/metadata.textproto new file mode 100644 index 00000000000..70cbe802de8 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_input_output_power_test/metadata.textproto @@ -0,0 +1,15 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "67be4256-6965-4a6d-bc68-322c878cbc73" +plan_id: "TRANSCEIVER-4" +description: "Telemetry: 400ZR RX input and TX output power telemetry values streaming." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + } +} diff --git a/feature/platform/transceiver/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 new file mode 100644 index 00000000000..58b330065ac --- /dev/null +++ b/feature/platform/transceiver/tests/zr_input_output_power_test/zr_input_output_power_test.go @@ -0,0 +1,207 @@ +package zr_input_output_power_test + +import ( + "flag" + "testing" + "time" + + "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 = 10 * time.Second + inactiveOCHRxPower = -30.0 + inactiveOCHTxPower = -30.0 + inactiveTransceiverRxPower = -20.0 + rxPowerReadingError = 2 + txPowerReadingError = 0.5 + timeout = 10 * time.Minute +) + +var ( + frequencies = []uint64{191400000, 196100000} + targetOpticalPowers = []float64{-9, -13} + operationalModeFlag = flag.Int("operational_mode", 1, "vendor-specific operational-mode for the channel") + operationalMode uint16 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestOpticalPower(t *testing.T) { + dut := ondatra.DUT(t, "dut") + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + fptest.ConfigureDefaultNetworkInstance(t, dut) + + var ( + trs = make(map[string]string) + ochs = make(map[string]string) + ) + + for _, p := range dut.Ports() { + // Check the port PMD is 400ZR. + if p.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s PMD is %v, not 400ZR", p.Name(), p.PMD()) + } + + // Get transceiver and optical channel. + trs[p.Name()] = gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()) + ochs[p.Name()] = gnmi.Get(t, dut, gnmi.OC().Component(trs[p.Name()]).Transceiver().Channel(0).AssociatedOpticalChannel().State()) + } + + for _, frequency := range frequencies { + for _, targetOpticalPower := range targetOpticalPowers { + // Configure OCH component and OTN and ETH logical channels. + for _, p := range dut.Ports() { + cfgplugins.ConfigOpticalChannel(t, dut, ochs[p.Name()], frequency, targetOpticalPower, operationalMode) + } + + // Create sample steams for each port. + ochStreams := make(map[string]*samplestream.SampleStream[*oc.Component_OpticalChannel]) + trStreams := make(map[string]*samplestream.SampleStream[*oc.Component_Transceiver_Channel]) + interfaceStreams := make(map[string]*samplestream.SampleStream[*oc.Interface]) + for portName, och := range ochs { + ochStreams[portName] = samplestream.New(t, dut, gnmi.OC().Component(och).OpticalChannel().State(), samplingInterval) + trStreams[portName] = samplestream.New(t, dut, gnmi.OC().Component(trs[portName]).Transceiver().Channel(0).State(), samplingInterval) + interfaceStreams[portName] = samplestream.New(t, dut, gnmi.OC().Interface(portName).State(), samplingInterval) + defer ochStreams[portName].Close() + defer trStreams[portName].Close() + defer interfaceStreams[portName].Close() + } + + // Enable interface. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), true) + } + + // 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) + } + + time.Sleep(3 * samplingInterval) // Wait an extra sample interval to ensure the device has time to process the change. + + validateAllSampleStreams(t, dut, true, interfaceStreams, ochStreams, trStreams, targetOpticalPower) + + // Disable interface. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), false) + } + + // Wait for streaming telemetry to report the channels as down. + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + } + time.Sleep(3 * samplingInterval) // Wait an extra sample interval to ensure the device has time to process the change. + + validateAllSampleStreams(t, dut, false, interfaceStreams, ochStreams, trStreams, targetOpticalPower) + + // Re-enable transceivers. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), true) + } + + // 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) + } + time.Sleep(3 * samplingInterval) // Wait an extra sample interval to ensure the device has time to process the change. + + validateAllSampleStreams(t, dut, true, interfaceStreams, ochStreams, trStreams, targetOpticalPower) + } + } +} + +// validateAllSampleStreams validates all the sample streams. +func validateAllSampleStreams(t *testing.T, dut *ondatra.DUTDevice, isEnabled bool, interfaceStreams map[string]*samplestream.SampleStream[*oc.Interface], ochStreams map[string]*samplestream.SampleStream[*oc.Component_OpticalChannel], transceiverStreams map[string]*samplestream.SampleStream[*oc.Component_Transceiver_Channel], targetOpticalPower float64) { + for _, p := range dut.Ports() { + for valIndex := range interfaceStreams[p.Name()].All() { + if valIndex >= len(ochStreams[p.Name()].All()) || valIndex >= len(transceiverStreams[p.Name()].All()) { + break + } + operStatus := validateSampleStream(t, interfaceStreams[p.Name()].All()[valIndex], ochStreams[p.Name()].All()[valIndex], transceiverStreams[p.Name()].All()[valIndex], p.Name(), targetOpticalPower) + switch operStatus { + case oc.Interface_OperStatus_UP: + if !isEnabled { + t.Errorf("Invalid %v operStatus value: want DOWN, got %v", p.Name(), operStatus) + } + case oc.Interface_OperStatus_DOWN: + if isEnabled { + t.Errorf("Invalid %v operStatus value: want UP, got %v", p.Name(), operStatus) + } + } + } + } +} + +// validateSampleStream validates the stream data. +func validateSampleStream(t *testing.T, interfaceData *ygnmi.Value[*oc.Interface], ochData *ygnmi.Value[*oc.Component_OpticalChannel], transceiverData *ygnmi.Value[*oc.Component_Transceiver_Channel], portName string, targetOpticalPower float64) oc.E_Interface_OperStatus { + if interfaceData == nil { + t.Errorf("Data not received for port %v.", portName) + return oc.Interface_OperStatus_UNSET + } + interfaceValue, ok := interfaceData.Val() + if !ok { + t.Errorf("Channel data is empty for port %v.", portName) + return oc.Interface_OperStatus_UNSET + } + operStatus := interfaceValue.GetOperStatus() + if operStatus == oc.Interface_OperStatus_UNSET { + t.Errorf("Link state data is empty for port %v", portName) + return oc.Interface_OperStatus_UNSET + } + ochValue, ok := ochData.Val() + if !ok { + t.Errorf("Terminal Device data is empty for port %v.", portName) + return oc.Interface_OperStatus_UNSET + } + if inPow := ochValue.GetInputPower(); inPow == nil { + t.Errorf("InputPower data is empty for port %v", portName) + } else { + validatePowerValue(t, portName, "OpticalChannelInputPower", inPow.GetInstant(), inPow.GetMin(), inPow.GetMax(), inPow.GetAvg(), targetOpticalPower-rxPowerReadingError, targetOpticalPower+rxPowerReadingError, inactiveOCHRxPower, operStatus) + } + if outPow := ochValue.GetOutputPower(); outPow == nil { + t.Errorf("OutputPower data is empty for port %v", portName) + } else { + validatePowerValue(t, portName, "OpticalChannelOutputPower", outPow.GetInstant(), outPow.GetMin(), outPow.GetMax(), outPow.GetAvg(), targetOpticalPower-txPowerReadingError, targetOpticalPower+txPowerReadingError, inactiveOCHTxPower, operStatus) + } + transceiverValue, ok := transceiverData.Val() + if !ok { + t.Errorf("Transceiver data is empty for port %v.", portName) + return oc.Interface_OperStatus_UNSET + } + if inPow := transceiverValue.GetInputPower(); inPow == nil { + t.Errorf("InputPower data is empty for port %v", portName) + } else { + validatePowerValue(t, portName, "TransceiverInputPower", inPow.GetInstant(), inPow.GetMin(), inPow.GetMax(), inPow.GetAvg(), targetOpticalPower-rxPowerReadingError, targetOpticalPower+rxPowerReadingError, inactiveTransceiverRxPower, operStatus) + } + return operStatus +} + +// validatePowerValue validates the power value. +func validatePowerValue(t *testing.T, portName, pm string, instant, min, max, avg, minAllowed, maxAllowed, inactiveValue float64, operStatus oc.E_Interface_OperStatus) { + switch operStatus { + case oc.Interface_OperStatus_UP: + if instant < minAllowed || instant > maxAllowed { + t.Errorf("Invalid %v sample when %v is UP --> min : %v, max : %v, avg : %v, instant : %v", pm, portName, min, max, avg, instant) + return + } + case oc.Interface_OperStatus_DOWN: + if instant > inactiveValue { + t.Errorf("Invalid %v sample when %v is DOWN --> min : %v, max : %v, avg : %v, instant : %v", pm, portName, min, max, avg, instant) + return + } + } + t.Logf("Valid %v sample when %v is %v --> min : %v, max : %v, avg : %v, instant : %v", pm, portName, operStatus, min, max, avg, instant) +} diff --git a/feature/platform/transceiver/zr_inventory_test/README.md b/feature/platform/transceiver/tests/zr_inventory_test/README.md similarity index 93% rename from feature/platform/transceiver/zr_inventory_test/README.md rename to feature/platform/transceiver/tests/zr_inventory_test/README.md index bbb78e3bf09..9576331082b 100644 --- a/feature/platform/transceiver/zr_inventory_test/README.md +++ b/feature/platform/transceiver/tests/zr_inventory_test/README.md @@ -1,4 +1,4 @@ -# TRANSCEIVER-7: Telemetry: 400ZR module inventory information. +# TRANSCEIVER-7: Telemetry: 400ZR Optics inventory info streaming ## Summary @@ -30,7 +30,7 @@ Validate 400ZR modules report correct inventory information. streaming telemetry paths above. * Reset the optic by enabling and disabling the transceiver state through /components/component/transceiver/config/enabled. - * Wait atleast 20 seconds in between toggling transceiver state. + * Wait at least 20 seconds in between toggling transceiver state. * Verify the ZR optics still reports correct inventory information. * Telemetry subscription should be ON_CHANGE and streamed data should be of type String. @@ -84,3 +84,12 @@ Validate 400ZR modules report correct inventory information. * /platform/components/component/state/mfg-date * /platform/components/component/state/hardware-version * /platform/components/component/state/firmware-version + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/platform/transceiver/tests/zr_inventory_test/metadata.textproto b/feature/platform/transceiver/tests/zr_inventory_test/metadata.textproto new file mode 100644 index 00000000000..c6eefbc266e --- /dev/null +++ b/feature/platform/transceiver/tests/zr_inventory_test/metadata.textproto @@ -0,0 +1,23 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "376d3561-4271-4efc-be63-3eec7b56b86d" +plan_id: "TRANSCEIVER-7" +description: "Telemetry: 400ZR Optics inventory info streaming" +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + component_mfg_date_unsupported: true + } +} \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_inventory_test/zr_inventory_test.go b/feature/platform/transceiver/tests/zr_inventory_test/zr_inventory_test.go new file mode 100644 index 00000000000..0d99c348697 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_inventory_test/zr_inventory_test.go @@ -0,0 +1,117 @@ +package zr_inventory_test + +import ( + "testing" + "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" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +const ( + samplingInterval = 10 * time.Second + timeout = 5 * time.Minute + waitInterval = 30 * time.Second +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func verifyAllInventoryValues(t *testing.T, pStreamsStr []*samplestream.SampleStream[string], pStreamsUnion []*samplestream.SampleStream[oc.Component_Type_Union]) { + for _, stream := range pStreamsStr { + inventoryStr := stream.Next() + if inventoryStr == nil { + t.Fatalf("Inventory telemetry %v was not streamed in the most recent subscription interval", stream) + } + inventoryVal, ok := inventoryStr.Val() + if !ok { + t.Fatalf("Inventory telemetry %q is not present or valid, expected ", inventoryStr) + } else { + t.Logf("Inventory telemetry %q is valid: %q", inventoryStr, inventoryVal) + } + } + + for _, stream := range pStreamsUnion { + inventoryUnion := stream.Next() + if inventoryUnion == nil { + t.Fatalf("Inventory telemetry %v was not streamed in the most recent subscription interval", stream) + } + inventoryVal, ok := inventoryUnion.Val() + if !ok { + t.Fatalf("Inventory telemetry %q is not present or valid, expected ", inventoryUnion) + } else { + t.Logf("Inventory telemetry %q is valid: %q", inventoryUnion, inventoryVal) + } + + } +} + +func TestInventory(t *testing.T) { + dut := ondatra.DUT(t, "dut") + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + fptest.ConfigureDefaultNetworkInstance(t, dut) + cfgplugins.InterfaceConfig(t, dut, dp1) + cfgplugins.InterfaceConfig(t, dut, dp2) + + // Derive transceiver names from ports. + tr1 := gnmi.Get(t, dut, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + tr2 := gnmi.Get(t, dut, gnmi.OC().Interface(dp2.Name()).Transceiver().State()) + + if (dp1.PMD() != ondatra.PMD400GBASEZR) || (dp2.PMD() != ondatra.PMD400GBASEZR) { + t.Fatalf("Transceivers types (%v, %v): (%v, %v) are not 400ZR, expected %v", tr1, tr2, dp1.PMD(), dp2.PMD(), ondatra.PMD400GBASEZR) + } + component1 := gnmi.OC().Component(tr1) + + // 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) + + var p1StreamsStr []*samplestream.SampleStream[string] + var p1StreamsUnion []*samplestream.SampleStream[oc.Component_Type_Union] + + // TODO: b/333021032 - Uncomment the description check from the test once the bug is fixed. + p1StreamsStr = append(p1StreamsStr, + samplestream.New(t, dut, component1.SerialNo().State(), samplingInterval), + samplestream.New(t, dut, component1.PartNo().State(), samplingInterval), + samplestream.New(t, dut, component1.MfgName().State(), samplingInterval), + samplestream.New(t, dut, component1.HardwareVersion().State(), samplingInterval), + samplestream.New(t, dut, component1.FirmwareVersion().State(), samplingInterval), + // samplestream.New(t, dut1, component1.Description().State(), samplingInterval), + ) + if !deviations.ComponentMfgDateUnsupported(dut) { + p1StreamsStr = append(p1StreamsStr, samplestream.New(t, dut, component1.MfgDate().State(), samplingInterval)) + } + p1StreamsUnion = append(p1StreamsUnion, samplestream.New(t, dut, component1.Type().State(), samplingInterval)) + + verifyAllInventoryValues(t, p1StreamsStr, p1StreamsUnion) + + // Disable or shut down the interface on the DUT. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), false) + } + // Wait for channels to be down. + gnmi.Await(t, dut, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + gnmi.Await(t, dut, gnmi.OC().Interface(dp2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + + t.Logf("Interfaces are down: %v, %v", dp1.Name(), dp2.Name()) + verifyAllInventoryValues(t, p1StreamsStr, p1StreamsUnion) + + time.Sleep(waitInterval) + // Re-enable interfaces. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), 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) + + t.Logf("Interfaces are up: %v, %v", dp1.Name(), dp2.Name()) + verifyAllInventoryValues(t, p1StreamsStr, p1StreamsUnion) +} diff --git a/feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md b/feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md new file mode 100644 index 00000000000..05823ee7047 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md @@ -0,0 +1,112 @@ +# TRANSCEIVER-9: Telemetry: 400ZR TX laser bias current telemetry values streaming. + +## Summary + +Validate 400ZR optics modules report accurate laser bias current telemetry +values. + +As per [CMIS](https://www.oiforum.com/wp-content/uploads/CMIS3p0_Third_Party_Spec.pdf): + +Measured Tx laser bias current is represented as a 16-bit unsigned integer with +the current defined as the 12 full 16-bit value (0 to 65535) with LSB equal to +2 uA times the multiplier from Byte 01h:160. For a multiplier of 13 1, +this yields a total measurement range of 0 to 131 mA. + +Accuracy must be better than +/-10% of the manufacturer's nominal value over +specified operating temperature and voltage. + + +## TRANSCEIVER-9.1 + +* 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. Connection + between the modules should pass through an optical switch that can be + controlled through automation to simulate a fiber cut as needed. +* To establish a point to point ZR link ensure the following: + * Both transceivers states are enabled + * Both transceivers are set to a valid target TX output power + example -9 dBm + * Both transceivers are tuned to a valid centre frequency + example 193.1 THz +* With the ZR link is established as explained above, verify that the + following ZR transceiver telemetry paths exist and are streamed for both + the ZR optics + + * /components/component/optical-channel/state/laser-bias-current/instant + * /components/component/optical-channel/state/laser-bias-current/avg + * /components/component/optical-channel/state/laser-bias-current/min + * /components/component/optical-channel/state/laser-bias-current/max + + +## TRANSCEIVER-9.2 + +* When the modules or the devices are still in a boot stage, they must not + stream any invalid string values like "nil" or "-inf". + +* Laser bias current values must always be of type decimal64. + When laser is in off state 0 must be reported as a valid value. + +**Note:** For min, max, and avg values, 10 second sampling is preferred. If the + min, max average values or the 10 seconds sampling is not supported, + the sampling interval used must be specified and this must be + captured by adding a deviation to the test. + +## TRANSCEIVER-9.3 + +* Verify that the TX laser bias current is updated after an interface + enable / disable state change. + + * Enable a pair of ZR interfaces on the DUT as explained above. + * Verify the ZR optics TX laser bias current telemetry values are + in the normal range. + * Use /interfaces/interface/config/enabled to disable the interfaces so + that the TX laser is squelched / turned off. + * Verify with interface state disabled and link down, decimal64 0 value + is streamed for both optics TX laser bias current. + * Re-enable the optics using /interfaces/interface/config/enabled. + * Verify the ZR optics TX laser bias current telemetry values are + updated to the value in the normal range again. + * Typical measurement range 0 to 131 mA. + +## TRANSCEIVER-9.4 + +* Verify that the TX laser bias current is updated after transceiver power + ON / OFF state change. + + * Enable a pair of ZR interfaces on the DUT as explained above. + * Verify the ZR optics TX laser bias current telemetry values are + in the normal range. + * Use /components/component/transceiver/config/enabled to power off the + transceiver so that the TX laser is squelched / turned off. + * Verify with transceiver state disabled and link down, no value + is streamed for both optics TX laser bias current. + * Re-enable the optics using + /components/component/transceiver/config/enabled. + * Verify the ZR optics TX laser bias current telemetry values are + updated to the value in the normal range again. + * Typical measurement range 0 to 131 mA. + +## 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 ## + /components/component/transceiver/config/enabled: + platform_type: [ "OPTICAL_CHANNEL" ] + /interfaces/interface/config/enabled: + ## State Paths ## + /components/component/optical-channel/state/laser-bias-current/instant: + platform_type: [ "OPTICAL_CHANNEL" ] + /components/component/optical-channel/state/laser-bias-current/avg: + platform_type: [ "OPTICAL_CHANNEL" ] + /components/component/optical-channel/state/laser-bias-current/min: + platform_type: [ "OPTICAL_CHANNEL" ] + /components/component/optical-channel/state/laser-bias-current/max: + platform_type: [ "OPTICAL_CHANNEL" ] + +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/feature/platform/transceiver/tests/zr_laser_bias_current_test/metadata.textproto b/feature/platform/transceiver/tests/zr_laser_bias_current_test/metadata.textproto new file mode 100644 index 00000000000..a16fa3b71a1 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_laser_bias_current_test/metadata.textproto @@ -0,0 +1,18 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "3559ab05-7b5b-40e5-86d8-63eb0e668c8a" +plan_id: "TRANSCEIVER-9" +description: "Telemetry: 400ZR TX laser bias current telemetry values streaming." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + missing_zr_optical_channel_tunable_parameters_telemetry: true + } +} diff --git a/feature/platform/transceiver/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 new file mode 100644 index 00000000000..bdc31e8f363 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_laser_bias_current_test/zr_laser_bias_current_test.go @@ -0,0 +1,164 @@ +// 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 zr_laser_bias_current_test + +import ( + "testing" + "time" + + "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/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Topology: +// dut:port1 <--> port2:dut +// + +func verifyLaserBiasValue(t *testing.T, laserBiasValue float64) { + t.Helper() + if laserBiasValue <= 0 && laserBiasValue >= 131 { + t.Errorf("The laser bias value is not between 0 and 131") + } +} + +func verifyLaserBiasCurrentAll(t *testing.T, p1Stream *samplestream.SampleStream[*oc.Component_OpticalChannel_LaserBiasCurrent], dut1 *ondatra.DUTDevice) { + laserBias := p1Stream.Next() + if laserBias == nil { + t.Fatalf("laserBias telemetry was not streamed in the most recent subscription interval") + } + laserBiasVal, ok := laserBias.Val() + if !ok { + t.Fatalf("LaserBias telemetry is not present") + } + laserBiasInstant := laserBiasVal.GetInstant() + t.Logf("laserBias Instant value: %f", laserBiasInstant) + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut1) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + laserBiasMin := laserBiasVal.GetMin() + verifyLaserBiasValue(t, laserBiasMin) + t.Logf("laserBias Min value: %f", laserBiasMin) + laserBiasMax := laserBiasVal.GetMax() + verifyLaserBiasValue(t, laserBiasMax) + t.Logf("laserBias Max value: %f", laserBiasMax) + laserBiasAvg := laserBiasVal.GetAvg() + verifyLaserBiasValue(t, laserBiasAvg) + t.Logf("laserBias Avg value: %f", laserBiasMin) + if laserBiasAvg >= laserBiasMin && laserBiasAvg <= laserBiasMax { + t.Logf("The average %f is between the maximum and minimum values", laserBiasAvg) + } else { + t.Fatalf("The average is not between the maximum and minimum values Avg:%f Min:%f Max:%f", laserBiasAvg, laserBiasMin, laserBiasMax) + } + } +} + +func TestZRLaserBiasCurrentState(t *testing.T) { + dut1 := ondatra.DUT(t, "dut") + dp1 := dut1.Port(t, "port1") + dp2 := dut1.Port(t, "port2") + t.Logf("dut1: %v", dut1) + t.Logf("dut1 dp1 name: %v", dp1.Name()) + cfgplugins.InterfaceConfig(t, dut1, dp1) + cfgplugins.InterfaceConfig(t, dut1, dp2) + intUpdateTime := 2 * time.Minute + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + transceiverState := gnmi.Get(t, dut1, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + if dp1.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s Transceiver is not 400ZR its of type: %v", transceiverState, dp1.PMD()) + } + componentName := components.OpticalChannelComponentFromPort(t, dut1, dp1) + component := gnmi.OC().Component(componentName) + p1Stream := samplestream.New(t, dut1, component.OpticalChannel().LaserBiasCurrent().State(), 10*time.Second) + defer p1Stream.Close() + verifyLaserBiasCurrentAll(t, p1Stream, dut1) +} + +func TestZRLaserBiasCurrentStateInterfaceFlap(t *testing.T) { + dut1 := ondatra.DUT(t, "dut") + dp1 := dut1.Port(t, "port1") + dp2 := dut1.Port(t, "port2") + t.Logf("dut1: %v", dut1) + t.Logf("dut1 dp1 name: %v", dp1.Name()) + cfgplugins.InterfaceConfig(t, dut1, dp1) + cfgplugins.InterfaceConfig(t, dut1, dp2) + intUpdateTime := 2 * time.Minute + // Check interface is up + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + // Check if TRANSCEIVER is of type 400ZR + transceiverState := gnmi.Get(t, dut1, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + if dp1.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s Transceiver is not 400ZR its of type: %v", transceiverState, dp1.PMD()) + } + // Disable interface + d := &oc.Root{} + i := d.GetOrCreateInterface(dp1.Name()) + i.Enabled = ygot.Bool(false) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + gnmi.Replace(t, dut1, gnmi.OC().Interface(dp1.Name()).Config(), i) + componentName := components.OpticalChannelComponentFromPort(t, dut1, dp1) + component := gnmi.OC().Component(componentName) + p1Stream := samplestream.New(t, dut1, component.OpticalChannel().LaserBiasCurrent().State(), 10*time.Second) + defer p1Stream.Close() + verifyLaserBiasCurrentAll(t, p1Stream, dut1) + // Wait 120 sec cooling-off period + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_DOWN) + verifyLaserBiasCurrentAll(t, p1Stream, dut1) + // Enable interface + i.Enabled = ygot.Bool(true) + gnmi.Replace(t, dut1, gnmi.OC().Interface(dp1.Name()).Config(), i) + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + verifyLaserBiasCurrentAll(t, p1Stream, dut1) +} + +func TestZRLaserBiasCurrentStateTransceiverOnOff(t *testing.T) { + dut1 := ondatra.DUT(t, "dut") + dp1 := dut1.Port(t, "port1") + dp2 := dut1.Port(t, "port2") + t.Logf("dut1: %v", dut1) + t.Logf("dut1 dp1 name: %v", dp1.Name()) + cfgplugins.InterfaceConfig(t, dut1, dp1) + cfgplugins.InterfaceConfig(t, dut1, dp2) + intUpdateTime := 2 * time.Minute + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + transceiverState := gnmi.Get(t, dut1, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + // Check if TRANSCEIVER is of type 400ZR + if dp1.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s Transceiver is not 400ZR its of type: %v", transceiverState, dp1.PMD()) + } + componentName := components.OpticalChannelComponentFromPort(t, dut1, dp1) + component := gnmi.OC().Component(componentName) + p1Stream := samplestream.New(t, dut1, component.OpticalChannel().LaserBiasCurrent().State(), 10*time.Second) + defer p1Stream.Close() + verifyLaserBiasCurrentAll(t, p1Stream, dut1) + // power off interface transceiver + gnmi.Update(t, dut1, gnmi.OC().Component(dp1.Name()).Name().Config(), dp1.Name()) + gnmi.Update(t, dut1, gnmi.OC().Component(dp1.Name()).Transceiver().Enabled().Config(), false) + verifyLaserBiasCurrentAll(t, p1Stream, dut1) + // power on interface transceiver + gnmi.Update(t, dut1, gnmi.OC().Component(dp1.Name()).Transceiver().Enabled().Config(), true) + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + verifyLaserBiasCurrentAll(t, p1Stream, dut1) +} diff --git a/feature/platform/transceiver/tests/zr_logical_channels_test/README.md b/feature/platform/transceiver/tests/zr_logical_channels_test/README.md new file mode 100644 index 00000000000..eb5081775f2 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/README.md @@ -0,0 +1,142 @@ +# TRANSCEIVER-11: Telemetry: 400ZR Optics logical channels provisioning and related telemetry. + +## Summary + +Routing devices that support transceivers with built-in DSPs like 400ZR consume +the [OC-terminal-device model](https://openconfig.net/projects/models/schemadocs/jstree/openconfig-terminal-device.html) +model. +The ZR signal in these transceivers traverses through a series of +terminal-device/logical-channels. The series of logical-channel utilizes the +assignment/optical-channel leaf to create the relationship to +OPTICAL_CHANNEL. For 400ZR 1x400GE mode this heirarchy looks like: +400GE Eth. Logical Channel => 400G Coherent Logical Channel => OPTICAL_CHANNEL +Purpose of this test is to verify the logical channel provisioning and related +telemetry. + +## 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. Optics can be + connected through passive patch panels or an optical switch as needed, as + long as the overall link loss budget is kept under 2 - 3 dB. There is no + requirement to deploy a separate line system for the functional tests. + +## Testbed Type +* Typical test setup for this test is a DUT1 with 2 ports to 2 ATE ports or 2 + ports to a second DUT2. For most tests this setup should be sufficient. + Ref: [Typical ATE<>DUT Test bed](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) +* A and Z ends of the link should have same 400ZR PMD. For this test a + single DUT ZR port connected to a single ZR ATE port is also sufficient. + +Once the ZR link is estabished proceed to configure the following entities: + +### TRANSCEIVER 11.1 - Test Optical Channel and Tunable Parameters +* Ensure optical channel related tunable parameters are set through the + following OC paths such that + * Both transceivers state is enabled + * Both transceivers related optical channel tunable parameters are set + to a valid target TX output power example -10 dBm + * Both transceivers are tuned to a valid centre frequency + example 193.1 THz + * /components/component/transceiver/config/enabled + * /components/component/optical-channel/config/frequency + * /components/component/optical-channel/config/target-output-power + * /components/component/optical-channel/config/operational-mode + +### TRANSCEIVER 11.2 - Test Ethernet Logical Channels +* Ensure terminal-devic ethernet-logical-channels are set through the + following OC paths + * /terminal-device/logical-channels/channel/config/admin-state + * /terminal-device/logical-channels/channel/config/description + * /terminal-device/logical-channels/channel/config/index + * /terminal-device/logical-channels/channel/config/logical-channel-type + * /terminal-device/logical-channels/channel/config/rate-class + * /terminal-device/logical-channels/channel/config/trib-protocol + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/allocation + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/assignment-type + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/description + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/index + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/logical-channel + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/optical-channel +* Typical Settings for an Ethernet Logical Channel are shown below: + * logical-channel-type: PROT_ETHERNET + * trib-protocol: PROT_400GE + * rate-class: TRIB_RATE_400G + * admin-state: ENABLED + * description: ETH Logical Channel + * index: 40000 (unique integer value) +* Not that each logical-channel created above must be assigned an integer value that + is unique across the system. + +### TRANSCEIVER 11.3 - Test Coherent Logical Channels +* Ensure terminal-device coherent-logical-channels are set through the + following OC paths + * /terminal-device/logical-channels/channel/config/admin-state + * /terminal-device/logical-channels/channel/config/description + * /terminal-device/logical-channels/channel/config/index + * /terminal-device/logical-channels/channel/config/logical-channel-type + * /terminal-device/logical-channels/channel/config/rate-class + * /terminal-device/logical-channels/channel/config/trib-protocol + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/allocation + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/assignment-type + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/description + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/index + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/logical-channel + * /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/optical-channel +* Typical setting for a coherent logical channel are shown below: + * logical-channel-type: PROT_OTN + * admin-state: ENABLED + * description: Coherent Logical Channel + * index: 40004 (unique integer value) + +* With above optical and logical channels configured verify DUT is able to + stream corresponding telemetry leaves under these logical and optical + channels. List of such telemetry leaves covered under this test is documented + below under Telemetry Parameter coverage heading. + +**Note**: There are other telemetry and config leaves related to optical and + logical channelsthat are covered under separately published tests + under platforms/transceiver. + +## Config Parameter coverage + +* /components/component/transceiver/config/enabled +* /interfaces/interface/config/enabled +* /terminal-device/logical-channels/channel/config/admin-state +* /terminal-device/logical-channels/channel/config/description +* /terminal-device/logical-channels/channel/config/index +* /terminal-device/logical-channels/channel/config/logical-channel-type +* /terminal-device/logical-channels/channel/config/rate-class +* /terminal-device/logical-channels/channel/config/trib-protocol +* /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/allocation +* /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/assignment-type +* /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/description +* /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/index +* /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/logical-channel +* /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/optical-channel + +## Telemetry Parameter coverage + +* /components/component/transceiver/config/enabled +* /interfaces/interface/config/enabled +* /terminal-device/logical-channels/channel/state/admin-state +* /terminal-device/logical-channels/channel/state/description +* /terminal-device/logical-channels/channel/state/index +* /terminal-device/logical-channels/channel/state/logical-channel-type +* /terminal-device/logical-channels/channel/state/rate-class +* /terminal-device/logical-channels/channel/state/trib-protocol +* /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/description +* /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/optical-channel + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/platform/transceiver/tests/zr_logical_channels_test/metadata.textproto b/feature/platform/transceiver/tests/zr_logical_channels_test/metadata.textproto new file mode 100644 index 00000000000..fb942581ff5 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/metadata.textproto @@ -0,0 +1,27 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "0bef25c1-0ea2-4b44-8a37-07de9f870cae" +plan_id: "TRANSCEIVER-11" +description: "Telemetry: 400ZR Optics logical channels provisioning and related telemetry." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + otn_channel_trib_unsupported: true + eth_channel_ingress_parameters_unsupported: true + eth_channel_assignment_cisco_numbering: true + otn_channel_assignment_cisco_numbering: true + } +} \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_logical_channels_test/zr_logical_channels_test.go b/feature/platform/transceiver/tests/zr_logical_channels_test/zr_logical_channels_test.go new file mode 100644 index 00000000000..fdf5d9220e8 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/zr_logical_channels_test.go @@ -0,0 +1,324 @@ +package zr_logical_channels_test + +import ( + "flag" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/featureprofiles/internal/attrs" + "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" +) + +const ( + targetOutputPower = -9 + frequency = 193100000 + samplingInterval = 10 * time.Second + timeout = 10 * time.Minute + otnIndex1 = uint32(4001) + otnIndex2 = uint32(4002) + ethernetIndex1 = uint32(40001) + ethernetIndex2 = uint32(40002) +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: 30, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: 30, + } + operationalModeFlag = flag.Int("operational_mode", 1, "vendor-specific operational-mode for the channel") + operationalMode uint16 +) + +type testcase struct { + desc string + got any + want any +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func Test400ZRLogicalChannels(t *testing.T) { + dut := ondatra.DUT(t, "dut") + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + + fptest.ConfigureDefaultNetworkInstance(t, dut) + + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + + oc1 := components.OpticalChannelComponentFromPort(t, dut, p1) + oc2 := components.OpticalChannelComponentFromPort(t, dut, p2) + 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.ConfigOTNChannel(t, dut, oc1, otnIndex1, ethernetIndex1) + cfgplugins.ConfigETHChannel(t, dut, p1.Name(), tr1, otnIndex1, ethernetIndex1) + cfgplugins.ConfigOpticalChannel(t, dut, oc2, frequency, targetOutputPower, operationalMode) + cfgplugins.ConfigOTNChannel(t, dut, oc2, otnIndex2, ethernetIndex2) + cfgplugins.ConfigETHChannel(t, dut, p2.Name(), tr2, otnIndex2, ethernetIndex2) + + ethChan1 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(ethernetIndex1).State(), samplingInterval) + defer ethChan1.Close() + ethChan2 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(ethernetIndex2).State(), samplingInterval) + defer ethChan2.Close() + otnChan1 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex1).State(), samplingInterval) + defer otnChan1.Close() + otnChan2 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex2).State(), samplingInterval) + defer otnChan2.Close() + + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + + validateEthernetChannelTelemetry(t, dut, otnIndex1, ethernetIndex1, ethChan1) + validateEthernetChannelTelemetry(t, dut, otnIndex2, ethernetIndex2, ethChan2) + validateOTNChannelTelemetry(t, dut, otnIndex1, ethernetIndex1, oc1, otnChan1) + validateOTNChannelTelemetry(t, dut, otnIndex2, ethernetIndex2, oc2, otnChan2) +} + +func validateEthernetChannelTelemetry(t *testing.T, dut *ondatra.DUTDevice, otnChIdx, ethernetChIdx uint32, stream *samplestream.SampleStream[*oc.TerminalDevice_Channel]) { + val := stream.Next() // value received in the gnmi subscription within 10 seconds + if val == nil { + t.Fatalf("Ethernet Channel telemetry stream not received in last 10 seconds") + } + ec, ok := val.Val() + if !ok { + t.Fatalf("Ethernet Channel telemetry stream empty in last 10 seconds") + } + tcs := []testcase{ + { + desc: "Index", + got: ec.GetIndex(), + want: ethernetChIdx, + }, + { + desc: "Description", + got: ec.GetDescription(), + want: "ETH Logical Channel", + }, + { + desc: "Logical Channel Type", + got: ec.GetLogicalChannelType().String(), + want: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_ETHERNET.String(), + }, + { + desc: "Trib Protocol", + got: ec.GetTribProtocol().String(), + want: oc.TransportTypes_TRIBUTARY_PROTOCOL_TYPE_PROT_400GE.String(), + }, + } + var assignmentIndexTestcases []testcase + + if deviations.EthChannelAssignmentCiscoNumbering(dut) { + assignmentIndexTestcases = []testcase{ + { + desc: "Assignment: Index", + got: ec.GetAssignment(1).GetIndex(), + want: uint32(1)}, + { + desc: "Assignment: Logical Channel", + got: ec.GetAssignment(1).GetLogicalChannel(), + want: otnChIdx, + }, + { + desc: "Assignment: Description", + got: ec.GetAssignment(1).GetDescription(), + want: "ETH to OTN", + }, + { + desc: "Assignment: Allocation", + got: ec.GetAssignment(1).GetAllocation(), + want: float64(400), + }, + { + desc: "Assignment: Type", + got: ec.GetAssignment(1).GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_LOGICAL_CHANNEL.String(), + }} + } else { + assignmentIndexTestcases = []testcase{ + { + desc: "Assignment: Index", + got: ec.GetAssignment(0).GetIndex(), + want: uint32(0), + }, + { + desc: "Assignment: Logical Channel", + got: ec.GetAssignment(0).GetLogicalChannel(), + want: otnChIdx, + }, + { + desc: "Assignment: Description", + got: ec.GetAssignment(0).GetDescription(), + want: "ETH to OTN", + }, + { + desc: "Assignment: Allocation", + got: ec.GetAssignment(0).GetAllocation(), + want: float64(400), + }, + { + desc: "Assignment: Type", + got: ec.GetAssignment(0).GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_LOGICAL_CHANNEL.String(), + }} + } + tcs = append(tcs, assignmentIndexTestcases...) + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("Ethernet Logical Channel: %s, diff (-got +want):\n%s", tc.desc, diff) + } + }) + } +} + +func validateOTNChannelTelemetry(t *testing.T, dut *ondatra.DUTDevice, otnChIdx uint32, ethChIdx uint32, opticalChannel string, stream *samplestream.SampleStream[*oc.TerminalDevice_Channel]) { + val := stream.Next() // value received in the gnmi subscription within 10 seconds + if val == nil { + t.Fatalf("OTN Channel telemetry stream not received in last 10 seconds") + } + cc, ok := val.Val() + if !ok { + t.Fatalf("OTN Channel telemetry stream empty in last 10 seconds") + } + tcs := []testcase{ + { + desc: "Description", + got: cc.GetDescription(), + want: "OTN Logical Channel", + }, + { + desc: "Index", + got: cc.GetIndex(), + want: otnChIdx, + }, + { + desc: "Logical Channel Type", + got: cc.GetLogicalChannelType().String(), + want: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_OTN.String(), + }, + } + var opticalChannelAssignmentIndexTestcases []testcase + + if deviations.OTNChannelAssignmentCiscoNumbering(dut) { + ciscoOpticalChannelFormat := strings.ReplaceAll(opticalChannel, "/", "_") // Ex: OpticalChannel0_0_0_18 + opticalChannelAssignmentIndexTestcases = []testcase{ + { + desc: "Assignment: Index", + got: cc.GetAssignment(1).GetIndex(), + want: uint32(1), + }, + { + desc: "Optical Channel Assignment: Optical Channel", + got: cc.GetAssignment(1).GetOpticalChannel(), + want: ciscoOpticalChannelFormat, + }, + { + desc: "Optical Channel Assignment: Description", + got: cc.GetAssignment(1).GetDescription(), + want: "OTN to Optical Channel", + }, + { + desc: "Optical Channel Assignment: Allocation", + got: cc.GetAssignment(1).GetAllocation(), + want: float64(400), + }, + { + desc: "Optical Channel Assignment: Type", + got: cc.GetAssignment(1).GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_OPTICAL_CHANNEL.String(), + }, + } + } else { + opticalChannelAssignmentIndexTestcases = []testcase{ + { + desc: "Assignment: Index", + got: cc.GetAssignment(0).GetIndex(), + want: uint32(0)}, + { + desc: "Optical Channel Assignment: Optical Channel", + got: cc.GetAssignment(0).GetOpticalChannel(), + want: opticalChannel, + }, + { + desc: "Optical Channel Assignment: Description", + got: cc.GetAssignment(0).GetDescription(), + want: "OTN to Optical Channel", + }, + { + desc: "Optical Channel Assignment: Allocation", + got: cc.GetAssignment(0).GetAllocation(), + want: float64(400), + }, + { + desc: "Optical Channel Assignment: Type", + got: cc.GetAssignment(0).GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_OPTICAL_CHANNEL.String(), + }, + } + } + tcs = append(tcs, opticalChannelAssignmentIndexTestcases...) + + if !deviations.OTNChannelTribUnsupported(dut) { + logicalChannelAssignmentTestcases := []testcase{ + { + desc: "Ethernet Assignment: Index", + got: cc.GetAssignment(1).GetIndex(), + want: uint32(1), + }, + { + desc: "Ethernet Assignment: Logical Channel", + got: cc.GetAssignment(1).GetLogicalChannel(), + want: ethChIdx, + }, + { + desc: "Ethernet Assignment: Description", + got: cc.GetAssignment(1).GetDescription(), + want: "OTN to ETH", + }, + { + desc: "Ethernet Assignment: Allocation", + got: cc.GetAssignment(1).GetAllocation(), + want: float64(400), + }, + { + desc: "Ethernet Assignment: Type", + got: cc.GetAssignment(1).GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_LOGICAL_CHANNEL.String(), + }, + } + tcs = append(tcs, logicalChannelAssignmentTestcases...) + } + + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("OTN Logical Channel: %s, diff (-got +want):\n%s", tc.desc, diff) + } + }) + } +} diff --git a/feature/platform/transceiver/tests/zr_low_power_mode_test/README.md b/feature/platform/transceiver/tests/zr_low_power_mode_test/README.md new file mode 100644 index 00000000000..c3403986887 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_low_power_mode_test/README.md @@ -0,0 +1,134 @@ +# TRANSCEIVER-13: Configuration: 400ZR Transceiver Low Power Mode Setting. + +## Summary + +Validate 400ZR transceiver is able to move to low power consumption mode when +the interface/config/enabled state is set to "False" + +**NOTE:** The Module Power Mode dictates the maximum electrical power that the +module is permitted to consume while operating in that Module Power Mode. +The Module Power Mode is a function of the state of the Module State Machine. +Two Module Power Modes are defined: + * In Low Power Mode (characteristic of all MSM steady states except + ModuleReady) the maximum module power consumption is defined in the form + factor-specific hardware specification. + * In High Power Mode (characteristic of the MSM state ModuleReady) the + implementation dependent maximum module power consumption is advertised in + the MaxPower Byte 00h:201. More details in the CMIS link below. + +Link to CMIS: +https://www.oiforum.com/wp-content/uploads/CMIS5p0_Third_Party_Spec.pdf + +## Procedure + +* Connect two ZR optics 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 target TX output power + example -10 dBm. + * Both transceivers are tuned to a valid centre frequency + example 193.1 THz. +## Testbed Type +* Typical test setup for this test is a DUT1 with 2 ports to 2 ATE ports or 2 + ports to a second DUT2. For most tests this setup should be sufficient. + Ref: [Typical ATE<>DUT Test bed](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) +* A and Z ends of the link should have same 400ZR PMD. For this test a + single DUT ZR port connected to a single ZR ATE port is also sufficient. + +Once the ZR link is estabished proceed with the following: +* Verify that the following ZR transceiver OC path when set to False is able + to move to the low power mode as defined in the CMIS. + + * /interfaces/interface/config/enabled + +* In low power mode the module's Management interface should be available, + entire paged management memory should be accessible. During this state, + the host may configure the module using the management interface to read + from and write to the management Memory Map. + +* The Data Path State of all lanes is still DPDeactivated in the ModuleLowPwr + state. + +* With module in low power mode verify that the module is still able to + report inventory information through the following OC paths. + + * /platform/components/component/state/serial-no + * /platform/components/component/state/part-no + * /platform/components/component/state/type + * /platform/components/component/state/description + * /platform/components/component/state/mfg-name + * /platform/components/component/state/mfg-date + * /platform/components/component/state/hardware-version + * /platform/components/component/state/firmware-version + +* With module in low power mode verify that the module laser is squelched + and it is no longer able to report output-power under the following OC + paths. + * /components/component/optical-channel/state/output-power/instant + * /components/component/optical-channel/state/output-power/avg + * /components/component/optical-channel/state/output-power/min + * /components/component/optical-channel/state/output-power/max + +* Set the interface/config/enabled state to True + + * Verify module is able to transition into High Power Mode. + * In this state module is still able to report all the inventory + information as verified above. + * In this state verify module is able to report a valid output power + through the following OC paths as provisioned earlier. + + * /components/component/optical-channel/state/output-power/instant + * /components/component/optical-channel/state/output-power/avg + * /components/component/optical-channel/state/output-power/min + * /components/component/optical-channel/state/output-power/max + + * Verify the ZR optics TX output power telemetry values are updated to + the value in the normal range again. + * Typical min/max value range for TX Output Power -13 to -9 dbm. + * values must always be of type decimal64. + * When link interfaces are in down state 0 must be reported as a valid + value. + + * When the modules or the devices are still in a boot stage, they must not + stream any invalid string values like "nil" or "-inf" until valid values + are available for streaming. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Configure parameter + /interfaces/interface/config/enabled: + # Telemetry Parameter coverage + /components/component/state/serial-no: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/state/part-no: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/state/type: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/state/description: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/state/mfg-name: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/state/mfg-date: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/state/hardware-version: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/state/firmware-version: + 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"] + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto b/feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto new file mode 100644 index 00000000000..011a0b7bb63 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto @@ -0,0 +1,17 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "92fbb383-47b3-40e6-ab33-77be99e450b3" +plan_id: "TRANSCEIVER-13" +description: "Configuration: 400ZR Transceiver Low Power Mode Setting." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + } +} diff --git a/feature/platform/transceiver/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 new file mode 100644 index 00000000000..c43e72e44a2 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_low_power_mode_test/zr_low_power_mode_test.go @@ -0,0 +1,181 @@ +// 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 zr_low_power_mode_test + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/components" + "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/ygot/ygot" +) + +const ( + intUpdateTime = 2 * time.Minute +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// validateStreamOutput validates that the OC path is streamed in the most recent subscription interval. +func validateStreamOutput(t *testing.T, streams map[string]*samplestream.SampleStream[string]) { + for key, stream := range streams { + output := stream.Next() + if output == nil { + t.Fatalf("OC path for %s not streamed in the most recent subscription interval", key) + } + value, ok := output.Val() + if !ok { + t.Fatalf("Error capturing streaming value for %s", key) + } + if reflect.TypeOf(value).Kind() != reflect.String { + t.Fatalf("Return value is not type string for key :%s", key) + } + if value == "" { + t.Fatalf("OC path empty for %s", key) + } + t.Logf("Value for OC path %s: %s", key, value) + } +} + +// validateOutputPower validates that the output power is streamed in the most recent subscription interval. +func validateOutputPower(t *testing.T, streams map[string]*samplestream.SampleStream[float64]) { + for key, stream := range streams { + outputStream := stream.Next() + if outputStream == nil { + t.Fatalf("OC path for %s not streamed in the most recent subscription interval", key) + } + outputPower, ok := outputStream.Val() + if !ok { + t.Fatalf("Error capturing streaming value for %s", key) + } + // Check output power value is of correct type + if reflect.TypeOf(outputPower).Kind() != reflect.Float64 { + t.Fatalf("Return value is not type float64 for key :%s", key) + } + t.Logf("Output power for %s: %f", key, outputPower) + } +} + +func TestLowPowerMode(t *testing.T) { + dut := ondatra.DUT(t, "dut") + cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port1")) + cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port2")) + samplingInterval := 10 * time.Second + for _, port := range []string{"port1", "port2"} { + t.Run(fmt.Sprintf("Port:%s", port), func(t *testing.T) { + dp := dut.Port(t, port) + gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + + // Derive transceiver names from ports. + tr := gnmi.Get(t, dut, gnmi.OC().Interface(dp.Name()).Transceiver().State()) + // Stream all inventory information. + streamSerialNo := samplestream.New(t, dut, gnmi.OC().Component(tr).SerialNo().State(), samplingInterval) + defer streamSerialNo.Close() + streamPartNo := samplestream.New(t, dut, gnmi.OC().Component(tr).PartNo().State(), samplingInterval) + defer streamPartNo.Close() + streamType := samplestream.New(t, dut, gnmi.OC().Component(tr).Type().State(), samplingInterval) + defer streamType.Close() + streamDescription := samplestream.New(t, dut, gnmi.OC().Component(tr).Description().State(), samplingInterval) + defer streamDescription.Close() + streamMfgName := samplestream.New(t, dut, gnmi.OC().Component(tr).MfgName().State(), samplingInterval) + defer streamMfgName.Close() + streamMfgDate := samplestream.New(t, dut, gnmi.OC().Component(tr).MfgDate().State(), samplingInterval) + defer streamMfgDate.Close() + streamHwVersion := samplestream.New(t, dut, gnmi.OC().Component(tr).HardwareVersion().State(), samplingInterval) + defer streamHwVersion.Close() + streamFirmwareVersion := samplestream.New(t, dut, gnmi.OC().Component(tr).FirmwareVersion().State(), samplingInterval) + defer streamFirmwareVersion.Close() + + allStream := map[string]*samplestream.SampleStream[string]{ + "serialNo": streamSerialNo, + "partNo": streamPartNo, + "description": streamDescription, + "mfgName": streamMfgName, + "mfgDate": streamMfgDate, + "hwVersion": streamHwVersion, + "firmwareVersion": streamFirmwareVersion, + } + validateStreamOutput(t, allStream) + + d := &oc.Root{} + i := d.GetOrCreateInterface(dp.Name()) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + // Disable interface + i.Enabled = ygot.Bool(false) + gnmi.Replace(t, dut, gnmi.OC().Interface(dp.Name()).Config(), i) + // Wait for interface to go down. + gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_DOWN) + + validateStreamOutput(t, allStream) + opticalChannelName := components.OpticalChannelComponentFromPort(t, dut, dp) + samplingInterval = time.Duration(gnmi.Get(t, dut, gnmi.OC().Component(opticalChannelName).OpticalChannel().OutputPower().Interval().State())) + opInst := samplestream.New(t, dut, gnmi.OC().Component(opticalChannelName).OpticalChannel().OutputPower().Instant().State(), samplingInterval) + defer opInst.Close() + if opInstN := opInst.Next(); opInstN != nil { + if val, ok := opInstN.Val(); ok && val != -40 { + t.Fatalf("streaming /components/component/optical-channel/state/output-power/instant is not expected to be reported") + } + } + + opAvg := samplestream.New(t, dut, gnmi.OC().Component(opticalChannelName).OpticalChannel().OutputPower().Avg().State(), samplingInterval) + defer opAvg.Close() + if opAvgN := opAvg.Next(); opAvgN != nil { + if val, ok := opAvgN.Val(); ok && val != -40 { + t.Fatalf("streaming /components/component/optical-channel/state/output-power/avg is not expected to be reported") + } + } + + opMin := samplestream.New(t, dut, gnmi.OC().Component(opticalChannelName).OpticalChannel().OutputPower().Min().State(), samplingInterval) + defer opMin.Close() + if opMinN := opMin.Next(); opMinN != nil { + if val, ok := opMinN.Val(); ok && val != -40 { + t.Fatalf("streaming /components/component/optical-channel/state/output-power/min is not expected to be reported") + } + } + + opMax := samplestream.New(t, dut, gnmi.OC().Component(opticalChannelName).OpticalChannel().OutputPower().Max().State(), samplingInterval) + defer opMax.Close() + if opMaxN := opMax.Next(); opMaxN != nil { + if val, ok := opMaxN.Val(); ok && val != -40 { + t.Fatalf("streaming /components/component/optical-channel/state/output-power/max is not expected to be reported") + } + } + + // Enable interface + i.Enabled = ygot.Bool(true) + gnmi.Replace(t, dut, gnmi.OC().Interface(dp.Name()).Config(), i) + gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + + powerStreamMap := map[string]*samplestream.SampleStream[float64]{ + "inst": opInst, + "avg": opAvg, + "min": opMin, + "max": opMax, + } + validateOutputPower(t, powerStreamMap) + cfgplugins.ValidateInterfaceConfig(t, dut, dp) + }) + } +} diff --git a/feature/platform/transceiver/tests/zr_pm_test/README.md b/feature/platform/transceiver/tests/zr_pm_test/README.md new file mode 100644 index 00000000000..943e6806179 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_pm_test/README.md @@ -0,0 +1,100 @@ +# TRANSCEIVER-6: Telemetry: 400ZR Optics performance metrics (pm) streaming. + +## Summary + +Validate 400ZR optics module reports performance metric (PM) data as defined in +module CMIS VDM(Versatile Diagnostics Monitor): +* eSNR is defined as the electrical Signal to Noise ratio at the decision sampling point in dB +* Q-value is the decibel (dB) value representing signal BER. +* pre-FEC BER bit error rate. + +## 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 target TX output power + example -10 dBm. + * Both transceivers are tuned to a valid centre frequency + example 193.1 THz. + +* With the link ZR link established as explained above, verify that the + following ZR transceiver telemetry paths exist and are streamed for both + the ZR optics. + * /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/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/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 + + +* For reported data check for validity min <= avg/instant <= max + +* When the modules or the devices are still in a boot stage, they must not + stream any invalid string values like "nil" or "-inf" until valid values + are available for streaming. + +* Q-value, eSNR and pre-Fec BER must always be of type decimal64. When link + interfaces are in down state 0.0 must be reported as a valid default value. + * Typical expected value range for eSNR is 13.5 to 18 dB +/-0.1 dB. + * Typical expected value for Pre-FEC BER should be less than 1.2E-2. + * Typical expected Q-value should be greater than 7 dB. + + +**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. + + +* Verify that the optics PM data is updated after the interface flaps. + + * Enable a pair of ZR interfaces on the DUT as explained above. + * Subscribe SAMPLE to the above PM leafs with a sample rate of 10 + seconds. + * Verify the ZR optics PMs are in the normal range. + * Use /components/component/transceiver/config/enabled to disable the + transceiver, wait 10 seconds and then re-enable the transceiver. + * Verify that the PM leafs report '0' during the reboot and no value + of nil or -inf is reported. + * Re-enable the interfaces on the DUT. + * Verify the ZR optics pre FEC PM is updated to the value in the normal + range again. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config Parameter coverage + /interfaces/interface/config/enabled: + /components/component/transceiver/config/enabled: + platform_type: ["OPTICAL_CHANNEL"] + # Telemetry Parameter coverage + /terminal-device/logical-channels/channel/otn/state/fec-uncorrectable-blocks: + /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/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/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: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_pm_test/metadata.textproto b/feature/platform/transceiver/tests/zr_pm_test/metadata.textproto new file mode 100644 index 00000000000..e7ad71ae9dd --- /dev/null +++ b/feature/platform/transceiver/tests/zr_pm_test/metadata.textproto @@ -0,0 +1,26 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "f5d5c225-8900-42fd-8771-8614895ae036" +plan_id: "TRANSCEIVER-6" +description: "Telemetry: 400ZR Optics performance metrics (pm) streaming." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + otn_channel_trib_unsupported: true + eth_channel_ingress_parameters_unsupported: true + eth_channel_assignment_cisco_numbering: true + cisco_pre_fec_ber_inactive_value: true + } + } \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_pm_test/zr_pm_test.go b/feature/platform/transceiver/tests/zr_pm_test/zr_pm_test.go new file mode 100644 index 00000000000..4ffd98c8287 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_pm_test/zr_pm_test.go @@ -0,0 +1,222 @@ +package zr_pm_test + +import ( + "flag" + "testing" + "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" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + samplingInterval = 10 * time.Second + minAllowedQValue = 7.0 + maxAllowedQValue = 14.0 + minAllowedPreFECBER = 1e-9 + maxAllowedPreFECBER = 1e-2 + minAllowedESNR = 10.0 + maxAllowedESNR = 25.0 + inactiveQValue = 0.0 + inactivePreFECBER = 0.0 + inactiveESNR = 0.0 + timeout = 10 * time.Minute + otnIndexBase = uint32(4000) + ethernetIndexBase = uint32(40000) +) + +var ( + frequencies = []uint64{191400000, 196100000} + targetOpticalPowers = []float64{-9, -13} + operationalModeFlag = flag.Int("operational_mode", 1, "vendor-specific operational-mode for the channel") + operationalMode uint16 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestPM(t *testing.T) { + dut := ondatra.DUT(t, "dut") + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + fptest.ConfigureDefaultNetworkInstance(t, dut) + + var ( + trs = make(map[string]string) + ochs = make(map[string]string) + otnIndexes = make(map[string]uint32) + ethIndexes = make(map[string]uint32) + ) + + for i, p := range dut.Ports() { + // Check the port PMD is 400ZR. + if p.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s PMD is %v, not 400ZR", p.Name(), p.PMD()) + } + + // Get transceiver and optical channel. + trs[p.Name()] = gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()) + ochs[p.Name()] = gnmi.Get(t, dut, gnmi.OC().Component(trs[p.Name()]).Transceiver().Channel(0).AssociatedOpticalChannel().State()) + + // Assign OTN and ethernet indexes. + otnIndexes[p.Name()] = otnIndexBase + uint32(i) + ethIndexes[p.Name()] = ethernetIndexBase + uint32(i) + } + + for _, frequency := range frequencies { + for _, targetOpticalPower := range targetOpticalPowers { + // Configure OCH component and OTN and ETH logical channels. + for _, p := range dut.Ports() { + cfgplugins.ConfigOpticalChannel(t, dut, ochs[p.Name()], frequency, targetOpticalPower, operationalMode) + cfgplugins.ConfigOTNChannel(t, dut, ochs[p.Name()], otnIndexes[p.Name()], ethIndexes[p.Name()]) + cfgplugins.ConfigETHChannel(t, dut, p.Name(), trs[p.Name()], otnIndexes[p.Name()], ethIndexes[p.Name()]) + } + + // Create sample steams for each port. + otnStreams := make(map[string]*samplestream.SampleStream[*oc.TerminalDevice_Channel]) + interfaceStreams := make(map[string]*samplestream.SampleStream[*oc.Interface]) + for portName, otnIndex := range otnIndexes { + otnStreams[portName] = samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex).State(), samplingInterval) + interfaceStreams[portName] = samplestream.New(t, dut, gnmi.OC().Interface(portName).State(), samplingInterval) + defer otnStreams[portName].Close() + defer interfaceStreams[portName].Close() + } + + // Enable interface. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), true) + } + + // 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) + } + time.Sleep(3 * samplingInterval) // Wait an extra sample interval to ensure the device has time to process the change. + + validateAllSamples(t, dut, true, interfaceStreams, otnStreams) + + // Disable interface. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), false) + } + + // Wait for streaming telemetry to report the channels as down. + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + } + time.Sleep(3 * samplingInterval) // Wait an extra sample interval to ensure the device has time to process the change. + + validateAllSamples(t, dut, false, interfaceStreams, otnStreams) + + // Re-enable transceivers. + for _, p := range dut.Ports() { + cfgplugins.ToggleInterface(t, dut, p.Name(), true) + } + + // 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) + } + time.Sleep(3 * samplingInterval) // Wait an extra sample interval to ensure the device has time to process the change. + + validateAllSamples(t, dut, true, interfaceStreams, otnStreams) + } + } +} + +// validateAllSamples validates all the sample streams. +func validateAllSamples(t *testing.T, dut *ondatra.DUTDevice, isEnabled bool, interfaceStreams map[string]*samplestream.SampleStream[*oc.Interface], otnStreams map[string]*samplestream.SampleStream[*oc.TerminalDevice_Channel]) { + for _, p := range dut.Ports() { + for valIndex := range interfaceStreams[p.Name()].All() { + if valIndex >= len(otnStreams[p.Name()].All()) { + break + } + operStatus := validateSampleStream(t, dut, interfaceStreams[p.Name()].All()[valIndex], otnStreams[p.Name()].All()[valIndex], p.Name()) + switch operStatus { + case oc.Interface_OperStatus_UP: + if !isEnabled { + t.Errorf("Invalid %v operStatus value: want DOWN, got %v", p.Name(), operStatus) + } + case oc.Interface_OperStatus_DOWN: + if isEnabled { + t.Errorf("Invalid %v operStatus value: want UP, got %v", p.Name(), operStatus) + } + } + } + } +} + +// validateSampleStream validates the stream data. +func validateSampleStream(t *testing.T, dut *ondatra.DUTDevice, interfaceData *ygnmi.Value[*oc.Interface], terminalDeviceData *ygnmi.Value[*oc.TerminalDevice_Channel], portName string) oc.E_Interface_OperStatus { + if interfaceData == nil { + t.Errorf("Data not received for port %v.", portName) + return oc.Interface_OperStatus_UNSET + } + interfaceValue, ok := interfaceData.Val() + if !ok { + t.Errorf("Channel data is empty for port %v.", portName) + return oc.Interface_OperStatus_UNSET + } + operStatus := interfaceValue.GetOperStatus() + if operStatus == oc.Interface_OperStatus_UNSET { + t.Errorf("Link state data is empty for port %v", portName) + return oc.Interface_OperStatus_UNSET + } + terminalDeviceValue, ok := terminalDeviceData.Val() + if !ok { + t.Errorf("Terminal Device data is empty for port %v.", portName) + return oc.Interface_OperStatus_UNSET + } + otn := terminalDeviceValue.GetOtn() + if otn == nil { + t.Errorf("OTN data is empty for port %v", portName) + return operStatus + } + if b := otn.GetPreFecBer(); b == nil { + t.Errorf("PreFECBER data is empty for port %v", portName) + } else { + if deviations.CiscoPreFECBERInactiveValue(dut) { + validatePMValue(t, portName, "PreFECBER", b.GetInstant(), b.GetMin(), b.GetMax(), b.GetAvg(), minAllowedPreFECBER, maxAllowedPreFECBER, 0.5, operStatus) + } else { + validatePMValue(t, portName, "PreFECBER", b.GetInstant(), b.GetMin(), b.GetMax(), b.GetAvg(), minAllowedPreFECBER, maxAllowedPreFECBER, inactivePreFECBER, operStatus) + } + } + if e := otn.GetEsnr(); e == nil { + t.Errorf("ESNR data is empty for port %v", portName) + } else { + validatePMValue(t, portName, "esnr", e.GetInstant(), e.GetMin(), e.GetMax(), e.GetAvg(), minAllowedESNR, maxAllowedESNR, inactiveESNR, operStatus) + } + if q := otn.GetQValue(); q == nil { + t.Errorf("QValue data is empty for port %v", portName) + } else { + validatePMValue(t, portName, "QValue", q.GetInstant(), q.GetMin(), q.GetMax(), q.GetAvg(), minAllowedQValue, maxAllowedQValue, inactiveQValue, operStatus) + } + return operStatus +} + +// validatePMValue validates the pm value. +func validatePMValue(t *testing.T, portName, pm string, instant, min, max, avg, minAllowed, maxAllowed, inactiveValue float64, operStatus oc.E_Interface_OperStatus) { + switch operStatus { + case oc.Interface_OperStatus_UP: + if instant < minAllowed || instant > maxAllowed { + t.Errorf("Invalid %v sample when %v is UP --> min : %v, max : %v, avg : %v, instant : %v", pm, portName, min, max, avg, instant) + return + } + case oc.Interface_OperStatus_DOWN: + if instant > inactiveValue { + t.Errorf("Invalid %v sample when %v is DOWN --> min : %v, max : %v, avg : %v, instant : %v", pm, portName, min, max, avg, instant) + return + } + } + t.Logf("Valid %v sample when %v is %v --> min : %v, max : %v, avg : %v, instant : %v", pm, portName, operStatus, min, max, avg, instant) +} diff --git a/feature/platform/transceiver/tests/zr_supply_voltage_test/README.md b/feature/platform/transceiver/tests/zr_supply_voltage_test/README.md new file mode 100644 index 00000000000..7624af5654c --- /dev/null +++ b/feature/platform/transceiver/tests/zr_supply_voltage_test/README.md @@ -0,0 +1,64 @@ +# TRANSCEIVER-12: Telemetry: 400ZR Transceiver Supply Voltage streaming. + +## Summary + +Validate 400ZR transceivers report module level internally measured input supply +voltage in 100 µV increments as defined in the CMIS. + +Link to CMIS: +https://www.oiforum.com/wp-content/uploads/CMIS5p0_Third_Party_Spec.pdf + +## Procedure + +* Connect two ZR optics 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 target TX output power + example -10 dBm. + * Both transceivers are tuned to a valid centre frequency + example 193.1 THz. + +## Testbed Type +* Typical test setup for this test is a DUT1 with 2 ports to 2 ATE ports or 2 + ports to a second DUT2. For most tests this setup should be sufficient. + Ref: [Typical ATE<>DUT Test bed](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) +* A and Z ends of the link should have same 400ZR PMD. For this test a + single DUT ZR port connected to a single ZR ATE port is also sufficient. + +Once the ZR link is estabished proceed with the following: +* verify that the following ZR transceiver telemetry paths exist and are + streamed for both the ZR optics. + * /components/component/transceiver/state/supply-voltage/instant + +* If the modules or the devices are in a boot stage, they must not stream + any invalid string values like "nil" or "-inf". +* Reported supply voltage value must always be of type decimal64. +* Verify the module supply voltage is reported correctly with optics + interface in disabled state. + + * Use /interfaces/interface/config/enabled to disable the interfaces and + wait 120 seconds before taking the supply voltage reading again. + * Verify the module is able to stream the supply voltage data in this + state. + * For reported data check for validity min <= avg/instant <= max + * If the modules or the devices are in a boot stage, they must not stream + any invalid string values like "nil" or "-inf". + * Reported supply voltage value must always be of type decimal64. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config Parameter coverage + /interfaces/interface/config/enabled: + # Telemetry Parameter coverage + /components/component/transceiver/state/supply-voltage/instant: + platform_type: ["OPTICAL_CHANNEL"] + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_supply_voltage_test/metadata.textproto b/feature/platform/transceiver/tests/zr_supply_voltage_test/metadata.textproto new file mode 100644 index 00000000000..ce02315ba93 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_supply_voltage_test/metadata.textproto @@ -0,0 +1,17 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "7e0251e4-8c5c-4dff-9683-8c21c817816c" +plan_id: "TRANSCEIVER-12" +description: "Telemetry: 400ZR Transceiver Supply Voltage streaming." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + } +} diff --git a/feature/platform/transceiver/tests/zr_supply_voltage_test/zr_supply_voltage_test.go b/feature/platform/transceiver/tests/zr_supply_voltage_test/zr_supply_voltage_test.go new file mode 100644 index 00000000000..3fc74a0e274 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_supply_voltage_test/zr_supply_voltage_test.go @@ -0,0 +1,99 @@ +// 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 zr_supply_voltage_test + +import ( + "fmt" + "reflect" + "testing" + "time" + + "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/ygot/ygot" +) + +const ( + samplingInterval = 10 * time.Second + intUpdateTime = 2 * time.Minute +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func verifyVoltageValue(t *testing.T, pStream *samplestream.SampleStream[float64], path string) float64 { + voltageSample := pStream.Next() + if voltageSample == nil { + t.Fatalf("Voltage telemetry %s was not streamed in the most recent subscription interval", path) + } + voltageVal, ok := voltageSample.Val() + if !ok { + t.Fatalf("Voltage %q telemetry is not present", voltageSample) + } + // Check voltage return value of correct type + if reflect.TypeOf(voltageVal).Kind() != reflect.Float64 { + t.Fatalf("Return value is not type float64") + } + t.Logf("Voltage sample value %s: %v", path, voltageVal) + return voltageVal +} + +func TestZrSupplyVoltage(t *testing.T) { + dut := ondatra.DUT(t, "dut") + cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port1")) + cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port2")) + + for _, port := range []string{"port1", "port2"} { + t.Run(fmt.Sprintf("Port:%s", port), func(t *testing.T) { + dp := dut.Port(t, port) + t.Logf("Port %s", dp.Name()) + + gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + + // Derive transceiver names from ports. + tr := gnmi.Get(t, dut, gnmi.OC().Interface(dp.Name()).Transceiver().State()) + component := gnmi.OC().Component(tr) + + streamInst := samplestream.New(t, dut, component.Transceiver().SupplyVoltage().Instant().State(), samplingInterval) + defer streamInst.Close() + + volInst := verifyVoltageValue(t, streamInst, "Instant") + t.Logf("Port %s instant voltage: %v", dp.Name(), volInst) + + d := &oc.Root{} + i := d.GetOrCreateInterface(dp.Name()) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + // Disable interface + i.Enabled = ygot.Bool(false) + gnmi.Replace(t, dut, gnmi.OC().Interface(dp.Name()).Config(), i) + // Wait for the cooling-off period + gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_DOWN) + + volInstNew := verifyVoltageValue(t, streamInst, "Instant") + t.Logf("Port %s instant voltage after port down: %v", dp.Name(), volInstNew) + + // Enable interface again. + i.Enabled = ygot.Bool(true) + gnmi.Replace(t, dut, gnmi.OC().Interface(dp.Name()).Config(), i) + // Wait for the cooling-off period + gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + }) + } +} diff --git a/feature/platform/transceiver/tests/zr_temperature_test/README.md b/feature/platform/transceiver/tests/zr_temperature_test/README.md new file mode 100644 index 00000000000..750acf0bcb5 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_temperature_test/README.md @@ -0,0 +1,79 @@ +# TRANSCEIVER-8: Telemetry: 400ZR Optics module temperature streaming. + +## Summary + +Validate 400ZR optics report module level internally measured temperature +in 1/256 degree Celsius increments as defined in the CMIS. + +Link to CMIS: +https://www.oiforum.com/wp-content/uploads/CMIS5p0_Third_Party_Spec.pdf + +## Procedure + +* Connect two ZR optics 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 target TX output power + example -10 dBm. + * Both transceivers are tuned to a valid centre frequency + example 193.1 THz. +* With the ZR link established as explained above, verify that the + following ZR transceiver telemetry paths exist and are streamed for both + the ZR optics. + * /platform/components/component/state/temperature/instant + * /platform/components/component/state/temperature/min + * /platform/components/component/state/temperature/max + * /platform/components/component/state/temperature/avg +* For reported data check for validity min <= avg/instant <= max + +* If the modules or the devices are in a boot stage, they must not stream + any invalid string values like "nil" or "-inf". +* Reported temperature value must always be of type decimal64. + + +**Note:** For min, max, and avg values, 10 second sampling is preferred. If the + min, max average values or the 10 seconds sampling is not supported, + the sampling interval used must be specified and this must be + captured by adding a deviation to the test. + + +* Verify the module temperature is reported correctly with optics interface + in disabled state. + + * Use /interfaces/interface/config/enabled to disable the interfaces and + wait 120 seconds(cooling off period) before taking the temperature + reading again. + * Verify the module is able to stream the temperature data in this state. + * Verify the module reported temperature in this state is always less + than the module temperature captured during steady state operation with + interface state enabled. + * For reported data check for validity min <= avg/instant <= max + + * If the modules or the devices are in a boot stage, they must not stream + any invalid string values like "nil" or "-inf". + * Reported temperature value must always be of type decimal64. + +## 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 ## + /interfaces/interface/config/enabled: + ## State Paths ## + /components/component/state/temperature/instant: + platform_type: [ "TRANSCEIVER" ] + /components/component/state/temperature/min: + platform_type: [ "TRANSCEIVER" ] + /components/component/state/temperature/max: + platform_type: [ "TRANSCEIVER" ] + /components/component/state/temperature/avg: + platform_type: [ "TRANSCEIVER" ] + +rpcs: + gnmi: + gNMI.Subscribe: +``` + diff --git a/feature/platform/transceiver/tests/zr_temperature_test/metadata.textproto b/feature/platform/transceiver/tests/zr_temperature_test/metadata.textproto new file mode 100644 index 00000000000..73787e4b97c --- /dev/null +++ b/feature/platform/transceiver/tests/zr_temperature_test/metadata.textproto @@ -0,0 +1,26 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "c78087a2-2586-4052-89d0-df4c621086ad" +plan_id: "TRANSCEIVER-8" +description: "Telemetry: 400ZR Optics module temperature streaming." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + missing_zr_optical_channel_tunable_parameters_telemetry: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + use_parent_component_for_temperature_telemetry: true + } +} \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_temperature_test/zr_temperature_test.go b/feature/platform/transceiver/tests/zr_temperature_test/zr_temperature_test.go new file mode 100644 index 00000000000..38f3c3f133a --- /dev/null +++ b/feature/platform/transceiver/tests/zr_temperature_test/zr_temperature_test.go @@ -0,0 +1,200 @@ +// 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 zr_temperature_test + +import ( + "reflect" + "testing" + "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" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + sensorType = oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_SENSOR +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Topology: +// +// dut:port1 <--> port2:dut + +func verifyTemperatureSensorValue(t *testing.T, pStream *samplestream.SampleStream[float64], sensorName string) float64 { + temperatureSample := pStream.Next() + if temperatureSample == nil { + t.Fatalf("Temperature telemetry %s was not streamed in the most recent subscription interval", sensorName) + } + temperatureVal, ok := temperatureSample.Val() + if !ok { + t.Fatalf("Temperature %q telemetry is not present", temperatureSample) + } + // Check temperature return value of correct type + if reflect.TypeOf(temperatureVal).Kind() != reflect.Float64 { + t.Fatalf("Return value is not type float64") + } else if temperatureVal <= 0 && temperatureVal >= 300 { + t.Fatalf("The variable temperature instent is not between 0 and 300") + } + t.Logf("Temperature sample value %s: %v", sensorName, temperatureVal) + return temperatureVal +} + +func TestZRTemperatureState(t *testing.T) { + dut1 := ondatra.DUT(t, "dut") + dp1 := dut1.Port(t, "port1") + dp2 := dut1.Port(t, "port2") + t.Logf("dut1: %v", dut1) + t.Logf("dut1 dp1 name: %v", dp1.Name()) + intUpdateTime := 2 * time.Minute + cfgplugins.InterfaceConfig(t, dut1, dp1) + cfgplugins.InterfaceConfig(t, dut1, dp2) + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + transceiverName := gnmi.Get(t, dut1, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + // Check if TRANSCEIVER is of type 400ZR + if dp1.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s Transceiver is not 400ZR its of type: %v", transceiverName, dp1.PMD()) + } + compWithTemperature := gnmi.OC().Component(transceiverName) + if !deviations.UseParentComponentForTemperatureTelemetry(dut1) { + subcomponents := gnmi.LookupAll[*oc.Component_Subcomponent](t, dut1, compWithTemperature.SubcomponentAny().State()) + for _, s := range subcomponents { + subc, ok := s.Val() + if ok { + sensorComponent := gnmi.Get[*oc.Component](t, dut1, gnmi.OC().Component(subc.GetName()).State()) + if sensorComponent.GetType() == sensorType { + scomponent := gnmi.OC().Component(sensorComponent.GetName()) + if scomponent != nil { + compWithTemperature = scomponent + } + } + } + } + } + p1StreamInstant := samplestream.New(t, dut1, compWithTemperature.Temperature().Instant().State(), 10*time.Second) + temperatureInstant := verifyTemperatureSensorValue(t, p1StreamInstant, "Instant") + t.Logf("Port1 dut1 %s Instant Temperature: %v", dp1.Name(), temperatureInstant) + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut1) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + p1StreamAvg := samplestream.New(t, dut1, compWithTemperature.Temperature().Avg().State(), 10*time.Second) + p1StreamMin := samplestream.New(t, dut1, compWithTemperature.Temperature().Min().State(), 10*time.Second) + p1StreamMax := samplestream.New(t, dut1, compWithTemperature.Temperature().Max().State(), 10*time.Second) + + temperatureMax := verifyTemperatureSensorValue(t, p1StreamMax, "Max") + t.Logf("Port1 dut1 %s Max Temperature: %v", dp1.Name(), temperatureMax) + temperatureMin := verifyTemperatureSensorValue(t, p1StreamMin, "Min") + t.Logf("Port1 dut1 %s Min Temperature: %v", dp1.Name(), temperatureMin) + temperatureAvg := verifyTemperatureSensorValue(t, p1StreamAvg, "Avg") + t.Logf("Port1 dut1 %s Avg Temperature: %v", dp1.Name(), temperatureAvg) + if temperatureAvg >= temperatureMin && temperatureAvg <= temperatureMax { + t.Logf("The average is between the maximum and minimum values") + } else { + t.Fatalf("The average is not between the maximum and minimum values, Avg:%v Max:%v Min:%v", temperatureAvg, temperatureMax, temperatureMin) + } + p1StreamMin.Close() + p1StreamMax.Close() + p1StreamAvg.Close() + } + p1StreamInstant.Close() +} + +func TestZRTemperatureStateInterfaceFlap(t *testing.T) { + dut1 := ondatra.DUT(t, "dut") + dp1 := dut1.Port(t, "port1") + dp2 := dut1.Port(t, "port2") + t.Logf("dut1: %v", dut1) + t.Logf("dut1 dp1 name: %v", dp1.Name()) + cfgplugins.InterfaceConfig(t, dut1, dp1) + cfgplugins.InterfaceConfig(t, dut1, dp2) + intUpdateTime := 2 * time.Minute + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + transceiverName := gnmi.Get(t, dut1, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) + // Check if TRANSCEIVER is of type 400ZR + if dp1.PMD() != ondatra.PMD400GBASEZR { + t.Fatalf("%s Transceiver is not 400ZR its of type: %v", transceiverName, dp1.PMD()) + } + // Disable interface + d := &oc.Root{} + i := d.GetOrCreateInterface(dp1.Name()) + i.Enabled = ygot.Bool(false) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + gnmi.Replace(t, dut1, gnmi.OC().Interface(dp1.Name()).Config(), i) + compWithTemperature := gnmi.OC().Component(transceiverName) + if !deviations.UseParentComponentForTemperatureTelemetry(dut1) { + subcomponents := gnmi.LookupAll[*oc.Component_Subcomponent](t, dut1, compWithTemperature.SubcomponentAny().State()) + for _, s := range subcomponents { + subc, ok := s.Val() + if ok { + sensorComponent := gnmi.Get[*oc.Component](t, dut1, gnmi.OC().Component(subc.GetName()).State()) + if sensorComponent.GetType() == sensorType { + scomponent := gnmi.OC().Component(sensorComponent.GetName()) + if scomponent != nil { + compWithTemperature = scomponent + } + } + } + } + } + p1StreamInstant := samplestream.New(t, dut1, compWithTemperature.Temperature().Instant().State(), 10*time.Second) + p1StreamAvg := samplestream.New(t, dut1, compWithTemperature.Temperature().Avg().State(), 10*time.Second) + p1StreamMin := samplestream.New(t, dut1, compWithTemperature.Temperature().Min().State(), 10*time.Second) + p1StreamMax := samplestream.New(t, dut1, compWithTemperature.Temperature().Max().State(), 10*time.Second) + // Wait 120 sec cooling-off period + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_DOWN) + temperatureInstant := verifyTemperatureSensorValue(t, p1StreamInstant, "Instant") + t.Logf("Port1 dut1 %s Instant Temperature: %v", dp1.Name(), temperatureInstant) + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut1) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + temperatureMax := verifyTemperatureSensorValue(t, p1StreamMax, "Max") + t.Logf("Port1 dut1 %s Max Temperature: %v", dp1.Name(), temperatureMax) + temperatureMin := verifyTemperatureSensorValue(t, p1StreamMin, "Min") + t.Logf("Port1 dut1 %s Min Temperature: %v", dp1.Name(), temperatureMin) + temperatureAvg := verifyTemperatureSensorValue(t, p1StreamAvg, "Avg") + t.Logf("Port1 dut1 %s Avg Temperature: %v", dp1.Name(), temperatureAvg) + } + i = d.GetOrCreateInterface(dp1.Name()) + i.Enabled = ygot.Bool(true) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + // Enable interface + gnmi.Replace(t, dut1, gnmi.OC().Interface(dp1.Name()).Config(), i) + gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) + temperatureInstant = verifyTemperatureSensorValue(t, p1StreamInstant, "Instant") + t.Logf("Port1 dut1 %s Instant Temperature: %v", dp1.Name(), temperatureInstant) + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut1) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + temperatureMax := verifyTemperatureSensorValue(t, p1StreamMax, "Max") + t.Logf("Port1 dut1 %s Max Temperature: %v", dp1.Name(), temperatureMax) + temperatureMin := verifyTemperatureSensorValue(t, p1StreamMin, "Min") + t.Logf("Port1 dut1 %s Min Temperature: %v", dp1.Name(), temperatureMin) + temperatureAvg := verifyTemperatureSensorValue(t, p1StreamAvg, "Avg") + t.Logf("Port1 dut1 %s Avg Temperature: %v", dp1.Name(), temperatureAvg) + if temperatureAvg >= temperatureMin && temperatureAvg <= temperatureMax { + t.Logf("The average is between the maximum and minimum values") + } else { + t.Fatalf("The average is not between the maximum and minimum values") + } + } +} diff --git a/feature/platform/transceiver/tests/zr_tunable_parameters_test/README.md b/feature/platform/transceiver/tests/zr_tunable_parameters_test/README.md new file mode 100644 index 00000000000..0e47ff590d9 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_tunable_parameters_test/README.md @@ -0,0 +1,184 @@ +# TRANSCEIVER-5: Configuration: 400ZR channel frequency, output TX launch power and operational mode setting. + +## Summary + +Validate setting 400ZR tunable parameters channel frequency, output TX launch +power and operational mode and verify corresponding telemetry values. + +### Goals + +* Verify full C band frequency tunability for 100GHz line system grid. +* Verify full C band frequency tunability for 75GHz line system grid. +* Verify adjustable range of transmit output power across -13 to -9 dBm in + steps of 1 dB. +* Verify that the ZR module Host Interface ID and Media Interface ID + combination to ZR module AppSel mapping can be configured through the OC + `operational-mode`. `operational-mode` is a construct in OpenConfig that + masks features related to line port transmission. OC operational modes + provides a platform-defined summary of information such as symbol rate, + modulation, pulse shaping, etc. + +**Note** For standard ZR, OIF 400ZR with C-FEC is the default mode however as we +move to 400ZR++ and 800ZR, optic AppSel code would need to be configured +explicitly through OC operational mode. + +## TRANSCEIVER-5.1 + +* 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. Connection between + the modules should pass through an optical switch that can be controlled + through automation to simulate a fiber cut. +* To establish a point to point ZR link ensure the following: + + * Both transceivers states are enabled. + * Validate setting 400ZR optics module tunable laser center frequency + across frequency range 196.100 - 191.400 THz for 100GHz grid. + * Validate setting 400ZR optics module tunable laser center frequency + across frequency range 196.100 - 191.375 THz for 75GHz grid. + * Specific frequency details can be found in 400ZR implementation + agreement under sections 15.1 ad 15.2 Operating frequency channel + definitions. Link to IA below, + * https://www.oiforum.com/wp-content/uploads/OIF-400ZR-01.0_reduced2.pdf + * Validate adjustable range of transmit output power across -13 to -9 dBm + range in steps of 1dB. So the module’s output power will be set to -13, + -12, -11, -10, -9 dBm in each step. As an example this can be validated + for the module's default frequency of 193.1 THz. + +* With the ZR link established as explained above, for each configured + frequency and TX output power value verify that the following ZR transceiver + telemetry paths exist and are streamed for both the ZR optics. + + * Frequency + * /components/component/optical-channel/state/frequency + * /components/component/optical-channel/state/carrier-frequency-offset/instant + * /components/component/optical-channel/state/carrier-frequency-offset/avg + * /components/component/optical-channel/state/carrier-frequency-offset/min + * /components/component/optical-channel/state/carrier-frequency-offset/max + * TX Output Power + * /components/component/optical-channel/state/output-power/instant + * /components/component/optical-channel/state/output-power/avg + * /components/component/optical-channel/state/output-power/min + * /components/component/optical-channel/state/output-power/max + * Operational Mode + * /components/component/optical-channel/state/operational-mode + +* With above streamed data verify + + * For each center frequency, laser frequency offset should not be more + than +/- 1.8 GHz max. + * For each center frequency, streamed value should be in Mhz units. Test + should fail if the streamed value is in Hz or THz units. As an example + 193.1 THz would be 193100000 in MHz. + * When set to a specific target output power, transmit power control + absolute accuracy should be within +/- 1 dBm of the target value. + * For reported data check for validity: min <= avg/instant <= max + +## TRANSCEIVER-5.2 + +* When the modules or the devices are still in a boot stage, they must not + stream any invalid string values like "nil" or "-inf". + +* Frequency must be specified as uint64 in MHz. Streamed values for frequency + offset must be of type decimal64. + +* TX Output power must be of type decimal64. + +## TRANSCEIVER-5.3 + +* Verify that the optics Tunable Frequency and TX output power tunes back to + the correct value as per configuration after the interface flaps. + + * Enable a pair of ZR interfaces on the DUT as explained above. + * Verify the ZR optics frequency and TX output power telemetry values are + set in the normal range. + * Disable or shut down the interface on the DUT. + * Verify with interfaces in down state both optics are still streaming + configured value for frequency. + * Verify for the TX output power with interface in down state a decimal64 + value of -40 dB is streamed. + * Re-enable the interfaces on the DUT. + * Verify the ZR optics tune back to the correct frequency and TX output + power as per the configuration and related telemetry values are updated + to the value in the normal range again. + +* With above test also verify + + * Laser frequency offset should not be more than +/- 1.8 GHz max from the + configured centre frequency. + * When set to a specific target output power, transmit power control + absolute accuracy should be within +/- 1 dBm of the target configured + output power. + * For reported data check for validity: min <= avg/instant <= max + +## TRANSCEIVER-5.4 + +* Verify that the optics Tunable Frequency and TX output power tunes back to + the correct value as per configuration after a fiber cut. + + * Enable a pair of ZR interfaces on the DUT as explained above. + * Verify the ZR optics Frequency and TX output power telemetry values are + in the normal range. + * Simulate a fiber cut using the optical switch that sits in-between the + DUT ports. + * Verify with link in down state due to fiber cut both optics are + streaming uint64 for frequency and decimal64 for TX output power. + * Re-enable the optical switch connection to clear the fiber cut fault. + * Verify the ZR optics is able to stay tuned to the correct frequency and + TX output power as per the configuration. + +* With above test also verify + + * Laser frequency offset should not be more than +/- 1.8 GHz max from the + configured centre frequency. + * When set to a specific target output power, transmit power control + absolute accuracy should be within +/- 1 dBm of the target configured + output power. + * For reported data check for validity: min <= avg/instant <= 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 communicated. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /components/component/transceiver/config/enabled: + platform_type: ["TRANSCEIVER"] + /components/component/optical-channel/config/frequency: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/config/target-output-power: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/config/operational-mode: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/frequency: + 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"] + /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/operational-mode: + platform_type: ["OPTICAL_CHANNEL"] + +rpcs: + gnmi: + gNMI.Set: + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +FFF diff --git a/feature/platform/transceiver/tests/zr_tunable_parameters_test/metadata.textproto b/feature/platform/transceiver/tests/zr_tunable_parameters_test/metadata.textproto new file mode 100644 index 00000000000..731414603ab --- /dev/null +++ b/feature/platform/transceiver/tests/zr_tunable_parameters_test/metadata.textproto @@ -0,0 +1,24 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "47c623ec-69b3-4bca-ae07-012feff496b8" +plan_id: "TRANSCEIVER-5" +description: "Configuration: 400ZR channel frequency, output TX launch power and operational mode setting." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_zr_optical_channel_tunable_parameters_telemetry: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + operational_mode_unsupported:true + } +} \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go b/feature/platform/transceiver/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go new file mode 100644 index 00000000000..f152d83c96b --- /dev/null +++ b/feature/platform/transceiver/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go @@ -0,0 +1,309 @@ +package zr_tunable_parameters_test + +import ( + "fmt" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/attrs" + "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/ygot/ygot" +) + +const ( + samplingInterval = 10 * time.Second + frequencyTolerance = 1800 + dp16QAM = 1 +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: 30, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: 30, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} +func Test400ZRTunableFrequency(t *testing.T) { + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + fptest.ConfigureDefaultNetworkInstance(t, dut) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + oc1 := opticalChannelFromPort(t, dut, p1) + oc2 := opticalChannelFromPort(t, dut, p2) + streamOC1 := samplestream.New(t, dut, gnmi.OC().Component(oc1).State(), 10*time.Second) + defer streamOC1.Close() + streamOC2 := samplestream.New(t, dut, gnmi.OC().Component(oc2).State(), 10*time.Second) + defer streamOC2.Close() + tests := []struct { + description string + startFreq uint64 + endFreq uint64 + freqStep uint64 + targetOutputPower float64 + }{ + { + // Validate setting 400ZR optics module tunable laser center frequency + // across frequency range 196.100 - 191.400 THz for 100GHz grid. + description: "100GHz grid", + startFreq: 191400000, + endFreq: 196100000, + freqStep: 100000 * 4, + targetOutputPower: -13, + }, + { + // Validate setting 400ZR optics module tunable laser center frequency + // across frequency range 196.100 - 191.375 THz for 75GHz grid. + description: "75GHz grid", + startFreq: 191375000, + endFreq: 196100000, + freqStep: 75000 * 6, + targetOutputPower: -9, + }, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + for freq := tc.startFreq; freq <= tc.endFreq; freq += tc.freqStep { + t.Run(fmt.Sprintf("Freq: %v", freq), func(t *testing.T) { + opticalChannel1Config := &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(tc.targetOutputPower), + Frequency: ygot.Uint64(freq), + OperationalMode: ygot.Uint16(dp16QAM)} + opticalChannel2Config := &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(tc.targetOutputPower), + Frequency: ygot.Uint64(freq), + OperationalMode: ygot.Uint16(dp16QAM)} + + if deviations.OperationalModeUnsupported(dut) { + opticalChannel1Config.OperationalMode = nil + opticalChannel2Config.OperationalMode = nil + } + gnmi.Replace(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Config(), opticalChannel1Config) + gnmi.Replace(t, dut, gnmi.OC().Component(oc2).OpticalChannel().Config(), opticalChannel2Config) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, freq, tc.targetOutputPower) + }) + } + }) + } +} +func Test400ZRTunableOutputPower(t *testing.T) { + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + fptest.ConfigureDefaultNetworkInstance(t, dut) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + oc1 := opticalChannelFromPort(t, dut, p1) + oc2 := opticalChannelFromPort(t, dut, p2) + streamOC1 := samplestream.New(t, dut, gnmi.OC().Component(oc1).State(), 10*time.Second) + defer streamOC1.Close() + streamOC2 := samplestream.New(t, dut, gnmi.OC().Component(oc2).State(), 10*time.Second) + defer streamOC2.Close() + tests := []struct { + description string + frequency uint64 + startTargetOutputPower float64 + endTargetOutputPower float64 + targetOutputPowerStep float64 + }{ + { + // Validate adjustable range of transmit output power across -13 to -9 dBm + // range in steps of 1dB. So the module’s output power will be set to -13, + // -12, -11, -10, -9 dBm in each step. + description: "adjustable range of transmit output power across -13 to -9 dBm range in steps of 1dB", + frequency: 193100000, + startTargetOutputPower: -13, + endTargetOutputPower: -9, + targetOutputPowerStep: 1, + }, + } + for _, tc := range tests { + for top := tc.startTargetOutputPower; top <= tc.endTargetOutputPower; top += tc.targetOutputPowerStep { + t.Run(fmt.Sprintf("Target Power: %v", top), func(t *testing.T) { + opticalChannel1Config := &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(top), + Frequency: ygot.Uint64(tc.frequency), + OperationalMode: ygot.Uint16(dp16QAM), + } + opticalChannel2Config := &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(top), + Frequency: ygot.Uint64(tc.frequency), + OperationalMode: ygot.Uint16(dp16QAM), + } + if deviations.OperationalModeUnsupported(dut) { + opticalChannel1Config.OperationalMode = nil + opticalChannel2Config.OperationalMode = nil + } + + gnmi.Replace(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Config(), opticalChannel1Config) + gnmi.Replace(t, dut, gnmi.OC().Component(oc2).OpticalChannel().Config(), opticalChannel2Config) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, tc.frequency, top) + }) + } + } +} +func Test400ZRInterfaceFlap(t *testing.T) { + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + fptest.ConfigureDefaultNetworkInstance(t, dut) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + oc1 := opticalChannelFromPort(t, dut, p1) + oc2 := opticalChannelFromPort(t, dut, p2) + streamOC1 := samplestream.New(t, dut, gnmi.OC().Component(oc1).State(), 10*time.Second) + defer streamOC1.Close() + streamOC2 := samplestream.New(t, dut, gnmi.OC().Component(oc2).State(), 10*time.Second) + defer streamOC2.Close() + targetPower := float64(-9) + frequency := uint64(193100000) + + opticalChannel1Config := &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(targetPower), + Frequency: ygot.Uint64(frequency), + OperationalMode: ygot.Uint16(dp16QAM), + } + opticalChannel2Config := &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(targetPower), + Frequency: ygot.Uint64(frequency), + OperationalMode: ygot.Uint16(dp16QAM), + } + + if deviations.OperationalModeUnsupported(dut) { + opticalChannel1Config.OperationalMode = nil + opticalChannel2Config.OperationalMode = nil + } + + gnmi.Replace(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Config(), opticalChannel1Config) + gnmi.Replace(t, dut, gnmi.OC().Component(oc2).OpticalChannel().Config(), opticalChannel2Config) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + t.Run("Telemetry before flap", func(t *testing.T) { + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, frequency, targetPower) + }) + // Disable or shut down the interface on the DUT. + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), false) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Enabled().Config(), false) + // Verify with interfaces in down state both optics are still streaming + // configured value for frequency. + // Verify for the TX output power with interface in down state a decimal64 + // value of -40 dB is streamed. + t.Run("Telemetry during interface disabled", func(t *testing.T) { + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, frequency, -40) + }) + // Re-enable the interfaces on the DUT. + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), true) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Enabled().Config(), true) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_UP) + // Verify the ZR optics tune back to the correct frequency and TX output + // power as per the configuration and related telemetry values are updated + // to the value in the normal range again. + t.Run("Telemetry after flap", func(t *testing.T) { + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, frequency, targetPower) + }) +} +func validateOpticsTelemetry(t *testing.T, streams []*samplestream.SampleStream[*oc.Component], frequency uint64, outputPower float64) { + dut := ondatra.DUT(t, "dut") + var ocs []*oc.Component_OpticalChannel + for _, s := range streams { + val := s.Next() + if val == nil { + t.Fatal("Optical channel streaming telemetry not received") + } + v, ok := val.Val() + if !ok { + t.Fatal("Optical channel streaming telemetry empty") + } + ocs = append(ocs, v.GetOpticalChannel()) + } + + for _, oc := range ocs { + opm := oc.GetOperationalMode() + inst := oc.GetCarrierFrequencyOffset().GetInstant() + avg := oc.GetCarrierFrequencyOffset().GetAvg() + min := oc.GetCarrierFrequencyOffset().GetMin() + max := oc.GetCarrierFrequencyOffset().GetMax() + if got, want := opm, uint16(dp16QAM); got != want && !deviations.OperationalModeUnsupported(dut) { + t.Errorf("Optical-Channel: operational-mode: got %v, want %v", got, want) + } + // Laser frequency offset should not be more than +/- 1.8 GHz max from the + // configured centre frequency. + if inst < -1*frequencyTolerance || inst > frequencyTolerance { + t.Errorf("Optical-Channel: carrier-frequency-offset not in tolerable range, got: %v, want: (+/-)%v", inst, frequencyTolerance) + } + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + // For reported data check for validity: min <= avg/instant <= max + if min > inst { + t.Errorf("Optical-Channel: carrier-frequency-offset min: %v greater than carrier-frequency-offset instant: %v", min, inst) + } + if max < inst { + t.Errorf("Optical-Channel: carrier-frequency-offset max: %v less than carrier-frequency-offset instant: %v", max, inst) + } + if min > avg { + t.Errorf("Optical-Channel: carrier-frequency-offset min: %v greater than carrier-frequency-offset avg: %v", min, avg) + } + if max < avg { + t.Errorf("Optical-Channel: carrier-frequency-offset max: %v less than carrier-frequency-offset avg: %v", max, avg) + } + } + inst = oc.GetOutputPower().GetInstant() + avg = oc.GetOutputPower().GetAvg() + min = oc.GetOutputPower().GetMin() + max = oc.GetOutputPower().GetMax() + // When set to a specific target output power, transmit power control + // absolute accuracy should be within +/- 1 dBm of the target configured + // output power. + if inst < outputPower-1 || inst > outputPower+1 { + t.Errorf("Optical-Channel: output-power not in tolerable range, got: %v, want: %v", inst, outputPower) + } + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + // For reported data check for validity: min <= avg/instant <= max + if min > inst { + t.Errorf("Optical-Channel: output-power min: %v greater than output-power instant: %v", min, inst) + } + if max < inst { + t.Errorf("Optical-Channel: output-power max: %v less than output-power instant: %v", max, inst) + } + if min > avg { + t.Errorf("Optical-Channel: output-power min: %v greater than output-power avg: %v", min, avg) + } + if max < avg { + t.Errorf("Optical-Channel: output-power max: %v less than output-power avg: %v", max, avg) + } + } + if got, want := oc.GetFrequency(), frequency; got != want { + t.Errorf("Optical-Channel: frequency: %v, want: %v", got, want) + } + } +} + +// opticalChannelFromPort returns the connected optical channel component name for a given ondatra port. +func opticalChannelFromPort(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port) string { + t.Helper() + tr := gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()) + return gnmi.Get(t, dut, gnmi.OC().Component(tr).Transceiver().Channel(0).AssociatedOpticalChannel().State()) +} diff --git a/feature/platform/transceiver/zr_db_q_value_test/README.md b/feature/platform/transceiver/zr_db_q_value_test/README.md deleted file mode 100644 index b62da10dba8..00000000000 --- a/feature/platform/transceiver/zr_db_q_value_test/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# TRANSCEIVER-6: Telemetry: 400ZR Optics Q-value streaming. - -## Summary - -Validate 400ZR optics module reports Q-value performance data as defined in -module CMIS VDM(Versatile Diagnostics Monitor). -Q-value is the decibel (dB) value representing signal BER. - -## 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 target TX output power - example -10 dBm. - * Both transceivers are tuned to a valid centre frequency - example 193.1 THz. - -* With the link ZR link established as explained above, verify that the - following ZR transceiver telemetry paths exist and are streamed for both - the ZR optics. - * /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 - -* For reported data check for validity min <= avg/instant <= max - -* When the modules or the devices are still in a boot stage, they must not - stream any invalid string values like "nil" or "-inf" until valid values - are available for streaming. - -* Q-value must always be of type decimal64. When link interfaces are in down - state 0.0 must be reported as a valid default value. - - -**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. - - -* Verify that the optics Q-value is updated after the interface flaps. - - * Enable a pair of ZR interfaces on the DUT as explained above. - * Subscribe SAMPLE to the above q-value leafs with a sample rate of 10 - seconds. - * Verify the ZR optics Q-value PMs are in the normal range. - * Use /components/component/transceiver/config/enabled to disable the - transceiver, wait 20 seconds and then re-enable the transceiver. - * Verify that the q-value leafs report '0' during the reboot and no value - of nil or -inf is reported. - * Re-enable the interfaces on the DUT. - * Verify the ZR optics pre FEC PM is updated to the value in the normal - range again. Typical expected value should be greater than 7 dB. - -## Config Parameter coverage - -* /components/component/oc-transceiver:transceiver/oc-transceiver/config/enabled - -## Telemetry Parameter coverage - -* /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 diff --git a/feature/platform/transceiver/zr_tunable_parameters_test/README.md b/feature/platform/transceiver/zr_tunable_parameters_test/README.md deleted file mode 100644 index fa658d60c27..00000000000 --- a/feature/platform/transceiver/zr_tunable_parameters_test/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# TRANSCEIVER-5: Configuration: 400ZR channel frequency and output TX launch power setting. - -## Summary - -Validate setting 400ZR tunable parameters channel frequency and output TX -launch power and verify corresponding telemetry values. - -### Goals -* Verify full C band frequency tunability for 100GHz line system grid. -* Verify full C band frequency tunability for 75GHz line system grid. -* Verify adjustable range of transmit output power across -13 to -9 dBm - in steps of 1 dB. - - -## TRANSCEIVER-5.1 - -* 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. Connection - between the modules should pass through an optical switch that can be - controlled through automation to simulate a fiber cut. -* To establish a point to point ZR link ensure the following: - * Both transceivers states are enabled. - * Validate setting 400ZR optics module tunable laser center frequency - across frequency range 196.100 - 191.400 THz for 100GHz grid. - * Validate setting 400ZR optics module tunable laser center frequency - across frequency range 196.100 - 191.375 THz for 75GHz grid. - * Specific frequency details can be found in 400ZR implementation - agreement under sections 15.1 ad 15.2 Operating frequency channel - definitions. Link to IA below, - * https://www.oiforum.com/wp-content/uploads/OIF-400ZR-01.0_reduced2.pdf - * Validate adjustable range of transmit output power across -13 to -9 dBm - range in steps of 1dB. So the module’s output power will be set to -13, - -12, -11, -10, -9 dBm in each step. As an example this can be validated - for the module's default frequency of 193.1 THz. - -* With the ZR link established as explained above, for each configured - frequency and TX output power value verify that the following ZR - transceiver telemetry paths exist and are streamed for both the ZR optics. - * Frequency - * /components/component/optical-channel/state/frequency - * /components/component/optical-channel/state/carrier-frequency-offset/instant - * /components/component/optical-channel/state/carrier-frequency-offset/avg - * /components/component/optical-channel/state/carrier-frequency-offset/min - * /components/component/optical-channel/state/carrier-frequency-offset/max - * TX Output Power - * /components/component/optical-channel/state/output-power/instant - * /components/component/optical-channel/state/output-power/avg - * /components/component/optical-channel/state/output-power/min - * /components/component/optical-channel/state/output-power/max - -* With above streamed data verify - * For each center frequency, laser frequency offset should not be more than - +/- 1.8 GHz max. - * For each center frequency, streamed value should be in Mhz units. Test - should fail if the streamed value is in Hz or THz units. As an example - 193.1 THz would be 193100000 in MHz. - * When set to a specific target output power, transmit power control - absolute accuracy should be within +/- 1 dBm of the target value. - * For reported data check for validity: min <= avg/instant <= max - - -## TRANSCEIVER-5.2 - -* When the modules or the devices are still in a boot stage, they must not - stream any invalid string values like "nil" or "-inf". - -* Frequency must be specified as uint64 in MHz. Streamed values for frequency - offset must be of type decimal64. - -* TX Output power must be of type decimal64. - -## TRANSCEIVER-5.3 - -* Verify that the optics Tunable Frequency and TX output power tunes back to - the correct value as per configuration after the interface flaps. - - * Enable a pair of ZR interfaces on the DUT as explained above. - * Verify the ZR optics frequency and TX output power telemetry values are - set in the normal range. - * Disable or shut down the interface on the DUT. - * Verify with interfaces in down state both optics are streaming uint 0 - value for frequency. - * Verify for the TX output power with interface in down state a decimal64 - value of -40 dB is streamed. - * Re-enable the interfaces on the DUT. - * Verify the ZR optics tune back to the correct frequency and TX output - power as per the configuration and related telemetry values are updated - to the value in the normal range again. - -* With above test also verify - * Laser frequency offset should not be more than +/- 1.8 GHz max from the - configured centre frequency. - * When set to a specific target output power, transmit power control - absolute accuracy should be within +/- 1 dBm of the target configured - output power. - * For reported data check for validity: min <= avg/instant <= max - -## TRANSCEIVER-5.4 - -* Verify that the optics Tunable Frequency and TX output power tunes back to - the correct value as per configuration after a fiber cut. - - * Enable a pair of ZR interfaces on the DUT as explained above. - * Verify the ZR optics Frequency and TX output power telemetry values are - in the normal range. - * Simulate a fiber cut using the optical switch that sits in-between the - DUT ports. - * Verify with link in down state due to fiber cut both optics are streaming - uint64 for frequency and decimal64 for TX output power. - * Re-enable the optical switch connection to clear the fiber cut fault. - * Verify the ZR optics is able to stay tuned to the correct frequency and - TX output power as per the configuration. - -* With above test also verify - * Laser frequency offset should not be more than +/- 1.8 GHz max from the - configured centre frequency. - * When set to a specific target output power, transmit power control - absolute accuracy should be within +/- 1 dBm of the target configured - output power. - * For reported data check for validity: min <= avg/instant <= 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 - communicated. - -## Config Parameter coverage - -* /components/component/transceiver/config/enabled -* /components/component/optical-channel/config/frequency -* /components/component/optical-channel/config/target-output-power - -## Telemetry Parameter coverage - -* Frequency - * /components/component/optical-channel/state/frequency - * /components/component/optical-channel/state/carrier-frequency-offset/instant - * /components/component/optical-channel/state/carrier-frequency-offset/avg - * /components/component/optical-channel/state/carrier-frequency-offset/min - * /components/component/optical-channel/state/carrier-frequency-offset/max -* TX Output Power - * /components/component/optical-channel/state/output-power/instant - * /components/component/optical-channel/state/output-power/avg - * /components/component/optical-channel/state/output-power/min - * /components/component/optical-channel/state/output-power/max \ No newline at end of file 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 new file mode 100644 index 00000000000..b71df8ef5ec --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/README.md @@ -0,0 +1,168 @@ +# PF-1.3: Policy-based IPv4 GRE Decapsulation + +## Summary + +This test verifies the functionality of policy-based forwarding (PF) to decapsulate GRE-encapsulated traffic. +The test verified IPv4, IPv6 and MPLS encapsulated traffic. +The test also confirms the correct forwarding of traffic not matching the decapsulation policy. + +## 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 an egress port. + + ``` + | | + [ ATE Port 1 ] ---- | DUT | ---- [ ATE Port 2 ] + | | + ``` + +* ATE Port 1: Generates GRE-encapsulated traffic with various inner (original) destinations. +* ATE Port 2: Receives decapsulated traffic whose inner destination matches the policy. + +### DUT Configuration + +1. Interfaces: Configure all DUT ports as singleton IP interfaces. + +2. Static Routes/LSPs: + * Configure an IPv4 static route to GRE decapsulation destination (DECAP-DST) to Null0. + * Configure static routes for encapsulated traffic destinations IPV4-DST1 and IPV6-DST1 towards ATE Port 2. + * Configure static MPLS label binding (LBL1) towards ATE Port 2. Next hop of ATE Port 1 should be indicated for MPLS pop action. + * Configure static routes for destination IPV4-DST2 and IPV6-DST2 towards ATE Port 2. + +3. Policy-Based Forwarding: + * Rule 1: Match GRE traffic with destination DECAP-DST using destination-address-prefix-set and decapsulate. + * Rule 2: Match all other traffic and forward (no decapsulation). + * Apply the defined policy with to the ingress ATE Port 1 interface. + + **TODO:** OC model does not have a provision to apply decap policy at the network-instance level for traffic destined to device loopback interface (see Cisco CLI config exepmt below). Needs clarification and/or augmentation by vendors if required. [PR #1150](https://github.com/openconfig/public/pull/1150) + + ``` + vrf-policy + vrf default address-family ipv4 policy type pbr input DECAP-POLICY + ``` + + +### PF-1.3.1: GRE Decapsulation of IPv4 traffic +- Push DUT configuration. + +Traffic: +- Generate GRE-encapsulated traffic from ATE Port 1 with destinations matching DECAP-DST. +- Inner IPv4 destination should match IPV4-DST1. +- Inner-packet DSCP value should be set to 32. + +Verification: +- Decapsulated IPv4 traffic is received on ATE Port 2. +- No packet loss. +- Inner-packet DSCP should be preserved. +- PF counters reflect decapsulated packets. + +### PF-1.3.2: GRE Decapsulation of IPv6 traffic +- Push DUT configuration. + +Traffic: +- Generate IPv6 GRE-encapsulated traffic from ATE Port 1 with destinations matching DECAP-DST. +- Inner IPv6 destination should match IPV6-DST1. +- Inner-packet traffic-class should be set to 128. + +Verification: +- Decapsulated IPv6 traffic is received on ATE Port 2. +- No packet loss. +- Inner-packet traffic-class should be preserved. +- PF counters reflect decapsulated packets. + + +### PF-1.3.3: GRE Decapsulation of IPv4-over-MPLS traffic +- Push DUT configuration. + +Traffic: +- Generate GRE-encapsulated IPv4-over-MPLS traffic from ATE Port 1 with destinations matching DECAP-DST. +- Encapsulated MPLS top label should match LBL1. +- Inner IPv4 packet DSCP should be set to 32. + +Verification: +- Decapsulated IPv4 traffic is received on ATE Port 2. +- No packet loss. +- TTL should be taken from the outer GRE header, decremented by 1 and copied to egress IP packet header. +- Inner-packet DSCP should be preserved. +- PF counters reflect decapsulated packets. + + +### PF-1.3.4: GRE Decapsulation of IPv6-over-MPLS traffic +- Push DUT configuration. + +Traffic: +- Generate GRE-encapsulated IPv4-over-MPLS traffic from ATE Port 1 with destinations matching DECAP-DST. +- Encapsulated MPLS top label should match LBL1. +- Inner IPv6 packet traffic-class should be set to 128. + +Verification: +- Decapsulated IPv6 traffic is received on ATE Port 2. +- No packet loss. +- TTL should be taken from the outer GRE header, decremented by 1 and copied to egress IP packet header. +- Inner-packet traffic-class should be preserved. +- PF counters reflect decapsulated packets. + + + +### PF-1.3.5: GRE Decapsulation of multi-label MPLS traffic +- Push DUT configuration. + +Traffic: +- Generate GRE-encapsulated MPLS traffic from ATE Port 1 with destinations matching DECAP-DST. +- MPLS packets will have 2 labels. +- Top label should match LBL1. +- MPLS second label can be any. +- MPLS EXP bit on both labels should be set to 4. + +Verification: +- Decapsulated MPLS traffic is received on ATE Port 2. +- TTL should be taken from the outer GRE header, decremented by 1 and copied to egress MPLS packet header. +- No packet loss. +- PF counters reflect decapsulated packets. +- EXP should set to original value. + + +### PF-1.3.6: GRE Pass-through (Negative) +- Push DUT configuration. + +Traffic: +- Generate GRE-encapsulated traffic from ATE Port 1 with destinations that match IPV4-DST1/IPV6-DST2. + +Verification: +- Traffic is forwarded to ATE Port 2 unchanged. + + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # match condition + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/destination-address-prefix-set: + # decap action + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-gre: + # application to the interface + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-forwarding-policy: + # TODO: provision apply decap to network-instance level does not exist. Needs clarification and/or augmentation by vendors. + + # 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 +``` + +## Required DUT platform + +* FFF 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 new file mode 100644 index 00000000000..fc7ba59b02a --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/README.md @@ -0,0 +1,145 @@ +# PF-1.2: Policy-based traffic GRE Encapsulation to IPv4 GRE tunnel + +## Summary + +The test verifies policy forwarding(PF) encapsulation action to IPv4 GRE tunnel when matching on source/destination. + +## Testbed type + +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Test environment setup + +* DUT has an ingress port and 2 egress ports. + + ``` + | | --eBGP-- | ATE Port 2 | + [ ATE Port 1 ] ---- | DUT | | | + | | --eBGP-- | ATE Port 3 | + ``` + +* Traffic is generated from ATE Port 1. +* ATE Port 2 is used as the destination port for encapsulated + traffic. +* ATE Port 3 is used as the fallback destination for + pass-through traffic. + +#### Configuration + +1. All DUT Ports are configured as a singleton IP interfaces. Configure MTU of 9216 (L2) on ATE Port1, MTU 2000 on ATE Port 2, 3 + +2. IPv4 and IPv6 static routes to test destination networks IPV4-DST/IPV6-DST are configured on DUT towards ATE Port 3. + +3. Another set of IPv4 static routes to 32x IPv4 GRE encap destinations towards ATE Port 2. + +4. 2 IPv4 and 2 IPv6 source prefixes will be used to generate tests traffic +(SRC1-SRC2). Apply policy-forwarding with 4 rules to DUT Port 1: + - Match IPV4-SRC1 and accept/foward. + - Match IPV6-SRC1 and accept/foward. + - Match IPV4-SRC2 and encapsulate to 32 IPv4 GRE destinations. + - Match IPV6-SRC2 and encapsulate to 32 IPv4 GRE destinations. + +5. Set GRE encap source to device's loopback interface. +6. Either `identifying-prefix` or `targets/target/config/destination` can be used to configure GRE destinations based on vendor implementation. +7. Configure QoS classifier for incoming traffic on ATE Port1 for IPv4 and IPv6 traffic. + QoS classifier should remark egress packet to the matching ingress DSCP value (eg. match DSCP 32, set egress DSCP 32). + 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 +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. + +Verify: + +* All traffic received on ATE Port 2 GRE-encapsulated. +* No packet loss when forwarding. +* 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 +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. + +Verify: + +* All traffic received on ATE Port 2 GRE-encapsulated. +* No packet loss when forwarding. +* Traffic equally load-balanced across 32 GRE destinations. +* Verify PF packet counters matching traffic generated. + +### PF-1.1.3: Verify PF IPV4 forward action +Generate traffic on ATE Port 1 from sources IPV4-SRC1 to IPV4-DST. + +Verify: + +* All traffic received on ATE Port 3. +* No packet loss when forwarding. + +### PF-1.1.4: Verify PF IPV6 forward action +Generate traffic on ATE Port 1 from sources IPV6-SRC1 to IPV6-DST. + +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 +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. + +### PF-1.1.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. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # match condition + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/source-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/source-address: + # encap action + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encapsulate-gre/targets/target/config/id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encapsulate-gre/targets/target/config/source: + # either destination or identifying-prefix can be specified based on specific vendor implementation. + /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/config/identifying-prefix: + # application to the interface + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-forwarding-policy: + + # 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 +``` + +## Required DUT platform + +* MFF +* FFF \ No newline at end of file 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 new file mode 100644 index 00000000000..a1ebcd45420 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/README.md @@ -0,0 +1,97 @@ +# PF-1.1: IPv4/IPv6 policy-forwarding to indirect NH matching DSCP/TC. + +## Summary + +The test verifies policy-forwarding(PF) when matching specific DSCP values in IPv4/IPv6 header and redirecting traffic to an indirect BGP next-hop. + +2 right-most bits are used for this test with all the possibles combinations of 3 left-most DSCP bits: `...011`. + +## Testbed type + +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Test environment setup + +* DUT has an ingress port (Port 1) and 2 egress ports combined. + + ``` + | | --eBGP-- | ATE Port 2 | + [ ATE Port 1 ] ---- | DUT | | | + | | -------- | ATE Port 3 | + ``` + +* Traffic is generated from ATE Port 1. +* ATE Port 2 is used as the destination port for PF. eBGP peerings + on this port announces BG4IE NH. +* ATE Port 3 is used as the fallback destination when PF NH routes + are withdrawn. + +#### Configuration + +1. All DUT Ports are configured as a singleton IP interfaces. + +2. Static routes (ST1) to test IPv4 and IPv6 destination networks (IPV4-DST1/IPV6-DST1) are configured on DUT towards ATE Port 3. + +3. eBGP session is configured on DUT port 2. Indirect /32 (IPV-NH-V4) and /128 (IPV-NH-V6) prefixes are announced via eBGP from ATE Port 2. + +4. PF is configured on DUT port 1 to match the traffic marked with rightmost 2 bits set in DSCP to 11. PF action is to redirect to BGP-announced next-hops (IPV-NH-V4/IPV-NH-V6): + * List of DSCP values (6-bit) to be matched [3, 11, 19, 27, 35, 43, 51, 59] + * Matching rules for IPv6 should map the above 6-bit DSCP values to the leftmost 6-bits of IPv6 traffic-class. + * PF should permit the rest of the traffic. + +### PF-1.1.1: Verify PF next-hop action +Generate traffic on ATE Port 1 to test IPv4 and IPv6 destination networks (IPV4-DST1/IPV6-DST1) with DSCP/TC rightmost 2 bits set to `11`. Generate flows for every DSCP value in the set [3, 11, 19, 27, 35, 43, 51, 59]. +IPv6 flows should use TC 8-bit values [12, 44, 76, 108, 172, 163, 204, 236] + +Verify: + +* All traffic received on ATE Port 2. +* No packet loss when forwarding. +* Verify PF packet counters matching traffic generated. + +### PF-1.1.2: Verify PF no-match action +Generate traffic on ATE Port 1 to test IPv4 and IPv6 destination networks (IPV4-DST1/IPV6-DST1) with DSCP/TC rightmost 2 bits set to `00`. Generate flows for every DSCP/TC values in the set [0, 8, 16, 24, 32, 40, 48, 56]. IPv6 flows should use TC 8-bit values [0, 32, 64, 96, 128, 160, 192, 224] + +Verify: + +* All traffic received on ATE Port 3. +* No packet loss when forwarding. + +### PF-1.1.3: Verify PF without NH present +Withdraw next-hop prefixes (IPV-NH-V4/IPV-NH-V6) from BGP announcement. Generate traffic on ATE Port 1 to test IPv4 and IPv6 destination (IPV4-DST1/IPV6-DST1) networks with DSCP/TC rightmost 2 bits set to `11`. Generate flows for every IPv4 DSCP value in the set [3, 11, 19, 27, 35, 43, 51, 59] and IPv6 TC [0, 32, 64, 96, 128, 160, 192, 224]. + +Verify: + +* All traffic received on ATE Port 3. +* Traffic follows fallbacks static routes (ST1). +* No packet loss when forwarding. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # PF configuration + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/next-hop: + # application to the interface + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-forwarding-policy: + + # 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 +``` + +## Required DUT platform + * MFF + * FFF \ No newline at end of file diff --git a/feature/policy_forwarding/otg_tests/prefix_set_test/README.md b/feature/policy_forwarding/otg_tests/prefix_set_test/README.md new file mode 100644 index 00000000000..20ccedcaaa8 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/prefix_set_test/README.md @@ -0,0 +1,148 @@ +# RT-1.53: prefix-list test + +## Summary + +- Prefix list is updated and replaced correctly after restarting the process + with supports gNOI to validate that internal state of OC agent is in sync + with the running configuration. + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed + +## Procedure + +### Applying configuration + +For each section of configuration below, prepare a gnmi.SetBatch with all the +configuration items appended to one SetBatch. Then apply the configuration to +the DUT in one gnmi.Set using the `replace` option + +### RT-1.53.1 [TODO:https://github.com/openconfig/featureprofiles/issues/3306] + +#### Create a prefix-set with 2 prefixes + +* Create a prefix-set with name "prefix-set-a" + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name +* Set the mode to IPv4 + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode +* Define two prefixes 10.240.31.48/28 and 173.36.128.0/20 with mask "exact" + * /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 +* Validate that the prefix-list is created correctly with two prefixes i.e. + 10.240.31.48/28 and 173.36.128.0/20 + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range + +### RT-1.53.2 [TODO:https://github.com/openconfig/featureprofiles/issues/3306] + +#### Replace the prefix-set by replacing an existing prefix with new prefix + +* Define two prefixes 10.240.31.48/28 and 173.36.144.0/20 with mask "exact" + * /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 +* Replace the previous prefix-list +* Validate that the prefix-list is created correctly with two prefixes i.e. + 10.240.31.48/28 and 173.36.144.0/20 + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range + +### RT-1.53.3 [TODO:https://github.com/openconfig/featureprofiles/issues/3306] + +### Replace the prefix-set with 2 existing and a new prR + +* Define three prefixes 10.240.31.48/28, 10.240.31.64/28 and 173.36.144.0/20 + with mask "exact" + * /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 +* Replace the previous prefix-list +* Validate that the prefix-list is created correctly with three prefixes + 10.240.31.48/28, 10.240.31.64/28 and 173.36.144.0/20 + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix + * /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range + +### RT-1.53.4 [TODO:https://github.com/openconfig/featureprofiles/issues/3306] + +### Create prefix list and replace with gnmi. + +* Send a gNMI SET request that contains below prefixes under TAG_3_IPV4 prefix-set + ``` + 10.240.31.48/28 + 10.244.187.32/28 + 173.36.128.0/20 + 173.37.128.0/20 + 173.38.128.0/20 + 173.39.128.0/20 + 173.40.128.0/20 + 173.41.128.0/20 + 173.42.128.0/20 + 173.43.128.0/20 + ``` +* Validate that the prefix-list is created correctly with 10 prefixes. +* Use gNOI to kill the process supporting gNMI. +* Send a gNMI SET request that contains additional prefixes within the same + prefix-set, TAG_3_IPV4. + ``` + 173.49.128.0/20 + 173.46.128.0/20 + 10.240.31.48/28 + 173.44.128.0/20 + 173.43.128.0/20 + 173.47.128.0/20 + 173.40.128.0/20 + 173.37.128.0/20 + 173.39.128.0/20 + 173.38.128.0/20 + 173.42.128.0/20 + 10.244.187.32/28 + 173.41.128.0/20 + 173.36.128.0/20 + 173.50.128.0/20 + 173.51.128.0/20 + 173.52.128.0/20 + 173.53.128.0/20 + 173.54.128.0/20 + 173.55.128.0/20 + 173.48.128.0/20 + 173.45.128.0/20 + ``` +* Validate that the prefix-list is created correctly with 22 prefixes. + +## OpenConfig Path and RPC Coverage + +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 + ### prefix-set + /routing-policy/defined-sets/prefix-sets/prefix-set/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: + + ## State paths + ### prefix-list + /routing-policy/defined-sets/prefix-sets/prefix-set/state/name: + /routing-policy/defined-sets/prefix-sets/prefix-set/state/mode: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: + gnoi: + system.System.KillProcess: +``` + +## Required DUT platform + +- vRX diff --git a/feature/policy_forwarding/otg_tests/prefix_set_test/metadata.textproto b/feature/policy_forwarding/otg_tests/prefix_set_test/metadata.textproto new file mode 100644 index 00000000000..b47a1749263 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/prefix_set_test/metadata.textproto @@ -0,0 +1,15 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "619040ac-21a0-403f-b38e-6d5d0aed433a" +plan_id: "RT-1.53" +description: "prefix-list test" +testbed: TESTBED_DUT +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + skip_prefix_set_mode: true + } + } \ No newline at end of file diff --git a/feature/policy_forwarding/otg_tests/prefix_set_test/prefix_set_test.go b/feature/policy_forwarding/otg_tests/prefix_set_test/prefix_set_test.go new file mode 100644 index 00000000000..c52f4887069 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/prefix_set_test/prefix_set_test.go @@ -0,0 +1,170 @@ +// 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 prefix_set_test + +import ( + "testing" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +const ( + prefixSetA = "PFX_SET_A" + tag3IPv4 = "TAG_3_IPV4" + pfx1 = "10.240.31.48/28" + pfx2 = "173.36.128.0/20" + pfx3 = "173.36.144.0/20" + pfx4 = "10.240.31.64/28" + mskLen = "exact" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestPrefixSet(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + dutOcRoot := &oc.Root{} + rp := dutOcRoot.GetOrCreateRoutingPolicy() + ds := rp.GetOrCreateDefinedSets() + + // create a prefix-set with 2 prefixes + v4PrefixSet := ds.GetOrCreatePrefixSet(prefixSetA) + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + v4PrefixSet.GetOrCreatePrefix(pfx1, mskLen) + v4PrefixSet.GetOrCreatePrefix(pfx2, mskLen) + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetA).Config(), v4PrefixSet) + prefixSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetA).State()) + if len(prefixSet.Prefix) != 2 { + t.Errorf("Prefix set has %v prefixes, want 2", len(prefixSet.Prefix)) + } + for _, pfx := range []string{pfx1, pfx2} { + if x := prefixSet.GetPrefix(pfx, mskLen); x == nil { + t.Errorf("%s not found in prefix-set %s", pfx, prefixSetA) + } + } + + // replace the prefix-set by replacing an existing prefix with new prefix + v4PrefixSet = ds.GetOrCreatePrefixSet(prefixSetA) + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + v4PrefixSet.GetOrCreatePrefix(pfx1, mskLen) + v4PrefixSet.GetOrCreatePrefix(pfx3, mskLen) + v4PrefixSet.DeletePrefix(pfx2, mskLen) + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetA).Config(), v4PrefixSet) + prefixSet = gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetA).State()) + if len(prefixSet.Prefix) != 2 { + t.Errorf("Prefix set has %v prefixes, want 2", len(prefixSet.Prefix)) + } + for _, pfx := range []string{pfx1, pfx3} { + if x := prefixSet.GetPrefix(pfx, mskLen); x == nil { + t.Errorf("%s not found in prefix-set %s", pfx, prefixSetA) + } + } + + // replace the prefix-set with 2 existing and a new prefix + v4PrefixSet = ds.GetOrCreatePrefixSet(prefixSetA) + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + v4PrefixSet.GetOrCreatePrefix(pfx1, mskLen) + v4PrefixSet.GetOrCreatePrefix(pfx3, mskLen) + v4PrefixSet.GetOrCreatePrefix(pfx4, mskLen) + v4PrefixSet.DeletePrefix(pfx2, mskLen) + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetA).Config(), v4PrefixSet) + prefixSet = gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(prefixSetA).State()) + if len(prefixSet.Prefix) != 3 { + t.Errorf("Prefix set has %v prefixes, want 3", len(prefixSet.Prefix)) + } + for _, pfx := range []string{pfx1, pfx3, pfx4} { + if x := prefixSet.GetPrefix(pfx, mskLen); x == nil { + t.Errorf("%s not found in prefix-set %s", pfx, prefixSetA) + } + } +} + +func TestPrefixSetWithOCAgentRestart(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + dutOcRoot := &oc.Root{} + rp := dutOcRoot.GetOrCreateRoutingPolicy() + ds := rp.GetOrCreateDefinedSets() + v4PrefixSet := ds.GetOrCreatePrefixSet(tag3IPv4) + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + v4PrefixSet.GetOrCreatePrefix("10.240.31.48/28", mskLen) + v4PrefixSet.GetOrCreatePrefix("10.244.187.32/28", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.36.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.37.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.38.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.39.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.40.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.41.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.42.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.43.128.0/20", mskLen) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).Config(), v4PrefixSet) + prefixSet := gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).State()) + if got, want := len(prefixSet.Prefix), 10; got != want { + t.Errorf("Prefix set has %v prefixes, want %v", got, want) + } + + gnoi.KillProcess(t, dut, gnoi.OCAGENT, gnoi.SigTerm, true, true) + + v4PrefixSet = ds.GetOrCreatePrefixSet(tag3IPv4) + if !deviations.SkipPrefixSetMode(dut) { + v4PrefixSet.SetMode(oc.PrefixSet_Mode_IPV4) + } + v4PrefixSet.GetOrCreatePrefix("173.49.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.46.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("10.240.31.48/28", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.44.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.43.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.47.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.40.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.37.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.39.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.38.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.42.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("10.244.187.32/28", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.41.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.36.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.50.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.51.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.52.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.53.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.54.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.55.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.48.128.0/20", mskLen) + v4PrefixSet.GetOrCreatePrefix("173.45.128.0/20", mskLen) + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).Config(), v4PrefixSet) + prefixSet = gnmi.Get[*oc.RoutingPolicy_DefinedSets_PrefixSet](t, dut, gnmi.OC().RoutingPolicy().DefinedSets().PrefixSet(tag3IPv4).State()) + if got, want := len(prefixSet.Prefix), 22; got != want { + t.Errorf("Prefix set has %v prefixes, want %v", got, want) + } +} diff --git a/feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/README.md b/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/README.md similarity index 97% rename from feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/README.md rename to feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/README.md index 6f1c9db21b1..dd52ba7e1f4 100644 --- a/feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/README.md +++ b/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/README.md @@ -93,3 +93,12 @@ Test different VRF selection policies. * Native IPv6 * Flow#10: Native IPv6 flow with any source address and destination as ATE-DEST-IPv6-VLAN20 + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go b/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go similarity index 98% rename from feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go rename to feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go index 48431a522e5..960c8934087 100644 --- a/feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go +++ b/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go @@ -306,7 +306,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) *trafficFlows { topo.Ports().Add().SetName(p1.ID()) srcDev := topo.Devices().Add().SetName(ateSrc.Name) ethSrc := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth") - ethSrc.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p1.ID()) + ethSrc.Connection().SetPortName(p1.ID()) ethSrc.SetMac(ateSrc.MAC) ethSrc.Ipv4Addresses().Add().SetName(srcDev.Name() + ".IPv4").SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) ethSrc.Ipv6Addresses().Add().SetName(srcDev.Name() + ".IPv6").SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) @@ -315,7 +315,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) *trafficFlows { dstDev := topo.Devices().Add().SetName(ateDst.Name) ethDst := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth") - ethDst.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p2.ID()) + ethDst.Connection().SetPortName(p2.ID()) ethDst.SetMac(ateDst.MAC) ethDst.Vlans().Add().SetName(dstDev.Name() + "-VLAN").SetId(uint32(vlan10)) ethDst.Ipv4Addresses().Add().SetName(dstDev.Name() + ".IPv4").SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) @@ -324,7 +324,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) *trafficFlows { dstDev2 := topo.Devices().Add().SetName(ateDst2.Name) ethDst2 := dstDev2.Ethernets().Add().SetName(ateDst2.Name + ".Eth") - ethDst2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p2.ID()) + ethDst2.Connection().SetPortName(p2.ID()) ethDst2.SetMac(ateDst2.MAC) ethDst2.Vlans().Add().SetName(dstDev2.Name() + "-VLAN").SetId(uint32(vlan20)) ethDst2.Ipv4Addresses().Add().SetName(dstDev2.Name() + ".IPv4").SetAddress(ateDst2.IPv4).SetGateway(dutDst2.IPv4).SetPrefix(uint32(ateDst2.IPv4Len)) @@ -350,7 +350,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) *trafficFlows { v6.Src().SetValue(ateSrc.IPv6) v6.Dst().SetValue("2001:DB8:2::") nativeIPv6.Size().SetFixed(512) - nativeIPv6.Rate().SetChoice("percentage").SetPercentage(5) + nativeIPv6.Rate().SetPercentage(5) t.Logf("Pushing config to ATE and starting protocols...") ate.OTG().PushConfig(t, topo) @@ -376,7 +376,7 @@ func createIPv4Flow(name string, top gosnappi.Config, dst attrs.Attributes, srcI flow.Packet().Add().Ipv6() } flow.Size().SetFixed(512) - flow.Rate().SetChoice("percentage").SetPercentage(5) + flow.Rate().SetPercentage(5) return flow } diff --git a/feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto b/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto similarity index 96% rename from feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto rename to feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto index 27f20808b20..86c15e72596 100644 --- a/feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto +++ b/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto @@ -32,7 +32,6 @@ platform_exceptions: { deviations: { static_protocol_name: "STATIC" interface_config_vrf_before_address: true - deprecated_vlan_id: true interface_enabled: true default_network_instance: "default" } diff --git a/feature/experimental/policy/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md b/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md similarity index 53% rename from feature/experimental/policy/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md rename to feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md index 741f901e70b..944b0aa33ff 100644 --- a/feature/experimental/policy/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md +++ b/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md @@ -50,21 +50,21 @@ 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. -## Config Parameter Coverage - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/config/type - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/policy-id - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/dscp-set - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/protocol - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/interfaces/interface/interface-id - * /openconfig-network-instance/network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-vrf-selection-policy - -## Paths - -* /openconfig-network-instance/network-instances/network-instance/policy-forwarding -* /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy -* /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule -* /openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4 -* /openconfig-network-instance/network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-vrf-selection-policy +## OpenConfig Path and RPC Coverage +```yaml +paths: + /network-instances/network-instance/policy-forwarding/policies/policy/config/type: + /network-instances/network-instance/policy-forwarding/policies/policy/policy-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/sequence-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/protocol: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance: + /network-instances/network-instance/policy-forwarding/interfaces/interface/interface-id: + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-vrf-selection-policy: +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/policy/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto b/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto similarity index 97% rename from feature/experimental/policy/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto rename to feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto index f3b9f600304..aebaee33c52 100644 --- a/feature/experimental/policy/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto +++ b/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto @@ -39,7 +39,6 @@ platform_exceptions: { vendor: ARISTA } deviations: { - deprecated_vlan_id: true interface_enabled: true default_network_instance: "default" } diff --git a/feature/experimental/policy/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 similarity index 97% rename from feature/experimental/policy/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go rename to feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go index c58e635be57..02a6a4980ce 100644 --- a/feature/experimental/policy/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 @@ -153,7 +153,7 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice, dut *ondatra.DUTDevice) 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().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p1.ID()) + 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)) @@ -164,28 +164,28 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice, dut *ondatra.DUTDevice) if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { ethDst.Vlans().Add().SetName(atePort2.Name + "vlan").SetId(1) } - ethDst.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p2.ID()) + 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().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p2.ID()) + 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().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p2.ID()) + 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().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(p2.ID()) + 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)) diff --git a/feature/qos/ecn/feature.textproto b/feature/qos/ecn/feature.textproto index a7840e719cb..b76984b3c87 100644 --- a/feature/qos/ecn/feature.textproto +++ b/feature/qos/ecn/feature.textproto @@ -1,3 +1,6 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { name: "qos_ecn" version: 1 diff --git a/feature/qos/ecn/otg_tests/DSCP-transparency/README.md b/feature/qos/ecn/otg_tests/DSCP_transparency/README.md similarity index 100% rename from feature/qos/ecn/otg_tests/DSCP-transparency/README.md rename to feature/qos/ecn/otg_tests/DSCP_transparency/README.md diff --git a/feature/qos/feature.textproto b/feature/qos/feature.textproto index 49b504206a7..4e439a3762d 100644 --- a/feature/qos/feature.textproto +++ b/feature/qos/feature.textproto @@ -1,3 +1,6 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { name: "qos" version: 1 diff --git a/feature/qos/otg_tests/bursty_traffic_test/README.md b/feature/qos/otg_tests/bursty_traffic_test/README.md index c3d16c87cc0..9d8793b67ec 100644 --- a/feature/qos/otg_tests/bursty_traffic_test/README.md +++ b/feature/qos/otg_tests/bursty_traffic_test/README.md @@ -85,47 +85,52 @@ Verify that DUT does not drop bursty traffic. * BE1 * BE0 -## 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 - -* 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 - -## 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 +## OpenConfig Path and RPC Coverage + +This yaml 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. + +```yaml +paths: + ## Config paths + ### 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: + + ### 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: + + ## State paths + /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: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/qos/otg_tests/bursty_traffic_test/bursty_traffic_test.go b/feature/qos/otg_tests/bursty_traffic_test/bursty_traffic_test.go index 35c0e943897..f0504b744a8 100644 --- a/feature/qos/otg_tests/bursty_traffic_test/bursty_traffic_test.go +++ b/feature/qos/otg_tests/bursty_traffic_test/bursty_traffic_test.go @@ -361,7 +361,6 @@ func TestBurstyTraffic(t *testing.T) { flow.Size().SetFixed(uint32(data.frameSize)) flow.Rate().SetPercentage(float32(data.trafficRate)) - flow.Duration().SetChoice("burst") flow.Duration().Burst().SetPackets(uint32(data.burstPackets)).SetGap(uint32(data.burstMinGap)) flow.Duration().Burst().InterBurstGap().SetBytes(float64(data.burstGap)) @@ -372,22 +371,12 @@ func TestBurstyTraffic(t *testing.T) { var counterNames []string counters := make(map[string]map[string]uint64) - if !deviations.QOSDroppedOctets(dut) { - counterNames = []string{ - - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", - "dutQosDroppedOctetsAfterTraffic", - } - } else { - counterNames = []string{ - - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedPktsAfterTraffic", - } + counterNames = []string{ + "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", + "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", + "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", + "dutQosDroppedOctetsAfterTraffic", } for _, name := range counterNames { @@ -421,18 +410,18 @@ func TestBurstyTraffic(t *testing.T) { } counters["dutQosDroppedPktsBeforeTraffic"][data.queue], _ = count.Val() - if !deviations.QOSDroppedOctets(dut) { - count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) - if !ok { - t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp3.Name(), data.queue, timeout) - } - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp3.Name(), data.queue, timeout) } + counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() + } t.Logf("Running traffic 1 on DUT interfaces: %s => %s ", dp1.Name(), dp3.Name()) t.Logf("Running traffic 2 on DUT interfaces: %s => %s ", dp2.Name(), dp3.Name()) t.Logf("Sending traffic flows: \n%v\n\n", trafficFlows) + time.Sleep(30 * time.Second) ate.OTG().StartTraffic(t) time.Sleep(30 * time.Second) ate.OTG().StopTraffic(t) @@ -448,9 +437,8 @@ func TestBurstyTraffic(t *testing.T) { counters["dutQosPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).TransmitPkts().State()) counters["dutQosOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).TransmitOctets().State()) counters["dutQosDroppedPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedPkts().State()) - if !deviations.QOSDroppedOctets(dut) { - counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State()) - } + counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State()) + t.Logf("ateInPkts: %v, txPkts %v, Queue: %v", counters["ateInPkts"][data.queue], counters["dutQosPktsAfterTraffic"][data.queue], data.queue) if ateTxPkts == 0 { t.Fatalf("TxPkts == 0, want >0.") @@ -490,13 +478,11 @@ func TestBurstyTraffic(t *testing.T) { } } - if !deviations.QOSDroppedOctets(dut) { - ateDropOctetCounterDiff := (counters["ateOutPkts"][data.queue] - counters["ateInPkts"][data.queue]) * uint64(data.frameSize) - dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] - t.Logf("Queue %q: ateDropOctetCounterDiff: %v dutDropOctetCounterDiff: %v", data.queue, ateDropOctetCounterDiff, dutDropOctetCounterDiff) - if dutDropOctetCounterDiff < ateDropOctetCounterDiff { - t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want >= %v", data.queue, dutDropOctetCounterDiff, ateDropOctetCounterDiff) - } + ateDropOctetCounterDiff := (counters["ateOutPkts"][data.queue] - counters["ateInPkts"][data.queue]) * uint64(data.frameSize) + dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] + t.Logf("Queue %q: ateDropOctetCounterDiff: %v dutDropOctetCounterDiff: %v", data.queue, ateDropOctetCounterDiff, dutDropOctetCounterDiff) + if dutDropOctetCounterDiff < ateDropOctetCounterDiff { + t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want >= %v", data.queue, dutDropOctetCounterDiff, ateDropOctetCounterDiff) } } diff --git a/feature/qos/otg_tests/bursty_traffic_test/metadata.textproto b/feature/qos/otg_tests/bursty_traffic_test/metadata.textproto index 0f9af70b1d7..7ee4ccb49c0 100644 --- a/feature/qos/otg_tests/bursty_traffic_test/metadata.textproto +++ b/feature/qos/otg_tests/bursty_traffic_test/metadata.textproto @@ -17,10 +17,6 @@ platform_exceptions: { platform: { vendor: JUNIPER } - deviations: { - explicit_interface_ref_definition: true - qos_dropped_octets: true - } } platform_exceptions: { platform: { 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 new file mode 100644 index 00000000000..be2d7963ed2 --- /dev/null +++ b/feature/qos/otg_tests/egress_strict_priority_scheduler_test/README.md @@ -0,0 +1,224 @@ +# DP-1.15: Egress Strict Priority scheduler + +## Summary + +This test validates the proper functionality of an egress strict priority scheduler on a network device. By configuring multiple priority queues with specific traffic classes and generating traffic loads that exceed interface capacity, we will verify that the scheduler adheres to the strict priority scheme, prioritizing higher-priority traffic even under congestion. + +## Testbed type + +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Test environment setup + +* DUT has 2 ingress ports and 1 egress port with the same port speed. The + interface can be a physical interface or LACP bundle interface with the + same aggregated speed. + + ``` + | | ---- | ATE Port 1 | + [ ATE Port 3 ] ---- | DUT | | | + | | ---- | ATE Port 2 | + ``` + +* Traffic classes: + + * We will use 6 traffic classes NC1, AF4, AF3, AF2, AF1 and BE1. + +* Traffic types: + + * All the traffic tests apply to both IPv4 and IPv6 and also MPLS traffic. + +* Queue types: + + * NC1/AF4/AF3/AF2/AF1/BE1 will have strict priority queues (be1 - priority 6, af1 - priority 5, ..., nc1 - priority 1) + +* Test results should be independent of the location of interfaces. For + example, 2 input interfaces and output interface could be located on + + * Same ASIC-based forwarding engine + * Different ASIC-based forwarding engine on same line card + * Different ASIC-based forwarding engine on different line cards + +* Test results should be the same for port speeds 100G and 400G. + +* Counters should be also verified for each test case: + + * /qos/interfaces/interface/output/queues/queue/state/transmit-pkts + * /qos/interfaces/interface/output/queues/queue/state/dropped-pkts + * transmit-pkts should be equal to the number of Rx pkts on Ixia port + * dropped-pkts should be equal to diff between the number of Tx and the + number Rx pkts on Ixia ports + +* Latency: + + * Should be < 100000ns + +#### Configuration + +* Forwarding Classes: Configure six forwarding classes (be1, af1, af2, af3, af4, nc1) based on the classification table provided. +* Egress Scheduler: Apply a multi-level strict-priority scheduling policy on the desired egress interface. Assign priorities to each forwarding class according to the strict priority test traffic tables (be1 - priority 6, af1 - priority 5, ..., nc1 - priority 1). + +* Classification table + + IPv4 TOS | IPv6 TC | MPLS EXP | Forwarding class + ------------- | ----------------------- | ----------------------- | --------------------- + 0 | 0-7 | 0 | be1 + 1 | 8-15 | 1 | af1 + 2 | 16-23 | 2 | af2 + 3 | 24-31 | 3 | af3 + 4,5 | 32-47 | 4,5 | af4 + 6,7 | 48-63 | 6,7 | nc1 + +### DP-1.15.1: Egress Strict Priority scheduler for IPv4 Traffic + +* Traffic Generation: + * Traffic Profiles: Define traffic profiles for each forwarding class using the ATE, adhering to the linerates (%) specified in the strict priority test traffic tables. + + * Strict Priority Test traffic table for ATE Port 1 + + Forwarding class | Priority | Traffic linerate (%) | Frame size | Expected Loss % + ----------------- |--------------------- | --------------------------- |--------------------- | ----------------------------------- + be1 | 6 | 12 | 512 | 100 + af1 | 5 | 12 | 512 | 100 + af2 | 4 | 10 | 512 | 50 + af3 | 3 | 12 | 512 | 0 + af4 | 2 | 30 | 512 | 0 + nc1 | 1 | 1 | 512 | 0 + + * Strict Priority Test traffic table for ATE Port 2 + + Forwarding class | Priority | Traffic linerate (%) | Frame size | Expected Loss % + ----------------- |--------------------- | --------------------------- |--------------------- | ----------------------------------- + be1 | 6 | 12 | 512 | 100 + af1 | 5 | 12 | 512 | 100 + af2 | 4 | 10 | 512 | 50 + af3 | 3 | 12 | 512 | 0 + af4 | 2 | 30 | 512 | 0 + nc1 | 1 | 1 | 512 | 0 + + +* Verification: + * 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. + +### DP-1.15.2: Egress Strict Priority scheduler for IPv6 Traffic + +* Traffic Generation: + * Traffic Profiles: Define traffic profiles for each forwarding class using the ATE, adhering to the linerates (%) specified in the strict priority test traffic tables. + + * Strict Priority Test traffic table for ATE Port 1 + + Forwarding class | Priority | Traffic linerate (%) | Frame size | Expected Loss % + ----------------- |--------------------- | --------------------------- |--------------------- | ----------------------------------- + be1 | 6 | 12 | 512 | 100 + af1 | 5 | 12 | 512 | 100 + af2 | 4 | 10 | 512 | 50 + af3 | 3 | 12 | 512 | 0 + af4 | 2 | 30 | 512 | 0 + nc1 | 1 | 1 | 512 | 0 + + * Strict Priority Test traffic table for ATE Port 2 + + Forwarding class | Priority | Traffic linerate (%) | Frame size | Expected Loss % + ----------------- |--------------------- | --------------------------- |--------------------- | ----------------------------------- + be1 | 6 | 12 | 512 | 100 + af1 | 5 | 12 | 512 | 100 + af2 | 4 | 10 | 512 | 50 + af3 | 3 | 12 | 512 | 0 + af4 | 2 | 30 | 512 | 0 + nc1 | 1 | 1 | 512 | 0 + + +* Verification: + * 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. + +### DP-1.15.3: Egress Strict Priority scheduler for MPLS Traffic + +* Traffic Generation: + * Traffic Profiles: Define traffic profiles for each forwarding class using the ATE, adhering to the linerates (%) specified in the strict priority test traffic tables. + + * Strict Priority Test traffic table for ATE Port 1 + + Forwarding class | Priority | Traffic linerate (%) | Frame size | Expected Loss % + ----------------- |--------------------- | --------------------------- |--------------------- | ----------------------------------- + be1 | 6 | 12 | 512 | 100 + af1 | 5 | 12 | 512 | 100 + af2 | 4 | 10 | 512 | 50 + af3 | 3 | 12 | 512 | 0 + af4 | 2 | 30 | 512 | 0 + nc1 | 1 | 1 | 512 | 0 + + * Strict Priority Test traffic table for ATE Port 2 + + Forwarding class | Priority | Traffic linerate (%) | Frame size | Expected Loss % + ----------------- |--------------------- | --------------------------- |--------------------- | ----------------------------------- + be1 | 6 | 12 | 512 | 100 + af1 | 5 | 12 | 512 | 100 + af2 | 4 | 10 | 512 | 50 + af3 | 3 | 12 | 512 | 0 + af4 | 2 | 30 | 512 | 0 + nc1 | 1 | 1 | 512 | 0 + + +* Verification: + * 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. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config paths + ### Classifiers + /qos/classifiers/classifier/config/name: + /qos/classifiers/classifier/config/type: + /qos/classifiers/classifier/terms/term/config/id: + /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/conditions/mpls/config/traffic-class: + /qos/classifiers/classifier/terms/term/actions/config/target-group: + + ### 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/type: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + + ### 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: + + ## State paths + /qos/interfaces/interface/output/queues/queue/state/name: + /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: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* MFF - A modular form factor device containing LINECARDs, FABRIC and redundant CONTROLLER_CARD components +* FFF - fixed form factor diff --git a/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/README.md b/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/README.md new file mode 100644 index 00000000000..cf4b779a975 --- /dev/null +++ b/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/README.md @@ -0,0 +1,220 @@ +# DP-1.5: Egress Strict Priority scheduler with bursty traffic + +## Summary + +This test verifies the behavior of an egress strict priority scheduler under bursty traffic conditions. By configuring multiple priority queues with specific traffic classes and generating bursty traffic that exceeds the interface capacity in short durations, we will validate that the scheduler maintains strict priority order, prioritizing the transmission of higher-priority traffic even during bursts, potentially leading to drops in lower-priority traffic. + +## Testbed type + +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Test environment setup + +* DUT has 2 ingress ports and 1 egress port with the same port speed. The + interface can be a physical interface or LACP bundle interface with the + same aggregated speed. + + ``` + | | ---- | ATE Port 1 | + [ ATE Port 3 ] ---- | DUT | | | + | | ---- | ATE Port 2 | + ``` + +* Traffic classes: + * We will use 6 traffic classes NC1, AF4, AF3, AF2, AF1 and BE1. + +* Traffic types: + * All the traffic tests apply to both IPv4 and IPv6 and also MPLS traffic. + +* Queue types: + * NC1/AF4/AF3/AF2/AF1/BE1 will have strict priority queues (be1 - priority 6, af1 - priority 5, ..., nc1 - priority 1) + +* Test results should be independent of the location of interfaces. For + example, 2 input interfaces and output interface could be located on + * Same ASIC-based forwarding engine + * Different ASIC-based forwarding engine on same line card + * Different ASIC-based forwarding engine on different line cards + +* Test results should be the same for port speeds 100G and 400G. + +* Counters should be also verified for each test case: + * /qos/interfaces/interface/output/queues/queue/state/transmit-pkts + * /qos/interfaces/interface/output/queues/queue/state/dropped-pkts + * transmit-pkts should be equal to the number of Rx pkts on Ixia port + * dropped-pkts should be equal to diff between the number of Tx and the + number Rx pkts on Ixia ports + +* Latency: + * Should be < 100000ns + +#### Configuration + +* Forwarding Classes: Configure six forwarding classes (be1, af1, af2, af3, af4, nc1) based on the classification table provided. +* Classification table + + IPv4 TOS | IPv6 TC | MPLS EXP | Forwarding Group + ------------- | ----------------------- | ----------------------- | --------------------- + 0 | 0-7 | 0 | be1 + 1 | 8-15 | 1 | af1 + 2 | 16-23 | 2 | af2 + 3 | 24-31 | 3 | af3 + 4,5 | 32-47 | 4,5 | af4 + 6,7 | 48-63 | 6,7 | nc1 + +* Egress Scheduler: Apply a multi-level strict-priority scheduling policy on the desired egress interface. Assign priorities to each forwarding class as below: + * be1 - priority 6 + * af1 - priority 5 + * af2 - priority 4 + * af3 - priority 3 + * af4 - priority 2 + * nc1 - priority 1 + +### DP1-5.1 Egress Strict Priority scheduler with bursty traffic for IPv4 + +* Traffic Generation: + * Generate the IPv4 traffic as below + + * Interface Port 1: + + 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 + + * Interface Port 2: + + Forwarding Group | Traffic linerate (%) | Frame size | Burst size | Inter-pkt gap | inter-burst gap | 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 + +* Verification: + * 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. + +### DP1-5.2 Egress Strict Priority scheduler with bursty traffic for IPv6 + +* Traffic Generation: + * Generate the IPv6 traffic as below + + * Interface Port 1: + + 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 + + * Interface Port 2: + + Forwarding Group | Traffic linerate (%) | Frame size | Burst size | Inter-pkt gap | inter-burst gap | 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 + +* Verification: + * 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. + +### DP1-5.3 Egress Strict Priority scheduler with bursty traffic for MPLS + +* Traffic Generation: + * Generate the MPLS traffic as below + + * Interface Port 1: + + 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 + + * Interface Port 2: + + Forwarding Group | Traffic linerate (%) | Frame size | Burst size | Inter-pkt gap | inter-burst gap | 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 + +* Verification: + * 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. + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config paths + ### Classifiers + /qos/classifiers/classifier/config/name: + /qos/classifiers/classifier/config/type: + /qos/classifiers/classifier/terms/term/config/id: + /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/conditions/mpls/config/traffic-class: + /qos/classifiers/classifier/terms/term/actions/config/target-group: + + ### 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: + + ### 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: + + ## State paths + /qos/interfaces/interface/output/queues/queue/state/name: + /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: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* MFF - A modular form factor device containing LINECARDs, FABRIC and redundant CONTROLLER_CARD components +* FFF - fixed form factor diff --git a/feature/qos/otg_tests/ingress_police_default/README.md b/feature/qos/otg_tests/ingress_police_default/README.md new file mode 100644 index 00000000000..df47044e638 --- /dev/null +++ b/feature/qos/otg_tests/ingress_police_default/README.md @@ -0,0 +1,195 @@ +# DP-2.4 Police traffic on input matching all packets using 1 rate, 2 color marker + +## Summary + +Use the gRIBI applied ip entries from TE-18.1 gRIBI. +Configure an ingress scheduler to police traffic using a 1 rate, 2 color policer and attach the scheduler to the interface without a 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 + +Use TE-18.1 test environment setup. + +## Procedure + +### DP-2.4.1 Generate and push configuration + +* Generate config for 2 scheduler polices with an input rate limit. +* Apply scheduler to DUT subinterface with vlan. +* Use gnmi.Replace to push the config to the DUT. + +```json +{ + "openconfig-qos": { + "scheduler-policies": [ + { + "scheduler-policy": null, + "config": { + "name": "limit_1Gb" + }, + "schedulers": [ + { + "scheduler": null, + "config": { + "sequence": 1, + "type": "ONE_RATE_TWO_COLOR" + }, + "inputs": [ + { + "input": "my input policer 1Gb", + "config": { + "id": "my input policer 1Gb", + "input-type": "QUEUE", + "queue": "dummy_input_queue_A" + } + } + ], + "one-rate-two-color": { + "config": { + "cir": 1000000000, + "bc": 100000, + "queuing-behavior": "POLICE" + }, + "exceed-action": { + "config": { + "drop": true + } + } + } + } + ] + }, + { + "scheduler-policy": null, + "config": { + "name": "limit_2Gb" + }, + "schedulers": [ + { + "scheduler": null, + "config": { + "sequence": 1, + "type": "ONE_RATE_TWO_COLOR" + }, + "inputs": [ + { + "input": "my input policer 2Gb", + "config": { + "id": "my input policer 2Gb", + "input-type": "QUEUE", + "queue": "dummy_input_queue_B" + } + } + ], + "one-rate-two-color": { + "config": { + "cir": 2000000000, + "bc": 100000, + "queuing-behavior": "POLICE" + }, + "exceed-action": { + "config": { + "drop": true + } + } + } + } + ] + } + ], + # + # Interfaces input are mapped to the desired scheduler. + "interfaces": [ + { + "interface": null, + "config": { + "interface-id": "PortChannel1.100" + }, + "input": { + "scheduler-policy": { + "config": { + "name": "limit_group_A_1Gb" + } + } + } + }, + { + "interface": null, + "config": { + "interface-id": "PortChannel1.200" + }, + "input": { + "scheduler-policy": { + "config": { + "name": "limit_group_B_1Gb" + } + } + } + } + ] + } +} +``` + +### DP-2.4.2 Test traffic + +* Send traffic + * Send flow A traffic from ATE port 1 to DUT for dest_A at 0.7Gbps (note cir is 1Gbps). + * Send flow B traffic from ATE port 1 to DUT for to dest_B at 1.5Gbps (note cir is 2Gbps). + * Validate qos counters per DUT. + * Validate qos counters by ATE port. + * Validate packets are received by ATE port 2. + * Validate DUT qos interface scheduler counters count packets as conforming-pkts and conforming-octets + * Validate at OTG that 0 packets are lost on flow A and flow B + * When the outer packet is IPv6, the flow-label should be inspected on the ATE. + * If the inner packet is IPv4, the outer IPv6 flow label should be computed based on the IPv4 5 tuple src,dst address and ports, plus protocol. + * If the inner packet is IPv6, the inner flow label should be copied to the outer packet. + * To validate the flow label, use the ATE to verify that the packets for + * flow A all have the same flow label + * flow B have the same flow label + * flow A and B labels do not match + * Increase traffic on flow to dest_B to 2Gbps + * Validate that flow dest_B experiences ~50% packet loss (+/- 1%) + + +#### 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 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: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF + + diff --git a/feature/qos/otg_tests/ingress_police_nhg/README.md b/feature/qos/otg_tests/ingress_police_nhg/README.md new file mode 100644 index 00000000000..cb694f05ac4 --- /dev/null +++ b/feature/qos/otg_tests/ingress_police_nhg/README.md @@ -0,0 +1,433 @@ +# DP-2.2 QoS scheduler with 1 rate 2 color policer, classifying on next-hop group + +## Summary + +Use the gRIBI applied IP entries from DP-2.1 gRIBI. Configure an ingress scheduler +to police traffic using a 1 rate, 2 color policer. Configure a classifier to match +traffic on a next-hop-group. Apply the configuration to a VLAN on an aggregate +interface. 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 + +Use DP-2.1 test environment setup. + +## Procedure + +### DP-2.2.1 Generate and push policer configuration + +* Generate config for 2 classifiers which match on next-hop-group. +* Generate config for 2 forwarding-groups mapped to "dummy" input queues + * Note that the DUT is not required to have an input queue, the dummy queue + satisfies the OC schema which requires defining nodes mapping + classfier->forwarding-group->queue->scheduler +* Generate config for 2 scheduler-policies to police traffic +* Generate config to apply classifer and scheduler to DUT subinterface. +* Use gnmi.Replace to push the config to the DUT. + +```json +{ + # + # A classifer is created to match packets belonging to certain + # next-hop-groups and map them either forwarding-group input_dest_A or + # input_dest_B + "openconfig-qos": { + "classifers": [ + { + "classifer": "“dest_A”", + "config": { + "name": "“dest_A”" + }, + "terms": [ + { + "term": null, + "config": { + "id": "match_1_dest_A1" + }, + "conditions": { + "next-hop-group": { + "config": { + # TODO: new OC path needed, string related to /afts/next-hop-groups/next-hop-group/state/next-hop-group-id + "name": "nhg_A1" + } + } + }, + "actions": { + "config": { + "target-group": "input_dest_A" + } + } + }, + { + "term": null, + "config": { + "id": "match_1_dest_A2" + }, + "conditions": { + "next-hop-group": { + "config": { + "name": "nhg_A2" + } + } + }, + "actions": { + "config": { + "target-group": "input_dest_A" + } + } + } + ] + }, + { + "classifer": "“dest_B”", + "config": { + "name": "“dest_B”" + }, + "terms": [ + { + "term": null, + "config": { + "id": "match_1_dest_B1" + }, + "conditions": { + "next-hop-group": { + "config": { + "name": "nhg_B1" + } + } + }, + "actions": { + "config": { + "target-group": "input_dest_B" + } + } + }, + { + "term": null, + "config": { + "id": "match_1_dest_B2" + }, + "conditions": { + "next-hop-group": { + "config": { + "name": "nhg_B2" + } + } + }, + "actions": { + "config": { + "target-group": "input_dest_B" + } + } + } + ] + } + ], + # + # Forwarding groups are created named input_dest_A and input_dest_B. + # These are mapped to 'fake' queues + "forwarding-groups": [ + { + "forwarding-group": "input_dest_A", + "config": { + "name": "input_dest_A", + "output-queue": "dummy_input_queue_A" + } + }, + { + "forwarding-group": "input_dest_B", + "config": { + "name": "input_dest_B", + "output-queue": "dummy_input_queue_B" + } + } + ], + "queues": [ + { + "queue": null, + "config": { + "name": "dummy_input_queue_A" + } + }, + { + "queue": null, + "config": { + "name": "dummy_input_queue_B" + } + } + ], + # + # Two scheduler policies are created, limit_1Gb and limit_2Gb + # and are associated with the dummy queue they are servicing. + "scheduler-policies": [ + { + "scheduler-policy": null, + "config": { + "name": "limit_1Gb" + }, + "schedulers": [ + { + "scheduler": null, + "config": { + "sequence": 1, + "type": "ONE_RATE_TWO_COLOR" + }, + "inputs": [ + { + "input": "my input policer 1Gb", + "config": { + "id": "my input policer 1Gb", + "input-type": "QUEUE", + "queue": "dummy_input_queue_A" + } + } + ], + "one-rate-two-color": { + "config": { + "cir": 1000000000, + "bc": 100000, + "queuing-behavior": "POLICE" + }, + "exceed-action": { + "config": { + "drop": true + } + } + } + } + ] + }, + { + "scheduler-policy": null, + "config": { + "name": "limit_2Gb" + }, + "schedulers": [ + { + "scheduler": null, + "config": { + "sequence": 1, + "type": "ONE_RATE_TWO_COLOR" + }, + "inputs": [ + { + "input": "my input policer 2Gb", + "config": { + "id": "my input policer 2Gb", + "input-type": "QUEUE", + "queue": "dummy_input_queue_B" + } + } + ], + "one-rate-two-color": { + "config": { + "cir": 2000000000, + "bc": 100000, + "queuing-behavior": "POLICE" + }, + "exceed-action": { + "config": { + "drop": true + } + } + } + } + ] + } + ], + # + # Interfaces input are mapped to the desired classifier and scheduler. + "interfaces": [ + { + "interface": null, + "config": { + "interface-id": "PortChannel1.100" + }, + "input": { + "classifiers": [ + { + "classifier": null, + "config": { + "name": "dest_A", + "type": "IPV4" + } + } + ], + "scheduler-policy": { + "config": { + "name": "limit_group_A_1Gb" + } + } + } + }, + { + "interface": null, + "config": { + "interface-id": "PortChannel1.200" + }, + "input": { + "classifiers": [ + { + "classifier": null, + "config": { + "name": "dest_B", + "type": "IPV4" + } + } + ], + "scheduler-policy": { + "config": { + "name": "limit_group_B_1Gb" + } + } + } + } + ] + } +} +``` + +### DP-2.2.2 push gRIBI AFT encapsulation rules with next-hop-group-id + +Create a gRIBI client and send this proto message to the DUT to create AFT +entries. Note the next-hop-groups here include a `next_hop_group_id` field +which matches the +`/qos/classifiers/classifier/condition/next-hop-group/config/name` leaf. + +```proto +# +# aft entries used for network instance "NI_A" +IPv6Entry {2001:DB8:2::2/128 (NI_A)} -> NHG#100 (DEFAULT VRF) +IPv4Entry {203.0.113.2/32 (NI_A)} -> NHG#100 (DEFAULT VRF) -> { + {NH#101, DEFAULT VRF} +} + +# this nexthop specifies a MPLS in UDP encapsulation +NH#101 -> { + encap-headers { + encap-header { + index: 1 + mpls { + pushed_mpls_label_stack: [101,] + } + } + encap-header { + index: 2 + udpv6 { + src_ip: "outer_ipv6_src" + dst_ip: "outer_ipv6_dst_A" + dst_udp_port: "outer_dst_udp_port" + ip_ttl: "outer_ip-ttl" + dscp: "outer_dscp" + } + } + } + next_hop_group_id: "nhg_A" # TODO: new OC path /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/next-hop-group-id + 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 + udpv6 { + 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" # TODO: new OC path /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/next-hop-group-id + network_instance: "DEFAULT" +} +``` + +### DP-2.2.3 Test flow policing + +* Send traffic + * Send flow A traffic from ATE port 1 to DUT for dest_A at 0.7Gbps (note cir is 1Gbps). + * Send flow B traffic from ATE port 1 to DUT for to dest_B at 1.5Gbps (note cir is 2Gbps). + * Validate packets are received by ATE port 2. + * Validate DUT qos interface scheduler counters count packets as conforming-pkts and conforming-octets + * Validate at OTG that 0 packets are lost on flow A and flow B + * Increase traffic on flow to dest_A to 2Gbps + * Validate that flow dest_A experiences ~50% packet loss (+/- 1%) + * Stop traffic + +### DP-2.2.3 IPv6 flow label validiation + + * Send 100 packets for flow A and flow B. (Use an OTG fixed packet count flow) + * When the outer packet is IPv6, the flow-label should be inspected on the ATE. + * If the inner packet is IPv4, the outer IPv6 flow label should be computed based on the IPv4 5 tuple src,dst address and ports, plus protocol. + * If the inner packet is IPv6, the inner flow label should be copied to the outer packet. + * To validate the flow label, use the ATE to verify that the packets for + * flow A all have the same flow label + * flow B have the same flow label + * flow A and B labels do not match + +#### 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 forwarding-groups config + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + + # qos queue config + /qos/queues/queue/config/name: + + # qos interfaces config + /qos/interfaces/interface/config/interface-id: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/type: + /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: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF 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 new file mode 100644 index 00000000000..93feae4293b --- /dev/null +++ b/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/README.md @@ -0,0 +1,153 @@ +# DP-1.16: Ingress traffic classification and rewrite + +## Summary + +This test aims to validate the functionality of ingress traffic classification and subsequent packet remarking (rewrite) on a Device Under Test (DUT). The DUT's configuration will be evaluated against the OpenConfig QOS model, and traffic flows will be analyzed to ensure proper classification, 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 the DUT's ingress and egress interfaces. + +### Configuration + +* Apply QoS classifiers using the OpenConfig QOS model, matching packets based on DSCP/TC/EXP values as per the classification table. +* Configure packet remarking rules based on the marking table. +* QoS Classification and Marking table + + * Classification table + + IPv4 TOS | IPv6 TC | MPLS EXP | Forwarding Group + ------------- | ----------------------- | ----------------------- | --------------------- + 0 | 0-7 | 0 | be1 + 1 | 8-15 | 1 | af1 + 2 | 16-23 | 2 | af2 + 3 | 24-31 | 3 | af3 + 4,5 | 32-47 | 4,5 | af4 + 6,7 | 48-63 | 6,7 | nc1 + + * Marking table + + Forwarding Group | IPv4 TOS | IPv6 TC | MPLS EXP + --------------------|------------- | ----------------------- | ----------------------- + be1 | 0 | 0 | 0 + af1 | 1 | 8 | 1 + af2 | 2 | 16 | 2 + af3 | 3 | 24 | 3 + af4 | 4 | 32 | 4 + nc1 | 6 | 48 | 6 + +### DP-1.16.1 Ingress Classification and rewrite of IPv4 packets with various DSCP values + +* Traffic: + * Generate IPv4 traffic from ATE Port 1 with various DSCP values +* 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. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. +### DP-1.16.2 Ingress Classification and rewrite of IPv6 packets with various TC values + +* Traffic: + * Generate IPv6 traffic from ATE Port 1 with various TC values +* 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. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. +### DP-1.16.3 Ingress Classification and rewrite of MPLS traffic with swap action + +* Configuration: + * Configure Static MPLS LSP MPLS swap/forward actions for a specific labels range (100101-100200). +* Traffic: + * Generate MPLS traffic from ATE Port 1 with labels between 1000101 and 1000200 +* 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. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. +### DP-1.16.4 Ingress Classification and rewrite of IPv4-over-MPLS traffic with pop action + +* Configuration: + * Configure Static MPLS LSP with MPLS pop and IPv4/IPv6 forward actions for a specific labels range (100020-100100). +* Traffic: + * Generate MPLS traffic from ATE Port 1 with labels between 100020 and 1000100 +* 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. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. +### DP-1.16.5 Ingress Classification and rewrite of IPv6-over-MPLS traffic with pop action + +* Configuration: + * Configure Static MPLS LSP with MPLS pop and IPv4/IPv6 forward actions for a specific labels range (100020-100100). +* Traffic: + * Generate MPLS traffic from ATE Port 1 with labels between 100020 and 1000100 +* 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. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. +### DP-1.16.6 Ingress Classification and rewrite of IPv4 packets traffic with label push action + +* Configuration: + * Configure Static MPLS LSP with MPLS label (=100201) push action to a IPv4 subnet destination DST1. +* Traffic: + * Generate IPv4 traffic from ATE Port 1 with destinations matching DST1. +* 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. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. +### DP-1.16.7 Ingress Classification and rewrite of IPv6 packets traffic with label push action + +* Configuration: + * Configure Static MPLS LSP with MPLS label (=100202) push action to a IPv6 subnet destination DST2. +* Traffic: + * Generate IPv6 traffic from ATE Port 1 with destinations matching DST2. +* 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. + * 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/mixed_sp_wrr_traffic_test/README.md b/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/README.md index 09c58f2d08b..adbb4e22acf 100644 --- a/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/README.md +++ b/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/README.md @@ -150,3 +150,59 @@ forwards AF3, AF2, AF1, BE1 and BE0 based on weight. * /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 + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: + /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: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /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: + + ## State paths: + /qos/forwarding-groups/forwarding-group/state/name: + /qos/forwarding-groups/forwarding-group/state/output-queue: + /qos/queues/queue/state/name: + /qos/classifiers/classifier/state/name: + /qos/classifiers/classifier/state/type: + /qos/classifiers/classifier/terms/term/actions/state/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set: + /qos/classifiers/classifier/terms/term/state/id: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight: + +rpcs: + gnmi: + gNMI.Set: + Replace: \ No newline at end of file diff --git a/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/metadata.textproto b/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/metadata.textproto index cf011351b89..34786e74bae 100644 --- a/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/metadata.textproto +++ b/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/metadata.textproto @@ -18,8 +18,6 @@ platform_exceptions: { vendor: JUNIPER } deviations: { - explicit_interface_ref_definition: true - qos_dropped_octets: true scheduler_input_weight_limit: true } } diff --git a/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/mixed_sp_wrr_traffic_test.go b/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/mixed_sp_wrr_traffic_test.go index 4016f751681..1ef4e4f42d5 100644 --- a/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/mixed_sp_wrr_traffic_test.go +++ b/feature/qos/otg_tests/mixed_sp_wrr_traffic_test/mixed_sp_wrr_traffic_test.go @@ -530,19 +530,11 @@ func TestMixedSPWrrTraffic(t *testing.T) { var counterNames []string counters := make(map[string]map[string]uint64) - if !deviations.QOSDroppedOctets(dut) { - counterNames = []string{ - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", - "dutQosDroppedOctetsAfterTraffic", - } - } else { - counterNames = []string{ - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedPktsAfterTraffic", - } + counterNames = []string{ + "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", + "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", + "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", + "dutQosDroppedOctetsAfterTraffic", } for _, name := range counterNames { @@ -576,13 +568,11 @@ func TestMixedSPWrrTraffic(t *testing.T) { } counters["dutQosDroppedPktsBeforeTraffic"][data.queue], _ = count.Val() - if !deviations.QOSDroppedOctets(dut) { - count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) - if !ok { - t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp3.Name(), data.queue, timeout) - } - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp3.Name(), data.queue, timeout) } + counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() } t.Logf("Running traffic 1 on DUT interfaces: %s => %s ", dp1.Name(), dp3.Name()) @@ -601,9 +591,7 @@ func TestMixedSPWrrTraffic(t *testing.T) { counters["dutQosPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).TransmitPkts().State()) counters["dutQosOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).TransmitOctets().State()) counters["dutQosDroppedPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedPkts().State()) - if !deviations.QOSDroppedOctets(dut) { - counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State()) - } + counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State()) t.Logf("ateInPkts: %v, txPkts %v, Queue: %v", counters["ateInPkts"][data.queue], counters["dutQosPktsAfterTraffic"][data.queue], data.queue) // Calculate aggregated throughput: @@ -666,14 +654,12 @@ func TestMixedSPWrrTraffic(t *testing.T) { } } - if !deviations.QOSDroppedOctets(dut) { - ateDropOctetCounterDiff := (counters["ateOutPkts"][data.queue] - counters["ateInPkts"][data.queue]) * uint64(data.frameSize) - dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] - t.Logf("Queue %q: ateDropOctetCounterDiff: %v dutDropOctetCounterDiff: %v", data.queue, ateDropOctetCounterDiff, dutDropOctetCounterDiff) - if dutDropOctetCounterDiff < ateDropOctetCounterDiff { - if !deviations.DequeueDeleteNotCountedAsDrops(dut) { - t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want >= %v", data.queue, dutDropOctetCounterDiff, ateDropOctetCounterDiff) - } + ateDropOctetCounterDiff := (counters["ateOutPkts"][data.queue] - counters["ateInPkts"][data.queue]) * uint64(data.frameSize) + dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] + t.Logf("Queue %q: ateDropOctetCounterDiff: %v dutDropOctetCounterDiff: %v", data.queue, ateDropOctetCounterDiff, dutDropOctetCounterDiff) + if dutDropOctetCounterDiff < ateDropOctetCounterDiff { + if !deviations.DequeueDeleteNotCountedAsDrops(dut) { + t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want >= %v", data.queue, dutDropOctetCounterDiff, ateDropOctetCounterDiff) } } 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 d2cd200b781..2c357ec3166 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 @@ -136,3 +136,59 @@ Verify that DUT drops AF4, AF3, AF2, AF1, BE1 and BE0 before NC1. * /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 + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: + /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: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /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: + + ## State paths: + /qos/forwarding-groups/forwarding-group/state/name: + /qos/forwarding-groups/forwarding-group/state/output-queue: + /qos/queues/queue/state/name: + /qos/classifiers/classifier/state/name: + /qos/classifiers/classifier/state/type: + /qos/classifiers/classifier/terms/term/actions/state/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set: + /qos/classifiers/classifier/terms/term/state/id: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight: + +rpcs: + gnmi: + gNMI.Set: + Replace: \ No newline at end of file diff --git a/feature/qos/otg_tests/one_sp_queue_traffic_test/metadata.textproto b/feature/qos/otg_tests/one_sp_queue_traffic_test/metadata.textproto index 5ad022dedf8..24e2c3193d3 100644 --- a/feature/qos/otg_tests/one_sp_queue_traffic_test/metadata.textproto +++ b/feature/qos/otg_tests/one_sp_queue_traffic_test/metadata.textproto @@ -17,9 +17,6 @@ platform_exceptions: { platform: { vendor: JUNIPER } - deviations: { - explicit_interface_ref_definition: true - } } platform_exceptions: { platform: { diff --git a/feature/qos/otg_tests/qos_basic_test/README.md b/feature/qos/otg_tests/qos_basic_test/README.md index 27c82a5bd18..e836aa0fc66 100644 --- a/feature/qos/otg_tests/qos_basic_test/README.md +++ b/feature/qos/otg_tests/qos_basic_test/README.md @@ -164,3 +164,59 @@ Verify that DUT supports QoS config and forward QoS traffic correctly. ## Required DUT platform * FFF + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: + /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: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /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: + + ## State paths: + /qos/forwarding-groups/forwarding-group/state/name: + /qos/forwarding-groups/forwarding-group/state/output-queue: + /qos/queues/queue/state/name: + /qos/classifiers/classifier/state/name: + /qos/classifiers/classifier/state/type: + /qos/classifiers/classifier/terms/term/actions/state/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set: + /qos/classifiers/classifier/terms/term/state/id: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight: + +rpcs: + gnmi: + gNMI.Set: + Replace: \ No newline at end of file diff --git a/feature/qos/otg_tests/qos_basic_test/metadata.textproto b/feature/qos/otg_tests/qos_basic_test/metadata.textproto index e85163ec382..36abf3c642b 100644 --- a/feature/qos/otg_tests/qos_basic_test/metadata.textproto +++ b/feature/qos/otg_tests/qos_basic_test/metadata.textproto @@ -19,8 +19,7 @@ platform_exceptions: { } deviations: { ecn_profile_required_definition: true - explicit_interface_ref_definition: true - qos_dropped_octets: true + no_zero_suppression: true } } platform_exceptions: { diff --git a/feature/qos/otg_tests/qos_basic_test/qos_basic_test.go b/feature/qos/otg_tests/qos_basic_test/qos_basic_test.go index b4ae086cc22..a136165b316 100644 --- a/feature/qos/otg_tests/qos_basic_test/qos_basic_test.go +++ b/feature/qos/otg_tests/qos_basic_test/qos_basic_test.go @@ -15,6 +15,7 @@ package qos_basic_test import ( + "context" "testing" "time" @@ -114,6 +115,10 @@ func TestBasicConfigWithTraffic(t *testing.T) { ConfigureQoS(t, dut) } + if deviations.NoZeroSuppression(dut) { + configureNoZeroSuppression(t, dut) + } + // Configure ATE interfaces. ate := ondatra.ATE(t, "ate") ap1 := ate.Port(t, "port1") @@ -399,24 +404,13 @@ func TestBasicConfigWithTraffic(t *testing.T) { ate.OTG().StartProtocols(t) counters := make(map[string]map[string]uint64) - var counterNames []string - - if !deviations.QOSDroppedOctets(dut) { - counterNames = []string{ - - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", - "dutQosDroppedOctetsAfterTraffic", - } - } else { - counterNames = []string{ - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedPktsAfterTraffic", - } + var counterNames = []string{ + "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", + "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", + "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", + "dutQosDroppedOctetsAfterTraffic", } for _, name := range counterNames { @@ -450,13 +444,12 @@ func TestBasicConfigWithTraffic(t *testing.T) { } counters["dutQosDroppedPktsBeforeTraffic"][data.queue], _ = count.Val() - if !deviations.QOSDroppedOctets(dut) { - count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) - if !ok { - t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp3.Name(), data.queue, timeout) - } - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp3.Name(), data.queue, timeout) } + counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() + } t.Logf("Running traffic 1 on DUT interfaces: %s => %s ", dp1.Name(), dp3.Name()) @@ -476,9 +469,8 @@ func TestBasicConfigWithTraffic(t *testing.T) { counters["dutQosPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).TransmitPkts().State()) counters["dutQosOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).TransmitOctets().State()) counters["dutQosDroppedPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedPkts().State()) - if !deviations.QOSDroppedOctets(dut) { - counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State()) - } + counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State()) + t.Logf("ateInPkts: %v, txPkts %v, Queue: %v", counters["ateInPkts"][data.queue], counters["dutQosPktsAfterTraffic"][data.queue], data.queue) if ateTxPkts == 0 { @@ -519,13 +511,12 @@ func TestBasicConfigWithTraffic(t *testing.T) { } } - if !deviations.QOSDroppedOctets(dut) { - dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] - t.Logf("Queue %q: dutDropOctetCounterDiff: %v", data.queue, dutDropOctetCounterDiff) - if dutDropOctetCounterDiff != 0 { - t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want 0", data.queue, dutDropOctetCounterDiff) - } + dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] + t.Logf("Queue %q: dutDropOctetCounterDiff: %v", data.queue, dutDropOctetCounterDiff) + if dutDropOctetCounterDiff != 0 { + t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want 0", data.queue, dutDropOctetCounterDiff) } + } // gnmi subscribe sample mode(10 and 15 seconds sample interval) for queue counters @@ -545,12 +536,11 @@ func TestBasicConfigWithTraffic(t *testing.T) { if len(droppedPkts) < minWant { t.Errorf("DroppedPkts: got %d, want >= %d", len(droppedPkts), minWant) } - if !deviations.QOSDroppedOctets(dut) { - droppedOctets := gnmi.Collect(t, gnmiOpts(t, dut, sampleInterval), gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), subscribeTimeout).Await(t) - if len(droppedOctets) < minWant { - t.Errorf("DroppedOctets: got %d, want >= %d", len(droppedOctets), minWant) - } + droppedOctets := gnmi.Collect(t, gnmiOpts(t, dut, sampleInterval), gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedOctets().State(), subscribeTimeout).Await(t) + if len(droppedOctets) < minWant { + t.Errorf("DroppedOctets: got %d, want >= %d", len(droppedOctets), minWant) } + } } }) @@ -1759,3 +1749,52 @@ func gnmiOpts(t *testing.T, dut *ondatra.DUTDevice, interval time.Duration) *gnm ygnmi.WithSampleInterval(interval), ) } +func configureNoZeroSuppression(t *testing.T, dut *ondatra.DUTDevice) { + // Disable Zero suppression + t.Logf("Disable zero suppression:\n%s", dut.Vendor()) + var config string + switch dut.Vendor() { + case ondatra.JUNIPER: + config = disableZeroSuppression() + t.Logf("Push the CLI config:\n%s", config) + + default: + t.Errorf("Invalid configuration") + } + gnmiClient := dut.RawAPIs().GNMI(t) + gpbSetRequest := buildCliConfigRequest(config) + + t.Log("gnmiClient Set CLI config") + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmiClient.Set() with unexpected error: %v", err) + } +} + +func buildCliConfigRequest(config string) *gpb.SetRequest { + // Build config with Origin set to cli and Ascii encoded config. + 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 disableZeroSuppression() string { + return (` + services { + analytics { + zero-suppression { + no-zero-suppression; + } + } + }`) +} diff --git a/feature/qos/otg_tests/qos_output_queue_counters_test/README.md b/feature/qos/otg_tests/qos_output_queue_counters_test/README.md index eb8bb04af09..9fab7d0d7e8 100644 --- a/feature/qos/otg_tests/qos_output_queue_counters_test/README.md +++ b/feature/qos/otg_tests/qos_output_queue_counters_test/README.md @@ -13,7 +13,7 @@ Validate QoS interface output queue counters. * /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 - + ## Config Parameter coverage * /interfaces/interface/config/enabled @@ -26,3 +26,59 @@ Validate QoS interface output queue counters. * /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 + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: + /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: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /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: + + ## State paths: + /qos/forwarding-groups/forwarding-group/state/name: + /qos/forwarding-groups/forwarding-group/state/output-queue: + /qos/queues/queue/state/name: + /qos/classifiers/classifier/state/name: + /qos/classifiers/classifier/state/type: + /qos/classifiers/classifier/terms/term/actions/state/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set: + /qos/classifiers/classifier/terms/term/state/id: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight: + +rpcs: + gnmi: + gNMI.Set: + Replace: \ No newline at end of file diff --git a/feature/qos/otg_tests/qos_output_queue_counters_test/metadata.textproto b/feature/qos/otg_tests/qos_output_queue_counters_test/metadata.textproto index f6a3bbf0e12..75b7900893e 100644 --- a/feature/qos/otg_tests/qos_output_queue_counters_test/metadata.textproto +++ b/feature/qos/otg_tests/qos_output_queue_counters_test/metadata.textproto @@ -19,8 +19,6 @@ platform_exceptions: { } deviations: { scheduler_input_weight_limit: true - explicit_interface_ref_definition: true - qos_dropped_octets: true } } platform_exceptions: { diff --git a/feature/qos/otg_tests/qos_output_queue_counters_test/qos_output_queue_counters_test.go b/feature/qos/otg_tests/qos_output_queue_counters_test/qos_output_queue_counters_test.go index 98e78968967..82353b5eef1 100644 --- a/feature/qos/otg_tests/qos_output_queue_counters_test/qos_output_queue_counters_test.go +++ b/feature/qos/otg_tests/qos_output_queue_counters_test/qos_output_queue_counters_test.go @@ -101,12 +101,12 @@ func TestQoSCounters(t *testing.T) { dev1 := top.Devices().Add().SetName(ateSrcName) eth1 := dev1.Ethernets().Add().SetName(dev1.Name() + ".eth").SetMac(ateSrcMac) - eth1.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap1.ID()) + eth1.Connection().SetPortName(ap1.ID()) eth1.Ipv4Addresses().Add().SetName(dev1.Name() + ".ipv4").SetAddress(ateSrcIp).SetGateway(ateSrcGateway).SetPrefix(uint32(prefixLen)) dev2 := top.Devices().Add().SetName(ateDstName) eth2 := dev2.Ethernets().Add().SetName(dev2.Name() + ".eth").SetMac(ateDstMac) - eth2.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap2.ID()) + eth2.Connection().SetPortName(ap2.ID()) eth2.Ipv4Addresses().Add().SetName(dev2.Name() + ".ipv4").SetAddress(ateDstIp).SetGateway(ateDstGateway).SetPrefix(uint32(prefixLen)) queues := netutil.CommonTrafficQueues(t, dut) @@ -178,22 +178,12 @@ func TestQoSCounters(t *testing.T) { var counterNames []string counters := make(map[string]map[string]uint64) - if !deviations.QOSDroppedOctets(dut) { - counterNames = []string{ - - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", - "dutQosDroppedOctetsAfterTraffic", - } - } else { - counterNames = []string{ - - "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", - "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", - "dutQosDroppedPktsAfterTraffic", - } + counterNames = []string{ + "ateOutPkts", "ateInPkts", "dutQosPktsBeforeTraffic", "dutQosOctetsBeforeTraffic", + "dutQosPktsAfterTraffic", "dutQosOctetsAfterTraffic", "dutQosDroppedPktsBeforeTraffic", + "dutQosDroppedOctetsBeforeTraffic", "dutQosDroppedPktsAfterTraffic", + "dutQosDroppedOctetsAfterTraffic", } for _, name := range counterNames { @@ -226,13 +216,12 @@ func TestQoSCounters(t *testing.T) { } counters["dutQosDroppedPktsBeforeTraffic"][data.queue], _ = count.Val() - if !deviations.QOSDroppedOctets(dut) { - count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp2.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) - if !ok { - t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp2.Name(), data.queue, timeout) - } - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp2.Name()).Output().Queue(data.queue).DroppedOctets().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedOctets count for queue %q on interface %q not available within %v", dp2.Name(), data.queue, timeout) } + counters["dutQosDroppedOctetsBeforeTraffic"][data.queue], _ = count.Val() + } ate.OTG().PushConfig(t, top) @@ -254,9 +243,8 @@ func TestQoSCounters(t *testing.T) { counters["dutQosPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp2.Name()).Output().Queue(data.queue).TransmitPkts().State()) counters["dutQosOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp2.Name()).Output().Queue(data.queue).TransmitOctets().State()) counters["dutQosDroppedPktsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp2.Name()).Output().Queue(data.queue).DroppedPkts().State()) - if !deviations.QOSDroppedOctets(dut) { - counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp2.Name()).Output().Queue(data.queue).DroppedOctets().State()) - } + counters["dutQosDroppedOctetsAfterTraffic"][data.queue] = gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp2.Name()).Output().Queue(data.queue).DroppedOctets().State()) + t.Logf("ateInPkts: %v, txPkts %v, Queue: %v", counters["ateInPkts"][data.queue], counters["dutQosPktsAfterTraffic"][data.queue], data.queue) ateTxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().OutPkts().State()) @@ -296,12 +284,10 @@ func TestQoSCounters(t *testing.T) { } } - if !deviations.QOSDroppedOctets(dut) { - dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] - t.Logf("Queue %q: dutDropOctetCounterDiff: %v", data.queue, dutDropOctetCounterDiff) - if dutDropOctetCounterDiff != 0 { - t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want 0", data.queue, dutDropOctetCounterDiff) - } + dutDropOctetCounterDiff := counters["dutQosDroppedOctetsAfterTraffic"][data.queue] - counters["dutQosDroppedOctetsBeforeTraffic"][data.queue] + t.Logf("Queue %q: dutDropOctetCounterDiff: %v", data.queue, dutDropOctetCounterDiff) + if dutDropOctetCounterDiff != 0 { + t.Errorf("Get dutDropOctetCounterDiff for queue %q: got %v, want 0", data.queue, dutDropOctetCounterDiff) } } diff --git a/feature/qos/otg_tests/two_sp_queue_traffic_test/README.md b/feature/qos/otg_tests/two_sp_queue_traffic_test/README.md index 69c06f27d9b..4fdc68784a5 100644 --- a/feature/qos/otg_tests/two_sp_queue_traffic_test/README.md +++ b/feature/qos/otg_tests/two_sp_queue_traffic_test/README.md @@ -160,3 +160,59 @@ Verify that DUT drops AF3, AF2, AF1, BE1 and BE0 before AF4 before NC1. * /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 + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: + /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: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /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: + + ## State paths: + /qos/forwarding-groups/forwarding-group/state/name: + /qos/forwarding-groups/forwarding-group/state/output-queue: + /qos/queues/queue/state/name: + /qos/classifiers/classifier/state/name: + /qos/classifiers/classifier/state/type: + /qos/classifiers/classifier/terms/term/actions/state/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set: + /qos/classifiers/classifier/terms/term/state/id: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight: + +rpcs: + gnmi: + gNMI.Set: + Replace: \ No newline at end of file diff --git a/feature/qos/otg_tests/two_sp_queue_traffic_test/metadata.textproto b/feature/qos/otg_tests/two_sp_queue_traffic_test/metadata.textproto index bc1316ff266..a439925272e 100644 --- a/feature/qos/otg_tests/two_sp_queue_traffic_test/metadata.textproto +++ b/feature/qos/otg_tests/two_sp_queue_traffic_test/metadata.textproto @@ -18,7 +18,6 @@ platform_exceptions: { vendor: JUNIPER } deviations: { - explicit_interface_ref_definition: true scheduler_input_weight_limit: true } } diff --git a/feature/qos/otg_tests/wrr_traffic_test/README.md b/feature/qos/otg_tests/wrr_traffic_test/README.md index 1a981ac4d5e..c9361579b70 100644 --- a/feature/qos/otg_tests/wrr_traffic_test/README.md +++ b/feature/qos/otg_tests/wrr_traffic_test/README.md @@ -145,3 +145,59 @@ Verify that DUT forwards AF3, AF2, AF1, BE0 and BE1 traffic based on WRR weight. * /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 + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: + /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: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /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: + + ## State paths: + /qos/forwarding-groups/forwarding-group/state/name: + /qos/forwarding-groups/forwarding-group/state/output-queue: + /qos/queues/queue/state/name: + /qos/classifiers/classifier/state/name: + /qos/classifiers/classifier/state/type: + /qos/classifiers/classifier/terms/term/actions/state/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set: + /qos/classifiers/classifier/terms/term/state/id: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight: + +rpcs: + gnmi: + gNMI.Set: + Replace: \ No newline at end of file diff --git a/feature/qos/otg_tests/wrr_traffic_test/metadata.textproto b/feature/qos/otg_tests/wrr_traffic_test/metadata.textproto index d50a81212bb..f81dfe8c4b3 100644 --- a/feature/qos/otg_tests/wrr_traffic_test/metadata.textproto +++ b/feature/qos/otg_tests/wrr_traffic_test/metadata.textproto @@ -18,7 +18,6 @@ platform_exceptions: { vendor: JUNIPER } deviations: { - explicit_interface_ref_definition: true scheduler_input_weight_limit: true } } diff --git a/feature/qos/tests/qos_ecn_config_test/README.md b/feature/qos/tests/qos_ecn_config_test/README.md index 025b4b5a595..adb5a3f2d79 100644 --- a/feature/qos/tests/qos_ecn_config_test/README.md +++ b/feature/qos/tests/qos_ecn_config_test/README.md @@ -60,7 +60,7 @@ Verify QoS ECN feature configuration. * ECN * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/config/min-threshold-percent - * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold-percent + * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold-percent * qos/queue-management-profiles/queue-management-profile/wred/uniform/config/min-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/config/enable-ecn @@ -79,7 +79,7 @@ Verify QoS ECN feature configuration. * ECN * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/state/min-threshold-percent - * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold-percent + * [TODO] qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold-percent * qos/queue-management-profiles/queue-management-profile/wred/uniform/state/min-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold * qos/queue-management-profiles/queue-management-profile/wred/uniform/state/enable-ecn @@ -96,3 +96,39 @@ Verify QoS ECN feature configuration. ## platform * vRX + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/min-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/enable-ecn: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/weight: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/drop: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-drop-probability-percent: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/output/queues/queue/config/queue-management-profile: + + ## State paths: + + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/min-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/enable-ecn: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/weight: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/drop: + /qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-drop-probability-percent: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/output/queues/queue/state/queue-management-profile: + +rpcs: + gnmi: + gNMI.Set: + Replace: +``` \ No newline at end of file diff --git a/feature/qos/tests/qos_ecn_config_test/metadata.textproto b/feature/qos/tests/qos_ecn_config_test/metadata.textproto index 729421003ce..2a2d548b8bb 100644 --- a/feature/qos/tests/qos_ecn_config_test/metadata.textproto +++ b/feature/qos/tests/qos_ecn_config_test/metadata.textproto @@ -9,10 +9,6 @@ platform_exceptions: { platform: { vendor: JUNIPER } - deviations: { - state_path_unsupported: true - drop_weight_leaves_unsupported: true - } } platform_exceptions: { platform: { diff --git a/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go b/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go index 0e9f12ffedf..ae94ee7fb04 100644 --- a/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go +++ b/feature/qos/tests/qos_ecn_config_test/qos_ecn_config_test.go @@ -302,46 +302,114 @@ func testQoSOutputIntfConfig(t *testing.T, q *oc.Qos) { dp := dut.Port(t, "port2") queues := netutil.CommonTrafficQueues(t, dut) + ecnConfig := struct { + ecnEnabled bool + dropEnabled bool + minThreshold uint64 + maxThreshold uint64 + maxDropProbabilityPercent uint8 + weight uint32 + }{ + ecnEnabled: true, + dropEnabled: false, + minThreshold: uint64(80000), + maxThreshold: uint64(80000), + maxDropProbabilityPercent: uint8(100), + weight: uint32(0), + } + + queueMgmtProfile := q.GetOrCreateQueueManagementProfile("DropProfile") + queueMgmtProfile.SetName("DropProfile") + wred := queueMgmtProfile.GetOrCreateWred() + uniform := wred.GetOrCreateUniform() + uniform.SetEnableEcn(ecnConfig.ecnEnabled) + uniform.SetDrop(ecnConfig.dropEnabled) + wantMinThreshold := ecnConfig.minThreshold + wantMaxThreshold := ecnConfig.maxThreshold + if deviations.EcnSameMinMaxThresholdUnsupported(dut) { + wantMinThreshold = CiscoMinThreshold + wantMaxThreshold = CiscoMaxThreshold + } + uniform.SetMinThreshold(wantMinThreshold) + uniform.SetMaxThreshold(wantMaxThreshold) + uniform.SetMaxDropProbabilityPercent(ecnConfig.maxDropProbabilityPercent) + if !deviations.QosSetWeightConfigUnsupported(dut) { + uniform.SetWeight(ecnConfig.weight) + } + cases := []struct { desc string queueName string ecnProfile string scheduler string + sequence uint32 + priority oc.E_Scheduler_Priority + inputID string + inputType oc.E_Input_InputType + weight uint64 }{{ desc: "output-interface-BE1", queueName: queues.BE1, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(1), }, { desc: "output-interface-BE0", queueName: queues.BE0, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(4), }, { desc: "output-interface-AF1", queueName: queues.AF1, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(8), }, { desc: "output-interface-AF2", queueName: queues.AF2, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(16), }, { desc: "output-interface-AF3", queueName: queues.AF3, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(1), + priority: oc.Scheduler_Priority_UNSET, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(32), }, { desc: "output-interface-AF4", queueName: queues.AF4, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(0), + priority: oc.Scheduler_Priority_STRICT, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(6), }, { desc: "output-interface-NC1", queueName: queues.NC1, ecnProfile: "DropProfile", scheduler: "scheduler", + sequence: uint32(0), + priority: oc.Scheduler_Priority_STRICT, + inputType: oc.Input_InputType_QUEUE, + weight: uint64(7), }} i := q.GetOrCreateInterface(dp.Name()) @@ -366,6 +434,14 @@ func testQoSOutputIntfConfig(t *testing.T, q *oc.Qos) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { qoscfg.SetForwardingGroup(t, dut, q, tc.queueName, tc.queueName) + s := schedulerPolicy.GetOrCreateScheduler(tc.sequence) + s.SetSequence(tc.sequence) + s.SetPriority(tc.priority) + input := s.GetOrCreateInput(tc.queueName) + input.SetId(tc.queueName) + input.SetInputType(tc.inputType) + input.SetQueue(tc.queueName) + input.SetWeight(tc.weight) output := i.GetOrCreateOutput() schedulerPolicy := output.GetOrCreateSchedulerPolicy() schedulerPolicy.SetName(tc.scheduler) diff --git a/feature/qos/tests/qos_policy_config_test/README.md b/feature/qos/tests/qos_policy_config_test/README.md index 2a556389500..949e9c4d835 100644 --- a/feature/qos/tests/qos_policy_config_test/README.md +++ b/feature/qos/tests/qos_policy_config_test/README.md @@ -172,3 +172,59 @@ Verify QoS policy feature configuration. * /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 + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. + +```yaml +paths: + ## Config paths: + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: + /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: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /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: + + ## State paths: + /qos/forwarding-groups/forwarding-group/state/name: + /qos/forwarding-groups/forwarding-group/state/output-queue: + /qos/queues/queue/state/name: + /qos/classifiers/classifier/state/name: + /qos/classifiers/classifier/state/type: + /qos/classifiers/classifier/terms/term/actions/state/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set: + /qos/classifiers/classifier/terms/term/state/id: + /qos/interfaces/interface/output/queues/queue/state/name: + /qos/interfaces/interface/input/classifiers/classifier/state/name: + /qos/interfaces/interface/output/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/state/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight: + +rpcs: + gnmi: + gNMI.Set: + Replace: \ No newline at end of file diff --git a/feature/qos/tests/qos_policy_config_test/metadata.textproto b/feature/qos/tests/qos_policy_config_test/metadata.textproto index 2933884e6da..bb92f5f7756 100644 --- a/feature/qos/tests/qos_policy_config_test/metadata.textproto +++ b/feature/qos/tests/qos_policy_config_test/metadata.textproto @@ -9,9 +9,4 @@ platform_exceptions: { platform: { vendor: JUNIPER } - deviations: { - state_path_unsupported: true - drop_weight_leaves_unsupported: true - explicit_interface_ref_definition: true - } } 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 b43b04be089..267cb0bddee 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 @@ -20,7 +20,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/featureprofiles/internal/qoscfg" "github.com/openconfig/ondatra" @@ -601,8 +600,8 @@ func testECNConfig(t *testing.T) { ecnEnabled: true, dropEnabled: false, minThreshold: uint64(80000), - maxThreshold: math.MaxUint32, - maxDropProbabilityPercent: uint8(1), + maxThreshold: uint64(80001), + maxDropProbabilityPercent: uint8(100), weight: uint32(0), } @@ -620,9 +619,6 @@ func testECNConfig(t *testing.T) { t.Logf("qos ECN QueueManagementProfile config cases: %v", ecnConfig) gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) - // TODO: Remove the following t.Skipf() after the config verification code has been tested. - t.Skipf("Skip the QoS config verification until it is tested against a DUT.") - // Verify the QueueManagementProfile is applied by checking the telemetry path state values. wredUniform := gnmi.OC().Qos().QueueManagementProfile("DropProfile").Wred().Uniform() if got, want := gnmi.Get(t, dut, wredUniform.EnableEcn().State()), ecnConfig.ecnEnabled; got != want { @@ -1440,13 +1436,11 @@ func testJuniperClassifierConfig(t *testing.T) { if got, want := gnmi.Get(t, dut, classifier.Type().State()), tc.classType; got != want { t.Errorf("classifier.Type().State(): got %v, want %v", got, want) } - if !deviations.StatePathsUnsupported(dut) { - if got, want := gnmi.Get(t, dut, term.Id().State()), tc.termID; got != want { - t.Errorf("term.Id().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, action.TargetGroup().State()), tc.targetGroup; got != want { - t.Errorf("action.TargetGroup().State(): got %v, want %v", got, want) - } + if got, want := gnmi.Get(t, dut, term.Id().State()), tc.termID; got != want { + t.Errorf("term.Id().State(): got %v, want %v", got, want) + } + if got, want := gnmi.Get(t, dut, action.TargetGroup().State()), tc.targetGroup; got != want { + t.Errorf("action.TargetGroup().State(): got %v, want %v", got, want) // This Transformer sorts a []uint8. trans := cmp.Transformer("Sort", func(in []uint8) []uint8 { @@ -1486,28 +1480,27 @@ func testJuniperClassifierConfig(t *testing.T) { targetGroup: "target-group-BE1", queueName: "0", }} - if !deviations.StatePathsUnsupported(dut) { - cases = append(cases, - struct { - desc string - inputClassifierType oc.E_Input_Classifier_Type - classifier string - classType oc.E_Qos_Classifier_Type - termID string - dscpSet []uint8 - targetGroup string - queueName string - }{ - desc: "Input Classifier Type IPV6", - inputClassifierType: oc.Input_Classifier_Type_IPV6, - classifier: "dscp_based_classifier_ipv6", - classType: oc.Qos_Classifier_Type_IPV6, - termID: "0", - targetGroup: "target-group-BE1", - dscpSet: []uint8{0, 1, 2, 3}, - queueName: "0", - }) - } + cases = append(cases, + struct { + desc string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + classType oc.E_Qos_Classifier_Type + termID string + dscpSet []uint8 + targetGroup string + queueName string + }{ + desc: "Input Classifier Type IPV6", + inputClassifierType: oc.Input_Classifier_Type_IPV6, + classifier: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "0", + targetGroup: "target-group-BE1", + dscpSet: []uint8{0, 1, 2, 3}, + queueName: "0", + }) + dp := dut.Port(t, "port1") ip := &oc.Interface{Name: ygot.String(dp.Name())} ip.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd @@ -1637,22 +1630,22 @@ func testJuniperSchedulerPoliciesConfig(t *testing.T) { scheduler := gnmi.OC().Qos().SchedulerPolicy("scheduler").Scheduler(tc.sequence) input := scheduler.Input(tc.inputID) - if !deviations.StatePathsUnsupported(dut) { - if got, want := gnmi.Get(t, dut, input.Id().State()), tc.inputID; got != want { - t.Errorf("input.Id().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, input.InputType().State()), oc.Input_InputType_QUEUE; got != want { - t.Errorf("input.InputType().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, input.Weight().State()), tc.weight; got != want { - t.Errorf("input.Weight().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, input.Queue().State()), tc.queueName; got != want { - t.Errorf("input.Queue().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, scheduler.Sequence().State()), tc.sequence; got != want { - t.Errorf("scheduler.Sequence().State(): got %v, want %v", got, want) - } + if got, want := gnmi.Get(t, dut, input.Id().State()), tc.inputID; got != want { + t.Errorf("input.Id().State(): got %v, want %v", got, want) + } + if got, want := gnmi.Get(t, dut, input.InputType().State()), oc.Input_InputType_QUEUE; got != want { + t.Errorf("input.InputType().State(): got %v, want %v", got, want) + } + if got, want := gnmi.Get(t, dut, input.Weight().State()), tc.weight; got != want { + t.Errorf("input.Weight().State(): got %v, want %v", got, want) + } + if got, want := gnmi.Get(t, dut, input.Queue().State()), tc.queueName; got != want { + t.Errorf("input.Queue().State(): got %v, want %v", got, want) + } + if got, want := gnmi.Get(t, dut, scheduler.Sequence().State()), tc.sequence; got != want { + t.Errorf("scheduler.Sequence().State(): got %v, want %v", got, want) + } + if tc.priority == oc.Scheduler_Priority_STRICT { if got, want := gnmi.Get(t, dut, scheduler.Priority().State()), tc.priority; got != want { t.Errorf("scheduler.Priority().State(): got %v, want %v", got, want) } @@ -1693,25 +1686,21 @@ func testJuniperSchedulerPoliciesConfig(t *testing.T) { if got, want := gnmi.Get(t, dut, wredUniform.MaxDropProbabilityPercent().State()), ecnConfig.maxDropProbabilityPercent; got != want { t.Errorf("wredUniform.MaxDropProbabilityPercent().State(): got %v, want %v", got, want) } - if !deviations.StatePathsUnsupported(dut) { - if got, want := gnmi.Get(t, dut, wredUniform.MinThreshold().State()), ecnConfig.minThreshold; got != want { - t.Errorf("wredUniform.MinThreshold().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, wredUniform.MaxThreshold().State()), ecnConfig.maxThreshold; got != want { - t.Errorf("wredUniform.MaxThreshold().State(): got %v, want %v", got, want) - } + if got, want := gnmi.Get(t, dut, wredUniform.MinThreshold().State()), ecnConfig.minThreshold; got != want { + t.Errorf("wredUniform.MinThreshold().State(): got %v, want %v", got, want) } - if !deviations.DropWeightLeavesUnsupported(dut) { - uniform.SetDrop(ecnConfig.dropEnabled) - uniform.SetWeight(ecnConfig.weight) - gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + if got, want := gnmi.Get(t, dut, wredUniform.MaxThreshold().State()), ecnConfig.maxThreshold; got != want { + t.Errorf("wredUniform.MaxThreshold().State(): got %v, want %v", got, want) + } + uniform.SetDrop(ecnConfig.dropEnabled) + uniform.SetWeight(ecnConfig.weight) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) - if got, want := gnmi.Get(t, dut, wredUniform.Drop().State()), ecnConfig.dropEnabled; got != want { - t.Errorf("wredUniform.Drop().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, wredUniform.Weight().State()), ecnConfig.weight; got != want { - t.Errorf("wredUniform.Weight().State(): got %v, want %v", got, want) - } + if got, want := gnmi.Get(t, dut, wredUniform.Drop().State()), ecnConfig.dropEnabled; got != want { + t.Errorf("wredUniform.Drop().State(): got %v, want %v", got, want) + } + if got, want := gnmi.Get(t, dut, wredUniform.Weight().State()), ecnConfig.weight; got != want { + t.Errorf("wredUniform.Weight().State(): got %v, want %v", got, want) } cases := []struct { @@ -1755,16 +1744,14 @@ func testJuniperSchedulerPoliciesConfig(t *testing.T) { // Verify the policy is applied by checking the telemetry path state values. policy := gnmi.OC().Qos().Interface(dp.Name()).Output().SchedulerPolicy() outQueue := gnmi.OC().Qos().Interface(dp.Name()).Output().Queue(tc.targetGroup) - if !deviations.StatePathsUnsupported(dut) { - if got, want := gnmi.Get(t, dut, policy.Name().State()), "scheduler"; got != want { - t.Errorf("policy.Name().State(): got %v, want %v", got, want) - } - if got, want := gnmi.Get(t, dut, outQueue.Name().State()), tc.targetGroup; 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 { - t.Errorf("outQueue.QueueManagementProfile().State(): got %v, want %v", got, want) - } + if got, want := gnmi.Get(t, dut, policy.Name().State()), "scheduler"; got != want { + t.Errorf("policy.Name().State(): got %v, want %v", got, want) + } + if got, want := gnmi.Get(t, dut, outQueue.Name().State()), tc.targetGroup; 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 { + 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 { t.Errorf("wredUniform.EnableEcn().State(): got %v, want %v", got, want) diff --git a/feature/experimental/replay/tests/diff_command_trees/README.md b/feature/replay/tests/diff_command_trees/README.md similarity index 62% rename from feature/experimental/replay/tests/diff_command_trees/README.md rename to feature/replay/tests/diff_command_trees/README.md index 91720b7d422..61ddc439b53 100644 --- a/feature/experimental/replay/tests/diff_command_trees/README.md +++ b/feature/replay/tests/diff_command_trees/README.md @@ -7,3 +7,17 @@ diff the command trees" error when applying certain gNMI config on Arista devices. At this time, no vendor is expected to run this test. + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/experimental/replay/tests/diff_command_trees/diff_command_trees_test.go b/feature/replay/tests/diff_command_trees/diff_command_trees_test.go similarity index 93% rename from feature/experimental/replay/tests/diff_command_trees/diff_command_trees_test.go rename to feature/replay/tests/diff_command_trees/diff_command_trees_test.go index d27a86e6200..113fb4a7005 100644 --- a/feature/experimental/replay/tests/diff_command_trees/diff_command_trees_test.go +++ b/feature/replay/tests/diff_command_trees/diff_command_trees_test.go @@ -29,9 +29,9 @@ func TestMain(m *testing.M) { } func TestReplay(t *testing.T) { - const logFile = "grpclog.pb" + const logFile = "https://storage.googleapis.com/featureprofiles-binarylogs/diff_command_trees.pb" t.Logf("Parsing log file: %v", logFile) - rec := replayer.ParseFile(t, logFile) + rec := replayer.ParseURL(t, logFile) dut := ondatra.DUT(t, "dut") portMap := map[string]string{} @@ -56,7 +56,7 @@ func TestReplay(t *testing.T) { } t.Logf("Creating gRPC clients to dut") - clients := &replayer.Clients{ + clients := &replayer.Config{ GNMI: dut.RawAPIs().GNMI(t), GRIBI: dut.RawAPIs().GRIBI(t), } diff --git a/feature/experimental/replay/tests/diff_command_trees/metadata.textproto b/feature/replay/tests/diff_command_trees/metadata.textproto similarity index 100% rename from feature/experimental/replay/tests/diff_command_trees/metadata.textproto rename to feature/replay/tests/diff_command_trees/metadata.textproto diff --git a/feature/replay/tests/p4rt_replay/README.md b/feature/replay/tests/p4rt_replay/README.md new file mode 100644 index 00000000000..f23fe835e7e --- /dev/null +++ b/feature/replay/tests/p4rt_replay/README.md @@ -0,0 +1,22 @@ +# Replay-1.2: P4RT Replay Test + +## Summary + +This is an example record/replay test. It is meant to reproduce an error when +replaying P4RT messages. + +At this time, no vendor is expected to run this test. + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/replay/tests/p4rt_replay/metadata.textproto b/feature/replay/tests/p4rt_replay/metadata.textproto new file mode 100644 index 00000000000..dbd5e2a32aa --- /dev/null +++ b/feature/replay/tests/p4rt_replay/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "65492a13-cccd-461a-9fc4-f47f1d45fa1b" +plan_id: "Replay-1.2" +description: "P4RT Replay Test" +testbed: TESTBED_DUT diff --git a/feature/replay/tests/p4rt_replay/p4rt_replay_test.go b/feature/replay/tests/p4rt_replay/p4rt_replay_test.go new file mode 100644 index 00000000000..08d02233041 --- /dev/null +++ b/feature/replay/tests/p4rt_replay/p4rt_replay_test.go @@ -0,0 +1,118 @@ +// Copyright 2024 Google Inc. All Rights Reserved. +// +// 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 p4rt_replay_test + +import ( + "context" + "flag" + "testing" + + "github.com/cisco-open/go-p4/utils" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + gpb "github.com/openconfig/gribi/v1/proto/service" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/replayer" + "github.com/openconfig/ygot/ygot" +) + +// Flag variable definitions +var ( + p4InfoFile = flag.String("p4info_file_location", "../../../p4rt/wbb.p4info.pb.txt", "Path to the p4info file.") +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// configureDeviceID configures p4rt device-id on the DUT. +func configureDeviceID(t *testing.T, dut *ondatra.DUTDevice) { + nodes := p4rtutils.P4RTNodesByPort(t, dut) + p4rtNode, ok := nodes["port1"] + if !ok { + t.Fatal("Couldn't find P4RT Node for port: port1") + } + c := oc.Component{} + c.Name = ygot.String(p4rtNode) + c.IntegratedCircuit = &oc.Component_IntegratedCircuit{} + c.IntegratedCircuit.NodeId = ygot.Uint64(1) + t.Logf("Configuring P4RT Node %s", p4rtNode) + gnmi.Replace(t, dut, gnmi.OC().Component(p4rtNode).Config(), &c) +} + +func TestReplay(t *testing.T) { + const logFile = "https://storage.googleapis.com/featureprofiles-binarylogs/p4rt_replay.pb" + t.Logf("Parsing log file: %v", logFile) + rec := replayer.ParseURL(t, logFile) + + dut := ondatra.DUT(t, "dut") + portMap := map[string]string{} + intfs, err := rec.Interfaces() + if err != nil { + t.Fatalf("Interfaces(): cannot get interfaces: %v", err) + } + + // This test only needs Port-Channel4, so remap only those ports to the dut's reserved ports. + available := dut.Ports() + t.Logf("PortChannel4: %v", intfs["Port-Channel4"]) + for _, member := range intfs["Port-Channel4"] { + if len(available) == 0 { + t.Fatalf("Ports(): not enough ports to satisfy Port-Channel4 remapping, members: %v", intfs["Port-Channel4"]) + } + portMap[member.Name] = available[0].Name() + available = available[1:] + } + + if err := rec.SetInterfaceMap(portMap); err != nil { + t.Fatalf("Transform(%v): cannot transform log: %v", portMap, err) + } + + configureDeviceID(t, dut) + + p4Info, err := utils.P4InfoLoad(p4InfoFile) + if err != nil { + t.Fatalf("wbbp4info.Get(): failed to get P4 Info file: %v", err) + } + + t.Logf("Creating gRPC clients to dut") + cfg := &replayer.Config{ + GNMI: dut.RawAPIs().GNMI(t), + GRIBI: dut.RawAPIs().GRIBI(t), + P4RT: dut.RawAPIs().P4RT(t), + P4Info: p4Info, + } + + t.Logf("Replaying parsed log to device %v", dut.Name()) + ctx := context.Background() + results := replayer.Replay(ctx, t, rec, cfg) + + // Validate that all gRIBI requests were programmed successfully. + for _, result := range results.GRIBI() { + if result.OperationID > 0 && result.ProgrammingResult != gpb.AFTResult_FIB_PROGRAMMED && result.ProgrammingResult != gpb.AFTResult_RIB_PROGRAMMED { + t.Errorf("Replay(): gRIBI Result failed: %v", result) + } + } + + // Validate that resulting gRIBI state matches the recorded one. + if diff := replayer.GRIBIDiff(rec, results); diff != "" { + t.Errorf("Replay(): unexpected diff in final gRIBI state (-want,+got): %v", diff) + } + + for i, result := range results.GNMI() { + t.Logf("Result [%v]: %v", i, result) + } +} diff --git a/feature/replay/tests/presession_test/README.md b/feature/replay/tests/presession_test/README.md new file mode 100644 index 00000000000..f1528f687b0 --- /dev/null +++ b/feature/replay/tests/presession_test/README.md @@ -0,0 +1,20 @@ +# Replay-1.0: Record/replay presession test + +## Summary + +This is an example record/replay test. +At this time, no vendor is expected to run this test. + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/experimental/replay/tests/presession_test/metadata.textproto b/feature/replay/tests/presession_test/metadata.textproto similarity index 100% rename from feature/experimental/replay/tests/presession_test/metadata.textproto rename to feature/replay/tests/presession_test/metadata.textproto diff --git a/feature/experimental/replay/tests/presession_test/presession_test.go b/feature/replay/tests/presession_test/presession_test.go similarity index 94% rename from feature/experimental/replay/tests/presession_test/presession_test.go rename to feature/replay/tests/presession_test/presession_test.go index 9ed9dea0cea..941ed0774e3 100644 --- a/feature/experimental/replay/tests/presession_test/presession_test.go +++ b/feature/replay/tests/presession_test/presession_test.go @@ -29,9 +29,9 @@ func TestMain(m *testing.M) { } func TestReplay(t *testing.T) { - const logFile = "grpclog.pb" + const logFile = "https://storage.googleapis.com/featureprofiles-binarylogs/presession_test.pb" t.Logf("Parsing log file: %v", logFile) - rec := replayer.ParseFile(t, logFile) + rec := replayer.ParseURL(t, logFile) dut := ondatra.DUT(t, "dut") portMap := map[string]string{} @@ -56,7 +56,7 @@ func TestReplay(t *testing.T) { } t.Logf("Creating gRPC clients to dut") - clients := &replayer.Clients{ + clients := &replayer.Config{ GNMI: dut.RawAPIs().GNMI(t), GRIBI: dut.RawAPIs().GRIBI(t), } diff --git a/feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/README.md b/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/README.md similarity index 91% rename from feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/README.md rename to feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/README.md index 4515b12bb87..f1e22c2734e 100644 --- a/feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/README.md +++ b/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/README.md @@ -25,17 +25,13 @@ and gRPC connections. * Ensure gNMI set/get requests are denied with incorrect login or incorrect password. -## Config Parameter coverage - -N/A - -## Telemetry Parameter coverage - -N/A - -## Protocol/RPC Parameter coverage - -N/A +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` ## Minimum DUT platform requirement diff --git a/feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/metadata.textproto b/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/metadata.textproto similarity index 100% rename from feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/metadata.textproto rename to feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/metadata.textproto diff --git a/feature/experimental/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 similarity index 92% rename from feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go rename to feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go index 1015e0454b6..8581b9ed14c 100644 --- a/feature/experimental/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 @@ -37,10 +37,6 @@ import ( tpb "github.com/openconfig/kne/proto/topo" ) -const ( - sshPort = 22 -) - func TestMain(m *testing.M) { fptest.RunTests(m) } @@ -54,26 +50,16 @@ func keyboardInteraction(password string) ssh.KeyboardInteractiveChallenge { } } -func gnmiClient(ctx context.Context, sshIP string, dut *ondatra.DUTDevice) (gpb.GNMIClient, error) { - // TODO(greg-dennis): Remove hard-coded gNMI port. - var gnmiPort int - switch dut.Vendor() { - case ondatra.ARISTA: - gnmiPort = 6030 - default: - gnmiPort = 9339 - } - - conn, err := grpc.DialContext( - ctx, - fmt.Sprintf("%s:%d", sshIP, gnmiPort), +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.DialContext => unexpected failure dialing GNMI (should not require auth): %w", err) + return nil, fmt.Errorf("grpc.NewClient => unexpected failure dialing GNMI (should not require auth): %w", err) } return gpb.NewGNMIClient(conn), nil } @@ -189,7 +175,13 @@ func TestAuthentication(t *testing.T) { if err != nil { t.Fatal(err) } - sshIP := sshService.GetOutsideIp() + 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") @@ -224,7 +216,7 @@ 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", fmt.Sprintf("%s:%d", sshIP, sshPort), &ssh.ClientConfig{ + sshClient, err := ssh.Dial("tcp", sshAddr, &ssh.ClientConfig{ User: tc.user, Auth: []ssh.AuthMethod{ ssh.KeyboardInteractive(keyboardInteraction(tc.pass)), @@ -247,7 +239,7 @@ func TestAuthentication(t *testing.T) { context.Background(), "username", tc.user, "password", tc.pass) - gnmi, err := gnmiClient(ctx, sshIP, dut) + gnmi, err := gnmiClient(dut, gnmiAddr) if err != nil { t.Fatal(err) } diff --git a/feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md b/feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md index 575d9fc8f96..bf0d49cfce4 100644 --- a/feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md +++ b/feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md @@ -35,21 +35,22 @@ for multi-transaction logins. For example, unreachable TACACS+ server(s). - all other fields should be omitted. - task_ids might be populate with platform-specific information - -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe - -### Parameter: -RecordRequest.timestamp!=0 -Record.service_request = CommandService - -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX - -## Protocol/RPC -gnsi.acctz.v1 +## 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 +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp!=0": true + "RecordResponse.service_request = CommandService": true +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md b/feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md index 2b6ea8b2c56..3ca077975e6 100644 --- a/feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md +++ b/feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md @@ -34,20 +34,22 @@ Test Accounting for authentication failures of multi-transaction logins - all other fields should be omitted. - task_ids might be populate with platform-specific information -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe - -### Parameter: -RecordRequest.timestamp!=0 -Record.service_request = CommandService - -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX - -## Protocol/RPC -gnsi.acctz.v1 +## 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 +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp!=0": true + "RecordResponse.service_request = CommandService": true +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md b/feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md index aa655d964c8..490e690223f 100644 --- a/feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md +++ b/feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md @@ -34,20 +34,22 @@ Test Accounting for authentication failures of uni-transaction logins - all other fields should be omitted. - task_ids might be populate with platform-specific information -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe - -### Parameter: -RecordRequest.timestamp!=0 -Record.service_request = CommandService - -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX - -## Protocol/RPC -gnsi.acctz.v1 +## 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 +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp!=0": true + "RecordResponse.service_request = CommandService": true +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/AccountingPrivEscalation/README.md b/feature/security/gnsi/acctz/AccountingPrivEscalation/README.md index 948b3fca7ab..80795e029df 100644 --- a/feature/security/gnsi/acctz/AccountingPrivEscalation/README.md +++ b/feature/security/gnsi/acctz/AccountingPrivEscalation/README.md @@ -34,20 +34,22 @@ Test Accounting for changing current privilege level, if supported. - all other fields should be omitted. - task_ids might be populate with platform-specific information -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe - -### Parameter: -RecordRequest.timestamp!=0 -Record.service_request = CommandService - -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX - -## Protocol/RPC -gnsi.acctz.v1 +## 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 +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp!=0": true + "RecordResponse.service_request = CommandService": true +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/README.md b/feature/security/gnsi/acctz/README.md index 70f50420b0f..4286a891888 100644 --- a/feature/security/gnsi/acctz/README.md +++ b/feature/security/gnsi/acctz/README.md @@ -33,10 +33,11 @@ Create a library of device configuration to be used for all of the gNSI.acctz.v1 - A User permitted to run some commands in each of the service classes of gnsi.acctz.v1.CommandService.CmdServiceType & gnsi.acctz.v1.GrpcService.GrpcServiceType, but not all ## gNSI Accounting (acctz) Tests: -- [ACCTZ-1.1 Record Subscribe Full](RecordSubscribeFull) -- [ACCTZ-2.1 Record Subscribe Partial](RecordSubscribePartial) -- [ACCTZ-3.1 Record Subscribe Non-gRPC](RecordSubscribeNongrpc) -- [ACCTZ-4.1 Record Response Truncation](RecordResponseTruncation/) +- [ACCTZ-1.1 Record Subscribe Full](tests/record_subscribe_full) +- [ACCTZ-2.1 Record Subscribe Partial](tests/record_subscribe_partial) +- [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/) diff --git a/feature/security/gnsi/acctz/RecordResponseTruncation/README.md b/feature/security/gnsi/acctz/RecordResponseTruncation/README.md deleted file mode 100644 index 4f038a23c99..00000000000 --- a/feature/security/gnsi/acctz/RecordResponseTruncation/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# ACCTZ-4.1 - gNSI.acctz.v1 (Accounting) Test Record Respponse Truncation - -## Summary -Test Record Response Truncation boolean is set - -## Procedure -- For an supported service class in gnsi.acctz.v1.CommandService.CmdServiceType: - - Run a few commands - - disconnect -- Establish gNSI connection to the DUT. -- Call gnsi.acctz.v1.Acctz.RecordSubscribe with RecordRequest.timestamp = (openconfig-system.system-global-state.boot-time - 24 hours) -- Verify that RecordResponse.history_istruncated = true. It should be true because there should be no records in the history equal to nor pre-dating this RecordRequest.timestamp. - -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe - -### Parameter: -RecordRequest.timestamp!=0 -RecordResponse.service_request = CommandService - -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX - -## Protocol/RPC -gnsi.acctz.v1 - -## Minimum DUT -vRX diff --git a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md b/feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md index c5d28e0aafb..b7775f06814 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md +++ b/feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md @@ -14,14 +14,18 @@ Test RecordSubscribe connection termination after idle timeout following 1 Recor - Wait at least longer than the idletimeout period - Verify that the DUT closes the gNSI connection at or shortly after the idletimeout period. -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe +## OpenConfig Path and RPC Coverage -### Parameter: +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. -## Telemetry Coverage -gnsi.acctz.v1 +TODO(OCRPC): Record may not be complete + +```yaml +paths: +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md b/feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md index 5e537748439..df8d552e8ad 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md +++ b/feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md @@ -10,14 +10,18 @@ Test RecordSubscribe connection termination after idle timeout without making Re - Wait at least longer than the idletimeout period (default: 120s) - Verify that the DUT closes the gNSI connection at or shortly after the idletimeout period. -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe +## OpenConfig Path and RPC Coverage -### Parameter: +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. -## Telemetry Coverage -gnsi.acctz.v1 +TODO(OCRPC): Record may not be complete + +```yaml +paths: +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/feature.textproto b/feature/security/gnsi/acctz/feature.textproto index 15e0ef02b2b..76c8f93c5c5 100644 --- a/feature/security/gnsi/acctz/feature.textproto +++ b/feature/security/gnsi/acctz/feature.textproto @@ -1,3 +1,6 @@ +# 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/acctz/tests/record_history_truncation/README.md b/feature/security/gnsi/acctz/tests/record_history_truncation/README.md new file mode 100644 index 00000000000..1f4436d776c --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_history_truncation/README.md @@ -0,0 +1,32 @@ +# ACCTZ-4.1: Record History Truncation + +## Summary +Test Record Response Truncation boolean is set + +## Procedure +- For an supported service class in gnsi.acctz.v1.CommandService.CmdServiceType: + - Run a few commands + - disconnect +- Establish gNSI connection to the DUT. +- Call gnsi.acctz.v1.Acctz.RecordSubscribe with RecordRequest.timestamp = (openconfig-system.system-global-state.boot-time - 24 hours) +- Verify that RecordResponse.history_istruncated = true. It should be true because there should be no records in the history equal to nor pre-dating this RecordRequest.timestamp. + +## 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 +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp!=0": true + "RecordResponse.service_request = CommandService": true +``` + +## Minimum DUT +vRX diff --git a/feature/security/gnsi/acctz/tests/record_history_truncation/metadata.textproto b/feature/security/gnsi/acctz/tests/record_history_truncation/metadata.textproto new file mode 100644 index 00000000000..d50184e352c --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_history_truncation/metadata.textproto @@ -0,0 +1,8 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "3ac6b788-687a-41c7-9324-0afb8d789e15" +plan_id: "ACCTZ-4.1" +description: "Record History Truncation" +testbed: TESTBED_DUT +platform_exceptions: {} diff --git a/feature/security/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go b/feature/security/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go new file mode 100644 index 00000000000..1c14c9ef1ab --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go @@ -0,0 +1,65 @@ +// 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 record_history_truncation_test + +import ( + "context" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/fptest" + acctzpb "github.com/openconfig/gnsi/acctz" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestAccountzRecordHistoryTruncation(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + systemState := gnmi.Get(t, dut, gnmi.OC().System().State()) + + bootTime := systemState.GetBootTime() + + // 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{ + Timestamp: timestamppb.New(recordStartTime), + }) + if err != nil { + t.Fatalf("Failed sending record request, error: %s", err) + } + + record, err := acctzSubClient.Recv() + if err != nil { + t.Fatalf("Failed receiving from accountz record subscribe client, error: %s", err) + } + + if record.GetHistoryIstruncated() != true { + t.Fatal("History is not truncated but should be.") + } +} diff --git a/feature/security/gnsi/acctz/tests/record_payload_truncation/README.md b/feature/security/gnsi/acctz/tests/record_payload_truncation/README.md new file mode 100644 index 00000000000..fe10d942750 --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_payload_truncation/README.md @@ -0,0 +1,31 @@ +# ACCTZ-4.2: Record Payload Truncation + +## Summary + +Test how large payload is handled. + +## Procedure + +1. Call a method with a payload that will exceed the maximum payload supported by the implementation for `CommandService.{cmd,cmd_args}` or`GrpcService.payloads`` or both, such as adding a large number of static routes. If the implementation supports configuration of this limit, it may be configured to artificially reduce the limit for easier testing. +2. Establish gNSI connection to the DUT. + 1. Call `gnsi.acctz.v1.Acctz.RecordSubscribe` with `RecordRequest.timestamp = T1`. T1 should be timestamp that covers the above gNMI SET action. + 2. Verify that The appropriate boolean should be set; one of `CommandService.{cmd_istruncated,cmd_args_istruncated}` or `GrpcService.payload_istruncated`. + 3. If an RPC, the contents of the payload field(s) is structured and must remain syntactically parsable. + +## 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 +paths: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: +``` + +## Minimum DUT + +vRX diff --git a/feature/security/gnsi/acctz/tests/record_payload_truncation/metadata.textproto b/feature/security/gnsi/acctz/tests/record_payload_truncation/metadata.textproto new file mode 100644 index 00000000000..714851304ee --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_payload_truncation/metadata.textproto @@ -0,0 +1,8 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ae09edfd-f64e-4095-b56d-021ce29b659b" +plan_id: "ACCTZ-4.2" +description: "Record Payload Truncation" +testbed: TESTBED_DUT +platform_exceptions: {} diff --git a/feature/security/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go b/feature/security/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go new file mode 100644 index 00000000000..ae532753f6b --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go @@ -0,0 +1,122 @@ +// 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 record_payload_truncation_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/fptest" + acctzpb "github.com/openconfig/gnsi/acctz" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type recordRequestResult struct { + record *acctzpb.RecordResponse + err error +} + +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. + ocRoot := &oc.Root{} + + for i := 0; i < 50; i++ { + 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") + for j := 0; j < 254; j++ { + staticProtocol.GetOrCreateStatic(fmt.Sprintf("10.%d.0.0/24", j)) + } + } + + 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) + } + + err = acctzSubClient.Send(&acctzpb.RecordRequest{ + Timestamp: timestamppb.New(startTime), + }) + if err != nil { + t.Fatalf("Failed sending record request, error: %s", err) + } + + for { + r := make(chan recordRequestResult) + + go func(r chan recordRequestResult) { + var response *acctzpb.RecordResponse + response, err = acctzSubClient.Recv() + 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): + done = true + } + + if done { + t.Fatal("Done receiving records and did not find our record...") + } + + if resp.err != nil { + t.Fatalf("Failed receiving record response, error: %s", resp.err) + } + + grpcServiceRecord := resp.record.GetGrpcService() + + if grpcServiceRecord.GetServiceType() != acctzpb.GrpcService_GRPC_SERVICE_TYPE_GNMI { + // Not our gnmi set, nothing to see here. + continue + } + + if grpcServiceRecord.RpcName != "/gnmi.gNMI/Set" { + continue + } + + if grpcServiceRecord.GetPayloadIstruncated() { + t.Log("Found truncated payload of gnmi.Set after start timestamp, success!") + break + } + } +} diff --git a/feature/security/gnsi/acctz/RecordSubscribeFull/README.md b/feature/security/gnsi/acctz/tests/record_subscribe_full/README.md similarity index 84% rename from feature/security/gnsi/acctz/RecordSubscribeFull/README.md rename to feature/security/gnsi/acctz/tests/record_subscribe_full/README.md index c0dea6911f7..56ead2cf8de 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeFull/README.md +++ b/feature/security/gnsi/acctz/tests/record_subscribe_full/README.md @@ -1,4 +1,4 @@ -# ACCTZ-1.1 - gNSI.acctz.v1 (Accounting) Test Record Subscribe Full +# ACCTZ-1.1: Record Subscribe Full ## Summary Test RecordSubscribe for all (since epoch) records @@ -37,20 +37,22 @@ Test RecordSubscribe for all (since epoch) records - task_ids might be populate with platform-specific information -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe +## OpenConfig Path and RPC Coverage -### Parameter: -RecordRequest.timestamp=0 -RecordResponse.service_request = GrpcService +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +TODO(OCRPC): Record may not be complete -## Protocol/RPC -gnsi.acctz.v1 +```yaml +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp=0": true + "RecordResponse.service_request = GrpcService": true +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_full/metadata.textproto b/feature/security/gnsi/acctz/tests/record_subscribe_full/metadata.textproto new file mode 100644 index 00000000000..9d42fb16744 --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_subscribe_full/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "c1352e24-e45c-4f6d-9b16-d28cc1a5093a" +plan_id: "ACCTZ-1.1" +description: "Record Subscribe Full" +testbed: TESTBED_DUT \ No newline at end of file diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_full/record_subscribe_full_test.go b/feature/security/gnsi/acctz/tests/record_subscribe_full/record_subscribe_full_test.go new file mode 100644 index 00000000000..4c6e329f8d2 --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_subscribe_full/record_subscribe_full_test.go @@ -0,0 +1,181 @@ +// 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 recordsubscribefull_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/security/acctz" + acctzpb "github.com/openconfig/gnsi/acctz" + "github.com/openconfig/ondatra" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type recordRequestResult struct { + record *acctzpb.RecordResponse + err error +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func prettyPrint(i any) string { + s, _ := json.MarshalIndent(i, "", "\t") + return string(s) +} + +func TestAccountzRecordSubscribeFull(t *testing.T) { + dut := ondatra.DUT(t, "dut") + acctz.SetupUsers(t, dut, false) + var records []*acctzpb.RecordResponse + + // Put enough time between the test starting and any prior events so we can easily know where + // our records start. + time.Sleep(5 * time.Second) + + startTime := time.Now() + newRecords := acctz.SendGnmiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendGnoiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendGnsiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendGribiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendP4rtRPCs(t, dut) + records = append(records, newRecords...) + + // Quick sleep to ensure all the records have been processed/ready for us. + time.Sleep(5 * time.Second) + + // Get gNSI record subscribe client. + requestTimestamp := ×tamppb.Timestamp{ + Seconds: 0, + Nanos: 0, + } + acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() + acctzSubClient, err := acctzClient.RecordSubscribe(context.Background(), &acctzpb.RecordRequest{Timestamp: requestTimestamp}) + if err != nil { + t.Fatalf("Failed sending accountz record request, error: %s", err) + } + defer acctzSubClient.CloseSend() + + var recordIdx int + var lastTimestampUnixMillis int64 + r := make(chan recordRequestResult) + + // Ignore proto fields which are set internally by the DUT (cannot be matched exactly) + // and compare them manually later. + popts := []cmp.Option{ + protocmp.Transform(), + protocmp.IgnoreFields(&acctzpb.RecordResponse{}, "timestamp", "task_ids"), + protocmp.IgnoreFields(&acctzpb.AuthzDetail{}, "detail"), + protocmp.IgnoreFields(&acctzpb.SessionInfo{}, "channel_id"), + } + + for { + if recordIdx >= len(records) { + t.Log("Out of records to process...") + break + } + + // Read single acctz record from stream into channel. + go func(r chan recordRequestResult) { + var response *acctzpb.RecordResponse + response, err = acctzSubClient.Recv() + r <- recordRequestResult{ + record: response, + err: err, + } + }(r) + + var done bool + var resp recordRequestResult + + // Read acctz record from channel for evaluation. + // Timeout and exit if no records received on the channel for some time. + select { + case rr := <-r: + resp = rr + case <-time.After(10 * time.Second): + done = true + } + + if done { + t.Log("Done receiving records...") + break + } + + if resp.err != nil { + t.Fatalf("Failed receiving record response, error: %s", resp.err) + } + + if resp.record.GetHistoryIstruncated() { + t.Errorf("History is truncated but it shouldn't be, Record Details: %s", prettyPrint(resp.record)) + } + + if !resp.record.Timestamp.AsTime().After(startTime) { + // Skipping record if it happened before test start time. + continue + } + + timestamp := resp.record.Timestamp.AsTime() + if timestamp.UnixMilli() == lastTimestampUnixMillis { + // This ensures that timestamps are actually changing for each record. + t.Errorf("Timestamp is the same as the previous timestamp, this shouldn't be possible!, Record Details: %s", prettyPrint(resp.record)) + } + lastTimestampUnixMillis = timestamp.UnixMilli() + + // Verify acctz proto bits. + if diff := cmp.Diff(resp.record, records[recordIdx], popts...); diff != "" { + t.Errorf("Got diff in got/want: %s", diff) + } + + // Verify record timestamp is after request timestamp. + if !timestamp.After(requestTimestamp.AsTime()) { + t.Errorf("Record timestamp is before record request timestamp %v, Record Details: %v", requestTimestamp.AsTime(), prettyPrint(resp.record)) + } + + // This channel check maybe should just go away entirely -- see: + // https://github.com/openconfig/gnsi/issues/98 + // In case of Nokia this is being set to the aaa session id just to have some hopefully + // useful info in this field to identify a "session" (even if it isn't necessarily ssh/grpc + // directly). + if resp.record.GetSessionInfo().GetChannelId() == "" { + t.Errorf("Channel Id is not populated for record: %v", prettyPrint(resp.record)) + } + + // Verify authz detail is populated for denied rpcs. + authzInfo := resp.record.GetGrpcService().GetAuthz() + if authzInfo.Status == acctzpb.AuthzDetail_AUTHZ_STATUS_DENY && authzInfo.GetDetail() == "" { + t.Errorf("Authorization detail is not populated for record: %v", prettyPrint(resp.record)) + } + + t.Logf("Processed Record: %s", prettyPrint(resp.record)) + recordIdx++ + } + + if recordIdx != len(records) { + t.Fatal("Did not process all records.") + } +} diff --git a/feature/security/gnsi/acctz/RecordSubscribeNongrpc/README.md b/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/README.md similarity index 82% rename from feature/security/gnsi/acctz/RecordSubscribeNongrpc/README.md rename to feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/README.md index bbd672b89f7..6ca2e7f8fc1 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeNongrpc/README.md +++ b/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/README.md @@ -1,4 +1,4 @@ -# ACCTZ-3.1 - gNSI.acctz.v1 (Accounting) Test Record Subscribe Non-gRPC +# ACCTZ-3.1: Record Subscribe Non-gRPC ## Summary Test Accounting for non-gRPC records @@ -6,7 +6,7 @@ Test Accounting for non-gRPC records ## Procedure - Record the time T0 for use later in this test - For each of the supported service classes in gnsi.acctz.v1.CommandService.CmdServiceType: - - Connect to a non-System-address on the DUT, recording the local and remote IP addresses and port numbers, + - Connect to the DUT, recording the local and remote IP addresses and port numbers, - Run a few commands that should be permitted and a few that should be denied. If command abbreviation is permitted, at least one command and its arguments should be abbreviated. - disconnect - Establish gNSI connection to the DUT. @@ -41,20 +41,24 @@ Test Accounting for non-gRPC records - If applicable to the service type, and session_info.stats != ONCE, ensure records for each connection are bracketed by LOGIN/LOGOUT records. -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe +## OpenConfig Path and RPC Coverage -### Parameter: -RecordRequest.timestamp!=0 -RecordResponse.service_request = CommandService +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +TODO(OCRPC): Record may not be complete + +```yaml +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp!=0": true + "RecordResponse.service_request = CommandService": true +``` -## Protocol/RPC -gnsi.acctz.v1 ## Minimum DUT vRX + diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/metadata.textproto b/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/metadata.textproto new file mode 100644 index 00000000000..a9a183ff323 --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "036d3d49-00dd-46ff-abe6-afc20768db6f" +plan_id: "ACCTZ-3.1" +description: "Record Subscribe Non-gRPC" +testbed: TESTBED_DUT \ No newline at end of file diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/record_subscribe_non_grpc_test.go b/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/record_subscribe_non_grpc_test.go new file mode 100644 index 00000000000..97812587dc9 --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/record_subscribe_non_grpc_test.go @@ -0,0 +1,186 @@ +// 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 recordsubscribenongrpc_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/security/acctz" + acctzpb "github.com/openconfig/gnsi/acctz" + "github.com/openconfig/ondatra" +) + +type recordRequestResult struct { + record *acctzpb.RecordResponse + err error +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func prettyPrint(i any) string { + s, _ := json.MarshalIndent(i, "", "\t") + return string(s) +} + +func TestAccountzRecordSubscribeNonGRPC(t *testing.T) { + dut := ondatra.DUT(t, "dut") + acctz.SetupUsers(t, dut, true) + var records []*acctzpb.RecordResponse + + // Put enough time between the test starting and any prior events so we can easily know where + // our records start. + time.Sleep(5 * time.Second) + + startTime := time.Now() + newRecords := acctz.SendSuccessCliCommand(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendFailCliCommand(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendShellCommand(t, dut) + records = append(records, newRecords...) + + // Quick sleep to ensure all the records have been processed/ready for us. + time.Sleep(5 * time.Second) + + // Get gNSI record subscribe client. + requestTimestamp := ×tamppb.Timestamp{ + Seconds: 0, + Nanos: 0, + } + acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() + acctzSubClient, err := acctzClient.RecordSubscribe(context.Background(), &acctzpb.RecordRequest{Timestamp: requestTimestamp}) + if err != nil { + t.Fatalf("Failed sending accountz record request, error: %s", err) + } + defer acctzSubClient.CloseSend() + + var recordIdx int + var lastTimestampUnixMillis int64 + var lastTaskID string + r := make(chan recordRequestResult) + + // Ignore proto fields which are set internally by the DUT (cannot be matched exactly) + // and compare them manually later. + popts := []cmp.Option{protocmp.Transform(), + protocmp.IgnoreFields(&acctzpb.RecordResponse{}, "timestamp", "task_ids"), + protocmp.IgnoreFields(&acctzpb.AuthzDetail{}, "detail"), + protocmp.IgnoreFields(&acctzpb.SessionInfo{}, "channel_id", "tty"), + } + + for { + if recordIdx >= len(records) { + t.Log("Out of records to process...") + break + } + + // Read single acctz record from stream into channel. + go func(r chan recordRequestResult) { + var response *acctzpb.RecordResponse + response, err = acctzSubClient.Recv() + r <- recordRequestResult{ + record: response, + err: err, + } + }(r) + + var done bool + var resp recordRequestResult + + // Read acctz record from channel for evaluation. + // Timeout and exit if no records received on the channel for some time. + select { + case rr := <-r: + resp = rr + case <-time.After(10 * time.Second): + done = true + } + + if done { + t.Log("Done receiving records...") + break + } + + if resp.err != nil { + t.Fatalf("Failed receiving record response, error: %s", resp.err) + } + + if !resp.record.Timestamp.AsTime().After(startTime) { + // Skipping record if it happened before test start time. + continue + } + + // Some task ids may be tracked multiple times (for start/stop accounting). If we see two in + // a row that are the same task, we can skip this record and continue. + currentTaskID := resp.record.TaskIds[0] + if currentTaskID == lastTaskID { + continue + } + lastTaskID = currentTaskID + + timestamp := resp.record.Timestamp.AsTime() + if timestamp.UnixMilli() == lastTimestampUnixMillis { + // This ensures that timestamps are actually changing for each record. + t.Errorf("Timestamp is the same as the previous timestamp, this shouldn't be possible!, Record Details: %s", prettyPrint(resp.record)) + } + lastTimestampUnixMillis = timestamp.UnixMilli() + + // Verify acctz proto bits. + if diff := cmp.Diff(resp.record, records[recordIdx], popts...); diff != "" { + t.Errorf("got diff in got/want: %s", diff) + } + + // Verify record timestamp is after request timestamp. + if !timestamp.After(requestTimestamp.AsTime()) { + t.Errorf("Record timestamp is before record request timestamp %v, Record Details: %v", requestTimestamp.AsTime(), prettyPrint(resp.record)) + } + + // This channel check maybe should just go away entirely -- see: + // https://github.com/openconfig/gnsi/issues/98 + // In case of Nokia this is being set to the aaa session id just to have some hopefully + // useful info in this field to identify a "session" (even if it isn't necessarily ssh/grpc + // directly). + if resp.record.GetSessionInfo().GetChannelId() == "" { + t.Errorf("Channel Id is not populated for record: %v", prettyPrint(resp.record)) + } + + // Tty only set for ssh records. + if resp.record.GetSessionInfo().GetTty() == "" { + t.Errorf("Should have tty allocated but not set, Record Details: %s", prettyPrint(resp.record)) + } + + // Verify authz detail is populated for denied cmds. + authzInfo := resp.record.GetCmdService().GetAuthz() + if authzInfo.Status == acctzpb.AuthzDetail_AUTHZ_STATUS_DENY && authzInfo.GetDetail() == "" { + t.Errorf("Authorization detail is not populated for record: %v", prettyPrint(resp.record)) + } + + t.Logf("Processed Record: %s", prettyPrint(resp.record)) + recordIdx++ + } + + if recordIdx != len(records) { + t.Fatal("Did not process all records.") + } +} diff --git a/feature/security/gnsi/acctz/RecordSubscribePartial/README.md b/feature/security/gnsi/acctz/tests/record_subscribe_partial/README.md similarity index 68% rename from feature/security/gnsi/acctz/RecordSubscribePartial/README.md rename to feature/security/gnsi/acctz/tests/record_subscribe_partial/README.md index ecfa9120c44..f7b38bf2cb2 100644 --- a/feature/security/gnsi/acctz/RecordSubscribePartial/README.md +++ b/feature/security/gnsi/acctz/tests/record_subscribe_partial/README.md @@ -1,4 +1,4 @@ -# ACCTZ-2.1 - gNSI.acctz.v1 (Accounting) Test Record Subscribe Partial +# ACCTZ-2.1: Record Subscribe Partial ## Summary Test RecordSubscribe for records since a non-zero timestamp @@ -12,20 +12,22 @@ Test RecordSubscribe for records since a non-zero timestamp - Call gnsi.acctz.v1.Acctz.RecordSubscribe with RecordRequest.timestamp = to the timestamp retained in the previous step. - Verify, as in the [ACCTZ-1.1 - Record Subscribe Full](../RecordSubscribeFull) test, that accurate accounting records are returned for the second and subsequent commands run in that test, and that a record is NOT returned for the first command (ie: with the same timestamp as in the request). -## Config Parameter -### Prefix: -/gnsi/acctz/v1/Acctz/RecordSubscribe +## OpenConfig Path and RPC Coverage -### Parameter: -RecordRequest.timestamp!=0 -RecordResponse.service_request = GrpcService +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. -## Telemetry Coverage -### Prefix: -Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +TODO(OCRPC): Record may not be complete -## Protocol/RPC -gnsi.acctz.v1 +```yaml +paths: + ### Prefix: + # Accounting does not currently support any telemetry; see https://github.com/openconfig/gnsi/issues/97 where it might become /system/aaa/acctz/XXX +rpcs: + gnsi: + acctz.v1.Acctz.RecordSubscribe: + "RecordRequest.timestamp!=0": true + "RecordResponse.service_request = GrpcService": true +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_partial/metadata.textproto b/feature/security/gnsi/acctz/tests/record_subscribe_partial/metadata.textproto new file mode 100644 index 00000000000..bb04d5949b7 --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_subscribe_partial/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "31dbda6b-5034-4a95-880c-25e4ce0676c4" +plan_id: "ACCTZ-2.1" +description: "Record Subscribe Partial" +testbed: TESTBED_DUT \ No newline at end of file diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_partial/record_subscribe_partial_test.go b/feature/security/gnsi/acctz/tests/record_subscribe_partial/record_subscribe_partial_test.go new file mode 100644 index 00000000000..136f2d3d7ad --- /dev/null +++ b/feature/security/gnsi/acctz/tests/record_subscribe_partial/record_subscribe_partial_test.go @@ -0,0 +1,201 @@ +// 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 recordsubscribepartial_test + +import ( + "context" + "encoding/json" + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/security/acctz" + acctzpb "github.com/openconfig/gnsi/acctz" + "github.com/openconfig/ondatra" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type recordRequestResult struct { + record *acctzpb.RecordResponse + err error +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func prettyPrint(i any) string { + s, _ := json.MarshalIndent(i, "", "\t") + return string(s) +} + +func TestAccountzRecordSubscribePartial(t *testing.T) { + dut := ondatra.DUT(t, "dut") + acctz.SetupUsers(t, dut, false) + var records []*acctzpb.RecordResponse + + // Put enough time between the test starting and any prior events so we can easily know where + // our records start. + time.Sleep(5 * time.Second) + + startTime := time.Now() + newRecords := acctz.SendGnmiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendGnoiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendGnsiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendGribiRPCs(t, dut) + records = append(records, newRecords...) + newRecords = acctz.SendP4rtRPCs(t, dut) + records = append(records, newRecords...) + + // Quick sleep to ensure all the records have been processed/ready for us. + time.Sleep(5 * time.Second) + + // Get gNSI record subscribe client. + acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() + acctzSubClient, err := acctzClient.RecordSubscribe(context.Background(), &acctzpb.RecordRequest{ + Timestamp: ×tamppb.Timestamp{ + Seconds: 0, + Nanos: 0, + }, + }) + if err != nil { + t.Fatalf("Failed sending first accountz record request, error: %s", err) + } + + firstResponse, err := acctzSubClient.Recv() + if err != nil { + t.Fatalf("Failed receiving first record response, error: %s", err) + } + + // Fetch fresh client. + acctzClient = dut.RawAPIs().GNSI(t).AcctzStream() + acctzSubClient, err = acctzClient.RecordSubscribe(context.Background(), &acctzpb.RecordRequest{ + Timestamp: firstResponse.Timestamp, + }) + if err != nil { + t.Fatalf("Failed sending second accountz record request, error: %s", err) + } + defer acctzSubClient.CloseSend() + + secondResponse, err := acctzSubClient.Recv() + if err != nil { + t.Fatalf("Failed receiving second record response, error: %s", err) + } + + if reflect.DeepEqual(firstResponse, secondResponse) { + t.Fatalf("Accountz server responded with same event on subsequent record request.") + } + + var recordIdx int + var lastTimestampUnixMillis int64 + r := make(chan recordRequestResult) + + // Ignore proto fields which are set internally by the DUT (cannot be matched exactly) + // and compare them manually later. + popts := []cmp.Option{protocmp.Transform(), + protocmp.IgnoreFields(&acctzpb.RecordResponse{}, "timestamp", "task_ids"), + protocmp.IgnoreFields(&acctzpb.AuthzDetail{}, "detail"), + protocmp.IgnoreFields(&acctzpb.SessionInfo{}, "channel_id"), + } + + for { + if recordIdx >= len(records) { + t.Log("Out of records to process...") + break + } + + // Read single acctz record from stream into channel. + go func(r chan recordRequestResult) { + var response *acctzpb.RecordResponse + response, err = acctzSubClient.Recv() + r <- recordRequestResult{ + record: response, + err: err, + } + }(r) + + var done bool + var resp recordRequestResult + + // Read acctz record from channel for evaluation. + // Timeout and exit if no records received on the channel for some time. + select { + case rr := <-r: + resp = rr + case <-time.After(10 * time.Second): + done = true + } + + if done { + t.Log("Done receiving records...") + break + } + + if resp.err != nil { + t.Fatalf("Failed receiving record response, error: %s", resp.err) + } + + if !resp.record.Timestamp.AsTime().After(startTime) { + // Skipping record, was before test start time. + continue + } + + timestamp := resp.record.Timestamp.AsTime() + if timestamp.UnixMilli() == lastTimestampUnixMillis { + // This ensures that timestamps are actually changing for each record. + t.Errorf("Timestamp is the same as the previous timestamp, this shouldn't be possible!, Record Details: %s", prettyPrint(resp.record)) + } + lastTimestampUnixMillis = timestamp.UnixMilli() + + // Verify acctz proto bits. + if diff := cmp.Diff(resp.record, records[recordIdx], popts...); diff != "" { + t.Errorf("got diff in got/want: %s", diff) + } + + // Verify record timestamp is after request timestamp. + if !timestamp.After(firstResponse.Timestamp.AsTime()) { + t.Errorf("Record timestamp is before record request timestamp %v, Record Details: %v", firstResponse.Timestamp.AsTime(), prettyPrint(resp.record)) + } + + // This channel check maybe should just go away entirely -- see: + // https://github.com/openconfig/gnsi/issues/98 + // In case of Nokia this is being set to the aaa session id just to have some hopefully + // useful info in this field to identify a "session" (even if it isn't necessarily ssh/grpc + // directly). + if resp.record.GetSessionInfo().GetChannelId() == "" { + t.Errorf("Channel Id is not populated for record: %v", prettyPrint(resp.record)) + } + + // Verify authz detail is populated for denied rpcs. + authzInfo := resp.record.GetGrpcService().GetAuthz() + if authzInfo.Status == acctzpb.AuthzDetail_AUTHZ_STATUS_DENY && authzInfo.GetDetail() == "" { + t.Errorf("Authorization detail is not populated for record: %v", prettyPrint(resp.record)) + } + + t.Logf("Processed Record: %s", prettyPrint(resp.record)) + recordIdx++ + } + + if recordIdx != len(records) { + t.Fatal("Did not process all records.") + } +} diff --git a/feature/security/gnsi/authz/feature.textproto b/feature/security/gnsi/authz/feature.textproto index ee175c6ff74..183c61709fa 100644 --- a/feature/security/gnsi/authz/feature.textproto +++ b/feature/security/gnsi/authz/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/security/gnsi/authz/tests/authz/README.md b/feature/security/gnsi/authz/tests/authz/README.md index 665c66be8a2..92cb5022af9 100644 --- a/feature/security/gnsi/authz/tests/authz/README.md +++ b/feature/security/gnsi/authz/tests/authz/README.md @@ -342,6 +342,9 @@ For each of the scenarios in this section, we need to exercise the following 3 a 1. Use `gNSI.Rotate` method to push and finalize policy `policy-normal-1`, with `create_on` = `100` and `version` = `policy-normal-1_v1`. 2. Ensure all results match per the above table for policy `policy-normal-1`. +* TODO: Authz-1.5, "Test principle prefix and suffix match" + * Test the behavior of [prefix and suffix match on principles](https://github.com/grpc/proposal/blob/eb0d8fcc93820d3039ac851f8a36bdf2554cab6a/A43-grpc-authorization-api.md?plain=1#L73-L74) + ### Authz-2, test rotation behavior * Authz-2.1, "Test only one rotation request at a time" @@ -397,3 +400,16 @@ For each of the scenarios in this section, we need to exercise the following 3 a 2. Reboot the device. 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 + +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 is not complete + +```yaml +rpcs: + gnsi: + authz.v1.Authz.Get: +``` + diff --git a/feature/security/gnsi/authz/tests/authz/authz1_4_test.go b/feature/security/gnsi/authz/tests/authz/authz1_4_test.go index 579a8c7710e..ec6f2008aa6 100644 --- a/feature/security/gnsi/authz/tests/authz/authz1_4_test.go +++ b/feature/security/gnsi/authz/tests/authz/authz1_4_test.go @@ -206,7 +206,7 @@ func TestAuthz1(t *testing.T) { // Pre-Test Section _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-everyone-can-gnmi-not-gribi"] @@ -229,7 +229,7 @@ func TestAuthz1(t *testing.T) { // Pre-Test Section _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-everyone-can-gribi-not-gnmi"] @@ -252,7 +252,7 @@ func TestAuthz1(t *testing.T) { dut := ondatra.DUT(t, "dut") _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate - 1 newpolicy, ok := policyMap["policy-gribi-get"] @@ -274,7 +274,7 @@ func TestAuthz1(t *testing.T) { } newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) // Rotate the policy. - newpolicy.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + newpolicy.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Verification of Policy for read-only to deny gRIBI Get and allow gNMI Get t.Run("Verification of Policy for read-only to deny gRIBI Get and allow gNMI Get", func(t *testing.T) { @@ -287,7 +287,7 @@ func TestAuthz1(t *testing.T) { // Pre-Test Section _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-normal-1"] @@ -313,7 +313,7 @@ func TestAuthz2(t *testing.T) { // Pre-Test Section _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-everyone-can-gnmi-not-gribi"] @@ -334,7 +334,7 @@ func TestAuthz2(t *testing.T) { autzRotateReq := &authzpb.RotateAuthzRequest_UploadRequest{ UploadRequest: &authzpb.UploadRequest{ Version: fmt.Sprintf("v0.%v", (time.Now().UnixNano())), - CreatedOn: uint64(time.Now().UnixMilli()), + CreatedOn: uint64(time.Now().Unix()), Policy: string(jsonPolicy), }, } @@ -348,9 +348,9 @@ func TestAuthz2(t *testing.T) { t.Fatalf("Error while receiving rotate request reply (client 1) %v", err) } // Rotate Request 2 - Before Finalizing the Request 1 - newpolicy, ok = policyMap["policy-everyone-can-gnmi-not-gribi"] + newpolicy, ok = policyMap["policy-everyone-can-gribi-not-gnmi"] if !ok { - t.Fatal("Policy policy-everyone-can-gnmi-not-gribi is not loaded from policy json file") + t.Fatal("Policy policy-everyone-can-gribi-not-gnmi is not loaded from policy json file") } newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) jsonPolicy, err = newpolicy.Marshal() @@ -365,7 +365,7 @@ func TestAuthz2(t *testing.T) { autzRotateReq = &authzpb.RotateAuthzRequest_UploadRequest{ UploadRequest: &authzpb.UploadRequest{ Version: fmt.Sprintf("v0.%v", (time.Now().UnixNano())), - CreatedOn: uint64(time.Now().UnixMilli()), + CreatedOn: uint64(time.Now().Unix()), Policy: string(jsonPolicy), }, } @@ -390,7 +390,7 @@ func TestAuthz2(t *testing.T) { // Pre-Test Section _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-gribi-get"] @@ -399,7 +399,7 @@ func TestAuthz2(t *testing.T) { } newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) // Rotate the policy. - newpolicy.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + newpolicy.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Verification of Policy for read_only to allow gRIBI Get and to deny gNMI Get t.Run("Verification of Policy for read_only to allow gRIBI Get and to deny gNMI Get", func(t *testing.T) { @@ -425,7 +425,7 @@ func TestAuthz2(t *testing.T) { autzRotateReq := &authzpb.RotateAuthzRequest_UploadRequest{ UploadRequest: &authzpb.UploadRequest{ Version: fmt.Sprintf("v0.%v", (time.Now().UnixNano())), - CreatedOn: uint64(time.Now().UnixMilli()), + CreatedOn: uint64(time.Now().Unix()), Policy: string(jsonPolicy), }, } @@ -459,7 +459,7 @@ func TestAuthz2(t *testing.T) { // Pre-Test Section _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-gribi-get"] @@ -468,7 +468,7 @@ func TestAuthz2(t *testing.T) { } newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) // Rotate the policy. - newpolicy.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + newpolicy.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Verification of Policy for read_only to allow gRIBI Get and to deny gNMI Get t.Run("Verification of Policy for read_only to allow gRIBI Get and to deny gNMI Get", func(t *testing.T) { @@ -495,7 +495,7 @@ func TestAuthz2(t *testing.T) { autzRotateReq := &authzpb.RotateAuthzRequest_UploadRequest{ UploadRequest: &authzpb.UploadRequest{ Version: fmt.Sprintf("v0.%v", (time.Now().UnixNano())), - CreatedOn: uint64(time.Now().UnixMilli()), + CreatedOn: uint64(time.Now().Unix()), Policy: string(jsonPolicy), }, } @@ -527,7 +527,7 @@ func TestAuthz2(t *testing.T) { // Pre-Test Section _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-gribi-get"] @@ -537,7 +537,7 @@ func TestAuthz2(t *testing.T) { newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) // Rotate the policy. prevVersion := fmt.Sprintf("v0.%v", (time.Now().UnixNano())) - newpolicy.Rotate(t, dut, uint64(time.Now().UnixMilli()), prevVersion, false) + newpolicy.Rotate(t, dut, uint64(time.Now().Unix()), prevVersion, false) newpolicy, ok = policyMap["policy-gnmi-get"] if !ok { @@ -556,7 +556,7 @@ func TestAuthz2(t *testing.T) { autzRotateReq := &authzpb.RotateAuthzRequest_UploadRequest{ UploadRequest: &authzpb.UploadRequest{ Version: prevVersion, - CreatedOn: uint64(time.Now().UnixMilli()), + CreatedOn: uint64(time.Now().Unix()), Policy: string(jsonPolicy), }, } @@ -576,7 +576,7 @@ func TestAuthz2(t *testing.T) { }) t.Logf("Preforming Rotate with the same version with force overwrite\n") - newpolicy.Rotate(t, dut, uint64(time.Now().UnixMilli()), prevVersion, true) + newpolicy.Rotate(t, dut, uint64(time.Now().Unix()), prevVersion, true) // Verification of Policy for read_only to allow gRIBI Get and to deny gNMI Get t.Run("Verification of Policy for read_only to allow gRIBI Get and to deny gNMI Get after rotate wth force overwrite", func(t *testing.T) { authz.Verify(t, dut, spiffeCertReadOnly, gnxi.RPCs.GribiGet, &authz.ExceptDeny{}, &authz.HardVerify{}) @@ -593,7 +593,7 @@ func TestAuthz3(t *testing.T) { setUpBaseline(t, dut) _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Rotate Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy object. newpolicy, ok := policyMap["policy-gribi-get"] @@ -603,7 +603,7 @@ func TestAuthz3(t *testing.T) { // Attach base Admin Policy // Rotate the policy. newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) - expCreatedOn := uint64(time.Now().UnixMilli()) + expCreatedOn := uint64(time.Now().Unix()) expVersion := fmt.Sprintf("v0.%v", (time.Now().UnixNano())) newpolicy.Rotate(t, dut, expCreatedOn, expVersion, false) t.Logf("New Rotated Authz Policy is %s", newpolicy.PrettyPrint(t)) @@ -635,9 +635,10 @@ func TestAuthz3(t *testing.T) { func TestAuthz4(t *testing.T) { // Pre-Test Section dut := ondatra.DUT(t, "dut") + setUpBaseline(t, dut) _, policyBefore := authz.Get(t, dut) t.Logf("Authz Policy of the Device %s before the Reboot Trigger is %s", dut.Name(), policyBefore.PrettyPrint(t)) - defer policyBefore.Rotate(t, dut, uint64(time.Now().UnixMilli()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) + defer policyBefore.Rotate(t, dut, uint64(time.Now().Unix()), fmt.Sprintf("v0.%v", (time.Now().UnixNano())), false) // Fetch the Desired Authorization Policy and Attach base Admin Policy Before Rotate newpolicy, ok := policyMap["policy-normal-1"] @@ -645,7 +646,7 @@ func TestAuthz4(t *testing.T) { t.Fatal("Policy policy-normal-1 is not loaded from policy json file") } newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) - expCreatedOn := uint64(time.Now().UnixMilli()) + expCreatedOn := uint64(time.Now().Unix()) expVersion := fmt.Sprintf("v0.%v", (time.Now().UnixNano())) t.Logf("New Authz Policy is %s", newpolicy.PrettyPrint(t)) newpolicy.Rotate(t, dut, expCreatedOn, expVersion, false) diff --git a/feature/security/gnsi/authz/tests/authz/testdata/policy.json b/feature/security/gnsi/authz/tests/authz/testdata/policy.json index be609532ab9..b3491e11aff 100644 --- a/feature/security/gnsi/authz/tests/authz/testdata/policy.json +++ b/feature/security/gnsi/authz/tests/authz/testdata/policy.json @@ -67,7 +67,7 @@ }, "request": { "paths": [ - "/gribi.gNMI/*" + "/gnmi.gNMI/*" ] } } @@ -225,4 +225,4 @@ } } ] - }] \ No newline at end of file + }] diff --git a/feature/security/gnsi/certz/baseline/README.md b/feature/security/gnsi/certz/baseline/README.md deleted file mode 100644 index 674741054ec..00000000000 --- a/feature/security/gnsi/certz/baseline/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Security Features to Test - -# Certificate Related Tests - -TODO: Add tests that will validate the following RPC endpoints for the certz service: - * ADDProfile - Add a new TLS service profile to the DUT. - * DeleteProfile - Delete an aged TLS service profile from the DUT. - * GetProfile - Get the current details of a TLS service profile from the DUT. - * OC Streaming - Validate that appropriate metrics are returned from streaming telemetry. diff --git a/feature/security/gnsi/certz/client_certificates/README.md b/feature/security/gnsi/certz/client_certificates/README.md index cea7ebc84ee..0efdc4f8f6c 100644 --- a/feature/security/gnsi/certz/client_certificates/README.md +++ b/feature/security/gnsi/certz/client_certificates/README.md @@ -8,6 +8,20 @@ identification information. The client certificate should have a SPIFFE Idenitifier embedded in it to be used as the identifier of the client to the server. +* SPIFFEE ID format + +``` +spiffe://.../role/ +``` + +* Example: + +``` +URI:spiffe://ca-issuer.sdn.wan.example.com/role/controller-role +``` + + + ## Baseline Setup ### Input Args @@ -42,8 +56,8 @@ gRPC service. Perform this for both RSA and ECDSA signed CA bundles and certificates. -Perform this for the permutations of 1, 2, 10, 1000 CA -trust_bundle configurations: (## indicates the 1, 2, 10, 1000 CA testdata) +Perform this for the permutations of 1, 2, 10, 1000, 20000 CA +trust_bundle configurations: (## indicates the 1, 2, 10, 1000, 20000 CA testdata) 1) Load the correct key-type trust bundle onto the device and client system: ca-##/trust_bundle_##_rsa.pem @@ -102,13 +116,19 @@ certificates: 5) Validate that the connection is properly torn down by the DUT. -## Config Parameter Coverage -## Telemetry Parameter Coverage +## 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 -## Protocol/RPC Parameter Coverage +```yaml +rpcs: + gnsi: + certz.v1.Certz.GetProfileList: +``` -None ## Minimum DUT Platform Requirement diff --git a/feature/security/gnsi/certz/feature.textproto b/feature/security/gnsi/certz/feature.textproto index 92288a1e337..e12f9bcbf7d 100644 --- a/feature/security/gnsi/certz/feature.textproto +++ b/feature/security/gnsi/certz/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/security/gnsi/certz/server_certificate_rotation/README.md b/feature/security/gnsi/certz/server_certificate_rotation/README.md index b8ee75eef46..8a2afe1c30b 100644 --- a/feature/security/gnsi/certz/server_certificate_rotation/README.md +++ b/feature/security/gnsi/certz/server_certificate_rotation/README.md @@ -96,13 +96,17 @@ Perform this test with both the RSA and ECDSA types. -## Config Parameter Coverage +## OpenConfig Path and RPC Coverage -## Telemetry Parameter 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. -## Protocol/RPC Parameter Coverage +TODO(OCRPC): Record may not be correct or complete -None +```yaml +rpcs: + gnsi: + certz.v1.Certz.Rotate: +``` ## Minimum DUT Platform Requirement diff --git a/feature/security/gnsi/certz/server_certificates/README.md b/feature/security/gnsi/certz/server_certificates/README.md index 6b75422f3a0..eed72366c9a 100644 --- a/feature/security/gnsi/certz/server_certificates/README.md +++ b/feature/security/gnsi/certz/server_certificates/README.md @@ -94,13 +94,17 @@ trust_bundles and certificates. 5) Validate that the connection is properly torn down by the DUT. -## Config Parameter Coverage +## OpenConfig Path and RPC Coverage -## Telemetry Parameter 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. -## Protocol/RPC Parameter Coverage +TODO(OCRPC): Record may not be correct or complete -None +```yaml +rpcs: + gnsi: + certz.v1.Certz.GetProfileList: +``` ## Minimum DUT Platform Requirement diff --git a/feature/security/gnsi/certz/test_data/README.md b/feature/security/gnsi/certz/test_data/README.md index 736b7cd9486..3ccc3362143 100644 --- a/feature/security/gnsi/certz/test_data/README.md +++ b/feature/security/gnsi/certz/test_data/README.md @@ -10,6 +10,7 @@ Creation of test data for use in TLS tests. * ca-02 - a set of two CAs where signatures are RSA or ECDSA. * ca-10 - a set of ten CAs where signatures are RSA or ECDSA. * ca-1000 - a set of one thousand CAs where signatures are RSA or ECDSA. + * ca-20000 - a set of twenty thousand CAs where signatures are RSA or ECDSA. * server_cert.cnf/server_cert_ext.cnf - server openssl profile configuration * client_cert.cnf/client_cert_ext.cnf - client openssl profile configuration diff --git a/feature/security/gnsi/certz/test_data/cleanup.sh b/feature/security/gnsi/certz/test_data/cleanup.sh index a8afee9db65..95b7cbaf6da 100755 --- a/feature/security/gnsi/certz/test_data/cleanup.sh +++ b/feature/security/gnsi/certz/test_data/cleanup.sh @@ -1,4 +1,5 @@ #!/bin/sh -for d in 01 02 10 1000; do - rm -f ca-${d}/* +for d in 01 02 10 1000 20000; do + find ca-${d}/ -type f -exec /usr/bin/rm -f {} \; + /usr/bin/rm -rf ca-${d}/ done diff --git a/feature/security/gnsi/certz/test_data/mk_cas.sh b/feature/security/gnsi/certz/test_data/mk_cas.sh index 602657ad541..06febedb804 100755 --- a/feature/security/gnsi/certz/test_data/mk_cas.sh +++ b/feature/security/gnsi/certz/test_data/mk_cas.sh @@ -2,10 +2,10 @@ # # Create test certificate authority content for feature # profile test cases. - +# # The list of directories of CA contents, also the count of CAs built # in each directory. -DIRS=(01 02 10 1000) +DIRS=(01 02 10 1000 20000) # The types of signatures to support for the CA Certs. TYPES=(rsa ecdsa) @@ -20,13 +20,16 @@ RSAKEYLEN=2048 LIFETIME=3650 # Create RSA and ECDSA CA keys, and associated certificates. -for d in ${DIRS[@]} ; do +for d in ${DIRS[@]} ; do if [ ! -d ca-${d} ] ; then mkdir ca-${d} fi + # Create a CA key and certificate for each of the DIRS count of + # keys / certs. Do this for each of the TYPES key types. for k in $(seq 1 ${d}); do - OFFSET=$(printf "%04i" ${k}) + OFFSET=$(printf "%05i" ${k}) for t in ${TYPES[@]}; do + # Generate the appropriate key type keys. case ${t} in rsa) openssl genrsa -out ca-${d}/ca-${OFFSET}-${t}-key.pem ${RSAKEYLEN} @@ -36,8 +39,9 @@ for d in ${DIRS[@]} ; do -out ca-${d}/ca-${OFFSET}-${t}-key.pem -genkey ;; esac - # Create a cert with the fresh key. + # Create a cert with the fresh key, require it to be a CA certificate. openssl req -new -x509 -nodes -days ${LIFETIME} \ + -addext basicConstraints=critical,CA:TRUE \ -key ca-${d}/ca-${OFFSET}-${t}-key.pem \ -out ca-${d}/ca-${OFFSET}-${t}-cert.pem \ -subj "/CN=CA ${OFFSET}/C=AQ/ST=NZ/L=NZ/O=OpenConfigFeatureProfiles" @@ -49,6 +53,11 @@ done for d in ${DIRS[@]}; do for t in ${TYPES[@]}; do cat ca-${d}/ca-*-${t}-cert.pem > ca-${d}/trust_bundle_${d}_${t}.pem + CERTS="" + for cf in ca-${d}/ca-*-${t}-cert.pem; do + CERTS="${CERTS} -certfile ${cf}" + done + openssl crl2pkcs7 -nocrl ${CERTS} -out ca-${d}/trust_bundle_${d}_${t}.p7b done done @@ -56,55 +65,46 @@ done # Two client and Two server certificates are all that are required per type. # * Create keys per type for each client/server certificate to create. # * Create CSRs per type for each client/server certificate to create. -# * Use the CA + extensions config to create the client certificates. +# * Use the CA + extensions config to create the client/server certificates. # -# Repeat for the server certificate creation. for d in ${DIRS[@]}; do if [ ! -d ca-${d} ] ; then mkdir ca-${d} fi for t in ${TYPES[@]}; do - OFFSET=$(printf "%04i" ${d}) + OFFSET=$(printf "%05i" ${d}) # Create both client and server cert keys for each type. + # use a/b here to signal the required 2 client or server certs/keys. for g in a b; do - case ${t} in - rsa) - openssl genrsa -out ca-${d}/client-${t}-${g}-key.pem ${RSAKEYLEN} - openssl genrsa -out ca-${d}/server-${t}-${g}-key.pem ${RSAKEYLEN} - ;; - ecdsa) - openssl ecparam -name ${CURVE} \ - -out ca-${d}/client-${t}-${g}-key.pem -genkey - openssl ecparam -name ${CURVE} \ - -out ca-${d}/server-${t}-${g}-key.pem -genkey - ;; - esac - - # Create the client and server requests. - openssl req -new -key ca-${d}/client-${t}-${g}-key.pem \ - -out ca-${d}/client-${t}-${g}-req.pem \ - -config client_cert.cnf - openssl req -new -key ca-${d}/server-${t}-${g}-key.pem \ - -out ca-${d}/server-${t}-${g}-req.pem \ - -config server_cert.cnf + for cs in client server; do + case ${t} in + rsa) + openssl genrsa -out ca-${d}/${cs}-${t}-${g}-key.pem ${RSAKEYLEN} + ;; + ecdsa) + openssl ecparam -name ${CURVE} \ + -out ca-${d}/${cs}-${t}-${g}-key.pem -genkey + ;; + esac + done + done - # Create the client and server complete certificates. - openssl x509 -req -in ca-${d}/client-${t}-${g}-req.pem \ - -CA ca-${d}/ca-${OFFSET}-${t}-${g}-cert.pem \ - -CAkey ca-${d}/ca-${OFFSET}-${t}-${g}-key.pem \ - -out ca-${d}/client-${t}-${g}-cert.pem \ - -CAcreateserial \ - -days ${LIFETIME} \ - -sha256 \ - -extfile client_cert_ext.cnf - openssl x509 -req -in ca-${d}/server-${t}-${g}-req.pem \ - -CA ca-${d}/ca-${OFFSET}-${t}-${g}-cert.pem \ - -CAkey ca-${d}/ca-${OFFSET}-${t}-${g}-key.pem \ - -out ca-${d}/server-${t}-${g}-cert.pem \ - -CAcreateserial \ - -days ${LIFETIME} \ - -sha256 \ - -extfile server_cert_ext.cnf + # Create the client and server requests, for both A and B (the 2 required certs) + for cs in client server; do + for g in a b ; do + openssl req -new -key ca-${d}/${cs}-${t}-${g}-key.pem \ + -out ca-${d}/${cs}-${t}-${g}-req.pem \ + -config ${cs}_cert.cnf + # Create the client and server complete certificates. + openssl x509 -req -in ca-${d}/${cs}-${t}-${g}-req.pem \ + -CA ca-${d}/ca-${OFFSET}-${t}-cert.pem \ + -CAkey ca-${d}/ca-${OFFSET}-${t}-key.pem \ + -out ca-${d}/${cs}-${t}-${g}-cert.pem \ + -CAcreateserial \ + -days ${LIFETIME} \ + -sha256 \ + -extfile ${cs}_cert_ext.cnf + done done done done diff --git a/feature/security/gnsi/certz/trust_bundle/README.md b/feature/security/gnsi/certz/trust_bundle/README.md index bc15e6aa10c..fbbee7d1e6d 100644 --- a/feature/security/gnsi/certz/trust_bundle/README.md +++ b/feature/security/gnsi/certz/trust_bundle/README.md @@ -62,19 +62,25 @@ Load the server certificate and key from each of the following CA sets: * 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. -## Config Parameter Coverage +## OpenConfig Path and RPC Coverage -## Telemetry Parameter 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. -## Protocol/RPC Parameter Coverage +TODO(OCRPC): Record may not be correct or complete + +```yaml +rpcs: + gnsi: + certz.v1.Certz.GetProfileList: +``` -None ## Minimum DUT Platform Requirement diff --git a/feature/security/gnsi/certz/trust_bundle_rotation/README.md b/feature/security/gnsi/certz/trust_bundle_rotation/README.md index 9be06f2b078..db80145a9a1 100644 --- a/feature/security/gnsi/certz/trust_bundle_rotation/README.md +++ b/feature/security/gnsi/certz/trust_bundle_rotation/README.md @@ -82,13 +82,18 @@ Perform this test with both the RSA and ECDSA types. 5) Verify that the server is still serving the certifcate properly. -## Config Parameter Coverage +## OpenConfig Path and RPC Coverage -## Telemetry Parameter 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. -## Protocol/RPC Parameter Coverage +TODO(OCRPC): Record may not be complete + +```yaml +rpcs: + gnsi: + certz.v1.Certz.Rotate: +``` -None ## Minimum DUT Platform Requirement diff --git a/feature/security/gnsi/credentialz/feature.textproto b/feature/security/gnsi/credentialz/feature.textproto index 964ff8207a1..4bfd23a1c3f 100644 --- a/feature/security/gnsi/credentialz/feature.textproto +++ b/feature/security/gnsi/credentialz/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/security/gnsi/credentialz/tests/README.md b/feature/security/gnsi/credentialz/tests/README.md index 52a5808d2eb..a6cd29841d9 100644 --- a/feature/security/gnsi/credentialz/tests/README.md +++ b/feature/security/gnsi/credentialz/tests/README.md @@ -4,8 +4,8 @@ Test gNSI Credentialz API behaviors. ## Request Examples -These example gNSI credentialz requests are examples that can be used with cases -below. +These gNSI credentialz requests are examples that can be used with cases below. +======= ### Configure a testuser and password @@ -63,6 +63,19 @@ stream.Send( ) ``` +### Configure and enable GLOME + +``` +stream.Send( + RotateHostParametersRequest { + enabled: true, + key: "4242424242424242424242424242424242424242424242", + key_version: 4, + url_prefix: "https://example.invalid", + } +) +``` + ### Populate Authorized Principals ``` @@ -133,8 +146,8 @@ resp := stream.Receive() #### Setup -* Set a username of "testuser" using gnsi.Credentialz -* Set a password of "i$V5^6IhD*tZ#eg1G@v3xdVZrQwj" (see RotateAccountCredentials, PasswordRequest, plaintext) +* 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 @@ -142,7 +155,11 @@ resp := stream.Receive() * 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` - username. + * 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. @@ -152,7 +169,7 @@ resp := stream.Receive() * Provide incorrect password, but correct username. * Authentication must fail. -### Credentialz-2, SSH pasword login disallowed +### Credentialz-2, SSH password login disallowed #### Setup * Set a username of `testuser` @@ -173,12 +190,23 @@ resp := stream.Receive() #### 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). @@ -189,7 +217,7 @@ resp := stream.Receive() * 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.test.com -V +52w + 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) @@ -197,6 +225,11 @@ resp := stream.Receive() #### 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 @@ -209,6 +242,14 @@ resp := stream.Receive() #### 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 @@ -236,10 +277,53 @@ resp := stream.Receive() * 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/security/gnsi/credentialz/tests/hiba_authentication/README.md b/feature/security/gnsi/credentialz/tests/hiba_authentication/README.md new file mode 100644 index 00000000000..89f3fc884c4 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/hiba_authentication/README.md @@ -0,0 +1,55 @@ +# Credentialz-5: Hiba Authentication + +## Summary + +Test that Credentialz properly configures (hiba) certificate authentication. + + +## Procedure + +* Follow the instructions for setting up a [HIBA CA](https://github.com/google/hiba/blob/main/CA.md) +* Set DUT allowed authentication types to only public key using gnsi.Credentialz +* Create a user `testuser` (with no certificate at this point) +* Set the AuthorizedPrincipalsCommand by setting the tool to `TOOL_HIBA_DEFAULT` + +* Perform the following tests and assert the expected result: + * Case 1: Failure + * Authenticate with the `testuser` username and the previously created public key via SSH + * Assert that authentication has failed (because the DUT doesn't have the Hiba host certificate at this point) + * Ensure that access rejects telemetry counter is incremented `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-rejects` + * Case 2: Success + * Configure the dut with the Hiba host certificate. + * Authenticate with the `testuser` username the previously created public key via SSH + * Assert that authentication has been successful + * 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 after successful login + `/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` + + +## 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 ## + /system/ssh-server/state/active-host-certificate-version: + /system/ssh-server/state/active-host-certificate-created-on: + /system/ssh-server/state/counters/access-accepts: + /system/ssh-server/state/counters/last-access-accept: + /system/ssh-server/state/counters/access-rejects: + +rpcs: + gnsi: + credentialz.v1.Credentialz.RotateHostParameters: +``` + + +## Minimum DUT platform requirement + +N/A \ No newline at end of file diff --git a/feature/security/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go b/feature/security/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go new file mode 100644 index 00000000000..d63699abff9 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go @@ -0,0 +1,163 @@ +// 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 hibaauthentication_test + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/featureprofiles/internal/security/credz" + "github.com/openconfig/ondatra/gnmi" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + cpb "github.com/openconfig/gnsi/credentialz" + "github.com/openconfig/ondatra" +) + +const ( + username = "testuser" + hostCertificateVersion = "v1.0" +) + +var ( + hostCertificateCreatedOn = time.Now().Unix() +) + +func TestMain(m *testing.M) { + fptest.RunTests(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) + credz.SetupUser(t, dut, username) + + // Set only public key authentication for our test. + credz.RotateAuthenticationTypes(t, dut, []cpb.AuthenticationType{ + cpb.AuthenticationType_AUTHENTICATION_TYPE_PUBKEY, + }) + + // Setup hiba for authorized principals command. + credz.RotateAuthorizedPrincipalCheck(t, dut, cpb.AuthorizedPrincipalCheckRequest_TOOL_HIBA_DEFAULT) + + t.Run("auth should fail hiba host certificate not present", func(t *testing.T) { + var startingRejectCounter uint64 + if !deviations.SSHServerCountersUnsupported(dut) { + startingRejectCounter, _ = credz.GetRejectTelemetry(t, dut) + } + + // Verify ssh with hiba fails as expected. + _, err := credz.SSHWithCertificate(t, target, username, fmt.Sprintf("%s/users", dir)) + if err == nil { + t.Fatalf("Dialing ssh succeeded, but we expected to fail") + } + + if !deviations.SSHServerCountersUnsupported(dut) { + endingRejectCounter, _ := credz.GetRejectTelemetry(t, dut) + if endingRejectCounter <= startingRejectCounter { + t.Fatalf("SSH server reject counter did not increment after unsuccessful login. startCounter: %v, endCounter: %v", startingRejectCounter, endingRejectCounter) + } + } + }) + + t.Run("auth should succeed ssh public key authorized for user with hiba granted certificate", func(t *testing.T) { + // Push host key/certificate to the dut. + credz.RotateAuthenticationArtifacts(t, + dut, + fmt.Sprintf("%s/hosts", dir), + fmt.Sprintf("%s/hosts", dir), + hostCertificateVersion, + uint64(hostCertificateCreatedOn), + ) + + // Setup trusted user ca on the dut. + credz.RotateTrustedUserCA(t, dut, dir) + + var startingAcceptCounter, startingLastAcceptTime uint64 + if !deviations.SSHServerCountersUnsupported(dut) { + startingAcceptCounter, startingLastAcceptTime = credz.GetAcceptTelemetry(t, dut) + } + + _, err := credz.SSHWithCertificate(t, target, username, fmt.Sprintf("%s/users", dir)) + if err != nil { + t.Fatalf("Dialing ssh failed, but we expected to succeed, errror: %s", err) + } + + // 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) + } + if startingLastAcceptTime == endingLastAcceptTime { + t.Fatalf("SSH server accept last timestamp did not update after successful login. Timestamp: %v", endingLastAcceptTime) + } + } + + // Verify host certificate telemetry. + sshServer := gnmi.Get(t, dut, gnmi.OC().System().SshServer().State()) + gotHostCertificateVersion := sshServer.GetActiveHostCertificateVersion() + if !cmp.Equal(gotHostCertificateVersion, hostCertificateVersion) { + t.Fatalf( + "Telemetry reports host certificate version is not correct\n\tgot: %s\n\twant: %s", + gotHostCertificateVersion, hostCertificateVersion, + ) + } + gotHostCertificateCreatedOn := sshServer.GetActiveHostCertificateCreatedOn() + if !cmp.Equal(time.Unix(0, int64(gotHostCertificateCreatedOn)), time.Unix(hostCertificateCreatedOn, 0)) { + t.Fatalf( + "Telemetry reports host certificate created on is not correct\n\tgot: %d\n\twant: %d", + gotHostCertificateCreatedOn, hostCertificateCreatedOn, + ) + } + }) + + t.Cleanup(func() { + // Cleanup to remove previous policy which only allowed key auth to make sure we don't leave dut in a + // state where we can't reset config for further tests. + credz.RotateAuthenticationTypes(t, dut, []cpb.AuthenticationType{ + cpb.AuthenticationType_AUTHENTICATION_TYPE_PASSWORD, + cpb.AuthenticationType_AUTHENTICATION_TYPE_PUBKEY, + cpb.AuthenticationType_AUTHENTICATION_TYPE_KBDINTERACTIVE, + }) + + // Remove user ca so subsequent fail cases work. + credz.RotateTrustedUserCA(t, dut, "") + + // Clear hiba for authorized principals command. + credz.RotateAuthorizedPrincipalCheck(t, dut, cpb.AuthorizedPrincipalCheckRequest_TOOL_UNSPECIFIED) + + // Remove host artifacts from the dut. + credz.RotateAuthenticationArtifacts(t, dut, "", "", "", 0) + }) +} diff --git a/feature/security/gnsi/credentialz/tests/hiba_authentication/metadata.textproto b/feature/security/gnsi/credentialz/tests/hiba_authentication/metadata.textproto new file mode 100644 index 00000000000..d27061b81a2 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/hiba_authentication/metadata.textproto @@ -0,0 +1,15 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "4083a01e-9b52-44c0-aa5c-994e78be66fe" +plan_id: "Credentialz-5" +description: "Hiba Authentication" +testbed: TESTBED_DUT +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + ssh_server_counters_unsupported: true + } +} diff --git a/feature/security/gnsi/credentialz/tests/host_certificates/README.md b/feature/security/gnsi/credentialz/tests/host_certificates/README.md new file mode 100644 index 00000000000..de9e92bb424 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/host_certificates/README.md @@ -0,0 +1,45 @@ +# Credentialz-3: Host Certificates + +## Summary + +Test that Credentialz can properly fetch and push SSH host certificates, and that the DUT sends +this certificate during SSH authentication. + + +## Procedure + +* Fetch the DUT's public key using gnsi.Credentialz + * If DUT doesnt have one, generate and set the private key using gnsi.Credentialz. +* Sign the DUT's public key with the ca key to create a host certificate. +* Add the newly created certificate to the DUT using gnsi.Credentialz +* Perform the following tests and assert the expected result: + * Case 1: Success + * SSH to the device and assert that the host key returned is the host key that was + pushed in the test set up + * 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` + + +## 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 ## + /system/ssh-server/state/active-host-certificate-version: + /system/ssh-server/state/active-host-certificate-created-on: + +rpcs: + gnsi: + credentialz.v1.Credentialz.GetPublicKeys: + credentialz.v1.Credentialz.RotateHostParameters: +``` + + +## Minimum DUT platform requirement + +N/A \ No newline at end of file diff --git a/feature/security/gnsi/credentialz/tests/host_certificates/host_certificates_test.go b/feature/security/gnsi/credentialz/tests/host_certificates/host_certificates_test.go new file mode 100644 index 00000000000..f728ae0f531 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/host_certificates/host_certificates_test.go @@ -0,0 +1,116 @@ +// 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 hostcertificates_test + +import ( + "fmt" + "net" + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/ondatra/gnmi" + + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/security/credz" + "github.com/openconfig/ondatra" + "golang.org/x/crypto/ssh" +) + +const ( + hostCertificateVersion = "v1.0" +) + +var ( + hostCertificateCreatedOn = time.Now().Unix() +) + +func TestMain(m *testing.M) { + fptest.RunTests(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) + + // Create ssh keys/certificates for CA & host. + credz.CreateSSHKeyPair(t, dir, "ca") + credz.CreateSSHKeyPair(t, dir, "dut") + + credz.RotateAuthenticationArtifacts(t, dut, dir, "", hostCertificateVersion, uint64(hostCertificateCreatedOn)) + dutKey := credz.GetDutPublicKey(t, dut) + credz.CreateHostCertificate(t, dir, dutKey) + credz.RotateAuthenticationArtifacts(t, dut, "", dir, hostCertificateVersion, uint64(hostCertificateCreatedOn)) + + t.Run("dut should return signed host certificate", func(t *testing.T) { + certificateContents, err := os.ReadFile(fmt.Sprintf("%s/dut-cert.pub", dir)) + if err != nil { + t.Fatalf("Failed reading host signed certificate, error: %s", err) + } + wantHostKey, _, _, _, err := ssh.ParseAuthorizedKey(certificateContents) + if err != nil { + t.Fatalf("Failed parsing host certificate authorized (cert)key: %s", err) + } + + // Verify correct host certificate is returned by the dut. + _, err = ssh.Dial( + "tcp", + target, + &ssh.ClientConfig{ + User: "admin", + Auth: []ssh.AuthMethod{}, + HostKeyCallback: func(hostname string, remote net.Addr, gotHostKey ssh.PublicKey) error { + if !cmp.Equal(gotHostKey, wantHostKey) { + t.Fatalf("Host presented key (cert) that does not match expected host certificate. got: %v, want: %v", gotHostKey, wantHostKey) + } + return nil + }, + }, + ) + if err == nil { + t.Fatal("Dial ssh succeeded, but we expected failure.") + } + + // Verify host certificate telemetry values. + sshServer := gnmi.Get(t, dut, gnmi.OC().System().SshServer().State()) + gotHostCertificateVersion := sshServer.GetActiveHostCertificateVersion() + if !cmp.Equal(gotHostCertificateVersion, hostCertificateVersion) { + t.Fatalf( + "Telemetry reports host certificate version is not correct\n\tgot: %s\n\twant: %s", + gotHostCertificateVersion, hostCertificateVersion, + ) + } + gotHostCertificateCreatedOn := sshServer.GetActiveHostCertificateCreatedOn() + if !cmp.Equal(time.Unix(0, int64(gotHostCertificateCreatedOn)), time.Unix(hostCertificateCreatedOn, 0)) { + t.Fatalf( + "Telemetry reports host certificate created on is not correct\n\tgot: %d\n\twant: %d", + gotHostCertificateCreatedOn, hostCertificateCreatedOn, + ) + } + }) +} diff --git a/feature/security/gnsi/credentialz/tests/host_certificates/metadata.textproto b/feature/security/gnsi/credentialz/tests/host_certificates/metadata.textproto new file mode 100644 index 00000000000..b0cbb9220fc --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/host_certificates/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "91120b0a-91ea-493c-8046-f52176ec2029" +plan_id: "Credentialz-3" +description: "Host Certificates" +testbed: TESTBED_DUT diff --git a/feature/security/gnsi/credentialz/tests/password_console_login/README.md b/feature/security/gnsi/credentialz/tests/password_console_login/README.md new file mode 100644 index 00000000000..312968bb057 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/password_console_login/README.md @@ -0,0 +1,40 @@ +# Credentialz-1: Password console login + +## Summary + +Test that Credentialz properly creates users and the associated password and that the DUT handles +authentication of those users properly. + + +## Procedure + +* Set a username of `testuser` with a password having following restrictions: + * Must be 24-32 characters long. + * Must use 4 of the 5 character classes ([a-z], [A-Z], [0-9], [!@#$%^&*(){}[]\|:;'"], [ ]). +* Perform the following tests and assert the expected result: + * Case 1: Success + * Authenticate with the `testuser` username and password created in the first step above. + * Authentication must result in success with a prompt. + * Case 2: Failure + * Authenticate with the `testuser` username and an *incorrect* password of `password` + * Assert that authentication has failed + * Case 3: Failure + * Authenticate with the invalid username `username` and a valid (for a different username) + password created in the first step above. + * Assert that authentication has failed + + +## 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 +rpcs: + gnsi: + credentialz.v1.Credentialz.RotateAccountCredentials: +``` + + +## Minimum DUT platform requirement + +N/A diff --git a/feature/security/gnsi/credentialz/tests/password_console_login/metadata.textproto b/feature/security/gnsi/credentialz/tests/password_console_login/metadata.textproto new file mode 100644 index 00000000000..790ee493aec --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/password_console_login/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "902e381c-8196-436e-8f5b-f945e57ff855" +plan_id: "Credentialz-1" +description: "Password console login" +testbed: TESTBED_DUT diff --git a/feature/security/gnsi/credentialz/tests/password_console_login/password_console_login_test.go b/feature/security/gnsi/credentialz/tests/password_console_login/password_console_login_test.go new file mode 100644 index 00000000000..97ac39b3c94 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/password_console_login/password_console_login_test.go @@ -0,0 +1,110 @@ +// 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 passwordconsolelogin_test + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/ondatra/gnmi" + + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/security/credz" + "github.com/openconfig/ondatra" +) + +const ( + username = "testuser" + passwordVersion = "v1.0" +) + +var ( + passwordCreatedOn = time.Now().Unix() +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestCredentialz(t *testing.T) { + dut := ondatra.DUT(t, "dut") + target := credz.GetDutTarget(t, dut) + + // Setup test user and password. + credz.SetupUser(t, dut, username) + password := credz.GeneratePassword() + credz.RotateUserPassword(t, dut, username, password, passwordVersion, uint64(passwordCreatedOn)) + + testCases := []struct { + name string + loginUser string + loginPassword string + expectFail bool + }{ + { + name: "auth should succeed", + loginUser: username, + loginPassword: password, + expectFail: false, + }, + { + name: "auth should fail bad username", + loginUser: "notadmin", + loginPassword: password, + expectFail: true, + }, + { + name: "auth should fail bad password", + loginUser: username, + loginPassword: "notthepassword", + 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) + if tc.expectFail { + 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() + + // 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, + ) + } + 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, + ) + } + }) + } +} diff --git a/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md b/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md new file mode 100644 index 00000000000..b594291d517 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md @@ -0,0 +1,71 @@ +# Credentialz-2: SSH Password Login Disallowed + +## Summary + +Test that Credentialz properly disallows password based SSH authentication when configured to do +so, furthermore, ensure that certificate based SSH authentication is allowed, and properly +accounted for. + + +## Procedure + +* 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 user.pub`. You will + find your certificate ending in `-cert.pub` +* Set DUT TrustedUserCAKeys using gnsi.Credentialz with the CA public key +* Set a username of `testuser` with a password having following restrictions: + * Must be 24-32 characters long. + * Must use 4 of the 5 character classes ([a-z], [A-Z], [0-9], [!@#$%^&*(){}[]\|:;'"], [ ]). +* Set DUT authentication types to permit only public key (PUBKEY) using gnsi.Credentialz +* Set DUT authorized_users for `testuser` with a principal of `my_principal` (configured above + when signing public key) +* Perform the following tests and assert the expected result: + * Case 1: Failure + * Authenticate with the `testuser` username and password created above + via SSH + * Assert that authentication has failed + * 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` + * Case 2: Success + * Authenticate with the `testuser` username and password created above + via console + * Assert that authentication has been successful (password authentication was only + disallowed for SSH) + * 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` + * Case 3: Success + * Authenticate with the `testuser` and certificate created above + * Assert that authentication has been successful + * Assert that gnsi accounting recorded the principal (`my_principal`) from the + certificate rather than the SSH username (`testuser`) + * 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` + + +## 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 ## + /system/ssh-server/state/counters/access-rejects: + /system/ssh-server/state/counters/last-access-reject: + /system/ssh-server/state/counters/access-accepts: + /system/ssh-server/state/counters/last-access-accept: + +rpcs: + gnsi: + credentialz.v1.Credentialz.RotateAccountCredentials: + credentialz.v1.Credentialz.RotateHostParameters: +``` + + +## Minimum DUT platform requirement + +N/A \ No newline at end of file diff --git a/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto b/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto new file mode 100644 index 00000000000..afd3a143542 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto @@ -0,0 +1,16 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "4632f134-71e6-46a2-ac2a-b1ff6a3444e6" +plan_id: "Credentialz-2" +description: "SSH Password Login Disallowed" +testbed: TESTBED_DUT + +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + ssh_server_counters_unsupported: true + } +} \ No newline at end of file diff --git a/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go b/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go new file mode 100644 index 00000000000..f2cd77e93e3 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go @@ -0,0 +1,188 @@ +// 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 sshpasswordlogindisallowed_test + +import ( + "context" + "os" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "testing" + "time" + + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/security/credz" + acctzpb "github.com/openconfig/gnsi/acctz" + cpb "github.com/openconfig/gnsi/credentialz" + "github.com/openconfig/ondatra" +) + +const ( + username = "testuser" + userPrincipal = "my_principal" + command = "show version" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestCredentialz(t *testing.T) { + dut := ondatra.DUT(t, "dut") + target := credz.GetDutTarget(t, dut) + recordStartTime := timestamppb.New(time.Now()) + + // 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) + + // Create ssh keys/certificates for CA & testuser. + credz.CreateSSHKeyPair(t, dir, "ca") + credz.CreateSSHKeyPair(t, dir, username) + credz.CreateUserCertificate(t, dir, userPrincipal) + + // Setup user and password. + credz.SetupUser(t, dut, username) + password := credz.GeneratePassword() + credz.RotateUserPassword(t, dut, username, password, "v1.0", uint64(time.Now().Unix())) + + credz.RotateTrustedUserCA(t, dut, dir) + credz.RotateAuthenticationTypes(t, dut, []cpb.AuthenticationType{ + cpb.AuthenticationType_AUTHENTICATION_TYPE_PUBKEY, + }) + credz.RotateAuthorizedPrincipal(t, dut, username, userPrincipal) + + t.Run("auth should fail ssh password authentication disallowed", func(t *testing.T) { + var startingRejectCounter, startingLastRejectTime uint64 + if !deviations.SSHServerCountersUnsupported(dut) { + startingRejectCounter, startingLastRejectTime = credz.GetRejectTelemetry(t, dut) + } + + // Verify ssh with password fails as expected. + _, err := credz.SSHWithPassword(target, username, password) + if err == nil { + t.Fatalf("Dialing ssh succeeded, but we expected to fail.") + } + + // Verify ssh counters. + if !deviations.SSHServerCountersUnsupported(dut) { + endingRejectCounter, endingLastRejectTime := credz.GetRejectTelemetry(t, dut) + if endingRejectCounter <= startingRejectCounter { + t.Fatalf("SSH server reject counter did not increment after unsuccessful login. startCounter: %v, endCounter: %v", startingRejectCounter, endingRejectCounter) + } + if startingLastRejectTime == endingLastRejectTime { + t.Fatalf("SSH server reject last timestamp did not update after unsuccessful login. Timestamp: %v", endingLastRejectTime) + } + } + }) + + t.Run("auth should succeed ssh certificate authentication allowed", func(t *testing.T) { + var startingAcceptCounter, startingLastAcceptTime uint64 + if !deviations.SSHServerCountersUnsupported(dut) { + startingAcceptCounter, startingLastAcceptTime = credz.GetAcceptTelemetry(t, dut) + } + + // Verify ssh with certificate succeeds. + conn, err := credz.SSHWithCertificate(t, target, username, dir) + if err != nil { + t.Fatalf("Dialing ssh failed, but we expected to succeed, error: %s", err) + } + defer conn.Close() + + // Send command for accounting. + sess, err := conn.NewSession() + if err != nil { + t.Fatalf("Failed creating ssh session, error: %s", err) + } + defer sess.Close() + sess.Run(command) + + // 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) + } + if startingLastAcceptTime == endingLastAcceptTime { + t.Fatalf("SSH server accept last timestamp did not update after successful login. Timestamp: %v", endingLastAcceptTime) + } + } + + // Verify accounting record. + acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + acctzSubClient, err := acctzClient.RecordSubscribe(ctx, &acctzpb.RecordRequest{Timestamp: recordStartTime}) + if err != nil { + t.Fatalf("Failed sending accountz record request, error: %s", err) + } + defer acctzSubClient.CloseSend() + + var foundRecord bool + for { + acctzResponse, err := acctzSubClient.Recv() + if err != nil { + if status.Code(err) == codes.DeadlineExceeded { + t.Log("Done receiving records...") + break + } + t.Fatalf( + "Failed receiving from accountz record subscribe client, "+ + "this could mean we didn't find the user identity and eventually timed "+ + "out with no more records to review, or another server error. Error: %s", + err, + ) + } + + // Skip non-ssh records. + if acctzResponse.GetCmdService() == nil { + continue + } + + reportedIdentity := acctzResponse.GetSessionInfo().GetUser().GetIdentity() + if reportedIdentity == username { + t.Logf("Found Record: %s", credz.PrettyPrint(acctzResponse)) + foundRecord = true + break + } + } + if !foundRecord { + t.Fatalf("Did not find accounting record for %s", username) + } + }) + + t.Cleanup(func() { + // Cleanup to remove previous policy which only allowed key auth to make sure we don't + // leave dut in a state where we can't reset config for further tests. + credz.RotateAuthenticationTypes(t, dut, []cpb.AuthenticationType{ + cpb.AuthenticationType_AUTHENTICATION_TYPE_PASSWORD, + cpb.AuthenticationType_AUTHENTICATION_TYPE_PUBKEY, + cpb.AuthenticationType_AUTHENTICATION_TYPE_KBDINTERACTIVE, + }) + }) +} diff --git a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/README.md b/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/README.md new file mode 100644 index 00000000000..fe13bbdb82f --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/README.md @@ -0,0 +1,52 @@ +# Credentialz-4: SSH Public Key Authentication + +## Summary + +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. + + +## Procedure + +* Create a user ssh keypair with `ssh-keygen` +* Set a username of `testuser` +* Perform the following tests and assert the expected result: + * Case 1: Failure + * Attempt to ssh into the server with the `testuser` username, presenting the ssh key. + * Assert that authentication has failed (because the key is not authorized) + * Case 2: Success + * Configure the previously created ssh public key as an authorized key for the + `testuser` using gnsi.Credentialz/AuthorizedKeysRequest + * Authenticate with the `testuser` username and the previously created public key via SSH + * Assert that authentication has been successful + * 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` + + +## 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 ## + /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 \ No newline at end of file diff --git a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/metadata.textproto b/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/metadata.textproto new file mode 100644 index 00000000000..3281da1a9d9 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/metadata.textproto @@ -0,0 +1,16 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "3e6294aa-8f9d-4c8d-9041-4a2f4cd84c36" +plan_id: "Credentialz-4" +description: "SSH Public Key Authentication" +testbed: TESTBED_DUT + +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + ssh_server_counters_unsupported: true + } +} \ No newline at end of file diff --git a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go b/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go new file mode 100644 index 00000000000..de5530d7b20 --- /dev/null +++ b/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go @@ -0,0 +1,123 @@ +// 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 sshpublickeyauthentication_test + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/featureprofiles/internal/security/credz" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" +) + +const ( + username = "testuser" + authorizedKeysListVersion = "v1.0" +) + +var ( + authorizedKeysListCreatedOn = time.Now().Unix() +) + +func TestMain(m *testing.M) { + fptest.RunTests(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.CreateSSHKeyPair(t, dir, username) + 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) + if err == nil { + t.Fatalf("Dialing ssh succeeded, but we expected to fail.") + } + }) + + t.Run("auth should succeed ssh public key authorized for user", func(t *testing.T) { + // Push authorized key to the dut. + credz.RotateAuthorizedKey(t, + dut, + dir, + username, + authorizedKeysListVersion, + uint64(authorizedKeysListCreatedOn)) + + var startingAcceptCounter, startingLastAcceptTime uint64 + if !deviations.SSHServerCountersUnsupported(dut) { + startingAcceptCounter, startingLastAcceptTime = credz.GetAcceptTelemetry(t, dut) + } + + // Verify ssh with key succeeds. + _, err := credz.SSHWithKey(t, target, username, dir) + if err != nil { + t.Fatalf("Dialing ssh failed, but we expected to succeed. error: %v", err) + } + + // 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) + } + if startingLastAcceptTime == endingLastAcceptTime { + t.Fatalf("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, + ) + } + 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, + ) + } + }) + + t.Cleanup(func() { + // Cleanup user authorized key after test. + credz.RotateAuthorizedKey(t, dut, "", username, "", 0) + }) +} diff --git a/feature/sflow/feature.textproto b/feature/sflow/feature.textproto index 2bf860d0b8f..e253000ce58 100644 --- a/feature/sflow/feature.textproto +++ b/feature/sflow/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/sflow/otg_tests/sflow_base_test/README.md b/feature/sflow/otg_tests/sflow_base_test/README.md index 840cdf540e8..b83b28e15e2 100644 --- a/feature/sflow/otg_tests/sflow_base_test/README.md +++ b/feature/sflow/otg_tests/sflow_base_test/README.md @@ -6,7 +6,7 @@ Verify configuration of sflow and sFlow sample data. ## Procedure -* SFLOW-1.1 Configure sFlow on DUT +* SFLOW-1.1 Configure sFlow on DUT on a non-default VRF * Configure DUT and ATE with 2 ports * Configure DUT to send sflow samples to ATE port 2 * Set sample source address, sample size 256Bytes, one sample per 1M packets and DSCP=32 @@ -20,10 +20,10 @@ Verify configuration of sflow and sFlow sample data. | mflow3 | 100000 | 512 | IP | TCP | | lflow3 | 100000 | 1500 | IP | TCP | -* Verify captured packets are formatted like an sFlow packet - * Verify sample size is 256B - * Verify 1 sample sent to collector address per 1M packets generated by ATE - * Verify sample packet is set with DSCP=32 + * Verify captured packets are formatted like an sFlow packet + * Verify sample size is 256B + * Verify 1 sample sent to collector address per 1M packets generated by ATE + * Verify sample packet is set with DSCP=32 * SFLOW-1.3 [TODO #2346]( https://github.com/openconfig/featureprofiles/issues/2346): Additional sflow packet verifications * Using the same packets captured in SFLOW-1.2 verify @@ -34,57 +34,57 @@ Verify configuration of sflow and sFlow sample data. * Next hop source mask * Next hop destination mask -## Config Parameter coverage - -/sampling/sflow/config/agent-id-ipv4 -/sampling/sflow/config/agent-id-ipv6 -/sampling/sflow/config/dscp -/sampling/sflow/config/egress-sampling-rate -/sampling/sflow/config/enabled -/sampling/sflow/config/ingress-sampling-rate -/sampling/sflow/config/polling-interval -/sampling/sflow/config/sample-size -/sampling/sflow/config/source-address -/sampling/sflow/interfaces/interface/config/name -/sampling/sflow/interfaces/interface/config/enabled -/sampling/sflow/interfaces/interface/config/egress-sampling-rate -/sampling/sflow/interfaces/interface/config/ingress-sampling-rate -/sampling/sflow/interfaces/interface/config/polling-interval - -/sampling/sflow/collectors/collector/address -/sampling/sflow/collectors/collector/config/address -/sampling/sflow/collectors/collector/config/network-instance -/sampling/sflow/collectors/collector/config/port -/sampling/sflow/collectors/collector/config/source-address -/sampling/sflow/collectors/collector/port - -## Telemetry Parameter coverage - -/sampling/sflow/state/agent-id-ipv4 -/sampling/sflow/state/agent-id-ipv6 -/sampling/sflow/state/dscp -/sampling/sflow/state/egress-sampling-rate -/sampling/sflow/state/enabled -/sampling/sflow/state/ingress-sampling-rate -/sampling/sflow/state/polling-interval -/sampling/sflow/state/sample-size -/sampling/sflow/state/source-address -/sampling/sflow/interfaces/interface/state/name -/sampling/sflow/interfaces/interface/state/enabled -/sampling/sflow/interfaces/interface/state/egress-sampling-rate -/sampling/sflow/interfaces/interface/state/ingress-sampling-rate -/sampling/sflow/interfaces/interface/state/polling-interval - -/sampling/sflow/collectors/collector/address -/sampling/sflow/collectors/collector/state/address -/sampling/sflow/collectors/collector/state/network-instance -/sampling/sflow/collectors/collector/state/port -/sampling/sflow/collectors/collector/state/source-address -/sampling/sflow/collectors/collector/port - -## Protocol/RPC Parameter coverage - -N/A +## OpenConfig Path and RPC Coverage + +```yaml +paths: + ## Config Parameter coverage + /sampling/sflow/config/agent-id-ipv4: + /sampling/sflow/config/agent-id-ipv6: + /sampling/sflow/config/dscp: + /sampling/sflow/config/egress-sampling-rate: + /sampling/sflow/config/enabled: + /sampling/sflow/config/ingress-sampling-rate: + /sampling/sflow/config/polling-interval: + /sampling/sflow/config/sample-size: + /sampling/sflow/interfaces/interface/config/name: + /sampling/sflow/interfaces/interface/config/enabled: + /sampling/sflow/interfaces/interface/config/egress-sampling-rate: + /sampling/sflow/interfaces/interface/config/ingress-sampling-rate: + /sampling/sflow/interfaces/interface/config/polling-interval: + + /sampling/sflow/collectors/collector/config/address: + /sampling/sflow/collectors/collector/config/network-instance: + /sampling/sflow/collectors/collector/config/port: + /sampling/sflow/collectors/collector/config/source-address: + + ## Telemetry Parameter coverage + /sampling/sflow/state/agent-id-ipv4: + /sampling/sflow/state/agent-id-ipv6: + /sampling/sflow/state/dscp: + /sampling/sflow/state/egress-sampling-rate: + /sampling/sflow/state/enabled: + /sampling/sflow/state/ingress-sampling-rate: + /sampling/sflow/state/polling-interval: + /sampling/sflow/state/sample-size: + /sampling/sflow/interfaces/interface/state/name: + /sampling/sflow/interfaces/interface/state/enabled: + /sampling/sflow/interfaces/interface/state/egress-sampling-rate: + /sampling/sflow/interfaces/interface/state/ingress-sampling-rate: + /sampling/sflow/interfaces/interface/state/polling-interval: + + /sampling/sflow/collectors/collector/address: + /sampling/sflow/collectors/collector/state/address: + /sampling/sflow/collectors/collector/state/network-instance: + /sampling/sflow/collectors/collector/state/port: + /sampling/sflow/collectors/collector/state/source-address: + /sampling/sflow/collectors/collector/port: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` ## Minimum DUT platform requirement diff --git a/feature/sflow/otg_tests/sflow_base_test/metadata.textproto b/feature/sflow/otg_tests/sflow_base_test/metadata.textproto index 38d913cd114..362a5a39bb7 100644 --- a/feature/sflow/otg_tests/sflow_base_test/metadata.textproto +++ b/feature/sflow/otg_tests/sflow_base_test/metadata.textproto @@ -1,4 +1,4 @@ -# proto-file: third_party/openconfig/featureprofiles/proto/metadata.proto +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata uuid: "25b389ff-8526-46e3-acf2-016e86aff406" @@ -21,6 +21,7 @@ platform_exceptions: { explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true + static_protocol_name: "static" } } platform_exceptions: { @@ -32,5 +33,7 @@ platform_exceptions: { interface_config_vrf_before_address: true interface_enabled: true default_network_instance: "default" + static_protocol_name: "STATIC" + sflow_source_address_update_unsupported: 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 704e4deaa4d..f2c61917635 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 @@ -15,39 +15,58 @@ package sflow_base_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/ondatra/netutil" "github.com/openconfig/ygot/ygot" ) const ( - ipv4PrefixLen = 30 - frameSize = 512 // size of packets in bytes to generate with ATE - packetsToSend = 10000000 // 10 million - ppsRate = 1000000 // 1 million - plenIPv4 = 30 - plenIPv6 = 126 - lossTolerance = 0 + ipv4PrefixLen = 30 + plenIPv4 = 30 + plenIPv6 = 126 + lossTolerance = 1 + mgmtVRF = "mvrf1" + sampleTolerance = 0.8 + samplingRate = 1000000 ) var ( - staticRoute = &cfgplugins.StaticRouteCfg{ - NIName: "DEFAULT", - Prefix: "192.0.2.128/30", - Nexthop: "192.0.2.6", + staticRouteV4 = &cfgplugins.StaticRouteCfg{ + NetworkInstance: mgmtVRF, + Prefix: "192.0.2.128/30", + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString("192.0.2.6"), + }, + } + staticRouteV6 = &cfgplugins.StaticRouteCfg{ + NetworkInstance: mgmtVRF, + Prefix: "2001:db8::128/126", + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "1": oc.UnionString("2001:db8::6"), + }, } dutSrc = &attrs.Attributes{ Desc: "DUT to ATE source", IPv4: "192.0.2.1", IPv4Len: plenIPv4, - IPv6: "2001:db8::2", + IPv6: "2001:db8::1", IPv6Len: plenIPv6, } dutDst = &attrs.Attributes{ @@ -57,37 +76,73 @@ var ( IPv6: "2001:db8::5", IPv6Len: plenIPv6, } -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -// configInterfaceDUT configures the DUT interfaces. -func configInterfaceDUT(p1 *ondatra.Port, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + ateSrc = &attrs.Attributes{ + Name: "ateSrc", + Desc: "ATE to DUT source", + IPv4: "192.0.2.2", + IPv4Len: plenIPv4, + IPv6: "2001:db8::2", + IPv6Len: plenIPv6, + MAC: "02:00:01:01:01:01", + } + ateDst = &attrs.Attributes{ + Name: "ateDst", + Desc: "ATE to DUT destination", + IPv4: "192.0.2.6", + IPv4Len: plenIPv4, + IPv6: "2001:db8::6", + IPv6Len: plenIPv6, + MAC: "02:00:02:01:01:01", + } - i := &oc.Interface{Name: ygot.String(p1.Name())} + loopbackSubIntfNum = 1 - i.Description = ygot.String(a.Desc) - i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd - if deviations.InterfaceEnabled(dut) { - i.Enabled = ygot.Bool(true) + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.1", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, } - s := i.GetOrCreateSubinterface(0) - s4 := s.GetOrCreateIpv4() - if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { - s4.Enabled = ygot.Bool(true) + flowConfigs = []flowConfig{ + { + name: "flowS", + packetsToSend: 1000000, + ppsRate: 100000, + frameSize: 64, + }, + { + name: "flowM", + packetsToSend: 1000000, + ppsRate: 100000, + frameSize: 512, + }, + { + name: "flowL", + packetsToSend: 1000000, + ppsRate: 100000, + frameSize: 1500, + }, } - s4.GetOrCreateAddress(a.IPv4).PrefixLength = ygot.Uint8(plenIPv4) +) - s6 := s.GetOrCreateIpv6() - if deviations.InterfaceEnabled(dut) { - s6.Enabled = ygot.Bool(true) - } - s6.GetOrCreateAddress(a.IPv6).PrefixLength = ygot.Uint8(plenIPv6) +type flowConfig struct { + name string + packetsToSend uint32 + ppsRate uint64 + frameSize uint32 +} + +type IPType string - return i +const ( + IPv4 = "IPv4" + IPv6 = "IPv6" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) } // configureDUTBaseline configures port1 and port2 on the DUT. @@ -96,10 +151,10 @@ 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(), configInterfaceDUT(p1, dutSrc, dut)) + 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(), configInterfaceDUT(p2, dutDst, dut)) + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), dutDst.NewOCInterface(p2.Name(), dut)) if deviations.ExplicitPortSpeed(dut) { fptest.SetPortSpeed(t, p1) @@ -112,24 +167,48 @@ func configureDUTBaseline(t *testing.T, dut *ondatra.DUTDevice) { } // TestSFlowTraffic configures a DUT for sFlow client and collector endpoint and uses ATE to send -// traffic which the DUT should sample and send sFlow packets to a collector. ATE captures the +// traffic which the DUT should sample and send sFlow packets to a collector. ATE captures the // sflow packets which are decoded by the test to verify they are valid sflow packets. func TestSFlowTraffic(t *testing.T) { dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + + ate := ondatra.ATE(t, "ate") + + switch dut.Vendor() { + case ondatra.JUNIPER: + loopbackSubIntfNum = 0 + } + + loopbackIntfName := netutil.LoopbackInterface(t, dut, loopbackSubIntfNum) + + // Configure DUT + if !deviations.InterfaceConfigVRFBeforeAddress(dut) { + configureDUTBaseline(t, dut) + configureLoopbackOnDUT(t, dut) + } - // configure interfaces on DUT - // TODO: consider refactoring interface configs into cfgplugins - configureDUTBaseline(t, dut) + fptest.ConfigureDefaultNetworkInstance(t, dut) + addInterfacesToVRF(t, dut, mgmtVRF, []string{p1.Name(), p2.Name(), loopbackIntfName}) + + // For interface configuration, Arista prefers config Vrf first then the IP address + if deviations.InterfaceConfigVRFBeforeAddress(dut) { + configureDUTBaseline(t, dut) + configureLoopbackOnDUT(t, dut) + } + + config := configureATE(t, ate) + otgutils.WaitForARP(t, ate.OTG(), config, "IPv4") srBatch := &gnmi.SetBatch{} - cfgplugins.NewStaticRouteCfg(srBatch, staticRoute, dut) + cfgplugins.NewStaticRouteCfg(srBatch, staticRouteV4, dut) + cfgplugins.NewStaticRouteCfg(srBatch, staticRouteV6, dut) srBatch.Set(t, dut) - sfBatch := &gnmi.SetBatch{} - cfgplugins.NewSFlowGlobalCfg(sfBatch, nil, dut) - cfgplugins.NewSFlowCollector(sfBatch, nil, dut) - t.Run("SFLOW-1.1_ReplaceDUTConfigSFlow", func(t *testing.T) { + sfBatch := &gnmi.SetBatch{} + cfgplugins.NewSFlowGlobalCfg(t, sfBatch, nil, dut, mgmtVRF, loopbackIntfName, dutlo0Attrs.IPv4, dutlo0Attrs.IPv6) sfBatch.Set(t, dut) gotSamplingConfig := gnmi.Get(t, dut, gnmi.OC().Sampling().Sflow().Config()) @@ -145,6 +224,7 @@ func TestSFlowTraffic(t *testing.T) { } t.Logf("Got sampling config: %v", json) }) + /* TODO: implement this when a suitable ygot.diffBatch function exists // Validate DUT sampling config matches what we set it to diff, err := ygot.Diff(gotSamplingConfig, sfBatch) @@ -156,6 +236,223 @@ func TestSFlowTraffic(t *testing.T) { } }) */ - // TODO: Configure ATE - // TODO: Send traffic, capture and decode + + t.Run("SFLOW-1.2_TestFlowFixed", func(t *testing.T) { + t.Run("SFLOW-1.2.1_IPv4", func(t *testing.T) { + enableCapture(t, ate, config, IPv4) + testFlowFixed(t, ate, config, IPv4) + }) + t.Run("SFLOW-1.2.2_IPv6", func(t *testing.T) { + enableCapture(t, ate, config, IPv6) + testFlowFixed(t, ate, config, IPv6) + }) + }) +} + +func testFlowFixed(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, ip IPType) { + for _, fc := range flowConfigs { + flowName := string(ip) + fc.name + t.Run(flowName, func(t *testing.T) { + createFlow(t, ate, config, fc, ip) + + cs := startCapture(t, ate, config) + + sleepTime := time.Duration(fc.packetsToSend/uint32(fc.ppsRate)) + 5 + ate.OTG().StartTraffic(t) + time.Sleep(sleepTime * time.Second) + ate.OTG().StopTraffic(t) + + stopCapture(t, ate, cs) + + otgutils.LogFlowMetrics(t, ate.OTG(), config) + otgutils.LogPortMetrics(t, ate.OTG(), config) + + loss := otgutils.GetFlowLossPct(t, ate.OTG(), flowName, 10*time.Second) + if loss > lossTolerance { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want %f", loss, float64(lossTolerance)) + } + + processCapture(t, ate, config, ip, fc) + }) + } +} + +func startCapture(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) gosnappi.ControlState { + t.Helper() + + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + ate.OTG().SetControlState(t, cs) + + return cs +} + +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) +} + +func processCapture(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, ip IPType, fc flowConfig) { + bytes := ate.OTG().GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(config.Ports().Items()[1].Name())) + 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(), ip, fc) +} + +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + config := gosnappi.NewConfig() + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + ateSrc.AddToOTG(config, p1, dutSrc) + ateDst.AddToOTG(config, p2, dutDst) + + ate.OTG().PushConfig(t, config) + ate.OTG().StartProtocols(t) + + return config +} + +func enableCapture(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, ip IPType) { + t.Helper() + + config.Captures().Clear() + // enable packet capture on this port + cap := config.Captures().Add().SetName("sFlowpacketCapture"). + SetPortNames([]string{config.Ports().Items()[1].Name()}). + SetFormat(gosnappi.CaptureFormat.PCAP) + filter := cap.Filters().Add() + if ip == IPv4 { + // filter on hex value of IPv4 - 203.0.113.1 + filter.Ipv4().Src().SetValue("cb007101") + } else { + // filter on hex value of IPv6 - 2001:db8::203:0:113:1 + filter.Ipv6().Src().SetValue("20010db8000000000203000001130001") + } + + ate.OTG().PushConfig(t, config) + ate.OTG().StartProtocols(t) + + pb, _ := config.Marshal().ToProto() + t.Log(pb.GetCaptures()) +} + +func addInterfacesToVRF(t *testing.T, dut *ondatra.DUTDevice, vrfname string, intfNames []string) { + root := &oc.Root{} + mgmtNI := root.GetOrCreateNetworkInstance(vrfname) + mgmtNI.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + for _, intfName := range intfNames { + vi := mgmtNI.GetOrCreateInterface(intfName) + vi.Interface = ygot.String(intfName) + vi.Subinterface = ygot.Uint32(0) + } + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(mgmtVRF).Config(), mgmtNI) + t.Logf("Added interface %v to VRF %s", intfNames, vrfname) +} + +func configureLoopbackOnDUT(t *testing.T, dut *ondatra.DUTDevice) { + loopbackIntfName := netutil.LoopbackInterface(t, dut, loopbackSubIntfNum) + loop := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + loop.Description = ygot.String(fmt.Sprintf("Port %s", loopbackIntfName)) + gnmi.Update(t, dut, gnmi.OC().Interface(loopbackIntfName).Config(), loop) + t.Logf("Got DUT IPv4, IPv6 loopback address: %v, %v", dutlo0Attrs.IPv4, dutlo0Attrs.IPv6) +} + +func createFlow(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, fc flowConfig, ip IPType) { + config.Flows().Clear() + + t.Log("Configuring traffic flow") + flow := config.Flows().Add().SetName(string(ip) + fc.name) + flow.Metrics().SetEnable(true) + flow.Size().SetFixed(fc.frameSize) + flow.Rate().SetPps(fc.ppsRate) + flow.Duration().FixedPackets().SetPackets(fc.packetsToSend) + e1 := flow.Packet().Add().Ethernet() + e1.Src().SetValues([]string{ateSrc.MAC}) + + switch ip { + case IPv4: + flow.TxRx().Device(). + SetTxNames([]string{"ateSrc.IPv4"}). + SetRxNames([]string{"ateDst.IPv4"}) + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(ateSrc.IPv4) + v4.Dst().SetValue(ateDst.IPv4) + case IPv6: + flow.TxRx().Device(). + SetTxNames([]string{"ateSrc.IPv6"}). + SetRxNames([]string{"ateDst.IPv6"}) + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(ateSrc.IPv6) + v6.Dst().SetValue(ateDst.IPv6) + } + + ate.OTG().PushConfig(t, config) + ate.OTG().StartProtocols(t) +} + +func validatePackets(t *testing.T, filename string, ip IPType, fc flowConfig) { + handle, err := pcap.OpenOffline(filename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + + loopbackIP := net.ParseIP(dutlo0Attrs.IPv4) + if ip == IPv6 { + loopbackIP = net.ParseIP(dutlo0Attrs.IPv6) + } + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + + found := false + packetCount := 0 + sflowSamples := uint32(0) + for packet := range packetSource.Packets() { + if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil { + ipv4, _ := ipLayer.(*layers.IPv4) + if ipv4.SrcIP.Equal(loopbackIP) { + t.Logf("tos %d, payload %d, content %d, length %d", ipv4.TOS, len(ipv4.Payload), len(ipv4.Contents), ipv4.Length) + if ipv4.TOS == 32 { + found = true + break + } + } + } else if ipLayer := packet.Layer(layers.LayerTypeIPv6); ipLayer != nil { + ipv6, _ := ipLayer.(*layers.IPv6) + if ipv6.SrcIP.Equal(loopbackIP) { + t.Logf("tos %d, payload %d, content %d, length %d", ipv6.TrafficClass, len(ipv6.Payload), len(ipv6.Contents), ipv6.Length) + if ipv6.TrafficClass == 32 { + found = true + break + } + } + } + + } + if !found { + t.Error("sflow packets not found") + } + + for packet := range packetSource.Packets() { + if sflowLayer := packet.Layer(layers.LayerTypeSFlow); sflowLayer != nil { + sflow := sflowLayer.(*layers.SFlowDatagram) + packetCount++ + sflowSamples += sflow.SampleCount + t.Logf("SFlow Packet count: %v - SampleCount: %v", packetCount, sflowSamples) + } + } + + expectedSampleCount := float64(fc.packetsToSend / samplingRate) + minAllowedSamples := expectedSampleCount * sampleTolerance + if sflowSamples < uint32(minAllowedSamples) { + t.Errorf("SFlow sample count %v, want > %v", sflowSamples, expectedSampleCount) + } } diff --git a/feature/staticroute/feature.textproto b/feature/staticroute/feature.textproto index ca5a09a3f02..9ee16d1323e 100644 --- a/feature/staticroute/feature.textproto +++ b/feature/staticroute/feature.textproto @@ -1,3 +1,6 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto +# proto-message: FeatureProfile + id { name: "staticroute" version: 1 diff --git a/feature/staticroute/otg_tests/basic_static_route_support_test/README.md b/feature/staticroute/otg_tests/basic_static_route_support_test/README.md new file mode 100644 index 00000000000..1127cffe981 --- /dev/null +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/README.md @@ -0,0 +1,228 @@ +# RT-1.26: Basic static route support + +## Summary + +- Static route ECMP must be supported +- Static route metric must be supported +- Static route Administrative Distance / Preference must be supported +- `set-tag` attribute must be supported for static routes +- Disabling recursive nexthop resolution must be supported + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed + +## Procedure + +#### Initial 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/IPv6 addresses on DUT and ATE the interfaces +* Configure one IPv4 destination i.e. `ipv4-network = 203.0.113.0/24` + connected to ATE port 1 and 2 +* Configure one IPv6 destination i.e. `ipv6-network = 2001:db8:128:128::/64` + connected to ATE port 1 and 2 + +### RT-1.26.1 + +#### Test to validate static route ECMP + +* Configure IPv4 static routes: + * Configure one IPv4 static route i.e. ipv4-route-a on the DUT for + destination `ipv4-network 203.0.113.0/24` with the next hop set to the + IPv4 address of ATE port-1 + * Configure another IPv4 static route i.e. ipv4-route-b on the DUT for + destination `ipv4-network 203.0.113.0/24` with the next hop set to the + IPv4 address of ATE port-2 +* Validate both the routes i.e. ipv4-route-[a|b] are configured and reported + correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/prefix +* Configure IPv6 static routes: + * Configure one IPv6 static route i.e. ipv6-route-a on the DUT for + destination `ipv6-network 2001:db8:128:128::/64` with the next hop set + to the IPv6 address of ATE port-1 + * Configure another IPv6 static route i.e. ipv6-route-b on the DUT for + destination `ipv6-network 2001:db8:128:128::/64` with the next hop set + to the IPv6 address of ATE port-2 +* Validate both the routes i.e. ipv6-route-[a|b] are configured and reported + correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/prefix +* Initiate traffic from ATE port-3 towards destination `ipv4-network + 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` +* Validate that traffic is received from DUT on both port-1 and port-2 and + ECMP works + +### RT-1.26.2 + +#### Test to validate static route metric + +* Configure metric of ipv4-route-b and ipv6-route-b to 100 + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric +* Validate that the metric is set correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/metric +* Initiate traffic from ATE port-3 towards destination `ipv4-network + 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` +* Validate that traffic is received from DUT on port-1 and not on port-2 + +### RT-1.26.3 + +#### Test to validate static route preference + +* Configure preference of ipv4-route-a and ipv6-route-a to 50 + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/preference +* Validate that the preference is set correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/preference +* Initiate traffic from ATE port-3 towards destination `ipv4-network + 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` +* Validate that traffic is now received from DUT on port-2 and not on port-1 + +### RT-1.26.4 + +#### Test to validate static route tag + +* Configure a tag of value 10 on ipv4 and ipv6 static routes + * /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag +* Validate the tag is set + * /network-instances/network-instance/protocols/protocol/static-routes/static/state/set-tag + +### RT-1.26.5 + +#### Test to validate IPv6 static route with IPv4 next-hop + +* Remove metric of 100 from ipv4-route-b and ipv6-route-b + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric +* Remove preference of 50 from ipv4-route-a and ipv6-route-a + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/preference +* Change the IPv6 next-hop of the ipv6-route-a with the next hop set to the + IPv4 address of ATE port-1 +* Change the IPv6 next-hop of the ipv6-route-b with the next hop set to the + IPv4 address of ATE port-2 +* Validate both the routes i.e. ipv6-route-[a|b] are configured and the IPv4 + next-hop is reported correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop +* Initiate traffic from ATE port-3 towards destination `ipv6-network + 2001:db8:128:128::/64` +* Validate that traffic is received from DUT on both port-1 and port-2 and + ECMP works + +### RT-1.26.6 + +#### Test to validate IPv4 static route with IPv6 next-hop + +* Change the IPv4 next-hop of the ipv4-route-a with the next hop set to the + IPv6 address of ATE port-1 +* Change the IPv4 next-hop of the ipv4-route-b with the next hop set to the + IPv6 address of ATE port-2 +* Validate both the routes i.e. ipv4-route-[a|b] are configured and the IPv6 + next-hop is reported correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/prefix + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop +* Initiate traffic from ATE port-3 towards destination `ipv4-network + 203.0.113.0/24` +* Validate that traffic is received from DUT on both port-1 and port-2 and + ECMP works + +### RT-1.26.7 + +#### Test to validate static route with DROP next-hop + +* Configure IPv4 static routes: + * Configure one IPv4 static route i.e. ipv4-route-a on the DUT for + destination `ipv4-network 203.0.113.0/24` with the next hop set to DROP + local-defined next hop +* Validate the route is configured and reported correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/prefix +* Configure IPv6 static routes: + * Configure one IPv6 static route i.e. ipv6-route-a on the DUT for + destination `ipv6-network 2001:db8:128:128::/64` with the next hop set + to DROP local-defined next hop +* Validate the route is configured and reported correctly + * /network-instances/network-instance/protocols/protocol/static-routes/static/prefix +* Initiate traffic from ATE port-3 towards destination `ipv4-network + 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` +* Validate that traffic is dropped on DUT and not received on port-1 and + port-2 + +### RT-1.26.8 + +#### Test to validate disabling of recursive next-hop resolution + +* Configure ipv4 and ipv6 ISIS between ATE port-1 <-> DUT port-1 and ATE + port-2 <-> DUT port2 + * /network-instances/network-instance/protocols/protocol/isis/global/afi-safi +* Configure one IPv4 /32 host route i.e. `ipv4-loopback = 198.51.100.100/32` + connected to ATE and advertised to DUT through both the IPv4 ISIS + adjacencies +* Configure one IPv6 /128 host route i.e. `ipv6-loopback = + 2001:db8::64:64::1/128` connected to ATE and advertised to DUT through both + the IPv6 ISIS adjacencies +* Configure one IPv4 static route i.e. ipv4-route on the DUT for destination + `ipv4-network 203.0.113.0/24` with the next hop of `ipv4-loopback + 198.51.100.100/32`. Remove all other existing next hops for the route. + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop +* Configure one IPv6 static route i.e. ipv6-route on the DUT for destination + `ipv6-network 2001:db8:128:128::/64` with the next hop of `ipv6-loopback = + 2001:db8::64:64::1/128`. Remove all other existing next hops for the route. + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop +* Initiate traffic from ATE port-3 towards destination `ipv4-network + 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` +* Validate that traffic is received from DUT (doesn't matter which port) +* Disable static route next-hop recursive lookup (set to false) + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/recurse +* Validate static route next-hop recursive lookup is disabled + * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/recurse +* Initiate traffic from ATE port-3 towards destination `ipv4-network + 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` +* Validate that traffic is NOT received from DUT + +### RT-1.26.9 + +#### Test to validate add and remove to next-hops in a static route + +* Configure one IPv4 static route i.e. ipv4-route with the next hop set to the + IPv4 address of ATE port-2(0 index) and port-3(1 index). +* Validate next-hops of `ipv4-route` static route and indexes. +* Update IPv4 static route i.e. ipv4-route with the next hop set to the IPv4 + address of ATE port-1(0 index), port-2(1 index), port-3(2 index) and + port-4(3 index). +* Validate next-hops of `ipv4-route` static route and indexes. +* Remove two next hops at index 0 and 3 added in previous step. +* Validate next-hops of `ipv4-route` static route and indexes. + +## 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 ## + /interfaces/interface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled: + /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix: + /network-instances/network-instance/protocols/protocol/static-routes/static/config/set-tag: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/preference: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/recurse: + + ## State Paths ## + /network-instances/network-instance/protocols/protocol/static-routes/static/state/prefix: + /network-instances/network-instance/protocols/protocol/static-routes/static/state/set-tag: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/next-hop: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/metric: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/preference: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/recurse: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` + +## Required DUT platform + +* FFF 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 new file mode 100644 index 00000000000..932e28281af --- /dev/null +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go @@ -0,0 +1,1454 @@ +package basic_static_route_support_test + +import ( + "fmt" + "net" + "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/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + isisName = "DEFAULT" + dutAreaAddr = "49.0001" + ateAreaAddr = "49.0002" + dutSysID = "1920.0000.2001" + ate1SysID = "640000000001" + ate2SysID = "640000000002" + v4Route = "203.0.113.0" + v4TrafficStart = "203.0.113.1" + v4RoutePrefix = uint32(24) + v6Route = "2001:db8:128:128::0" + v6TrafficStart = "2001:db8:128:128::1" + v6RoutePrefix = uint32(64) + v4LoopbackRoute = "198.51.100.100" + v4LoopbackRoutePrefix = uint32(32) + v6LoopbackRoute = "2001:db8:64:64::1" + v6LoopbackRoutePrefix = uint32(128) + v4Flow = "v4Flow" + v6Flow = "v6Flow" + trafficDuration = 2 * time.Minute + lossTolerance = float64(1) + ecmpTolerance = uint64(2) + port1Tag = "0x101" + port2Tag = "0x102" + dummyV6 = "2001:db8::192:0:2:d" + dummyMAC = "00:1A:11:00:0A:BC" + explicitMetricTolerance = float64(2) +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:1", + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:2", + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:5", + IPv6Len: ipv6PrefixLen, + } + + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:01:01:01:02", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:9", + IPv6Len: ipv6PrefixLen, + } + + atePort3 = attrs.Attributes{ + Name: "atePort3", + MAC: "02:00:01:01:01:03", + IPv4: "192.0.2.10", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:a", + IPv6Len: ipv6PrefixLen, + } + + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:d", + IPv6Len: ipv6PrefixLen, + } + + atePort4 = attrs.Attributes{ + Name: "atePort4", + MAC: "02:00:01:01:01:04", + IPv4: "192.0.2.14", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:db8::192:0:2:e", + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type ipAddr struct { + address string + prefix uint32 +} + +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() +} + +type testData struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + otgP1 gosnappi.Device + otgP2 gosnappi.Device + otgP3 gosnappi.Device + staticIPv4 ipAddr + staticIPv6 ipAddr + advertisedIPv4 ipAddr + advertisedIPv6 ipAddr +} + +func TestBasicStaticRouteSupport(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + devs := configureOTG(t, ate, top) + + td := testData{ + dut: dut, + ate: ate, + top: top, + otgP1: devs[0], + otgP2: devs[1], + otgP3: devs[2], + staticIPv4: ipAddr{address: v4Route, prefix: v4RoutePrefix}, + staticIPv6: ipAddr{address: v6Route, prefix: v6RoutePrefix}, + advertisedIPv4: ipAddr{address: v4Route, prefix: v4RoutePrefix}, + advertisedIPv6: ipAddr{address: v6Route, prefix: v6RoutePrefix}, + } + td.advertiseRoutesWithISIS(t) + td.configureOTGFlows(t) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + defer ate.OTG().StopProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + if err := td.awaitISISAdjacency(t, dut.Port(t, "port1"), isisName); err != nil { + t.Fatal(err) + } + if err := td.awaitISISAdjacency(t, dut.Port(t, "port2"), isisName); err != nil { + t.Fatal(err) + } + + tcs := []struct { + desc string + fn func(t *testing.T) + }{ + { + desc: "RT-1.26.1: Static Route ECMP", + fn: td.testStaticRouteECMP, + }, + { + desc: "RT-1.26.2: Static Route With Metric", + fn: td.testStaticRouteWithMetric, + }, + { + desc: "RT-1.26.3: Static Route With Preference", + fn: td.testStaticRouteWithPreference, + }, + { + desc: "RT-1.26.4: Static Route SetTag", + fn: td.testStaticRouteSetTag, + }, + { + desc: "RT-1.26.5: IPv6 Static Route With IPv4 Next Hop", + fn: td.testIPv6StaticRouteWithIPv4NextHop, + }, + { + desc: "RT-1.26.6: IPv4 Static Route With IPv6 Next Hop", + fn: td.testIPv4StaticRouteWithIPv6NextHop, + }, + { + desc: "RT-1.26.7: Static Route With Drop Next Hop", + fn: td.testStaticRouteWithDropNextHop, + }, + } + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + tc.fn(t) + }) + } +} + +func TestStaticRouteAddRemove(t *testing.T) { + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + configureOTG(t, ate, top) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + defer ate.OTG().StopProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + + prefix := ipAddr{address: v4Route, prefix: v4RoutePrefix} + 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(atePort2.IPv4), + "1": oc.UnionString(atePort3.IPv4), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) + validateStaticRoute(t, dut, prefix.cidr(t), sV4) + + // add 2 new nextHops, one at 0 index and another at 3 index + 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(atePort1.IPv4), + "1": oc.UnionString(atePort2.IPv4), + "2": oc.UnionString(atePort3.IPv4), + "3": oc.UnionString(atePort4.IPv4), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) + validateStaticRoute(t, dut, prefix.cidr(t), sV4) + + // remove previously added indexes + 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(atePort2.IPv4), + "1": oc.UnionString(atePort3.IPv4), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) + validateStaticRoute(t, dut, prefix.cidr(t), sV4) +} + +func validateStaticRoute(t *testing.T, dut *ondatra.DUTDevice, prefix string, sV4 *cfgplugins.StaticRouteCfg) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.Await(t, dut, sp.Static(prefix).Prefix().State(), 120*time.Second, prefix) + + if deviations.SkipStaticNexthopCheck(dut) { + nexthops := gnmi.LookupAll(t, dut, sp.Static(prefix).NextHopAny().NextHop().State()) + if got, want := len(nexthops), len(sV4.NextHops); got != want { + t.Errorf("Static route next hop count - %s: got: %v, want: %v", prefix, got, want) + } + } else { + // Validate both the routes i.e. ipv4-route-[a|b] are configured and reported + // correctly + gotStatic := gnmi.Get(t, dut, sp.Static(prefix).State()) + t.Logf("Static route %s: got: %v, want: %v", prefix, len(gotStatic.NextHop), len(sV4.NextHops)) + for index, nextHop := range gotStatic.NextHop { + if got, want := nextHop.GetNextHop(), sV4.NextHops[index]; got != want { + t.Errorf("Static route %s: got: %v, want: %v", prefix, got, want) + } + } + } +} + +func TestDisableRecursiveNextHopResolution(t *testing.T) { + dut := ondatra.DUT(t, "dut") + if deviations.UnsupportedStaticRouteNextHopRecurse(dut) { + t.Skip("Skipping Disable Recursive Next Hop Resolution Test. Deviation UnsupportedStaticRouteNextHopRecurse enabled.") + } + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + devs := configureOTG(t, ate, top) + + td := testData{ + dut: dut, + ate: ate, + top: top, + otgP1: devs[0], + otgP2: devs[1], + otgP3: devs[2], + staticIPv4: ipAddr{address: v4Route, prefix: v4RoutePrefix}, + staticIPv6: ipAddr{address: v6Route, prefix: v6RoutePrefix}, + advertisedIPv4: ipAddr{address: v4LoopbackRoute, prefix: v4LoopbackRoutePrefix}, + advertisedIPv6: ipAddr{address: v6LoopbackRoute, prefix: v6LoopbackRoutePrefix}, + } + + // Configure ipv4 and ipv6 ISIS between ATE port-1 <-> DUT port-1 and ATE + // port-2 <-> DUT port2. + // Configure one IPv4 /32 host route i.e. `ipv4-loopback = 198.51.100.100/32` + // connected to ATE and advertised to DUT through both the IPv4 ISIS + // adjacencies. + // Configure one IPv6 /128 host route i.e. `ipv6-loopback = + // 2001:db8::64:64::1/128` connected to ATE and advertised to DUT through both + // the IPv6 ISIS adjacencies. + td.advertiseRoutesWithISIS(t) + td.configureOTGFlows(t) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + defer ate.OTG().StopProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + if err := td.awaitISISAdjacency(t, dut.Port(t, "port1"), isisName); err != nil { + t.Fatal(err) + } + if err := td.awaitISISAdjacency(t, dut.Port(t, "port2"), isisName); err != nil { + t.Fatal(err) + } + + t.Run("RT-1.26.8: Disable Recursive Next Hop Resolution", func(t *testing.T) { + td.testRecursiveNextHopResolution(t) + td.testRecursiveNextHopResolutionDisabled(t) + }) +} + +func (td *testData) testRecursiveNextHopResolution(t *testing.T) { + b := &gnmi.SetBatch{} + // Configure one IPv4 static route i.e. ipv4-route on the DUT for destination + // `ipv4-network 203.0.113.0/24` with the next hop of `ipv4-loopback + // 198.51.100.100/32`. Remove all other existing next hops for the route. + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv4.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(td.advertisedIPv4.address), + }, + } + spV4, err := cfgplugins.NewStaticRouteCfg(b, sV4, td.dut) + if err != nil { + t.Fatal(err) + } + spV4.GetOrCreateNextHop("0").SetRecurse(true) + // Configure one IPv6 static route i.e. ipv6-route on the DUT for destination + // `ipv6-network 2001:db8:128:128::/64` with the next hop of `ipv6-loopback = + // 2001:db8::64:64::1/128`. Remove all other existing next hops for the route. + sV6 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv6.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(td.advertisedIPv6.address), + }, + } + spV6, err := cfgplugins.NewStaticRouteCfg(b, sV6, td.dut) + if err != nil { + t.Fatal(err) + } + spV6.GetOrCreateNextHop("0").SetRecurse(true) + + b.Set(t, td.dut) + + t.Run("Telemetry", func(t *testing.T) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + + _, ok := gnmi.Watch(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).State(), time.Second*60, func(v *ygnmi.Value[*oc.NetworkInstance_Protocol_Static]) bool { + val, present := v.Val() + return present && val.GetPrefix() == td.staticIPv4.cidr(t) + }).Await(t) + if !ok { + t.Errorf("IPv4 Static Route telemetry failed ") + } + _, ok = gnmi.Watch(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).State(), time.Second*60, func(v *ygnmi.Value[*oc.NetworkInstance_Protocol_Static]) bool { + val, present := v.Val() + return present && val.GetPrefix() == td.staticIPv6.cidr(t) + }).Await(t) + if !ok { + t.Errorf("IPv6 Static Route telemetry failed ") + } + + gotStatic := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.UnionString(td.advertisedIPv4.address); got != want { + t.Errorf("IPv4 Static Route next hop: got: %s, want: %s", got, want) + } + gotStatic = gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.UnionString(td.advertisedIPv6.address); got != want { + t.Errorf("IPv6 Static Route next hop: got: %s, want: %s", got, want) + } + }) + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv4-network + // 203.0.113.0/24` and `ipv6-network 2024:db8:128:128::/64` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) + + // Validate that traffic is received from DUT (doesn't matter which port) + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + if lossV4 > lossTolerance { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want 0%%", lossV4) + } + if lossV6 > lossTolerance { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want 0%%", lossV6) + } + }) +} + +func (td *testData) testRecursiveNextHopResolutionDisabled(t *testing.T) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + // Disable static route next-hop recursive lookup (set to false) + batch := &gnmi.SetBatch{} + gnmi.BatchReplace(batch, sp.Static(td.staticIPv4.cidr(t)).NextHop("0").Recurse().Config(), false) + gnmi.BatchReplace(batch, sp.Static(td.staticIPv6.cidr(t)).NextHop("0").Recurse().Config(), false) + batch.Set(t, td.dut) + + t.Run("Telemetry", func(t *testing.T) { + + _, ok := gnmi.Watch(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).State(), time.Second*30, func(v *ygnmi.Value[*oc.NetworkInstance_Protocol_Static]) bool { + val, present := v.Val() + return !present || (present && !val.GetNextHop("0").GetRecurse()) + }).Await(t) + if !ok { + t.Errorf("Unable to set recurse to false for v4 prefix") + } + + _, ok = gnmi.Watch(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).State(), time.Second*30, func(v *ygnmi.Value[*oc.NetworkInstance_Protocol_Static]) bool { + val, present := v.Val() + return !present || (present && !val.GetNextHop("0").GetRecurse()) + }).Await(t) + if !ok { + t.Errorf("Unable to set recurse to false for v6 prefix") + } + }) + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv4-network + // 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) + + // Validate that traffic is NOT received from DUT + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + if got, want := lossV4, float64(100); got != want { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want %f", got, want) + } + if got, want := lossV6, float64(100); got != want { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want %f", got, want) + } + }) +} + +func (td *testData) configureStaticRouteToATEP1AndP2(t *testing.T) { + b := &gnmi.SetBatch{} + // Configure IPv4 static routes: + // * Configure one IPv4 static route i.e. ipv4-route-a on the DUT for + // destination `ipv4-network 203.0.113.0/24` with the next hop set to the + // IPv4 address of ATE port-1 + // * Configure another IPv4 static route i.e. ipv4-route-b on the DUT for + // destination `ipv4-network 203.0.113.0/24` with the next hop set to the + // IPv4 address of ATE port-2 + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv4.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(atePort1.IPv4), + "1": oc.UnionString(atePort2.IPv4), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, td.dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + + // Configure IPv6 static routes: + // * Configure one IPv6 static route i.e. ipv6-route-a on the DUT for + // destination `ipv6-network 2001:db8:128:128::/64` with the next hop set + // to the IPv6 address of ATE port-1 + // * Configure another IPv6 static route i.e. ipv6-route-b on the DUT for + // destination `ipv6-network 2001:db8:128:128::/64` with the next hop set + // to the IPv6 address of ATE port-2 + sV6 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv6.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(atePort1.IPv6), + "1": oc.UnionString(atePort2.IPv6), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV6, td.dut); err != nil { + t.Fatalf("Failed to configure IPv6 static route: %v", err) + } + b.Set(t, td.dut) +} + +func (td *testData) deleteStaticRoutes(t *testing.T) { + b := &gnmi.SetBatch{} + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + gnmi.BatchDelete(b, sp.Static(td.staticIPv4.cidr(t)).Config()) + gnmi.BatchDelete(b, sp.Static(td.staticIPv6.cidr(t)).Config()) + b.Set(t, td.dut) +} + +func (td *testData) testStaticRouteECMP(t *testing.T) { + td.configureStaticRouteToATEP1AndP2(t) + defer td.deleteStaticRoutes(t) + + t.Run("Telemetry", func(t *testing.T) { + + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).Prefix().State(), 120*time.Second, td.staticIPv4.cidr(t)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).Prefix().State(), 120*time.Second, td.staticIPv6.cidr(t)) + + if deviations.SkipStaticNexthopCheck(td.dut) { + nexthops := gnmi.LookupAll(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).NextHopAny().NextHop().State()) + if len(nexthops) != 2 { + t.Errorf("IPv4 Static Route next hop: want %d nexthops,got %d nexthops", 2, len(nexthops)) + } + for _, nexthop := range nexthops { + if got, ok := nexthop.Val(); !ok || !(got != oc.UnionString(atePort1.IPv4) || got != oc.UnionString(atePort2.IPv4)) { + t.Errorf("IPv4 Static Route next hop:got %s,want %s or %s", got, oc.UnionString(atePort1.IPv4), oc.UnionString(atePort2.IPv4)) + } + } + nexthops = gnmi.LookupAll(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).NextHopAny().NextHop().State()) + if len(nexthops) != 2 { + t.Errorf("IPv6 Static Route next hop: want %d nexthops,got %d nexthops", 2, len(nexthops)) + } + for _, nexthop := range nexthops { + if got, ok := nexthop.Val(); !ok || !(got != oc.UnionString(atePort1.IPv6) || got != oc.UnionString(atePort2.IPv6)) { + t.Errorf("IPv6 Static Route next hop: got %s,want %s or %s", got, oc.UnionString(atePort1.IPv6), oc.UnionString(atePort2.IPv6)) + } + } + } else { + // Validate both the routes i.e. ipv4-route-[a|b] are configured and reported + // correctly + gotStatic := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.UnionString(atePort1.IPv4); got != want { + t.Errorf("IPv4 Static Route next hop: got: %s, want: %s", got, want) + } + if got, want := gotStatic.GetNextHop("1").GetNextHop(), oc.UnionString(atePort2.IPv4); got != want { + t.Errorf("IPv4 Static Route next hop: got: %s, want: %s", got, want) + } + // Validate both the routes i.e. ipv6-route-[a|b] are configured and reported + // correctly + gotStatic = gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.UnionString(atePort1.IPv6); got != want { + t.Errorf("IPv6 Static Route next hop: got: %s, want: %s", got, want) + } + if got, want := gotStatic.GetNextHop("1").GetNextHop(), oc.UnionString(atePort2.IPv6); got != want { + t.Errorf("IPv6 Static Route next hop: got: %s, want: %s", got, want) + } + } + }) + + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv4-network + // 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) + + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + if lossV4 > lossTolerance { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want 0%%", lossV4) + } + if lossV6 > lossTolerance { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want 0%%", lossV6) + } + + portCounters := egressTrackingCounters(t, td.ate, v4Flow) + if len(portCounters) != 2 { + t.Errorf("IPv4 egress tracking counters: got: %v, want: 2", len(portCounters)) + } + p1Counter, ok := portCounters[port1Tag] + if !ok { + t.Errorf("Port1 IPv4 egress tracking counter not found: %v", portCounters) + } + p2Counter, ok := portCounters[port2Tag] + if !ok { + t.Errorf("Port2 IPv4 egress tracking counter not found: %v", portCounters) + } + // Validate that traffic is received from DUT on both port-1 and port-2 and + // ECMP works + if got, want := p1Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv4 load balance error for port1, got: %v, want: %v", got, want) + } + if got, want := p2Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv4 load balance error for port2, got: %v, want: %v", got, want) + } + + portCounters = egressTrackingCounters(t, td.ate, v6Flow) + if len(portCounters) != 2 { + t.Errorf("IPv6 egress tracking counters: got: %v, want: 2", len(portCounters)) + } + p1Counter, ok = portCounters[port1Tag] + if !ok { + t.Errorf("Port1 IPv6 egress tracking counter not found: %v", portCounters) + } + p2Counter, ok = portCounters[port2Tag] + if !ok { + t.Errorf("Port2 IPv6 egress tracking counter not found: %v", portCounters) + } + // Validate that traffic is received from DUT on both port-1 and port-2 and + // ECMP works + if got, want := p1Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv6 load balance error for port1, got: %v, want: %v", got, want) + } + if got, want := p2Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv6 load balance error for port2, got: %v, want: %v", got, want) + } + }) +} + +func (td *testData) testStaticRouteWithMetric(t *testing.T) { + td.configureStaticRouteToATEP1AndP2(t) + defer td.deleteStaticRoutes(t) + + var port2Metric = uint32(100) + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + + // Configure metric of ipv4-route-b and ipv6-route-b to 100 + batch := &gnmi.SetBatch{} + if deviations.StaticRouteWithExplicitMetric(td.dut) { + // per the cisco specifications setting the metric is equivlent to setting the weight, so in this case + // we want the majority of the traffic to go over port 1 so setting the metric to 100 and port 2 as 1 + var port1Metric = uint32(100) + port2Metric = uint32(1) + gnmi.BatchReplace(batch, sp.Static(td.staticIPv4.cidr(t)).NextHop("0").Metric().Config(), port1Metric) + gnmi.BatchReplace(batch, sp.Static(td.staticIPv6.cidr(t)).NextHop("0").Metric().Config(), port1Metric) + + } + + gnmi.BatchReplace(batch, sp.Static(td.staticIPv4.cidr(t)).NextHop("1").Metric().Config(), port2Metric) + gnmi.BatchReplace(batch, sp.Static(td.staticIPv6.cidr(t)).NextHop("1").Metric().Config(), port2Metric) + batch.Set(t, td.dut) + + t.Run("Telemetry", func(t *testing.T) { + if deviations.MissingStaticRouteNextHopMetricTelemetry(td.dut) { + t.Skip("Skipping Telemetry check for Metric, since deviation MissingStaticRouteNextHopMetricTelemetry is enabled.") + } + gnmi.Await(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv4.cidr(t)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv6.cidr(t)) + // Validate that the metric is set correctly + if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).NextHop("1").Metric().State()), port2Metric; got != want { + t.Errorf("IPv4 Static Route metric for NextHop 1, got: %d, want: %d", got, want) + } + if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).NextHop("1").Metric().State()), port2Metric; got != want { + t.Errorf("IPv6 Static Route metric for NextHop 1, got: %d, want: %d", got, want) + } + }) + + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv4-network + // 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) + + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + if lossV4 > lossTolerance { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want 0%%", lossV4) + } + if lossV6 > lossTolerance { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want 0%%", lossV6) + } + // Validate that traffic is received from DUT on port-1 and not on port-2 + portCounters := egressTrackingCounters(t, td.ate, v4Flow) + _, rxV4 := otgutils.GetFlowStats(t, td.ate.OTG(), v4Flow, 20*time.Second) + port1Counter, ok := portCounters[port1Tag] + if !ok { + t.Errorf("Port1 IPv4 egress tracking counter not found: %v", portCounters) + } + + if deviations.StaticRouteWithExplicitMetric(td.dut) { + // validate traffic + got, want := float64(port1Counter)*100/float64(rxV4), float64(100) + expectedMinTraffic := want * (1 - explicitMetricTolerance/100) + if got < expectedMinTraffic { + t.Errorf("IPv4 traffic on port1, got: %v%%, expected to be at least %v%%", got, expectedMinTraffic) + } + } else { + // validate traffic default behavior + if got, want := float64(port1Counter)*100/float64(rxV4), float64(100); got+lossTolerance < want { + t.Errorf("IPv4 traffic on port1, got: %v, want: %v", got, want) + } + } + + // Validate that traffic is received from DUT on port-1 and not on port-2 + portCounters = egressTrackingCounters(t, td.ate, v6Flow) + _, rxV6 := otgutils.GetFlowStats(t, td.ate.OTG(), v6Flow, 20*time.Second) + port1Counter, ok = portCounters[port1Tag] + if !ok { + t.Errorf("Port1 IPv6 egress tracking counter not found: %v", portCounters) + } + if deviations.StaticRouteWithExplicitMetric(td.dut) { + // validate traffic + got, want := float64(port1Counter)*100/float64(rxV6), float64(100) + expectedMinTraffic := want * (1 - explicitMetricTolerance/100) + if got < expectedMinTraffic { + t.Errorf("IPv6 traffic on port1, got: %v%%, expected to be at least %v%%", got, expectedMinTraffic) + } + + } else { + // validate traffic default behavior + if got, want := float64(port1Counter)*100/float64(rxV6), float64(100); got+lossTolerance < want { + t.Errorf("IPv6 traffic on port1, got: %v, want: %v", got, want) + } + } + + }) +} + +func (td *testData) testStaticRouteWithPreference(t *testing.T) { + td.configureStaticRouteToATEP1AndP2(t) + defer td.deleteStaticRoutes(t) + + const port1Preference = uint32(50) + const port2Metric = uint32(100) + + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + + // Configure metric of ipv4-route-b and ipv6-route-b to 100 + batch := &gnmi.SetBatch{} + gnmi.BatchReplace(batch, sp.Static(td.staticIPv4.cidr(t)).NextHop("1").Metric().Config(), port2Metric) + gnmi.BatchReplace(batch, sp.Static(td.staticIPv6.cidr(t)).NextHop("1").Metric().Config(), port2Metric) + + // Configure preference of ipv4-route-a and ipv6-route-a to 50 + if deviations.SetMetricAsPreference(td.dut) { + // Lower metric indicate more favourable path. + // If we use Metric instead of Preference, we would need to have a port1Metric + // larger than port2Metric for traffic to pass through port 2 + port1Metric := port2Metric + port1Preference + gnmi.BatchReplace(batch, sp.Static(td.staticIPv4.cidr(t)).NextHop("0").Metric().Config(), port1Metric) + gnmi.BatchReplace(batch, sp.Static(td.staticIPv6.cidr(t)).NextHop("0").Metric().Config(), port1Metric) + } else { + gnmi.BatchReplace(batch, sp.Static(td.staticIPv4.cidr(t)).NextHop("0").Preference().Config(), port1Preference) + gnmi.BatchReplace(batch, sp.Static(td.staticIPv6.cidr(t)).NextHop("0").Preference().Config(), port1Preference) + } + batch.Set(t, td.dut) + + t.Run("Telemetry", func(t *testing.T) { + if deviations.SetMetricAsPreference(td.dut) { + t.Skip("Skipping Preference telemetry check since deviation SetMetricAsPreference is enabled") + } + gnmi.Await(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv4.cidr(t)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv6.cidr(t)) + // Validate that the preference is set correctly + if deviations.SkipStaticNexthopCheck(td.dut) { + gotStatic := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).State()) + indexes := gnmi.LookupAll(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).NextHopAny().Index().State()) + for _, index := range indexes { + if val, ok := index.Val(); ok { + if gotStatic.GetNextHop(val).GetNextHop() == oc.UnionString(atePort1.IPv4) { + if got, want := gotStatic.GetNextHop(val).GetPreference(), port1Preference; got != want { + t.Errorf("IPv4 Static Route preference for port1: got: %d, want: %d", got, want) + } + } + } else { + t.Errorf("Unable to fetch nexthop index") + } + } + gotStatic = gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).State()) + indexes = gnmi.LookupAll(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).NextHopAny().Index().State()) + for _, index := range indexes { + if val, ok := index.Val(); ok { + if gotStatic.GetNextHop(val).GetNextHop() == oc.UnionString(atePort1.IPv6) { + if got, want := gotStatic.GetNextHop(val).GetPreference(), port1Preference; got != want { + t.Errorf("IPv6 Static Route preference for port1: got: %d, want: %d", got, want) + } + } + } else { + t.Errorf("Unable to fetch nexthop index") + } + } + } else { + if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).NextHop("0").Preference().State()), port1Preference; got != want { + t.Errorf("IPv4 Static Route preference for NextHop 0, got: %d, want: %d", got, want) + } + if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).NextHop("0").Preference().State()), port1Preference; got != want { + t.Errorf("IPv6 Static Route preference for NextHop 0, got: %d, want: %d", got, want) + } + } + }) + + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv4-network + // 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) + + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + if lossV4 > lossTolerance { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want 0%%", lossV4) + } + if lossV6 > lossTolerance { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want 0%%", lossV6) + } + // Validate that traffic is now received from DUT on port-2 and not on port-1 + portCounters := egressTrackingCounters(t, td.ate, v4Flow) + _, rxV4 := otgutils.GetFlowStats(t, td.ate.OTG(), v4Flow, 20*time.Second) + port2Counter, ok := portCounters[port2Tag] + if !ok { + t.Errorf("Port2 IPv4 egress tracking counter not found: %v", portCounters) + } + if got, want := float64(port2Counter)*100/float64(rxV4), float64(100); got+lossTolerance < want { + t.Errorf("IPv4 traffic on port2, got: %v, want: %v", got, want) + } + // Validate that traffic is now received from DUT on port-2 and not on port-1 + portCounters = egressTrackingCounters(t, td.ate, v6Flow) + _, rxV6 := otgutils.GetFlowStats(t, td.ate.OTG(), v6Flow, 20*time.Second) + port2Counter, ok = portCounters[port2Tag] + if !ok { + t.Errorf("Port2 IPv6 egress tracking counter not found: %v", portCounters) + } + if got, want := float64(port2Counter)*100/float64(rxV6), float64(100); got+lossTolerance < want { + t.Errorf("IPv6 traffic on port2, got: %v, want: %v", got, want) + } + }) +} + +func (td *testData) testStaticRouteSetTag(t *testing.T) { + const tag = uint32(10) + + b := &gnmi.SetBatch{} + // Configure a tag of value 10 on ipv4 and ipv6 static routes + v4Cfg := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv4.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(atePort1.IPv4), + "1": oc.UnionString(atePort2.IPv4), + }, + } + sV4, err := cfgplugins.NewStaticRouteCfg(b, v4Cfg, td.dut) + if err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + sV4.SetTag, _ = sV4.To_NetworkInstance_Protocol_Static_SetTag_Union(tag) + + v6Cfg := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv6.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(atePort1.IPv6), + "1": oc.UnionString(atePort2.IPv6), + }, + } + sV6, err := cfgplugins.NewStaticRouteCfg(b, v6Cfg, td.dut) + if err != nil { + t.Fatalf("Failed to configure IPv6 static route: %v", err) + } + sV6.SetTag, _ = sV6.To_NetworkInstance_Protocol_Static_SetTag_Union(tag) + + b.Set(t, td.dut) + + defer td.deleteStaticRoutes(t) + + // Validate the tag is set + t.Run("Telemetry", func(t *testing.T) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv4.cidr(t)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv6.cidr(t)) + if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).SetTag().State()), oc.UnionUint32(tag); got != want { + t.Errorf("IPv4 Static Route SetTag, got: %d, want: %d", got, want) + } + if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).SetTag().State()), oc.UnionUint32(tag); got != want { + t.Errorf("IPv6 Static Route SetTag, got: %d, want: %d", got, want) + } + }) +} + +func (td *testData) testIPv6StaticRouteWithIPv4NextHop(t *testing.T) { + // Remove metric of 100 from ipv4-route-b and ipv6-route-b + // * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/metric + // Remove preference of 50 from ipv4-route-a and ipv6-route-a + // * /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/preference + // Change the IPv6 next-hop of the ipv6-route-a with the next hop set to the + // IPv4 address of ATE port-1 + // Change the IPv6 next-hop of the ipv6-route-b with the next hop set to the + // IPv4 address of ATE port-2 + if deviations.IPv6StaticRouteWithIPv4NextHopUnsupported(td.dut) { + t.Skip("Skipping Ipv6 with Ipv4 route unsupported. Deviation IPv4StaticRouteWithIPv6NextHopUnsupported enabled.") + } + b := &gnmi.SetBatch{} + var v6Cfg *cfgplugins.StaticRouteCfg + if deviations.IPv6StaticRouteWithIPv4NextHopRequiresStaticARP(td.dut) { + staticARPWithMagicUniversalIP(t, td.dut) + v6Cfg = &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv6.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(dummyV6), + }, + } + } else { + v6Cfg = &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv6.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(atePort1.IPv4), + "1": oc.UnionString(atePort2.IPv4), + }, + } + } + if _, err := cfgplugins.NewStaticRouteCfg(b, v6Cfg, td.dut); err != nil { + t.Fatalf("Failed to configure IPv6 static route: %v", err) + } + b.Set(t, td.dut) + + defer td.deleteStaticRoutes(t) + + // Validate both the routes i.e. ipv6-route-[a|b] are configured and the IPv4 + // next-hop is reported correctly + t.Run("Telemetry", func(t *testing.T) { + if deviations.IPv6StaticRouteWithIPv4NextHopRequiresStaticARP(td.dut) { + t.Skip("Telemetry not validated due to use of deviation: IPv6StaticRouteWithIPv4NextHopRequiresStaticARP.") + } + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv6.cidr(t)) + gotStatic := gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.UnionString(atePort1.IPv4); got != want { + t.Errorf("IPv6 Static Route next hop: got: %s, want: %s", got, want) + } + if got, want := gotStatic.GetNextHop("1").GetNextHop(), oc.UnionString(atePort2.IPv4); got != want { + t.Errorf("Static Route next hop: got: %s, want: %s", got, want) + } + }) + + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv6-network + // 2001:db8:128:128::/64` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) + + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + + if lossV6 > lossTolerance { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want 0%%", lossV6) + } + + portCounters := egressTrackingCounters(t, td.ate, v6Flow) + if len(portCounters) != 2 { + t.Errorf("IPv6 egress tracking counters: got: %v, want: 2", len(portCounters)) + } + p1Counter, ok := portCounters[port1Tag] + if !ok { + t.Errorf("Port1 IPv6 egress tracking counter not found: %v", portCounters) + } + p2Counter, ok := portCounters[port2Tag] + if !ok { + t.Errorf("Port2 IPv6 egress tracking counter not found: %v", portCounters) + } + // Validate that traffic is received from DUT on both port-1 and port-2 and + // ECMP works + if got, want := p1Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv6 load balance error for port1, got: %v, want: %v", got, want) + } + if got, want := p2Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv6 load balance error for port2, got: %v, want: %v", got, want) + } + }) +} + +func staticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + dummyIPCIDR := dummyV6 + "/128" + s2 := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(dummyIPCIDR), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + "0": { + Index: ygot.String("0"), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(p1.Name()), + }, + }, + "1": { + Index: ygot.String("1"), + 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)) + static, ok := gnmi.LookupConfig(t, dut, sp.Config()).Val() + if !ok || static == nil { + static = &oc.NetworkInstance_Protocol{ + Identifier: oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + Name: ygot.String(deviations.StaticProtocolName(dut)), + Static: map[string]*oc.NetworkInstance_Protocol_Static{ + dummyIPCIDR: s2, + }, + } + gnmi.Replace(t, dut, sp.Config(), static) + } else { + gnmi.Replace(t, dut, sp.Static(dummyIPCIDR).Config(), s2) + } +} + +func (td *testData) testIPv4StaticRouteWithIPv6NextHop(t *testing.T) { + b := &gnmi.SetBatch{} + // Change the IPv4 next-hop of the ipv4-route-a with the next hop set to the + // IPv6 address of ATE port-1 + // Change the IPv4 next-hop of the ipv4-route-b with the next hop set to the + // IPv6 address of ATE port-2 + if deviations.IPv4StaticRouteWithIPv6NextHopUnsupported(td.dut) { + t.Skip("Skipping Ipv4 with Ipv6 route unsupported. Deviation IPv4StaticRouteWithIPv6NextHopUnsupported enabled.") + } + v4Cfg := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv4.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(atePort1.IPv6), + "1": oc.UnionString(atePort2.IPv6), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, v4Cfg, td.dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, td.dut) + + defer td.deleteStaticRoutes(t) + + // Validate both the routes i.e. ipv4-route-[a|b] are configured and the IPv6 + // next-hop is reported correctly + t.Run("Telemetry", func(t *testing.T) { + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv4.cidr(t)) + + if deviations.SkipStaticNexthopCheck(td.dut) { + nexthops := gnmi.LookupAll(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).NextHopAny().NextHop().State()) + if len(nexthops) != 2 { + t.Errorf("IPv4 Static Route next hop: want %d nexthops,got %d nexthops", 2, len(nexthops)) + } + for _, nexthop := range nexthops { + if got, ok := nexthop.Val(); !ok || !(got != oc.UnionString(atePort1.IPv6) || got != oc.UnionString(atePort2.IPv6)) { + t.Errorf("IPv4 Static Route next hop: got %s,want %s or %s", got, oc.UnionString(atePort1.IPv6), oc.UnionString(atePort2.IPv6)) + } + } + } else { + gotStatic := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.UnionString(atePort1.IPv6); got != want { + t.Errorf("IPv4 Static Route next hop: got: %s, want: %s", got, want) + } + if got, want := gotStatic.GetNextHop("1").GetNextHop(), oc.UnionString(atePort2.IPv6); got != want { + t.Errorf("IPv4 Static Route next hop: got: %s, want: %s", got, want) + } + } + }) + + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv4-network + // 203.0.113.0/24` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) + + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + + if lossV4 > lossTolerance { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want 0%%", lossV4) + } + + portCounters := egressTrackingCounters(t, td.ate, v4Flow) + if len(portCounters) != 2 { + t.Errorf("IPv4 egress tracking counters: got: %v, want: 2", len(portCounters)) + } + p1Counter, ok := portCounters[port1Tag] + if !ok { + t.Errorf("Port1 IPv4 egress tracking counter not found: %v", portCounters) + } + p2Counter, ok := portCounters[port2Tag] + if !ok { + t.Errorf("Port2 IPv4 egress tracking counter not found: %v", portCounters) + } + // Validate that traffic is received from DUT on both port-1 and port-2 and + // ECMP works + if got, want := p1Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv4 load balance error for port1, got: %v, want: %v", got, want) + } + if got, want := p2Counter*100/(p1Counter+p2Counter), uint64(50); got < want-ecmpTolerance || got > want+ecmpTolerance { + t.Errorf("ECMP IPv4 load balance error for port2, got: %v, want: %v", got, want) + } + }) +} + +func (td *testData) testStaticRouteWithDropNextHop(t *testing.T) { + if deviations.StaticRouteWithDropNhUnsupported(td.dut) { + t.Skip("Skipping test static route with drop nexthop. Deviation StaticRouteWithDropNhUnsupported enabled.") + } + b := &gnmi.SetBatch{} + // Configure IPv4 static routes: + // * Configure one IPv4 static route i.e. ipv4-route-a on the DUT for + // destination `ipv4-network 203.0.113.0/24` with the next hop set to DROP + // local-defined next hop + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv4.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.LocalRouting_LOCAL_DEFINED_NEXT_HOP_DROP, + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, td.dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + + // Configure IPv6 static routes: + // * Configure one IPv6 static route i.e. ipv6-route-a on the DUT for + // destination `ipv6-network 2001:db8:128:128::/64` with the next hop set + // to DROP local-defined next hop + sV6 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(td.dut), + Prefix: td.staticIPv6.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.LocalRouting_LOCAL_DEFINED_NEXT_HOP_DROP, + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV6, td.dut); err != nil { + t.Fatalf("Failed to configure IPv6 static route: %v", err) + } + b.Set(t, td.dut) + + defer td.deleteStaticRoutes(t) + + t.Run("Telemetry", func(t *testing.T) { + if deviations.MissingStaticRouteDropNextHopTelemetry(td.dut) { + t.Skip("Skipping telemetry check for DROP next hop. Deviation MissingStaticRouteDropNextHopTelemetryenabled.") + } + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(td.dut)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv4.cidr(t)) + gnmi.Await(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).Prefix().State(), 30*time.Second, td.staticIPv6.cidr(t)) + + // Validate the route is configured and reported correctly + gotStatic := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.LocalRouting_LOCAL_DEFINED_NEXT_HOP_DROP; got != want { + t.Errorf("IPv4 Static Route next hop: got: %s, want: %s", got, want) + } + // Validate the route is configured and reported correctly + gotStatic = gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).State()) + if got, want := gotStatic.GetNextHop("0").GetNextHop(), oc.LocalRouting_LOCAL_DEFINED_NEXT_HOP_DROP; got != want { + t.Errorf("IPv6 Static Route next hop: got: %s, want: %s", got, want) + } + }) + + t.Run("Traffic", func(t *testing.T) { + // Initiate traffic from ATE port-3 towards destination `ipv4-network + // 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` + td.ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + td.ate.OTG().StopTraffic(t) + + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) + + // Validate that traffic is dropped on DUT and not received on port-1 and + // port-2 + otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) + if lossV4 != 100 { + t.Errorf("Loss percent for IPv4 Traffic: got: %f, want 100%%", lossV4) + } + if lossV6 != 100 { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want 100%%", lossV6) + } + }) +} + +func egressTrackingCounters(t *testing.T, ate *ondatra.ATEDevice, flow string) map[string]uint64 { + t.Helper() + etTags := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().Flow(flow).TaggedMetricAny().State()) + inPkts := map[string]uint64{} + for _, tags := range etTags { + for _, tag := range tags.Tags { + inPkts[tag.GetTagValue().GetValueAsHex()] = tags.GetCounters().GetInPkts() + } + } + return inPkts +} + +func (td *testData) configureOTGFlows(t *testing.T) { + t.Helper() + + srcV4 := td.otgP3.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + srcV6 := td.otgP3.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + dst1V4 := td.otgP1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dst1V6 := td.otgP1.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + dst2V4 := td.otgP2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dst2V6 := td.otgP2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + v4F := td.top.Flows().Add() + v4F.SetName(v4Flow).Metrics().SetEnable(true) + v4F.TxRx().Device().SetTxNames([]string{srcV4.Name()}).SetRxNames([]string{dst1V4.Name(), dst2V4.Name()}) + + v4FEth := v4F.Packet().Add().Ethernet() + v4FEth.Src().SetValue(atePort3.MAC) + + v4FIp := v4F.Packet().Add().Ipv4() + v4FIp.Src().SetValue(srcV4.Address()) + v4FIp.Dst().Increment().SetStart(v4TrafficStart).SetCount(254) + + udp := v4F.Packet().Add().Udp() + udp.DstPort().Increment().SetStart(1).SetCount(500).SetStep(1) + udp.SrcPort().Increment().SetStart(1).SetCount(500).SetStep(1) + + eth := v4F.EgressPacket().Add().Ethernet() + ethTag := eth.Dst().MetricTags().Add() + ethTag.SetName("MACTrackingv4").SetOffset(36).SetLength(12) + + v6F := td.top.Flows().Add() + v6F.SetName(v6Flow).Metrics().SetEnable(true) + v6F.TxRx().Device().SetTxNames([]string{srcV6.Name()}).SetRxNames([]string{dst1V6.Name(), dst2V6.Name()}) + + v6FEth := v6F.Packet().Add().Ethernet() + v6FEth.Src().SetValue(atePort3.MAC) + + v6FIP := v6F.Packet().Add().Ipv6() + v6FIP.Src().SetValue(srcV6.Address()) + v6FIP.Dst().Increment().SetStart(v6TrafficStart).SetCount(254) + + udp = v6F.Packet().Add().Udp() + udp.DstPort().Increment().SetStart(1).SetCount(500).SetStep(1) + udp.SrcPort().Increment().SetStart(1).SetCount(500).SetStep(1) + + eth = v6F.EgressPacket().Add().Ethernet() + ethTag = eth.Dst().MetricTags().Add() + ethTag.SetName("MACTrackingv6").SetOffset(36).SetLength(12) +} + +func (td *testData) awaitISISAdjacency(t *testing.T, p *ondatra.Port, isisName string) error { + t.Helper() + isis := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisName).Isis() + intf := isis.Interface(p.Name()) + if deviations.ExplicitInterfaceInDefaultVRF(td.dut) { + intf = isis.Interface(p.Name() + ".0") + } + query := intf.Level(2).AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, td.dut, query, time.Minute, func(v *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, _ := v.Val() + return v.IsPresent() && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + + if !ok { + return fmt.Errorf("timeout - waiting for adjacency state") + } + return nil +} + +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) + } +} + +func configureOTG(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) []gosnappi.Device { + t.Helper() + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + p3 := ate.Port(t, "port3") + p4 := ate.Port(t, "port4") + + d1 := atePort1.AddToOTG(top, p1, &dutPort1) + d2 := atePort2.AddToOTG(top, p2, &dutPort2) + d3 := atePort3.AddToOTG(top, p3, &dutPort3) + d4 := atePort4.AddToOTG(top, p4, &dutPort4) + return []gosnappi.Device{d1, d2, d3, d4} +} + +func (td *testData) advertiseRoutesWithISIS(t *testing.T) { + t.Helper() + + root := &oc.Root{} + ni := root.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(td.dut)) + isisP := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisName) + isisP.SetEnabled(true) + isis := isisP.GetOrCreateIsis() + + g := isis.GetOrCreateGlobal() + if deviations.ISISInstanceEnabledRequired(td.dut) { + g.Instance = ygot.String(isisName) + } + g.LevelCapability = oc.Isis_LevelType_LEVEL_2 + g.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddr, dutSysID)} + g.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + g.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + if deviations.ISISLevelEnabled(td.dut) { + isisLevel2.Enabled = ygot.Bool(true) + } + + p1Name := td.dut.Port(t, "port1").Name() + p2Name := td.dut.Port(t, "port2").Name() + if deviations.ExplicitInterfaceInDefaultVRF(td.dut) { + p1Name += ".0" + p2Name += ".0" + } + for _, intfName := range []string{p1Name, p2Name} { + isisIntf := isis.GetOrCreateInterface(intfName) + isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(intfName) + isisIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(td.dut) { + isisIntf.InterfaceRef = nil + } + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(td.dut) { + isisIntf.Af = nil + } + + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv4.Metric = ygot.Uint32(10) + isisIntfLevelAfiv4.Enabled = ygot.Bool(true) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + isisIntfLevelAfiv6.Metric = ygot.Uint32(10) + isisIntfLevelAfiv6.Enabled = ygot.Bool(true) + if deviations.MissingIsisInterfaceAfiSafiEnable(td.dut) { + isisIntfLevelAfiv4.Enabled = nil + isisIntfLevelAfiv6.Enabled = nil + } + } + gnmi.Update(t, td.dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(td.dut)).Config(), ni) + + dev1ISIS := td.otgP1.Isis().SetSystemId(ate1SysID).SetName(td.otgP1.Name() + ".ISIS") + dev1ISIS.Basic().SetHostname(dev1ISIS.Name()).SetLearnedLspFilter(true) + dev1ISIS.Advanced().SetAreaAddresses([]string{strings.Replace(ateAreaAddr, ".", "", -1)}) + dev1IsisInt := dev1ISIS.Interfaces().Add(). + SetEthName(td.otgP1.Ethernets().Items()[0].Name()).SetName("dev1IsisInt"). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetMetric(10) + dev1IsisInt.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + dev2ISIS := td.otgP2.Isis().SetSystemId(ate2SysID).SetName(td.otgP2.Name() + ".ISIS") + dev2ISIS.Basic().SetHostname(dev2ISIS.Name()).SetLearnedLspFilter(true) + dev2ISIS.Advanced().SetAreaAddresses([]string{strings.Replace(ateAreaAddr, ".", "", -1)}) + dev2IsisInt := dev2ISIS.Interfaces().Add(). + SetEthName(td.otgP2.Ethernets().Items()[0].Name()).SetName("dev2IsisInt"). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetMetric(10) + dev2IsisInt.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + // configure emulated network params + net2v4 := td.otgP1.Isis().V4Routes().Add().SetName("v4-isisNet-dev1").SetLinkMetric(10) + net2v4.Addresses().Add().SetAddress(td.advertisedIPv4.address).SetPrefix(td.advertisedIPv4.prefix) + net2v6 := td.otgP1.Isis().V6Routes().Add().SetName("v6-isisNet-dev1").SetLinkMetric(10) + net2v6.Addresses().Add().SetAddress(td.advertisedIPv6.address).SetPrefix(td.advertisedIPv6.prefix) + + net3v4 := td.otgP2.Isis().V4Routes().Add().SetName("v4-isisNet-dev2").SetLinkMetric(10) + net3v4.Addresses().Add().SetAddress(td.advertisedIPv4.address).SetPrefix(td.advertisedIPv4.prefix) + net3v6 := td.otgP2.Isis().V6Routes().Add().SetName("v6-isisNet-dev2").SetLinkMetric(10) + net3v6.Addresses().Add().SetAddress(td.advertisedIPv6.address).SetPrefix(td.advertisedIPv6.prefix) +} 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 new file mode 100644 index 00000000000..af7f010bc52 --- /dev/null +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto @@ -0,0 +1,51 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "fe01cad6-6775-45cd-a025-d960ca6c04af" +plan_id: "RT-1.26" +description: "Basic static route support" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + static_protocol_name: "STATIC" + interface_enabled: true + default_network_instance: "default" + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + missing_isis_interface_afi_safi_enable: true + isis_require_same_l1_metric_with_l2_metric: true + ipv6_static_route_with_ipv4_next_hop_requires_static_arp: true + set_metric_as_preference: true + missing_static_route_next_hop_metric_telemetry: true + unsupported_static_route_next_hop_recurse: true + missing_static_route_drop_next_hop_telemetry: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_static_route_with_ipv6_nh_unsupported: true + ipv6_static_route_with_ipv4_nh_unsupported: true + static_route_with_drop_nh: true + unsupported_static_route_next_hop_recurse: true + static_route_with_explicit_metric: true + interface_ref_config_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + ipv6_static_route_with_ipv4_nh_unsupported: true + skip_static_nexthop_check: true + isis_level_enabled: true + } +} +tags: TAGS_DATACENTER_EDGE diff --git a/feature/staticroute/tests/README.md b/feature/staticroute/tests/README.md deleted file mode 100644 index a9bc98b85a0..00000000000 --- a/feature/staticroute/tests/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# RT-1.25: Management network-instance default static route - -## Summary - -Validate static route functionality in Management network-instance (VRF). - -## Procedure - - -* Configure DUT with Management VRF and ATE1, ATE2 interfaces configured within this VRF -* Configure IPv4 and IPv6 default routes within Management VRF pointing to ATE2 interface -* Generate IPv4 and IPv6 traffic from ATE1 to any destination. -* Verify that traffic is received at ATE2 interface - -## Config Parameter coverage - -* /network-instances/network-instance/config/name -* /network-instances/network-instance/config/description -* /network-instances/network-instance/config/type - -* /network-instances/network-instance/interfaces/interface/config/id - - -* /network-instances/network-instance/protocols/protocol/static-routes/static -* /network-instances/network-instance/protocols/protocol/static-routes/static/prefix -* /network-instances/network-instance/protocols/protocol/static-routes/static/config -* /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix -* /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop -* /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/index -* /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config - - -## Telemetry Parameter coverage - * /network-instances/network-instance/protocols/protocol/static-routes/static/state - diff --git a/feature/staticroute/tests/static_route_test/README.md b/feature/staticroute/tests/static_route_test/README.md new file mode 100644 index 00000000000..c5857f40704 --- /dev/null +++ b/feature/staticroute/tests/static_route_test/README.md @@ -0,0 +1,46 @@ +# RT-1.25: Management network-instance default static route + +## Summary + +Validate static route functionality in Management network-instance (VRF). + +## Procedure + + +* Configure DUT with Management VRF and ATE1, ATE2 interfaces configured within this VRF +* Configure IPv4 and IPv6 default routes within Management VRF pointing to ATE2 interface +* Generate IPv4 and IPv6 traffic from ATE1 to any destination. +* Verify that traffic is received at ATE2 interface + +## 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(OCPATH): Specify leaves for non-leaf paths that have been commented out. + +```yaml +paths: + ## Config Paths ## + /network-instances/network-instance/config/name: + /network-instances/network-instance/config/description: + /network-instances/network-instance/config/type: + /network-instances/network-instance/interfaces/interface/config/id: + #/network-instances/network-instance/protocols/protocol/static-routes/static: + /network-instances/network-instance/protocols/protocol/static-routes/static/prefix: + #/network-instances/network-instance/protocols/protocol/static-routes/static/config: + /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix: + #/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/index: + #/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/index: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop: + + ## State Paths ## + #/network-instances/network-instance/protocols/protocol/static-routes/static/state: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` + diff --git a/feature/staticroute/tests/static_route_test/metadata.textproto b/feature/staticroute/tests/static_route_test/metadata.textproto new file mode 100644 index 00000000000..c0fdc628cb7 --- /dev/null +++ b/feature/staticroute/tests/static_route_test/metadata.textproto @@ -0,0 +1,16 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "6ed65b76-d20a-4aa3-a1e1-55077a23d02b" +plan_id: "RT-1.25" +description: "Management network-instance default static route" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + interface_enabled: true + } +} \ No newline at end of file diff --git a/feature/staticroute/tests/static_route_test/static_route_test.go b/feature/staticroute/tests/static_route_test/static_route_test.go new file mode 100644 index 00000000000..aea91b952ee --- /dev/null +++ b/feature/staticroute/tests/static_route_test/static_route_test.go @@ -0,0 +1,301 @@ +package static_route_test_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/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + tolerance = 0.01 + ipv4DstPfx = "203.0.1.1" + ipv6DstPfx = "2003:db8::1" + vrfName = "VRF1" +) + +var ( + ateSrc = attrs.Attributes{ + Name: "ateSrc", + MAC: "02:11:01:00:00:01", + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutSrc = attrs.Attributes{ + Desc: "DUT to ATE source", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutDst = attrs.Attributes{ + Desc: "DUT to ATE destination", + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + ateDst = attrs.Attributes{ + Name: "ateDst", + MAC: "02:12:01:00:00:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureInterfaceVRF(t *testing.T, + dut *ondatra.DUTDevice, + portName string) { + + // create vrf and apply on interface + v := &oc.NetworkInstance{ + Name: ygot.String(vrfName), + Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF, + } + vi := v.GetOrCreateInterface(portName) + vi.Interface = ygot.String(portName) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(vrfName).Config(), v) +} + +// configInterfaceDUT configures the interface with the Addrs. +func configInterfaceDUT(i *oc.Interface, + a *attrs.Attributes, + dut *ondatra.DUTDevice) *oc.Interface { + + i.Description = ygot.String(a.Desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s := i.GetOrCreateSubinterface(0) + + // s.NetworkInstanceName = ygot.String(vrfName) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + s4a := s4.GetOrCreateAddress(a.IPv4) + s4a.PrefixLength = ygot.Uint8(ipv4PrefixLen) + + // Add IPv6 stack. + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + s6.GetOrCreateAddress(a.IPv6).PrefixLength = ygot.Uint8(ipv6PrefixLen) + + return i +} + +// configureDUT configures port1, port2 on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + + p1 := dut.Port(t, "port1") + t.Logf("Configuring VRF on interface %s", p1.Name()) + configureInterfaceVRF(t, dut, p1.Name()) + + i1 := &oc.Interface{Name: ygot.String(p1.Name())} + i1.Enabled = ygot.Bool(true) + gnmi.Update(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutSrc, dut)) + + p2 := dut.Port(t, "port2") + t.Logf("Configuring VRF on interface %s", p2.Name()) + configureInterfaceVRF(t, dut, p2.Name()) + i2 := &oc.Interface{Name: ygot.String(p2.Name())} + i2.Enabled = ygot.Bool(true) + gnmi.Update(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutDst, dut)) + +} + +// configureATE configures port1 and port2 on the ATE. +func configureOTG(t *testing.T) gosnappi.Config { + t.Helper() + top := gosnappi.NewConfig() + port1 := top.Ports().Add().SetName("port1") + port2 := top.Ports().Add().SetName("port2") + + // Port1 Configuration. + 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. + 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 + +} +func createTrafficFlows(t *testing.T, + ate *ondatra.ATEDevice, + dut *ondatra.DUTDevice, + top gosnappi.Config, + ipv4DstPfx, ipv6DstPfx string) (gosnappi.Flow, gosnappi.Flow) { + + // Get DUT MAC interface for traffic MPLS flow + dutDstInterface := dut.Port(t, "port1").Name() + dstMac := gnmi.Get(t, dut, gnmi.OC().Interface(dutDstInterface).Ethernet().MacAddress().State()) + t.Logf("DUT remote MAC address is %s", dstMac) + + // Common setup for both IPv4 and IPv6 flows + setupFlow := func(ipVersion string, srcIP, dstIP string) gosnappi.Flow { + flowName := fmt.Sprintf("%s-to-%s-Flow:", srcIP, dstIP) + flow := top.Flows().Add().SetName(flowName) + flow.TxRx().Port(). + SetTxName(ate.Port(t, "port1").ID()). + SetRxNames([]string{ate.Port(t, "port2").ID()}) + + flow.Metrics().SetEnable(true) + flow.Rate().SetPps(500) + flow.Size().SetFixed(512) + flow.Duration().Continuous() + + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(ateSrc.MAC) + eth.Dst().SetValue(dstMac) + + if ipVersion == "IPv4" { + ip := flow.Packet().Add().Ipv4() + ip.Src().SetValue(srcIP) + // ip.Dst().SetValue(dstIP) + ip.Dst().Increment().SetStart(ipv4DstPfx).SetCount(200) + } else { + ip := flow.Packet().Add().Ipv6() + ip.Src().SetValue(srcIP) + // ip.Dst().SetValue(dstIP) + ip.Dst().Increment().SetStart(ipv6DstPfx).SetCount(200) + } + + return flow + } + + // Create IPv4 traffic flow + trafficFlowV4 := setupFlow("IPv4", ateSrc.IPv4, ipv4DstPfx) + + // Create IPv6 traffic flow + trafficFlowV6 := setupFlow("IPv6", ateSrc.IPv6, ipv6DstPfx) + + return trafficFlowV4, trafficFlowV6 +} + +// Send traffic and validate traffic. +func verifyTrafficStreams(t *testing.T, + ate *ondatra.ATEDevice, + top gosnappi.Config, + otg *otg.OTG, + trafficFlows ...gosnappi.Flow) { + t.Helper() + + t.Log("Starting traffic for 30 seconds") + ate.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + t.Log("Stopping traffic and waiting 10 seconds for traffic stats to complete") + ate.OTG().StopTraffic(t) + time.Sleep(10 * time.Second) + + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + // Loop through each flow to validate packets + for _, flow := range trafficFlows { + flowName := flow.Name() + txPkts := float32(gnmi.Get(t, otg, gnmi.OTG().Flow(flowName).Counters().OutPkts().State())) + rxPkts := float32(gnmi.Get(t, otg, gnmi.OTG().Flow(flowName).Counters().InPkts().State())) + + // Calculate the acceptable lower and upper bounds for rxPkts + lowerBound := txPkts * (1 - tolerance) + upperBound := txPkts * (1 + tolerance) + + if rxPkts < lowerBound || rxPkts > upperBound { + t.Errorf("Received packets for flow %s are outside of the acceptable range: %v (1%% tolerance from %v)", flowName, rxPkts, txPkts) + } else { + t.Logf("Received packets for flow %s are within the acceptable range: %v (1%% tolerance from %v)", flowName, rxPkts, txPkts) + } + } +} + +func configureStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + staticRoute1 := fmt.Sprintf("%s/%d", "0.0.0.0", uint32(0)) + staticRoute2 := fmt.Sprintf("%s/%d", "::0", uint32(0)) + + ni := oc.NetworkInstance{Name: ygot.String(vrfName)} + static := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + + sr1 := static.GetOrCreateStatic(staticRoute1) + nh1 := sr1.GetOrCreateNextHop("0") + nh1.NextHop = oc.UnionString(ateDst.IPv4) + + sr2 := static.GetOrCreateStatic(staticRoute2) + nh2 := sr2.GetOrCreateNextHop("0") + nh2.NextHop = oc.UnionString(ateDst.IPv6) + + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrfName).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) +} + +// TestMplsStaticLabel +func TestStaticRouteToDefaultRoute(t *testing.T) { + var top gosnappi.Config + var v4flow, v6flow gosnappi.Flow + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otgObj := ate.OTG() + + t.Run("configureDUT Interfaces", func(t *testing.T) { + // Configure the DUT + configureDUT(t, dut) + }) + + t.Run("Configure Static Route", func(t *testing.T) { + t.Log("Configure static route on DUT") + configureStaticRoute(t, dut) + }) + + t.Run("ConfigureOTG", func(t *testing.T) { + t.Logf("Configure OTG") + top = configureOTG(t) + v4flow, v6flow = createTrafficFlows(t, ate, dut, top, ipv4DstPfx, ipv6DstPfx) + + t.Log("pushing the following config to the OTG device") + t.Log(top.String()) + otgObj.PushConfig(t, top) + otgObj.StartProtocols(t) + + }) + t.Run("Start traffic and verify traffic", func(t *testing.T) { + verifyTrafficStreams(t, ate, top, otgObj, v4flow, v6flow) + }) +} diff --git a/feature/system/aaa/README.md b/feature/system/aaa/README.md new file mode 100644 index 00000000000..d91e7603bb4 --- /dev/null +++ b/feature/system/aaa/README.md @@ -0,0 +1,169 @@ +# SYS-3.1: AAA and TACACS+ Configuration Verification Test Suite + + +## Summary + +This test suite aims to thoroughly validate the correct implementation of the AAA (Authentication, Authorization, and Accounting) framework with TACACS+ and local authentication on a device. + +## Testbed type + +* [`featureprofiles/topologies/dut.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed) + +## Procedure + +### Test environment setup + +#### Configuration + +* Configure the loopback interface with IPv4 and IPv6 address and netmasks of /32, /64 respectively + +### SYS-3.1.1 User Configuration Test + +* Create a user on the DUT with the following parameters: + ```yaml + system: + aaa: + authentication: + users: + - user: + config: + username: "testuser" + password: "password" + role: SYSTEM_ROLE_ADMIN + ``` +* Verification: + * Get the device configuration via gNMI and verify the successful creation of the "testuser" account with the specified parameters. + +### SYS-3.1.2 TACACS+ Server Configuration Test + +* Configuration: + * Configure a TACACS server on the DUT as below: + * Host IP: 192.168.1.1 + * Port: 49 + * Key: tacacs_password + * Timeout: 4 + * Configure the source IP address of a selected interface for all outgoing TACACS+ packets as the loopback interface. + ```yaml + system: + aaa: + server-groups: + server-group: + config: + name: "my_server_group" + servers: + - server: + config: + name: "tacacs_server_1" + address: 192.168.1.1 + timeout: 4 + tacacs: + config: + port: 49 + source-address: dut_loopback_address + secret-key: acacs_password + ``` +* Verification: + * Get the device configuration via gNMI and verify that the TACACS+ server has been successfully created with the correct parameters. + +### SYS-3.1.3 AAA Authentication Configuration Test + +* Configuration: + * Configure the DUT to use TACACS+ as the primary authentication method and local authentication as a fallback option. + ```yaml + system: + aaa: + authentication: + config: + authentication-method: [TACACS_ALL, LOCAL] + ``` +* Verification: + * Use gNMI to get the device's current configuration and verify that the authentication settings match the intended design. + +### SYS-3.1.4 AAA Authorization Configuration Test + +* Configuration: + * Configure command authorization to exclusively utilize the TACACS+ server. + * Configure authorization for configuration mode to primarily use the TACACS+ server and fall back to local authorization if the TACACS+ server is unavailable or does not respond. + ```yaml + system: + aaa: + authorization: + config: + authorization-method: [TACACS_ALL, LOCAL] + events: + config: + - event-type: AAA_AUTHORIZATION_EVENT_COMMAND + - event-type: AAA_AUTHORIZATION_EVENT_CONFIG + ``` +* Verification: + * Use gNMI to Get the device's current configuration and verify its alignment with the intended authorization settings.. + +### SYS-3.1.5 AAA Accounting Configuration Test + +* Configuration: + * Activate accounting for command line interface (CLI) commands on the device under test (DUT). + * Activate accounting for login sessions on the DUT. + * Activate accounting for system event logs on the DUT. + ```yaml + system: + aaa: + accounting: + config: + name: "my_server_group" + accounting-method: TACACS_ALL + events: + config: + - event-type: AAA_ACCOUNTING_EVENT_COMMAND + record: START_STOP + - event-type: AAA_ACCOUNTING_EVENT_LOGIN + record: START_STOP + ``` +* 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. + +```yaml +paths: + ## Config paths + /system/aaa/authentication/users/user/config/username: + /system/aaa/authentication/users/user/config/password-hashed: + /system/aaa/authentication/users/user/config/role: + /system/aaa/authorization/config/authorization-method: + /system/aaa/accounting/config/accounting-method: + /system/aaa/accounting/events/event/config/event-type: + /system/aaa/accounting/events/event/config/record: + /system/aaa/server-groups/server-group/servers/server/config/address: + /system/aaa/server-groups/server-group/servers/server/config/timeout: + /system/aaa/server-groups/server-group/servers/server/tacacs/config/port: + /system/aaa/server-groups/server-group/servers/server/tacacs/config/secret-key: + /system/aaa/server-groups/server-group/servers/server/tacacs/config/secret-key-hashed: + /system/aaa/server-groups/server-group/servers/server/tacacs/config/source-address: + + ## State paths + /system/aaa/authentication/users/user/state/username: + /system/aaa/authentication/users/user/state/password-hashed: + /system/aaa/authentication/users/user/state/role: + /system/aaa/authorization/state/authorization-method: + /system/aaa/accounting/state/accounting-method: + /system/aaa/accounting/events/event/state/event-type: + /system/aaa/accounting/events/event/state/record: + /system/aaa/server-groups/server-group/servers/server/state/address: + /system/aaa/server-groups/server-group/servers/server/state/timeout: + /system/aaa/server-groups/server-group/servers/server/tacacs/state/port: + /system/aaa/server-groups/server-group/servers/server/tacacs/state/secret-key: + /system/aaa/server-groups/server-group/servers/server/tacacs/state/secret-key-hashed: + /system/aaa/server-groups/server-group/servers/server/tacacs/state/source-address: + + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* VRX diff --git a/feature/system/aaa/feature.textproto b/feature/system/aaa/feature.textproto index b9923cdff62..8dc185c3b3a 100644 --- a/feature/system/aaa/feature.textproto +++ b/feature/system/aaa/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/attestz/feature.textproto b/feature/system/attestz/feature.textproto index 62a7aec8cd7..69517dc0029 100644 --- a/feature/system/attestz/feature.textproto +++ b/feature/system/attestz/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/attestz/tests/README.md b/feature/system/attestz/tests/README.md index 8fc6e282d0d..169e35b6cfa 100644 --- a/feature/system/attestz/tests/README.md +++ b/feature/system/attestz/tests/README.md @@ -25,7 +25,7 @@ The test validates that the device completes TPM enrollment and attestation duri | ID | Case | Result | | --- | ---- | ------ | -| attestz-1.1 | Successful enrollment and attestation | Device obtained oIAK and oIDevID certs and passed attestation for all control cards | +| 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 | @@ -39,14 +39,14 @@ The test validates that the device completes TPM enrollment and attestation duri 2. Verify that correct IDevID cert was used for establishing TLS session: * Cert structure matches TCG specification [Section 8](https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf#page=55). * Cert is not expired. -  * Cert is signed by switch vendor CA. -  * Cert is tied to the active control card. + * Cert is signed by switch vendor CA. + * Cert is tied to the active control card. 3. Verify IAK cert: * Cert structure matches TCG spec (similar to IDevID above). -  * Cert is not expired. -  * Cert is signed by switch vendor CA. -  * Cert is tied to the active control card. -  * IAK and IDevID cert contain the same device serial number field. + * Cert is not expired. + * Cert is signed by switch vendor CA. + * Cert is tied to the active control card. + * IAK and IDevID cert contain the same device serial number field. 4. Verify that the device returned the correct `ControlCardVendorId` with all fields populated. 5. Issue owner IAK (oIAK) and owner IDevID (oIDevID) certs, which are based on the same underlying public keys, have the same structure and fields, but are signed by a different - owner - CA. 6. Call `RotateOIakCert` to store newly issued oIAK and oIDevID certs and verify successful response. @@ -59,19 +59,20 @@ The test validates that the device completes TPM enrollment and attestation duri 13. Verify oIAK cert is the same as the one installed earlier. 14. Verify all `pcr_values` match expectations. 15. Verify `quote_signature` signature with oIAK cert. -16. Use `pcr_values` and `tpms_quote_info` to recompute PCR Quote digest and verify that it matches the one used in `quote_signature`. +16. Use `pcr_values` and `quoted` to recompute PCR Quote digest and verify that it matches the one used in `quote_signature`. 17. Call `Attest` for standby control card with correct `ControlCardSelection`, random nonce, hash algo of choice (all should be supported and tested) and all PCR indices. 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 -The test validates that the device can rotate oIAK and oIAK certificates post-install. +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 | 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. @@ -88,6 +89,7 @@ The test validates that the device completes TPM attestation after initial boots | 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 | 1. Execute "Initial Install" workflow. 2. Provision the device with switch owner mTLS credentials (separate key pair and cert for each control card). diff --git a/feature/system/bootz/feature.textproto b/feature/system/bootz/feature.textproto index 5d47d980b31..e70994d5f07 100644 --- a/feature/system/bootz/feature.textproto +++ b/feature/system/bootz/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/control_plane_traffic/otg_tests/ingress_acl/README.md b/feature/system/control_plane_traffic/otg_tests/ingress_acl/README.md new file mode 100644 index 00000000000..ade9ff395aa --- /dev/null +++ b/feature/system/control_plane_traffic/otg_tests/ingress_acl/README.md @@ -0,0 +1,90 @@ +# SYS-2.1: Ingress control-plane ACL. + +## Summary + +The test verifies securing device control-plane access with an ingress access-control-list (ACL). + +## Testbed type + +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Test environment setup + +* DUT has an single ingress port with IPv4/IPv6 enabled. + + ATE <> DUT + +### Configuration + +1. Configure test address on the device loopback (secondary) + +2. Configure IPv4/IPv6 ACL/filters with following terms: + - Allow gRPC from any (lab management access) + - Allow SSH from MGMT-SRC + - Allow ICMP from MGMT-SRC + - Explicit deny + +3. Apply filter to control-plane ingress. + +## Test cases + +### SYS-2.1.1: Verify ingress control-plane ACL permit +Generate ICMP traffic to device loopback from MGMT-SRC +Generate SSH SYN packets to device loopback from MGMT-SRC + +Verify: + +* ACL counters for corresponding ACL entries are incrementing. +* Device responds to ICMP permitted +* Device sends TCP-ACK for SSH session + +### SYS-2.1.2: Verify control-plane ACL deny +Generate ICMP traffic to device loopback from UNKNOWN-SRC +Generate SSH SYN packets to device loopback from UNKNOWN-SRC + +Verify: + +* Explicit deny ACL counter is incrementing. +* Device does not respond to ICMP +* Device sends TCP-ACK for SSH session + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # acl definition + /acl/acl-sets/acl-set/config/name: + /acl/acl-sets/acl-set/config/type: + /acl/acl-sets/acl-set/config/description: + /acl/acl-sets/acl-set/acl-entries/acl-entry/config/sequence-id: + /acl/acl-sets/acl-set/acl-entries/acl-entry/config/description: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/source-address: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/protocol: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/source-address: + /acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/protocol: + /acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/destination-port: + # acl application + /system/control-plane-traffic/ingress/acl/acl-set/config/set-name: + /system/control-plane-traffic/ingress/acl/acl-set/config/type: + + # telemetry + /system/control-plane-traffic/ingress/acl/acl-set/state/set-name: + /system/control-plane-traffic/ingress/acl/acl-set/acl-entries/acl-entry/state/sequence-id: + /system/control-plane-traffic/ingress/acl/acl-set/acl-entries/acl-entry/state/matched-packets: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* MFF +* FFF +* VRX \ No newline at end of file diff --git a/feature/system/feature.textproto b/feature/system/feature.textproto index e7955038018..177499d7c8e 100644 --- a/feature/system/feature.textproto +++ b/feature/system/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/experimental/system/gnmi/benchmarking/internal/setup/setup.go b/feature/system/gnmi/benchmarking/internal/setup/setup.go similarity index 81% rename from feature/experimental/system/gnmi/benchmarking/internal/setup/setup.go rename to feature/system/gnmi/benchmarking/internal/setup/setup.go index cc2fa872227..1facbd3a15b 100644 --- a/feature/experimental/system/gnmi/benchmarking/internal/setup/setup.go +++ b/feature/system/gnmi/benchmarking/internal/setup/setup.go @@ -25,9 +25,11 @@ import ( "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" @@ -40,7 +42,6 @@ const ( ISISInstance = "DEFAULT" // PeerGrpName is BGP peer group name. PeerGrpName = "BGP-PEER-GROUP" - // DUTAs is DUT AS. DUTAs = 64500 // ATEAs is ATE AS. @@ -329,6 +330,69 @@ func ConfigureATE(t *testing.T, ate *ondatra.ATEDevice) { topo.StartProtocols(t) } +// ConfigureOTG function is to configure otg ports with ipv4, bgp and isis peers. +func ConfigureOTG(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + topo := gosnappi.NewConfig() + + for i, dp := range ate.Ports() { + + topo.Ports().Add().SetName(dp.ID()) + dev := topo.Devices().Add().SetName(dp.ID() + "dev") + eth := dev.Ethernets().Add().SetName(dp.ID() + ".Eth") + eth.Connection().SetPortName(dp.ID()) + mac := fmt.Sprintf("02:00:01:01:01:%02x", byte(i&0xff)) + + eth.SetMac(mac) + + ip := eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") + ip.SetAddress(ATEIPList[dp.ID()].String()).SetGateway(DUTIPList[dp.ID()].String()).SetPrefix(uint32(plenIPv4)) + + // Add BGP on ATE + bgpDut1 := dev.Bgp().SetRouterId(ip.Address()) + bgpDut1Peer := bgpDut1.Ipv4Interfaces().Add().SetIpv4Name(ip.Name()).Peers().Add().SetName(dp.ID() + ".BGP4.peer") + if dp.ID() == "port1" { + bgpDut1Peer.SetPeerAddress(DUTIPList[dp.ID()].String()).SetAsNumber(ATEAs2).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + } else { + bgpDut1Peer.SetPeerAddress(DUTIPList[dp.ID()].String()).SetAsNumber(ATEAs).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + } + bgpDut1Peer.Capability().SetIpv4Unicast(true) + bgpDut1Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + + // Add ISIS on ATE + devIsis := dev.Isis().SetSystemId(strconv.FormatInt(int64(i), 16)).SetName("devIsis" + dp.Name()) + devIsis.Basic().SetHostname(devIsis.Name()).SetLearnedLspFilter(true) + devIsis.Advanced().SetAreaAddresses([]string{"490002"}) + devIsisInt := devIsis.Interfaces().Add(). + SetEthName(eth.Name()). + SetName("devIsisInt"). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2) + devIsisInt.Authentication().SetAuthType("md5") + devIsisInt.Authentication().SetMd5(authPassword) + devIsisInt.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + if dp.ID() == "port1" { + // Add BGP routes and ISIS routes , ate port1 is ingress port. + dstBgp4PeerRoutes := bgpDut1Peer.V4Routes().Add().SetName("bgpNeti1") + dstBgp4PeerRoutes.SetNextHopIpv4Address(ip.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + dstBgp4PeerRoutes.Addresses().Add(). + SetAddress(AdvertiseBGPRoutesv4).SetPrefix(32).SetCount(RouteCount) + devIsisRoutes := devIsis.V4Routes().Add().SetName("isisnet1").SetLinkMetric(20) + devIsisRoutes.Addresses().Add(). + SetAddress(advertiseISISRoutesv4).SetPrefix(32).SetCount(RouteCount).SetStep(1) + } + } + + t.Log("Pushing config to ATE...") + otg.PushConfig(t, topo) + t.Log("Starting protocols to ATE...") + otg.StartProtocols(t) + otgutils.WaitForARP(t, otg, topo, "IPv4") +} + // VerifyISISTelemetry function to used verify ISIS telemetry on DUT // using OC isis telemetry path. func VerifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice) { diff --git a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/README.md b/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/README.md similarity index 59% rename from feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/README.md rename to feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/README.md index 80b76298f1e..43312a43c90 100644 --- a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/README.md +++ b/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/README.md @@ -35,21 +35,24 @@ defined in the case): * Measure time between t=0 and all BGP received routes on ATE to report changed metric. -## Config Parameter coverage - - * BGP - * /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/repeat-n - * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/as-number - - * ISIS - * /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric - * /network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/state/set-bit - -## Telemetry Parameter coverage - - * ISIS - * /interfaces/interfaces/levels/level/adjacencies/adjacency/state/adjacency-state - * BGP - * /afi-safis/afi-safi/state/prefixes/sent - * /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor +## 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 Parameter coverage + /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/repeat-n: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + /network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/state/set-bit: + + ## Telemetry Parameter coverage + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` diff --git a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go b/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go similarity index 86% rename from feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go rename to feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go index 2e0d93c84ca..53bdf999d3f 100644 --- a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go +++ b/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go @@ -17,17 +17,17 @@ package drained_configuration_convergence_time_test import ( - "net" "testing" "time" "github.com/google/go-cmp/cmp" - "github.com/openconfig/featureprofiles/feature/experimental/system/gnmi/benchmarking/internal/setup" + "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/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" ) @@ -136,6 +136,8 @@ func setPolicyPeerGroup(t *testing.T, dut *ondatra.DUTDevice, d *oc.Root, policy // isConverged function is used to check if ATE has received all the prefixes. func isConverged(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, ap *ondatra.Port) { + // Add 10 second timer for BGP update to propagate + time.Sleep(10 * time.Second) // Check if all prefixes are learned at ATE. statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)). Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() @@ -176,7 +178,6 @@ func verifyBGPAsPath(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevic gnmi.Replace(t, dut, dutPolicyConfPath.Config(), []string{setASpathPrependPolicy}) } t.Run("BGP-AS-PATH Verification", func(t *testing.T) { - at := gnmi.OC() for _, ap := range ate.Ports() { if ap.ID() == "port1" { // port1 is ingress, skip verification on ingress port. @@ -186,26 +187,17 @@ func verifyBGPAsPath(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevic // Validate if all prefixes are received by ATE. isConverged(t, dut, ate, ap) - rib := at.NetworkInstance(ap.Name()).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "0").Bgp().Rib() - prefixPath := rib.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast(). - NeighborAny().AdjRibInPre().RouteAny().WithPathId(0).Prefix() + prefixPath := gnmi.OTG().BgpPeer(ap.ID() + ".BGP4.peer").UnicastIpv4PrefixAny() - gnmi.WatchAll(t, ate, prefixPath.State(), time.Minute, func(v *ygnmi.Value[string]) bool { + gnmi.WatchAll(t, ate.OTG(), prefixPath.Address().State(), time.Minute, func(v *ygnmi.Value[string]) bool { _, present := v.Val() return present }).Await(t) singlepath := []uint32{setup.DUTAs, setup.DUTAs, setup.DUTAs, setup.DUTAs, setup.ATEAs2} - _, ok := gnmi.WatchAll(t, ate, rib.AttrSetAny().AsSegmentMap().State(), 5*time.Minute, func(v *ygnmi.Value[map[uint32]*oc.NetworkInstance_Protocol_Bgp_Rib_AttrSet_AsSegment]) bool { + _, ok := gnmi.WatchAll(t, ate.OTG(), prefixPath.AsPathAny().State(), 5*time.Minute, func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix_AsPath]) bool { val, present := v.Val() - if present { - for _, as := range val { - if cmp.Equal(as.Member, singlepath) { - return true - } - } - } - return false + return present && cmp.Diff(val.AsNumbers, singlepath) == "" }).Await(t) if !ok { t.Errorf("Obtained AS path on ATE is not as expected") @@ -221,12 +213,6 @@ func verifyBGPAsPath(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevic // verifyBGPSetMED is to Validate MED attribute using bgp rib telemetry on ATE. func verifyBGPSetMED(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { - // Build wantSetMED to compare the diff. - var wantSetMED []uint32 - for i := 0; i < setup.RouteCount; i++ { - wantSetMED = append(wantSetMED, bgpMED) - } - // Start the timer. start := time.Now() if deviations.RoutePolicyUnderAFIUnsupported(dut) { @@ -242,7 +228,7 @@ func verifyBGPSetMED(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevic } t.Run("BGP-MED-Verification", func(t *testing.T) { - at := gnmi.OC() + for _, ap := range ate.Ports() { if ap.ID() == "port1" { continue @@ -250,30 +236,14 @@ func verifyBGPSetMED(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevic // Validate if all prefixes are received by ATE. isConverged(t, dut, ate, ap) - rib := at.NetworkInstance(ap.Name()).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "0").Bgp().Rib() - routeP := rib.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Ipv4Unicast(). - NeighborAny().AdjRibInPre().RouteAny().WithPathId(0) - routes := gnmi.GetAll(t, ate, routeP.State()) - attrs := gnmi.GetAll(t, ate, rib.AttrSetAny().State()) - mask := net.IPv4Mask(255, 255, 255, 0) - masked := net.ParseIP(setup.AdvertiseBGPRoutesv4).Mask(mask) - var gotSetMED []uint32 - var pref []string - for _, route := range routes { - ip, _, _ := net.ParseCIDR(route.GetPrefix()) - pref = append(pref, route.GetPrefix()) - if ip.Mask(mask).Equal(masked) { - idx := route.GetAttrIndex() - if idx >= uint64(len(attrs)) { - t.Errorf("Invalid attr-index %d for prefix: %s", idx, route.GetPrefix()) - continue - } - gotSetMED = append(gotSetMED, attrs[idx].GetMed()) + + bgpPrefixes := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().BgpPeer(ap.ID()+".BGP4.peer").UnicastIpv4PrefixAny().State()) + for _, prefix := range bgpPrefixes { + if prefix.GetMultiExitDiscriminator() != bgpMED { + t.Errorf("Received Prefix Med %d Expected Med %d for Prefix %v", prefix.GetMultiExitDiscriminator(), bgpMED, prefix.GetAddress()) } } - if diff := cmp.Diff(wantSetMED, gotSetMED); diff != "" { - t.Errorf("obtained MED on ATE is not as expected, got %v, want %v, Prefixes %v", gotSetMED, wantSetMED, pref) - } + } }) // End the timer and calculate time taken to apply setMED. @@ -301,7 +271,7 @@ func TestEstablish(t *testing.T) { t.Log("Configure ATE with Interfaces, BGP, ISIS configs.") ate := ondatra.ATE(t, "ate") - setup.ConfigureATE(t, ate) + setup.ConfigureOTG(t, ate) t.Log("Verify BGP Session state , should be in ESTABLISHED State.") setup.VerifyBgpTelemetry(t, dut) diff --git a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go b/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go similarity index 72% rename from feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go rename to feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go index e14f163c63f..fbdbba61792 100644 --- a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go +++ b/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go @@ -20,11 +20,12 @@ import ( "testing" "time" - "github.com/openconfig/featureprofiles/feature/experimental/system/gnmi/benchmarking/internal/setup" + "github.com/openconfig/featureprofiles/feature/system/gnmi/benchmarking/internal/setup" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" "github.com/openconfig/ygnmi/ygnmi" ) @@ -65,27 +66,25 @@ func setISISMetric(t *testing.T, dut *ondatra.DUTDevice) { func verifyISISMetric(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { t.Run("ISIS Metric verification", func(t *testing.T) { - at := gnmi.OC() for _, ap := range ate.Ports() { if ap.ID() == "port1" { // Port1 is ingress, skip verification on ingress port continue } - const want = oc.Interface_OperStatus_UP - - if got := gnmi.Get(t, ate, at.Interface(ap.Name()).OperStatus().State()); got != want { - t.Errorf("%s oper-status got %v, want %v", ap, got, want) - } - is := at.NetworkInstance(ap.Name()).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, "0").Isis() - lsps := is.LevelAny().LspAny() - - _, ok := gnmi.WatchAll(t, ate, lsps.Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().PrefixAny().Metric().State(), 5*time.Minute, func(v *ygnmi.Value[uint32]) bool { - val, present := v.Val() - return present && val == setup.ISISMetric + got, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("devIsis"+ap.Name()).LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().PrefixAny().Metric().State(), time.Minute, func(v *ygnmi.Value[uint32]) bool { + metric, present := v.Val() + if present { + if metric == setup.ISISMetric { + return true + } + } + return false }).Await(t) + + metricInReceivedLsp, _ := got.Val() if !ok { - t.Errorf("Obtained Metric on ATE is not as expected") + t.Fatalf("Metric not matched. Expected %d got %d ", setup.ISISMetric, metricInReceivedLsp) } } }) @@ -96,28 +95,28 @@ func verifyISISMetric(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevi func verifyISISOverloadBit(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice) { t.Run("ISIS Overload bit verification", func(t *testing.T) { - at := gnmi.OC() for _, ap := range ate.Ports() { if ap.ID() == "port1" { // port1 is ingress, skip verification on ingress port continue } - const want = oc.Interface_OperStatus_UP + otg := ate.OTG() + _, ok := gnmi.WatchAll(t, otg, gnmi.OTG().IsisRouter("devIsis"+ap.Name()).LinkStateDatabase().LspsAny().Flags().State(), time.Minute, func(v *ygnmi.Value[[]otgtelemetry.E_Lsps_Flags]) bool { + flags, present := v.Val() + if present { + for _, flag := range flags { + if flag == otgtelemetry.Lsps_Flags_OVERLOAD { + return true + } + } + } + return false + }).Await(t) - if got := gnmi.Get(t, ate, at.Interface(ap.Name()).OperStatus().State()); got != want { - t.Errorf("%s oper-status got %v, want %v", ap, got, want) + if !ok { + t.Fatalf("OverLoad Bit not seen on learned lsp on ATE") } - // TODO: SetBit retrieval is not working in ATE. - // Ref: https://github.com/openconfig/featureprofiles/issues/1176 - // Below code will be uncommented once above issue is resolved. - - // is := at.NetworkInstance(ap.Name()).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, "0").Isis() - // lsps := is.LevelAny().LspAny() - // gotIsisSetBit := gnmi.GetAll(t, ate, lsps.Tlv(oc.IsisLsdbTypes_ISIS_TLV_TYPE_EXTENDED_IPV4_REACHABILITY).ExtendedIpv4Reachability().PrefixAny().SBit().State()) - // if diff := cmp.Diff(setup.ISISSetBitList, gotIsisSetBit); diff != "" { - // t.Errorf("obtained setBit on ATE is not as expected, got %v, want %v", gotIsisSetBit, setup.ISISSetBitList) - // } } }) } diff --git a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/metadata.textproto b/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/metadata.textproto similarity index 100% rename from feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/metadata.textproto rename to feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/metadata.textproto diff --git a/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md b/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md new file mode 100644 index 00000000000..2589aeda5c4 --- /dev/null +++ b/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md @@ -0,0 +1,42 @@ +# gNMI-1.2: Benchmarking: Full Configuration Replace + +## Summary + +Measure performance of full configuration replace. + +## Procedure + +Configure DUT with: + - The number of interfaces needed for the benchmarking test. + - One BGP peer per interface. + - One ISIS adjacency per interface. +Measure time required for Set operation to complete. +Modify descriptions of a subset of interfaces within the system. +Measure time for Set to complete. + +Notes: +This test does not measure the time to an entirely converged state, only to completion of the gNMI update. + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + +paths: + ## Config Parameter coverage + /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/repeat-n: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: + /network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/state/set-bit: + +``` + +## Minimum DUT Required + +vRX - Virtual Router Device + + diff --git a/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go b/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go similarity index 91% rename from feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go rename to feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go index cf590178c90..03d8878c59e 100644 --- a/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go +++ b/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "github.com/openconfig/featureprofiles/feature/experimental/system/gnmi/benchmarking/internal/setup" + "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" @@ -61,13 +61,12 @@ func TestGnmiFullConfigReplace(t *testing.T) { dut := ondatra.DUT(t, "dut") t.Log("Configure network instance on DUT") - dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) - gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + fptest.ConfigureDefaultNetworkInstance(t, dut) t.Log("Cleanup exisitng BGP and ISIS configs on DUT before configuring test configs") - dutBGPPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + dutBGPPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") gnmi.Delete(t, dut, dutBGPPath.Config()) - dutISISPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, setup.ISISInstance).Isis() + dutISISPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, setup.ISISInstance) gnmi.Delete(t, dut, dutISISPath.Config()) confP := gnmi.OC() diff --git a/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto b/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto similarity index 95% rename from feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto rename to feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto index 6f08f440766..64eed0547a0 100644 --- a/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto +++ b/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto @@ -23,7 +23,6 @@ platform_exceptions: { vendor: JUNIPER } deviations: { - route_policy_under_afi_unsupported: true isis_level_enabled: true } } diff --git a/feature/system/gnmi/cliorigin/feature.textproto b/feature/system/gnmi/cliorigin/feature.textproto index 7ac0a36ac4a..1d84d99726b 100644 --- a/feature/system/gnmi/cliorigin/feature.textproto +++ b/feature/system/gnmi/cliorigin/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md b/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md index c354b4ab31d..da701d9953f 100644 --- a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md +++ b/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md @@ -54,12 +54,21 @@ update: { TODO: Support other vendor CLIs and place examples here. -## Config Parameter Coverage +## OpenConfig Path and RPC Coverage -* origin: "cli" -* /qos/forwarding-groups/forwarding-group/config/output-queue -* /qos/queues/queue/config/name +The below yaml defines the OC paths intended to be covered by this test. OC +paths used for test setup are not listed here. -## Telemetry Parameter Coverage +```yaml +paths: + ## Config paths + /qos/forwarding-groups/forwarding-group/config/output-queue: + /qos/queues/queue/config/name: -* None + ## State paths: None + +rpcs: + gnmi: + gNMI.Set: + origin: "cli" +``` diff --git a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto b/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto index 48338a193e3..e59aae3a98e 100644 --- a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto +++ b/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto @@ -5,3 +5,11 @@ uuid: "3b8c5e39-3f00-43eb-a8ae-71dfc49e1e65" plan_id: "gNMI-1.12" description: "Mixed OpenConfig/CLI Origin" testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + qos_queue_requires_id: true + } +} \ No newline at end of file diff --git a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go b/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go index cdca89a7a61..5171aa1ee49 100644 --- a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go +++ b/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go @@ -18,12 +18,14 @@ package mixed_oc_cli_origin_support_test import ( "testing" - "github.com/google/go-cmp/cmp" + "github.com/openconfig/ygnmi/ygnmi" + + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ygnmi/schemaless" - "github.com/openconfig/ygnmi/ygnmi" ) func TestMain(m *testing.M) { @@ -31,86 +33,92 @@ func TestMain(m *testing.M) { } type testCase struct { - cliConfig string - queueName string - forwardGroupName string + addNonOcBatchUpdateF func(t *testing.T, mixedQuery *gnmi.SetBatch) + queueName string + forwardGroupName string } -// showRunningConfig returns the output of 'show running-config' on the device. -func showRunningConfig(t *testing.T, dut *ondatra.DUTDevice) string { - t.Helper() - return dut.CLI().Run(t, "show running-config") -} +func prepareDut(t *testing.T, dut *ondatra.DUTDevice, queueName string) { + t.Logf("Step 1: Delete and make sure QoS queue under test is not already set.") -// testQoSWithCLIAndOCUpdates carries out a mixed-origin test for a QoS test case. -// -// TODO: If a truly permanent example of a mutual dependency between CLI and OC -// exists that must be modelled partially in CLI even as OC modelling continues -// to mature, then consider changing this test to that instead. -func testQoSWithCLIAndOCUpdates(t *testing.T, dut *ondatra.DUTDevice, tCase testCase, subtreeReplace bool) { qosPath := gnmi.OC().Qos() - t.Logf("Step 1: Delete and make sure QoS queue under test is not already set.") - gnmi.Delete(t, dut, qosPath.Queue(tCase.queueName).Config()) - // Make sure the test queue does not exist. - if existingQueue := gnmi.LookupConfig(t, dut, qosPath.Queue(tCase.queueName).Config()); existingQueue.IsPresent() { - t.Fatalf("Detected an existing %v queue. This is unexpected.", tCase.queueName) + gnmi.Delete(t, dut, qosPath.Queue(queueName).Config()) + + if existingQueue := gnmi.LookupConfig(t, dut, qosPath.Queue(queueName).Config()); existingQueue.IsPresent() { + t.Fatalf("Detected an existing %v queue. This is unexpected.", queueName) } +} +func replaceAsNoOp(t *testing.T, dut *ondatra.DUTDevice, subTreeReplace bool) *oc.Root { t.Logf("Step 2: Retrieve current root OC config") - runningConfig := showRunningConfig(t, dut) - r := gnmi.Get(t, dut, gnmi.OC().Config()) + + ocConfig := gnmi.Get[*oc.Root](t, dut, gnmi.OC().Config()) + + qosPath := gnmi.OC().Qos() + qosConfig := ocConfig.GetOrCreateQos() + + fptest.LogQuery(t, "QoS update for the OC config:", qosPath.Config(), qosConfig) t.Logf("Step 3: Test that replacing device with current config is accepted and is a no-op.") var result *ygnmi.Result - if subtreeReplace { - result = gnmi.Replace(t, dut, qosPath.Config(), r.GetOrCreateQos()) + if subTreeReplace { + result = gnmi.Replace(t, dut, qosPath.Config(), qosConfig) } else { - result = gnmi.Replace(t, dut, gnmi.OC().Config(), r) + result = gnmi.Replace(t, dut, gnmi.OC().Config(), ocConfig) } + t.Logf("gnmi.Replace on root response: %+v", result.RawResponse) + return ocConfig +} + +func sendMixedOriginRequest( + t *testing.T, + dut *ondatra.DUTDevice, + ocConfig *oc.Root, + tCase testCase, + subTreeReplace bool, +) { t.Logf("Step 4: Construct and send mixed-origin SetRequest") + // Create OC addition to the config. - qos := r.GetOrCreateQos() - qos.GetOrCreateQueue(tCase.queueName) - qos.GetOrCreateForwardingGroup(tCase.forwardGroupName).SetOutputQueue(tCase.queueName) + qosConfig := ocConfig.GetOrCreateQos() - fptest.LogQuery(t, "QoS update for the OC config:", qosPath.Config(), qos) + qosConfig.GetOrCreateQueue(tCase.queueName) + qosConfig.GetOrCreateForwardingGroup(tCase.forwardGroupName).SetOutputQueue(tCase.queueName) - // Create and apply mixed CLI+OC SetRequest. - cliPath, err := schemaless.NewConfig[string]("", "cli") - if err != nil { - t.Fatalf("Failed to create CLI ygnmi query: %v", err) - } + qosPath := gnmi.OC().Qos() + + fptest.LogQuery(t, "QoS update for the OC config:", qosPath.Config(), qosConfig) mixedQuery := &gnmi.SetBatch{} - if subtreeReplace { - gnmi.BatchReplace(mixedQuery, qosPath.Config(), qos) + if subTreeReplace { + gnmi.BatchReplace(mixedQuery, qosPath.Config(), qosConfig) } else { - gnmi.BatchReplace(mixedQuery, gnmi.OC().Config(), r) + gnmi.BatchReplace(mixedQuery, gnmi.OC().Config(), ocConfig) } - gnmi.BatchUpdate(mixedQuery, cliPath, tCase.cliConfig) - result = mixedQuery.Set(t, dut) + + tCase.addNonOcBatchUpdateF(t, mixedQuery) + + result := mixedQuery.Set(t, dut) t.Logf("gnmiClient.Set() response: %+v", result.RawResponse) +} +func verifyChanges(t *testing.T, dut *ondatra.DUTDevice, tCase testCase) { t.Logf("Step 5: Verify QoS queue configuration has been accepted by the target") - // Validate CLI has changed - newRunningConfig := showRunningConfig(t, dut) - diff := cmp.Diff(runningConfig, newRunningConfig) - t.Logf("running config (-old, +new):\n%s", cmp.Diff(runningConfig, newRunningConfig)) - if diff == "" { - t.Errorf("CLI running-config expected to change but did not change after mixed-origin SetRequest.") - } + qosPath := gnmi.OC().Qos() + + qosPath.Queue(tCase.queueName).Config().PathStruct() // Validate new OC config has been accepted. - gotQueue := gnmi.Get(t, dut, qosPath.Queue(tCase.queueName).Config()) + gotQueue := gnmi.Get[*oc.Qos_Queue](t, dut, qosPath.Queue(tCase.queueName).Config()) if got := gotQueue.GetName(); got != tCase.queueName { t.Errorf("Get(DUT queue name): got %v, want %v", got, tCase.queueName) } - gotFG := gnmi.Get(t, dut, qosPath.ForwardingGroup(tCase.forwardGroupName).Config()) + gotFG := gnmi.Get[*oc.Qos_ForwardingGroup](t, dut, qosPath.ForwardingGroup(tCase.forwardGroupName).Config()) if got := gotFG.GetName(); got != tCase.forwardGroupName { t.Errorf("Get(DUT forwarding group name): got %v, want %v", got, tCase.forwardGroupName) } @@ -119,6 +127,26 @@ func testQoSWithCLIAndOCUpdates(t *testing.T, dut *ondatra.DUTDevice, tCase test } } +// testQoSWithCLIAndOCUpdates carries out a mixed-origin test for a QoS test case. +// +// TODO: If a truly permanent example of a mutual dependency between CLI and OC +// exists that must be modelled partially in CLI even as OC modelling continues +// to mature, then consider changing this test to that instead. +func testQoSWithCLIAndOCUpdates( + t *testing.T, + dut *ondatra.DUTDevice, + tCase testCase, + subTreeReplace bool, +) { + prepareDut(t, dut, tCase.queueName) + + ocConfig := replaceAsNoOp(t, dut, subTreeReplace) + + sendMixedOriginRequest(t, dut, ocConfig, tCase, subTreeReplace) + + verifyChanges(t, dut, tCase) +} + func TestQoSDependentCLIFullReplace(t *testing.T) { // TODO: Skipping this test case because it is not required to pass right now. t.Skip() @@ -134,19 +162,48 @@ func TestQoSDependentCLISubtreeReplace(t *testing.T) { } func getTestcase(t *testing.T, dut *ondatra.DUTDevice) testCase { - var cliConfig string - // TODO: additional vendor CLI to be added if and when necessary for compatibility with the OC QoS configuration. + tc := testCase{ + queueName: "TEST", + forwardGroupName: "target-group-TEST", + } + switch vendor := dut.Vendor(); vendor { case ondatra.ARISTA: - cliConfig = `qos traffic-class 0 name target-group-TEST + tc.addNonOcBatchUpdateF = func(t *testing.T, mixedQuery *gnmi.SetBatch) { + nonOCConfigPath, err := schemaless.NewConfig[string]("", "cli") + if err != nil { + t.Fatalf("Failed to create CLI ygnmi query: %v", err) + } + + nonOCConfig := `qos traffic-class 0 name target-group-TEST qos tx-queue 0 name TEST` + + gnmi.BatchUpdate(mixedQuery, nonOCConfigPath, nonOCConfig) + } + case ondatra.NOKIA: + // nokia deviates and uses native yang model rather than cli since its all the same thing + // for srlinux + tc.addNonOcBatchUpdateF = func(t *testing.T, mixedQuery *gnmi.SetBatch) { + nonOCConfigPath, err := schemaless.NewConfig[map[string]interface{}]("/qos", "srl_nokia") + if err != nil { + t.Fatalf("Failed to create CLI ygnmi query: %v", err) + } + + nonOCConfigJSON := map[string]interface{}{ + "queues": map[string]interface{}{ + "queue": []map[string]interface{}{ + { + "name": "TEST", + "queue-index": 0, + }, + }, + }, + } + gnmi.BatchUpdate(mixedQuery, nonOCConfigPath, nonOCConfigJSON) + } default: t.Skipf("Unsupported vendor device: %v", vendor) } - return testCase{ - cliConfig: cliConfig, - queueName: "TEST", - forwardGroupName: "target-group-TEST", - } + return tc } diff --git a/feature/system/gnmi/feature.textproto b/feature/system/gnmi/feature.textproto index ad8b231c431..b6bc47c2e4b 100644 --- a/feature/system/gnmi/feature.textproto +++ b/feature/system/gnmi/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/gnmi/get/feature.textproto b/feature/system/gnmi/get/feature.textproto index 740a884e128..55fd8121dd9 100644 --- a/feature/system/gnmi/get/feature.textproto +++ b/feature/system/gnmi/get/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/gnmi/metadata/feature.textproto b/feature/system/gnmi/metadata/feature.textproto index 5e3ce2a2739..e85b54ab1db 100644 --- a/feature/system/gnmi/metadata/feature.textproto +++ b/feature/system/gnmi/metadata/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/gnmi/metadata/tests/lagre_set_consistency_test/README.md b/feature/system/gnmi/metadata/tests/large_set_consistency_test/README.md similarity index 92% rename from feature/system/gnmi/metadata/tests/lagre_set_consistency_test/README.md rename to feature/system/gnmi/metadata/tests/large_set_consistency_test/README.md index 858bfd7cacb..e0b5b2ba32b 100644 --- a/feature/system/gnmi/metadata/tests/lagre_set_consistency_test/README.md +++ b/feature/system/gnmi/metadata/tests/large_set_consistency_test/README.md @@ -1,4 +1,4 @@ -# gNMI-1.14 OpenConfig metadata consistency during large config push +# gNMI-1.14: OpenConfig metadata consistency during large config push ## Summary This test verify if OpenConfig metadata leaf at root is updated according to pushed config and not reverted or otherway modified, Even if many rapid, concurrent non-write requests are served while setRequest is in process (and SetResponse is not send yet). @@ -40,3 +40,13 @@ dut.testbed ## Minimum DUT platform FFF + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: + +``` \ No newline at end of file diff --git a/feature/system/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go b/feature/system/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go new file mode 100644 index 00000000000..5dbc85b2572 --- /dev/null +++ b/feature/system/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go @@ -0,0 +1,278 @@ +package large_set_consistency_test + +import ( + "context" + "encoding/base64" + "encoding/json" + "sync" + "sync/atomic" + "testing" + "time" + + "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/ygnmi/ygnmi" + "github.com/openconfig/ygot/util" + "github.com/openconfig/ygot/ygot" + "google.golang.org/protobuf/proto" +) + +const ( + metadata1 = "1st_LARGE_CONFIGURATION" + metadata2 = "2nd_LARGE_CONFIGURATION" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: 30, + IPv6: "2001:0db8::192:0:2:1", + IPv6Len: 126, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: 30, + IPv6: "2001:0db8::192:0:2:5", + IPv6Len: 126, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// setEthernetFromBase merges the ethernet config from the interfaces in base config into +// the destination config. +func setEthernetFromBase(t testing.TB, config *oc.Root) { + t.Helper() + + for iname, iface := range config.Interface { + eb := config.GetInterface(iname).GetEthernet() + ec := iface.GetOrCreateEthernet() + if eb == nil || ec == nil { + continue + } + if err := ygot.MergeStructInto(ec, eb); err != nil { + t.Errorf("Cannot merge %s ethernet: %v", iname, err) + } + } +} + +// buildGNMIUpdate builds a gnmi update for a given ygot path and value. +func buildGNMIUpdate(t *testing.T, yPath ygnmi.PathStruct, val any) *gpb.Update { + t.Helper() + path, _, errs := ygnmi.ResolvePath(yPath) + if errs != nil { + t.Fatalf("Could not resolve the ygot path; %v", errs) + } + js, err := ygot.Marshal7951(val, ygot.JSONIndent(" "), &ygot.RFC7951JSONConfig{AppendModuleName: true, PreferShadowPath: true}) + if err != nil { + t.Fatalf("Could not encode value into JSON format: %v", err) + } + return &gpb.Update{ + Path: path, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{ + JsonIetfVal: js, + }, + }, + } +} + +// extractMetadataAnnotation extracts the metadata protobuf message from a gNMI GetResponse. +func extractMetadataAnnotation(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUTDevice) (string, int64) { + ns := getNotificationsUsingGNMIGet(t, gnmiClient, dut) + var getRespTimeStamp int64 + if got := len(ns); got == 0 { + t.Fatalf("number of notifications got %d, want > 0", got) + } + + var annotation any + for _, n := range ns { + getRespTimeStamp = n.GetTimestamp() + for _, u := range n.GetUpdate() { + path, err := util.JoinPaths(new(gpb.Path), u.GetPath()) + if err != nil || len(path.GetElem()) > 0 { + continue + } + + var v map[string]any + if err := json.Unmarshal(u.GetVal().GetJsonIetfVal(), &v); err == nil { + if metav, ok := v["@"]; ok { + if metaObj, ok := metav.(map[string]any); ok { + if annotation, ok = metaObj["openconfig-metadata:protobuf-metadata"]; ok { + break + } + } + } + } + } + if annotation != nil { + break + } + } + + var ok bool + var encoded string + if annotation == nil { + t.Fatal("received metadata object does not contain expected annotation") + } + if encoded, ok = annotation.(string); !ok { + t.Fatalf("got %T type for annotation, expected string type", annotation) + } + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + t.Fatalf("cannot decode base64 string, err: %v", err) + } + msg := &gpb.ModelData{} + if err := proto.Unmarshal(decoded, msg); err != nil { + t.Fatalf("cannot unmarshal received proto any msg, err: %v", err) + } + return msg.GetName(), getRespTimeStamp +} + +// buildGNMISetRequest builds gnmi set request with protobuf-metadata +func buildGNMISetRequest(t *testing.T, metadataText string, baselineConfig *oc.Root) *gpb.SetRequest { + 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, + }, + } + v, err := json.Marshal(j) + if err != nil { + t.Fatalf("marshal config failed with unexpected error: %v", err) + } + + gpbSetRequest := &gpb.SetRequest{ + Update: []*gpb.Update{{ + Path: &gpb.Path{ + Elem: []*gpb.PathElem{}, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_JsonIetfVal{JsonIetfVal: v}, + }, + }}, + } + + accompaniedPath := gnmi.OC().Config().PathStruct() + gpbSetRequest.Update = append(gpbSetRequest.Update, buildGNMIUpdate(t, accompaniedPath, baselineConfig)) + return gpbSetRequest +} + +// returns notifications using gnmi get +func getNotificationsUsingGNMIGet(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUTDevice) []*gpb.Notification { + getResponse, err := gnmiClient.Get(context.Background(), &gpb.GetRequest{ + Path: []*gpb.Path{{ + Elem: []*gpb.PathElem{}, + }}, + Type: gpb.GetRequest_CONFIG, + Encoding: gpb.Encoding_JSON_IETF, + }) + if err != nil { + t.Fatalf("Cannot fetch protobuf-metadata annotation from the DUT: %v", err) + } + + return getResponse.GetNotification() +} + +func checkMetadata1(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUTDevice, done *atomic.Int64) { + t.Helper() + got, getRespTimeStamp := extractMetadataAnnotation(t, gnmiClient, dut) + want := metadata1 + if got != want && getRespTimeStamp < done.Load() { + t.Errorf("extractMetadataAnnotation: got %v, want %v", got, want) + } +} + +func checkMetadata2(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUTDevice) { + t.Helper() + got, getRespTimeStamp := extractMetadataAnnotation(t, gnmiClient, dut) + want := metadata2 + t.Logf("getResp: %v ", getRespTimeStamp) + if got != want { + t.Errorf("extractMetadataAnnotation: got %v, want %v", got, want) + } +} + +func TestLargeSetConsistency(t *testing.T) { + done := &atomic.Int64{} + dut := ondatra.DUT(t, "dut") + + // configuring basic interface and network instance as some devices only populate OC after configuration + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + + fptest.ConfigureDefaultNetworkInstance(t, dut) + + // Configuring basic interface and network instance as some devices only populate OC after configuration. + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Type().Config(), + oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + + baselineConfig := fptest.GetDeviceConfig(t, dut) + setEthernetFromBase(t, baselineConfig) + gnmiClient := dut.RawAPIs().GNMI(t) + + // send 1st update request in one goroutine + gpbSetRequest := buildGNMISetRequest(t, metadata1, baselineConfig) + t.Log("gnmiClient Set 1st large config") + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmi.Set unexpected error: %v", err) + } + checkMetadata1(t, gnmiClient, dut, done) + + var wg sync.WaitGroup + ch := make(chan struct{}, 1) + + // sending 2nd update request in one goroutine + gpbSetRequest = buildGNMISetRequest(t, metadata2, baselineConfig) + wg.Add(1) + go func() { + defer wg.Done() + t.Log("gnmiClient Set 2nd large config") + setResp, err := gnmiClient.Set(context.Background(), gpbSetRequest) + if err != nil { + t.Errorf("gnmi.Set unexpected error: %v", err) + return + } + close(ch) + done.Store(setResp.GetTimestamp()) + }() + + for i := 0; i < 4; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + for { + select { + case <-ch: + t.Logf("[%d - exiting]", i) + return + default: + t.Logf("[%d - running] checking config protobuf-metadata", i) + time.Sleep(5 * time.Millisecond) + checkMetadata1(t, gnmiClient, dut, done) + } + } + }(i) + } + + wg.Wait() + time.Sleep(5 * time.Second) + checkMetadata2(t, gnmiClient, dut) +} diff --git a/feature/experimental/bgp/ate_tests/bgp_remove_private_as/metadata.textproto b/feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto similarity index 62% rename from feature/experimental/bgp/ate_tests/bgp_remove_private_as/metadata.textproto rename to feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto index 4a298ab13bd..c346a8d5da2 100644 --- a/feature/experimental/bgp/ate_tests/bgp_remove_private_as/metadata.textproto +++ b/feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto @@ -1,24 +1,24 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "2b4372c2-8827-4624-837c-f44b1a54edf7" -plan_id: "RT-1.11" -description: "BGP remove private AS" +uuid: "e9cdd09c-14b9-4e3c-b38c-584ef9611c3f" +plan_id: "gNMI-1.14" +description: "OpenConfig metadata consistency during large config push" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { vendor: ARISTA } deviations: { - interface_enabled: true default_network_instance: "default" } } platform_exceptions: { platform: { - vendor: NOKIA + vendor: CISCO } deviations: { - interface_enabled: true + skip_macaddress_check: true } } + diff --git a/feature/system/gnmi/set/feature.textproto b/feature/system/gnmi/set/feature.textproto index a9b786b86df..30b70f2e858 100644 --- a/feature/system/gnmi/set/feature.textproto +++ b/feature/system/gnmi/set/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/gnmi/set/tests/gnmi_set_test/README.md b/feature/system/gnmi/set/tests/gnmi_set_test/README.md index 1ee1a376cd2..f87f4045c14 100644 --- a/feature/system/gnmi/set/tests/gnmi_set_test/README.md +++ b/feature/system/gnmi/set/tests/gnmi_set_test/README.md @@ -4,6 +4,11 @@ Ensures that the device respects certain gNMI SetRequest corner case behaviors. +## Topology + +* ATE port-1 and DUT port-1 +* ATE port-2 and DUT port-2 + ## Procedure Each test should be implemented as three variants: @@ -191,3 +196,13 @@ This test checks that the static protocol name is usable. ## RPC Coverage * gNMI.Set + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: + +``` diff --git a/feature/system/gnmi/set/tests/gnmi_set_test/gnmi_set_test.go b/feature/system/gnmi/set/tests/gnmi_set_test/gnmi_set_test.go index fd6946fdee3..d169ecec2f9 100644 --- a/feature/system/gnmi/set/tests/gnmi_set_test/gnmi_set_test.go +++ b/feature/system/gnmi/set/tests/gnmi_set_test/gnmi_set_test.go @@ -15,12 +15,14 @@ package gnmi_set_test import ( + "context" "fmt" "regexp" "strconv" "strings" "sync" "testing" + "time" "flag" @@ -34,6 +36,7 @@ import ( "github.com/openconfig/ygnmi/schemaless" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" ) var ( @@ -42,16 +45,6 @@ var ( skipContainerOp = flag.Bool("skip_container_op", false, "Skip ContainerOp test cases.") skipItemOp = flag.Bool("skip_item_op", false, "Skip ItemOp test cases.") - // The following experimental flags fine-tune the RootOp and ContainerOp behavior. Some - // devices require the config to be pruned for these to work. We are still undecided - // whether they should be deviations; pending OpenConfig clarifications. - pruneComponents = flag.Bool("prune_components", true, "Prune components that are not ports. Use this to preserve the breakout-mode settings.") - pruneLLDP = flag.Bool("prune_lldp", true, "Prune LLDP config.") - setEthernetFromState = flag.Bool("set_ethernet_from_state", true, "Set interface/ethernet config from state, mostly to get the port-speed settings correct.") - - // This has no known effect except to reduce logspam while debugging. - pruneQoS = flag.Bool("prune_qos", true, "Prune QoS config.") - // Experimental flags that will likely become a deviation. cannotDeleteVRF = flag.Bool("cannot_delete_vrf", true, "Device cannot delete VRF.") // See "Note about cannotDeleteVRF" below. ) @@ -74,6 +67,29 @@ var ( } ) +// Options are optional parameters to pass when deleting configs from the collected running config used in removeStatementsBetweenWords +type Options struct { + interfaces []string +} + +// breakout struct parameters define the speed and number of physical channels +type breakout struct { + breakoutSpeed oc.E_IfEthernet_ETHERNET_SPEED + numPhysicalChannels *uint8 +} + +// showRunningConfig gets the running config from the router +func showRunningConfig(t testing.TB, dut *ondatra.DUTDevice) string { + if ondatra.DUT(t, "dut").Vendor() == ondatra.CISCO { + runningConfig, err := dut.RawAPIs().CLI(t).RunCommand(context.Background(), "show running-config") + if err != nil { + t.Fatalf("'show running-config' failed: %v", err) + } + return runningConfig.Output() + } + return "" +} + // Implementation Note // // Tests have three push variants: ItemOp, ContainerOp, and RootOp. @@ -108,8 +124,10 @@ func TestGetSet(t *testing.T) { // Configuring basic interface and network instance as some devices only populate OC after configuration. gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) - gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Type().Config(), - oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Config(), &oc.NetworkInstance{ + Name: ygot.String(deviations.DefaultNetworkInstance(dut)), + Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE, + }) scope := defaultPushScope(dut) @@ -142,12 +160,12 @@ func TestDeleteInterface(t *testing.T) { op.push(t, dut, config, scope) t.Run("VerifyBeforeDelete", func(t *testing.T) { - v1 := gnmi.Lookup(t, dut, q1) - if got1, ok := v1.Val(); !ok || got1 != want1 { + v1, ok := gnmi.Await(t, dut, q1, 60*time.Second, want1).Val() + if !ok { t.Errorf("State got %v, want %v", v1, want1) } - v2 := gnmi.Lookup(t, dut, q2) - if got2, ok := v2.Val(); !ok || got2 != want2 { + v2, ok := gnmi.Await(t, dut, q2, 60*time.Second, want2).Val() + if !ok { t.Errorf("State got %v, want %v", v2, want2) } }) @@ -156,14 +174,25 @@ func TestDeleteInterface(t *testing.T) { config.DeleteInterface(p1.Name()) config.DeleteInterface(p2.Name()) + + if len(config.Interface) == 0 { + config.Interface = nil + } + op.push(t, dut, config, scope) t.Run("VerifyAfterDelete", func(t *testing.T) { if v := gnmi.Lookup(t, dut, q1); v.IsPresent() { - t.Errorf("State got unwanted %v", v) + value, _ := v.Val() + if value != "" { + t.Errorf("State got unwanted %v", v) + } } if v := gnmi.Lookup(t, dut, q2); v.IsPresent() { - t.Errorf("State got unwanted %v", v) + value, _ := v.Val() + if value != "" { + t.Errorf("State got unwanted %v", v) + } } }) }) @@ -306,6 +335,10 @@ func TestDeleteNonDefaultVRF(t *testing.T) { config.DeleteInterface(p1.Name()) config.DeleteInterface(p2.Name()) + if deviations.ReorderCallsForVendorCompatibilty(dut) { + op.push(t, dut, config, scope) + } + ip1.ConfigOCInterface(config.GetOrCreateInterface(p1.Name()), dut) ip2.ConfigOCInterface(config.GetOrCreateInterface(p2.Name()), dut) @@ -313,8 +346,8 @@ func TestDeleteNonDefaultVRF(t *testing.T) { ni := config.GetOrCreateNetworkInstance(vrf) ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF - id1 := attachInterface(ni, p1.Name(), 0) - id2 := attachInterface(ni, p2.Name(), 0) + id1 := attachInterface(dut, ni, p1.Name(), 0) + id2 := attachInterface(dut, ni, p2.Name(), 0) op.push(t, dut, config, scope) @@ -326,7 +359,10 @@ func TestDeleteNonDefaultVRF(t *testing.T) { }) t.Log("Cleanup") - + if deviations.ReorderCallsForVendorCompatibilty(dut) { + config.DeleteInterface(p1.Name()) + config.DeleteInterface(p2.Name()) + } config.DeleteNetworkInstance(vrf) op.push(t, dut, config, scope) @@ -354,6 +390,7 @@ func testMoveInterfaceBetweenVRF(t *testing.T, dut *ondatra.DUTDevice, firstVRF, p1 := dut.Port(t, "port1") p2 := dut.Port(t, "port2") + var id1, id2 string scope := &pushScope{ interfaces: []string{p1.Name(), p2.Name()}, @@ -372,11 +409,18 @@ func testMoveInterfaceBetweenVRF(t *testing.T, dut *ondatra.DUTDevice, firstVRF, config.DeleteNetworkInstance(firstVRF) ni := config.GetOrCreateNetworkInstance(firstVRF) ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + // add interface to firstVRF + if deviations.ReorderCallsForVendorCompatibilty(dut) { + id1 = attachInterface(dut, ni, p1.Name(), 0) + id2 = attachInterface(dut, ni, p2.Name(), 0) + } } - firstni := config.GetOrCreateNetworkInstance(firstVRF) - id1 := attachInterface(firstni, p1.Name(), 0) - id2 := attachInterface(firstni, p2.Name(), 0) + if !deviations.ReorderCallsForVendorCompatibilty(dut) { + firstni := config.GetOrCreateNetworkInstance(firstVRF) + id1 = attachInterface(dut, firstni, p1.Name(), 0) + id2 = attachInterface(dut, firstni, p2.Name(), 0) + } config.DeleteNetworkInstance(secondVRF) if *cannotDeleteVRF { @@ -389,9 +433,11 @@ func testMoveInterfaceBetweenVRF(t *testing.T, dut *ondatra.DUTDevice, firstVRF, t.Run("VerifyBeforeMove", func(t *testing.T) { verifyInterface(t, dut, p1.Name(), &ip1) verifyInterface(t, dut, p2.Name(), &ip2) - verifyAttachment(t, dut, firstVRF, id1, p1.Name()) - verifyAttachment(t, dut, firstVRF, id2, p2.Name()) - + // verify the added interface to first Non default VRF + if !deviations.ReorderCallsForVendorCompatibilty(dut) || firstVRF != defaultVRF { + verifyAttachment(t, dut, firstVRF, id1, p1.Name()) + verifyAttachment(t, dut, firstVRF, id2, p2.Name()) + } // We don't check /network-instances/network-instance/vlans/vlan/members because // these are for L2 switched ports, not L3 routed ports. }) @@ -401,18 +447,37 @@ func testMoveInterfaceBetweenVRF(t *testing.T, dut *ondatra.DUTDevice, firstVRF, if firstVRF != defaultVRF { // It is not necessary to explicitly remove the interface attachments since the VRF // is being deleted. + // delete interface before deleting NI + if deviations.ReorderCallsForVendorCompatibilty(dut) { + config.DeleteInterface(p1.Name()) + config.DeleteInterface(p2.Name()) + } config.DeleteNetworkInstance(firstVRF) } else { - // Remove just the interface attachments but keep the VRF. - firstni.DeleteInterface(id1) - firstni.DeleteInterface(id2) + // Delete interface from default NI before modifying the attachement + if deviations.ReorderCallsForVendorCompatibilty(dut) { + config.DeleteInterface(p1.Name()) + config.DeleteInterface(p2.Name()) + } else { + // Remove just the interface attachments but keep the VRF. + firstni := config.GetOrCreateNetworkInstance(firstVRF) + firstni.DeleteInterface(id1) + firstni.DeleteInterface(id2) + } } + op.push(t, dut, config, scope) secondni := config.GetOrCreateNetworkInstance(secondVRF) secondni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF - attachInterface(secondni, p1.Name(), 0) - attachInterface(secondni, p2.Name(), 0) - + if deviations.ReorderCallsForVendorCompatibilty(dut) { + id1 = attachInterface(dut, secondni, p1.Name(), 0) + id2 = attachInterface(dut, secondni, p2.Name(), 0) + ip1.ConfigOCInterface(config.GetOrCreateInterface(p1.Name()), dut) + ip2.ConfigOCInterface(config.GetOrCreateInterface(p2.Name()), dut) + } else { + attachInterface(dut, secondni, p1.Name(), 0) + attachInterface(dut, secondni, p2.Name(), 0) + } op.push(t, dut, config, scope) t.Run("VerifyAfterMove", func(t *testing.T) { @@ -423,7 +488,11 @@ func testMoveInterfaceBetweenVRF(t *testing.T, dut *ondatra.DUTDevice, firstVRF, }) t.Log("Cleanup") - + // delete interface before deleting NI + if deviations.ReorderCallsForVendorCompatibilty(dut) { + config.DeleteInterface(p1.Name()) + config.DeleteInterface(p2.Name()) + } config.DeleteNetworkInstance(secondVRF) op.push(t, dut, config, scope) @@ -432,6 +501,12 @@ func testMoveInterfaceBetweenVRF(t *testing.T, dut *ondatra.DUTDevice, firstVRF, func TestStaticProtocol(t *testing.T) { dut := ondatra.DUT(t, "dut") + if deviations.SkipContainerOp(dut) { + *skipContainerOp = true + } + if deviations.StaticRouteNextHopInterfaceRefUnsupported(dut) { + t.Skip() + } defaultVRF := deviations.DefaultNetworkInstance(dut) staticName := deviations.StaticProtocolName(dut) @@ -469,8 +544,8 @@ func TestStaticProtocol(t *testing.T) { otherni := config.GetOrCreateNetworkInstance(otherVRF) otherni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF - id1 := attachInterface(otherni, p1.Name(), 0) - id2 := attachInterface(otherni, p2.Name(), 0) + id1 := attachInterface(dut, otherni, p1.Name(), 0) + id2 := attachInterface(dut, otherni, p2.Name(), 0) protocol := otherni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, staticName) @@ -501,12 +576,32 @@ func TestStaticProtocol(t *testing.T) { verifyInterface(t, dut, p2.Name(), &ip2) v1 := gnmi.Lookup(t, dut, q1) + if deviations.SkipStaticNexthopCheck(dut) { + + q2 := sp.Static(prefix1).NextHopAny().InterfaceRef().Interface().State() + val := gnmi.LookupAll(t, dut, q2) + if len(val) > 0 { + v1 = val[0] + } else { + t.Fatalf("Did not receive output for static nexthop lookup") + } + } if got, ok := v1.Val(); !ok || got != p1.Name() { t.Errorf("State got %v, want %v", v1, p1.Name()) } else { t.Logf("Verified %v", v1) } v2 := gnmi.Lookup(t, dut, q2) + if deviations.SkipStaticNexthopCheck(dut) { + + q3 := sp.Static(prefix2).NextHopAny().InterfaceRef().Interface().State() + val := gnmi.LookupAll(t, dut, q3) + if len(val) > 0 { + v2 = val[0] + } else { + t.Fatalf("Did not receive output for static nexthop lookup") + } + } if got, ok := v2.Val(); !ok || got != p2.Name() { t.Errorf("State got %v, want %v", v2, p2.Name()) } else { @@ -528,12 +623,32 @@ func TestStaticProtocol(t *testing.T) { verifyInterface(t, dut, p2.Name(), &ip2) v1 := gnmi.Lookup(t, dut, q1) + if deviations.SkipStaticNexthopCheck(dut) { + + q2 := sp.Static(prefix1).NextHopAny().InterfaceRef().Interface().State() + val := gnmi.LookupAll(t, dut, q2) + if len(val) > 0 { + v1 = val[0] + } else { + t.Fatalf("Did not receive output for static nexthop lookup") + } + } if got, ok := v1.Val(); !ok || got != p2.Name() { t.Errorf("State got %v, want %v", v1, p2.Name()) } else { t.Logf("Verified %v", v1) } v2 := gnmi.Lookup(t, dut, q2) + if deviations.SkipStaticNexthopCheck(dut) { + + q3 := sp.Static(prefix2).NextHopAny().InterfaceRef().Interface().State() + val := gnmi.LookupAll(t, dut, q3) + if len(val) > 0 { + v2 = val[0] + } else { + t.Fatalf("Did not receive output for static nexthop lookup") + } + } if got, ok := v2.Val(); !ok || got != p1.Name() { t.Errorf("State got %v, want %v", v2, p1.Name()) } else { @@ -542,7 +657,11 @@ func TestStaticProtocol(t *testing.T) { }) t.Log("Cleanup") - + // delete interface before deleting NI + if deviations.ReorderCallsForVendorCompatibilty(dut) { + config.DeleteInterface(p1.Name()) + config.DeleteInterface(p2.Name()) + } config.DeleteNetworkInstance(otherVRF) config.DeleteNetworkInstance(unusedVRF) op.push(t, dut, config, scope) @@ -578,7 +697,13 @@ func nextAggregates(t *testing.T, dut *ondatra.DUTDevice, n int) []string { agg := numRE.ReplaceAllStringFunc(firstAgg, func(_ string) string { return strconv.Itoa(i) }) - aggs = append(aggs, agg) + //some aggregate interface after firstAgg may already be present in the system. + _, present := gnmi.Lookup(t, dut, gnmi.OC().Interface(agg).Name().State()).Val() + if !present { + aggs = append(aggs, agg) + } else { + n++ + } } return aggs } @@ -609,8 +734,8 @@ func configAggregate(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDevic func verifyMember(t testing.TB, p *ondatra.Port, aggID string) { t.Helper() q := gnmi.OC().Interface(p.Name()).Ethernet().AggregateId().State() - v := gnmi.Lookup(t, p.Device(), q) - if got, ok := v.Val(); !ok || got != aggID { + v, ok := gnmi.Await(t, p.Device(), q, 60*time.Second, aggID).Val() + if !ok { t.Errorf("State got %v, want %v", v, aggID) } } @@ -619,9 +744,9 @@ func verifyMember(t testing.TB, p *ondatra.Port, aggID string) { func verifyAggregate(t testing.TB, dev gnmi.DeviceOrOpts, aggID string, a *attrs.Attributes) { t.Helper() q := gnmi.OC().Interface(aggID).Aggregation().LagType().State() - v := gnmi.Lookup(t, dev, q) const want = oc.IfAggregate_AggregationType_STATIC - if got, ok := v.Val(); !ok || got != want { + v, ok := gnmi.Await(t, dev, q, 60*time.Second, want).Val() + if !ok { t.Errorf("State got %v, want %v", v, want) } verifyInterface(t, dev, aggID, a) @@ -631,8 +756,8 @@ func verifyAggregate(t testing.TB, dev gnmi.DeviceOrOpts, aggID string, a *attrs func verifyInterface(t testing.TB, dev gnmi.DeviceOrOpts, name string, a *attrs.Attributes) { t.Helper() q := gnmi.OC().Interface(name).Subinterface(0).Ipv4().Address(a.IPv4).PrefixLength().State() - v := gnmi.Lookup(t, dev, q) - if got, ok := v.Val(); !ok || got != a.IPv4Len { + v, ok := gnmi.Await(t, dev, q, 60*time.Second, a.IPv4Len).Val() + if !ok { t.Errorf("State got %v, want %v", v, a.IPv4Len) } else { t.Logf("Verified %v", v) @@ -640,10 +765,14 @@ func verifyInterface(t testing.TB, dev gnmi.DeviceOrOpts, name string, a *attrs. } // attachInterface attaches an interface name and subinterface sub to a network instance. -func attachInterface(ni *oc.NetworkInstance, name string, sub int) string { +func attachInterface(dut *ondatra.DUTDevice, ni *oc.NetworkInstance, name string, sub int) string { id := name // Possibly vendor specific? May have to use sub. niface := ni.GetOrCreateInterface(id) niface.Interface = ygot.String(name) + niface.Subinterface = ygot.Uint32(uint32(sub)) + if deviations.InterfaceRefInterfaceIDFormat(dut) { + id = fmt.Sprintf("%s.%d", id, sub) + } return id } @@ -652,8 +781,8 @@ func attachInterface(ni *oc.NetworkInstance, name string, sub int) string { func verifyAttachment(t testing.TB, dev gnmi.DeviceOrOpts, vrf string, id string, name string) { t.Helper() q := gnmi.OC().NetworkInstance(vrf).Interface(id).Interface().State() - v := gnmi.Lookup(t, dev, q) - if got, ok := v.Val(); !ok || got != name { + v, ok := gnmi.Await(t, dev, q, 60*time.Second, name).Val() + if !ok { t.Errorf("State got %v, want %v", v, name) } else { t.Logf("Verified %v", v) @@ -693,7 +822,7 @@ func forEachPushOp( f func(t *testing.T, op pushOp, config *oc.Root), ) { baselineConfigOnce.Do(func() { - baselineConfig = getDeviceConfig(t, dut) + baselineConfig = fptest.GetDeviceConfig(t, dut) }) for _, op := range []pushOp{ @@ -703,81 +832,12 @@ func forEachPushOp( if op.shouldSkip() { t.Skip() } - o, err := ygot.DeepCopy(baselineConfig) - if err != nil { - t.Fatalf("Cannot copy baseConfig: %v", err) - } - config := o.(*oc.Root) + config := fptest.CopyDeviceConfig(t, dut, baselineConfig) f(t, op, config) }) } } -// getDeviceConfig gets a full config from a device but refurbishes it enough so it can be -// pushed out again. Ideally, we should be able to push the config we get from the same -// device without modification, but this is not explicitly defined in OpenConfig. -func getDeviceConfig(t testing.TB, dev gnmi.DeviceOrOpts) *oc.Root { - t.Helper() - - // Gets all the config (read-write) paths from root, not the state (read-only) paths. - config := gnmi.Get[*oc.Root](t, dev, gnmi.OC().Config()) - fptest.WriteQuery(t, "Untouched", gnmi.OC().Config(), config) - - if *pruneComponents { - for cname, component := range config.Component { - // Keep the port components in order to preserve the breakout-mode config. - if component.GetPort() == nil { - delete(config.Component, cname) - continue - } - // Need to prune subcomponents that may have a leafref to a component that was - // pruned. - component.Subcomponent = nil - } - } - - if *setEthernetFromState { - for iname, iface := range config.Interface { - if iface.GetEthernet() == nil { - continue - } - // Ethernet config may not contain meaningful values if it wasn't explicitly - // configured, so use its current state for the config, but prune non-config leaves. - intf := gnmi.Get(t, dev, gnmi.OC().Interface(iname).State()) - breakout := config.GetComponent(intf.GetHardwarePort()).GetPort().GetBreakoutMode() - e := intf.GetEthernet() - // Set port speed to unknown for non breakout interfaces - if breakout.GetGroup(1) == nil && e != nil { - e.SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_UNKNOWN) - } - ygot.PruneConfigFalse(oc.SchemaTree["Interface_Ethernet"], e) - if e.PortSpeed != 0 && e.PortSpeed != oc.IfEthernet_ETHERNET_SPEED_SPEED_UNKNOWN { - iface.Ethernet = e - } - } - } - - if *pruneLLDP && config.Lldp != nil { - config.Lldp.ChassisId = nil - config.Lldp.ChassisIdType = oc.Lldp_ChassisIdType_UNSET - } - - if *pruneQoS { - config.Qos = nil - } - - pruneUnsupportedPaths(config) - - fptest.WriteQuery(t, "Touched", gnmi.OC().Config(), config) - return config -} - -func pruneUnsupportedPaths(config *oc.Root) { - for _, ni := range config.NetworkInstance { - ni.Fdb = nil - } -} - // pushScope describe the config scope that the test case wants to modify. This is for // itemOp only; rootOp and containerOp ignore this. type pushScope struct { @@ -792,23 +852,6 @@ type pushOp interface { push(t testing.TB, dev gnmi.DeviceOrOpts, config *oc.Root, scope *pushScope) } -// setEthernetFromBase merges the ethernet config from the interfaces in base config into -// the destination config. -func setEthernetFromBase(t testing.TB, base *oc.Root, config *oc.Root) { - t.Helper() - - for iname, iface := range config.Interface { - eb := base.GetInterface(iname).GetEthernet() - ec := iface.GetOrCreateEthernet() - if eb == nil || ec == nil { - continue - } - if err := ygot.MergeStructInto(ec, eb); err != nil { - t.Errorf("Cannot merge %s ethernet: %v", iname, err) - } - } -} - // rootOp pushes config using replace at root. type rootOp struct{ base *oc.Root } @@ -817,11 +860,15 @@ func (rootOp) shouldSkip() bool { return *skipRootOp } func (op rootOp) push(t testing.TB, dev gnmi.DeviceOrOpts, config *oc.Root, _ *pushScope) { t.Helper() - if *setEthernetFromState { - setEthernetFromBase(t, op.base, config) - } fptest.WriteQuery(t, "RootOp", gnmi.OC().Config(), config) - gnmi.Replace(t, dev, gnmi.OC().Config(), config) + dut := ondatra.DUT(t, "dut") + if deviations.AddMissingBaseConfigViaCli(dut) { + if ondatra.DUT(t, "dut").Vendor() == ondatra.CISCO { + addMissingConfigForRootReplace(t, dev, config) + } + } else { + gnmi.Replace(t, dev, gnmi.OC().Config(), config) + } } // containerOp pushes config using replace of containers of lists directly under root in @@ -833,12 +880,25 @@ func (containerOp) shouldSkip() bool { return *skipContainerOp } func (op containerOp) push(t testing.TB, dev gnmi.DeviceOrOpts, config *oc.Root, _ *pushScope) { t.Helper() - if *setEthernetFromState { - setEthernetFromBase(t, op.base, config) - } fptest.WriteQuery(t, "ContainerOp", gnmi.OC().Config(), config) batch := &gnmi.SetBatch{} + if deviations.AddMissingBaseConfigViaCli(ondatra.DUT(t, "dut")) { + if ondatra.DUT(t, "dut").Vendor() == ondatra.CISCO { + supContainerConfig := addMissingConfigForContainerReplace(t, dev) + for port, data := range supContainerConfig { + gnmi.Update(t, ondatra.DUT(t, "dut"), gnmi.OC().Component(port).Config(), &oc.Component{ + Name: ygot.String(port), + }) + bmode := &oc.Component_Port_BreakoutMode{} + gp := bmode.GetOrCreateGroup(0) + gp.BreakoutSpeed = data.breakoutSpeed + gp.NumBreakouts = ygot.Uint8(*data.numPhysicalChannels + 1) + bmp := gnmi.OC().Component(port).Port().BreakoutMode() + gnmi.BatchReplace(batch, bmp.Config(), bmode) + } + } + } gnmi.BatchReplace(batch, interfacesQuery, &Interfaces{Interface: config.Interface}) gnmi.BatchReplace(batch, networkInstancesQuery, &NetworkInstances{NetworkInstance: config.NetworkInstance}) batch.Set(t, dev) @@ -852,9 +912,6 @@ func (itemOp) shouldSkip() bool { return *skipItemOp } func (op itemOp) push(t testing.TB, dev gnmi.DeviceOrOpts, config *oc.Root, scope *pushScope) { t.Helper() - if *setEthernetFromState { - setEthernetFromBase(t, op.base, config) - } fptest.WriteQuery(t, "ItemOp", gnmi.OC().Config(), config) batch := &gnmi.SetBatch{} @@ -895,15 +952,66 @@ var ( ) func init() { - var err error - interfacesQuery, err = schemaless.NewConfig[*Interfaces]("/interfaces", "openconfig") + // TODO(wenovus): Remove this workaround using ygnmi's Map() API once + // SetBatch is fixed for Map() API. + interfacesQuery = ygnmi.NewConfigQuery[*Interfaces]( + "", + false, + true, + true, + false, + false, + false, + createPS("/interfaces"), + func(vgs ygot.ValidatedGoStruct) (*Interfaces, bool) { + return new(Interfaces), true + }, + func() ygot.ValidatedGoStruct { + return nil + }, + func() *ytypes.Schema { return nil }, + nil, + nil, + ) + networkInstancesQuery = ygnmi.NewConfigQuery[*NetworkInstances]( + "", + false, + true, + true, + false, + false, + false, + createPS("/network-instances"), + func(vgs ygot.ValidatedGoStruct) (*NetworkInstances, bool) { + return new(NetworkInstances), true + }, + func() ygot.ValidatedGoStruct { + return nil + }, + func() *ytypes.Schema { return nil }, + nil, + nil, + ) +} + +func createPS(path string) ygnmi.PathStruct { + root := ygnmi.NewDeviceRootBase() + root.PutCustomData(ygnmi.OriginOverride, "openconfig") + + var ps ygnmi.PathStruct = root + protoPath, err := ygot.StringToStructuredPath(path) if err != nil { panic(err) } - networkInstancesQuery, err = schemaless.NewConfig[*NetworkInstances]("/network-instances", "openconfig") - if err != nil { - panic(err) + for _, elem := range protoPath.Elem { + keys := map[string]interface{}{} + for key, val := range elem.Key { + keys[key] = val + } + ps = ygnmi.NewNodePath([]string{elem.Name}, keys, ps) } + + return ps } type Interfaces struct { @@ -917,3 +1025,106 @@ type NetworkInstances struct { } func (*NetworkInstances) IsYANGGoStruct() {} + +func removeStatementsBetweenWords(inputStr, startWord, endWord string, opts ...*Options) string { + lines := strings.Split(inputStr, "\n") + result := []string{} + betweenWords := false + var start bool + for _, line := range lines { + if strings.HasPrefix(line, startWord) { + if len(opts) != 0 { + for _, opt := range opts { + for _, intf := range opt.interfaces { + if strings.Contains(line, intf) { + start = true + betweenWords = true + continue + } + } + } + } else { + start = true + betweenWords = true + continue + } + } + if strings.HasPrefix(line, endWord) { + betweenWords = false + if start == true { + start = false + continue + } + } + if !betweenWords { + result = append(result, line) + } + } + return strings.Join(result, "\n") +} + +func addMissingConfigForContainerReplace(t testing.TB, dev gnmi.DeviceOrOpts) map[string]breakout { + intfsState := gnmi.GetAll(t, dev, gnmi.OC().InterfaceAny().State()) + breakoutPortsMap := make(map[string]breakout) // which holds map of optic: {BreakoutSpeed:10, NumBreakouts:4} + port := make(map[string]uint8) + var trackspeed oc.E_IfEthernet_ETHERNET_SPEED + + for _, intf := range intfsState { + if intf.HardwarePort == nil || intf.PhysicalChannel == nil { + continue + } + hwp := strings.Split(intf.GetHardwarePort(), "Port")[1] + name := strings.Split(intf.GetName(), "GigE")[1] + channel := strconv.Itoa(int(intf.GetPhysicalChannel()[0])) + + if hwp+"/"+(channel) == name { + var speed oc.E_IfEthernet_ETHERNET_SPEED + + _, keyExists := breakoutPortsMap[intf.GetHardwarePort()] + if !keyExists && speed == oc.IfEthernet_ETHERNET_SPEED_UNSET { + if intf.GetEthernet().PortSpeed.String() == "SPEED_100GB" { + trackspeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } else if intf.GetEthernet().PortSpeed.String() == "SPEED_10GB" { + trackspeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_10GB + } + } + + numChannels := make([]*uint8, len(intf.GetPhysicalChannel())) + truncated := uint8(intf.GetPhysicalChannel()[0]) + numChannels[0] = &truncated + + _, keyExists = port[intf.GetHardwarePort()] + if !keyExists { + breakoutPortsMap[intf.GetHardwarePort()] = breakout{numPhysicalChannels: numChannels[0], breakoutSpeed: trackspeed} + port[intf.GetHardwarePort()] = 0 + } + + if port[intf.GetHardwarePort()] < *numChannels[0] { + breakoutPortsMap[intf.GetHardwarePort()] = breakout{numPhysicalChannels: numChannels[0], breakoutSpeed: trackspeed} + port[intf.GetHardwarePort()] = *numChannels[0] + } + } + } + return breakoutPortsMap +} + +func addMissingConfigForRootReplace(t testing.TB, dev gnmi.DeviceOrOpts, config *oc.Root) { + batch := &gnmi.SetBatch{} + running := showRunningConfig(t, ondatra.DUT(t, "dut")) + //editing config while removing NI and interface since it will be part of another replace call + data := "hostname " + strings.Split(running, "hostname ")[1] + modifiedStr := strings.Replace(data, "\r\n", "\n", -1) + // remove interface config from the running configure + fileString := removeStatementsBetweenWords(modifiedStr, "interface ", "!", &Options{interfaces: []string{"HundredGigE", "FourHundredGigE", "TenGigE", "Bundle-Ether", "Loopback", "MgmtEth0", "FortyGigE", "PTP0/RP"}}) + // remove router static config from the running config + fileString = removeStatementsBetweenWords(fileString, "router static ", "!") + // need to explicitly remove configured NI "BLUE" since it is still present in running config and will overwrite config parameter which doesn't set it + fileString = removeStatementsBetweenWords(fileString, "vrf BLUE", "!") + cliPath, err := schemaless.NewConfig[string]("", "cli") + if err != nil { + t.Fatalf("Failed to create CLI ygnmi query: %v", err) + } + gnmi.BatchReplace(batch, cliPath, fileString) + gnmi.BatchReplace(batch, gnmi.OC().Config(), config) + batch.Set(t, dev) +} diff --git a/feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto b/feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto index d4389c93441..13cf7f56ecd 100644 --- a/feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto +++ b/feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto @@ -1,16 +1,28 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "fe01cad6-6775-45cd-a025-d960ca6c04af" +uuid: "e50991a4-bb40-4162-8a86-717e68e10680" plan_id: "gNMI-1.15" description: "Set Requests" -testbed: TESTBED_DUT +testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { vendor: CISCO } deviations: { - ipv4_missing_enabled: true + skip_container_op: true + reorder_calls_for_vendor_compatibilty: true + add_missing_base_config_via_cli: true + skip_macaddress_check: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + skip_static_nexthop_check: true + interface_ref_interface_id_format: true } } platform_exceptions: { @@ -29,6 +41,7 @@ platform_exceptions: { vendor: NOKIA } deviations: { + static_route_next_hop_interface_ref_unsupported: true static_protocol_name: "static" interface_enabled: true } diff --git a/feature/system/health/tests/system_generic_health_check/README.md b/feature/system/health/tests/system_generic_health_check/README.md new file mode 100644 index 00000000000..fbb026098c2 --- /dev/null +++ b/feature/system/health/tests/system_generic_health_check/README.md @@ -0,0 +1,75 @@ +# Health-1.1: Generic Health Check + +## Summary + +Generic Health Check + +## Procedure + +* Capture the generic health check of the DUT, used modularly in pre/post and during various different tests: + * No system/kernel/process/component coredumps + * No high CPU spike or usage on control or forwarding plane + * No high memory utilization or usage on control or forwarding plane + * No processes/daemons high CPU/Memory utilization + * No generic drop counters + * QUEUE drops + * Interfaces + * VOQ + * Fabric drops + * ASIC drops + * No flow control frames tx/rx + * No CRC or Layer 1 errors on interfaces + * No config commit errors + * No system level alarms + * In spec hardware should be in proper state + * No hardware errors + * Major Alarms + * No HW component or SW processes crash +* TODO: + * DDOS/COPP violations + * No memory leaks + * No system errors or logs + * No CRC or Layer 1 errors fabric links + +## Config Parameter Coverage + +N/A + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + +paths: + ## Config Parameter coverage + + /system/processes/process/state/cpu-utilization: + /system/processes/process/state/memory-utilization: + /qos/interfaces/interface/input/queues/queue/state/dropped-pkts: + /qos/interfaces/interface/output/queues/queue/state/dropped-pkts: + /qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/dropped-pkts: + /interfaces/interface/state/counters/in-discards: + /interfaces/interface/state/counters/in-errors: + /interfaces/interface/state/counters/in-multicast-pkts: + /interfaces/interface/state/counters/in-unknown-protos: + /interfaces/interface/state/counters/out-discards: + /interfaces/interface/state/counters/out-errors: + /interfaces/interface/state/oper-status: + /interfaces/interface/state/admin-status: + /interfaces/interface/state/counters/out-octets: + /interfaces/interface/state/description: + /interfaces/interface/state/type: + /interfaces/interface/subinterfaces/subinterface/state/counters/in-discards: + /interfaces/interface/subinterfaces/subinterface/state/counters/in-errors: + /interfaces/interface/subinterfaces/subinterface/state/counters/in-unknown-protos: + /interfaces/interface/subinterfaces/subinterface/state/counters/out-discards: + /interfaces/interface/subinterfaces/subinterface/state/counters/out-errors: + /interfaces/interface/ethernet/state/counters/in-mac-pause-frames: + /interfaces/interface/ethernet/state/counters/out-mac-pause-frames: + /interfaces/interface/ethernet/state/counters/in-crc-errors: + /interfaces/interface/ethernet/state/counters/in-block-errors: +``` + +## Protocol/RPC Parameter Coverage diff --git a/feature/experimental/system/health/tests/system_generic_health_check/metadata.textproto b/feature/system/health/tests/system_generic_health_check/metadata.textproto similarity index 77% rename from feature/experimental/system/health/tests/system_generic_health_check/metadata.textproto rename to feature/system/health/tests/system_generic_health_check/metadata.textproto index 2d40fe0cce7..7c47220cea4 100644 --- a/feature/experimental/system/health/tests/system_generic_health_check/metadata.textproto +++ b/feature/system/health/tests/system_generic_health_check/metadata.textproto @@ -1,21 +1,22 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "a01b6e65-3b45-4e0c-a71d-f451c07fec6b" -plan_id: "Health-1.1" -description: "Generic Health Check" +uuid: "a01b6e65-3b45-4e0c-a71d-f451c07fec6b" +plan_id: "Health-1.1" +description: "Generic Health Check" testbed: TESTBED_DUT platform_exceptions: { platform: { vendor: JUNIPER } deviations: { - controller_card_cpu_utilization_unsupported: true linecard_cpu_utilization_unsupported: true consistent_component_names_unsupported: true + controller_card_cpu_utilization_unsupported: true fabric_drop_counter_unsupported: true linecard_memory_utilization_unsupported: true qos_voq_drop_counter_unsupported: true + qos_inqueue_drop_counter_unsupported: true } } tags: TAGS_AGGREGATION diff --git a/feature/experimental/system/health/tests/system_generic_health_check/system_generic_health_check_test.go b/feature/system/health/tests/system_generic_health_check/system_generic_health_check_test.go similarity index 95% rename from feature/experimental/system/health/tests/system_generic_health_check/system_generic_health_check_test.go rename to feature/system/health/tests/system_generic_health_check/system_generic_health_check_test.go index ee962069738..2c182375063 100644 --- a/feature/experimental/system/health/tests/system_generic_health_check/system_generic_health_check_test.go +++ b/feature/system/health/tests/system_generic_health_check/system_generic_health_check_test.go @@ -157,6 +157,10 @@ func TestComponentStatus(t *testing.T) { // check oper-status of the components is Active. for _, component := range checkComponents { t.Run(component, func(t *testing.T) { + compMtyVal, compMtyPresent := gnmi.Lookup(t, dut, gnmi.OC().Component(component).Empty().State()).Val() + if compMtyPresent && compMtyVal { + t.Skipf("INFO: Skip status check as %s is empty", component) + } val, present := gnmi.Lookup(t, dut, gnmi.OC().Component(component).OperStatus().State()).Val() if !present { t.Errorf("ERROR: Get component %s oper-status failed", component) @@ -366,12 +370,23 @@ func TestNoQueueDrop(t *testing.T) { for _, intf := range interfaces { t.Run(intf, func(t *testing.T) { qosInterface := gnmi.OC().Qos().Interface(intf) + if deviations.QOSInQueueDropCounterUnsupported(dut) { + t.Skipf("INFO: Skipping test due to %s does not support Queue Input Dropped packets", dut.Vendor()) + counters := gnmi.LookupAll(t, dut, qosInterface.Input().QueueAny().DroppedPkts().State()) + t.Logf("counters: %s", counters) + if len(counters) == 0 { + t.Errorf("%s Interface Queue Input Dropped packets Telemetry Value is not present", intf) + } + for queueID, dropPkt := range counters { + dropCount, present := dropPkt.Val() + if !present { + t.Errorf("%s Interface %s Telemetry Value is not present", intf, dropPkt.Path) + } else { + t.Logf("%s Interface %s, Queue %d has %d drop(s)", dropPkt.Path.GetOrigin(), intf, queueID, dropCount) + } + } + } cases := []testCase{ - { - desc: "Queue Input Dropped packets", - path: "/qos/interfaces/interface/input/queues/queue/state/dropped-pkts", - counters: gnmi.LookupAll(t, dut, qosInterface.Input().QueueAny().DroppedPkts().State()), - }, { desc: "Queue Output Dropped packets", path: "/qos/interfaces/interface/output/queues/queue/state/dropped-pkts", @@ -582,6 +597,7 @@ func TestInterfacesubIntfs(t *testing.T) { t.Fatalf("ERROR: subIntf index value doesn't exist") } subIntfPath := gnmi.OC().Interface(intf).Subinterface(subIntfIndex) + IntfPath := gnmi.OC().Interface(intf) subIntfState := gnmi.Get(t, dut, subIntfPath.State()) subIntf := subIntfState.GetName() @@ -613,7 +629,7 @@ func TestInterfacesubIntfs(t *testing.T) { t.Errorf("ERROR: Counter InMulticastPkts is not present on interface %s, %s", subIntf, intf) } - counters := subIntfPath.Counters() + counters := IntfPath.Counters() parentCounters := gnmi.OC().Interface(intf).Counters() cases := []struct { @@ -652,7 +668,7 @@ func TestInterfacesubIntfs(t *testing.T) { parentCounter: parentCounters.InFcsErrors().State(), }, } - + t.Logf("Verifying counters for Interfaces: %s", interfaces) for _, c := range cases { t.Run(c.desc, func(t *testing.T) { if val, present := gnmi.Lookup(t, dut, c.counter).Val(); present { diff --git a/feature/system/logging/remote_syslog/feature.textproto b/feature/system/logging/remote_syslog/feature.textproto index 0a9e873d314..6c14fbb34ba 100644 --- a/feature/system/logging/remote_syslog/feature.textproto +++ b/feature/system/logging/remote_syslog/feature.textproto @@ -11,6 +11,9 @@ # 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_logging_remote_syslog" diff --git a/feature/system/management/otg_tests/management_ha_test/README.md b/feature/system/management/otg_tests/management_ha_test/README.md new file mode 100644 index 00000000000..78b025ab7c2 --- /dev/null +++ b/feature/system/management/otg_tests/management_ha_test/README.md @@ -0,0 +1,174 @@ +# MGT-1: Management HA solution test + +## Summary + +- Test management HA + +## Testbed type + +* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed + +## Procedure + +### Applying configuration + +For each section of configuration below, prepare a gnmi.SetBatch with all the configuration items appended to one SetBatch. Then apply the configuration to the DUT in one gnmi.Set +using the `replace` option + +### Initial Setup: + +* Connect DUT port-1, 2 and 3 to ATE port-1, 2 and 3 +* Create VRF "mgmt" on DUT + * /network-instances/network-instance[name=mgmt]/config/name = mgmt + * /network-instances/network-instance[name=mgmt]/config/route-distinguisher = 64512:100 +* Create an IPv6 networks ```ateNet``` attached to ATE port-1, 2 and 3 +* Create a loopback interface "lo1" on DUT and assign it an IPv6 address + * /interfaces/interface[name=lo1]/config/name = lo1 + * /interfaces/interface[name=lo1]/config/type = softwareLoopback + * /interfaces/interface[name=lo1]/subinterfaces/subinterface[index=0]/ipv6/addresses/address/config/ip + * /interfaces/interface[name=lo1]/subinterfaces/subinterface[index=0]/ipv6/addresses/address/config/prefix-length +* Configure the loopback interface to participate in the VRF "mgmt" + * /network-instances/network-instance[name=mgmt]/interfaces/interface[name=lo1]/config/interface = lo1 + +##### Configure linecard ports to ATE using BGP + +* Configure IPv6 addresses on DUT and ATE ports 1 and 2. Configure them to participate in the VRF "mgmt" + * /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip + * /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length + * /network-instances/network-instance[name=mgmt]/interfaces/interface/config/interface +* Configure IPv6 eBGP between DUT Port-1 <--> ATE Port-1 and DUT Port-2 <--> ATE Port-2 in VRF "mgmt" + * /network-instances/network-instance[name=mgmt]/protocols/protocol[identifier=BGP, name=BGP]/global/config/as = 64512 + * /network-instances/network-instance[name=mgmt]/protocols/protocol[identifier=BGP, name=BGP]/global/config/router-id = + * /network-instances/network-instance[name=mgmt]/protocols/protocol[identifier=BGP, name=BGP]/neighbor/config/peer-as = 64511 +* Set default import and export policy to ```ACCEPT_ROUTE``` for the eBGP sessions + * /network-instances/network-instance[name=mgmt]/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy + * /network-instances/network-instance[name=mgmt]/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy +* Advertise a default route from ATE to DUT throught both the BGP sessions +* Redistribute the loopback interface from DUT to ATE through both the BGP sessions + ##### Configure redistribution + * Set address-family to ```IPV6``` + * /network-instances/network-instance/table-connections/table-connection/config/address-family + * Configure source protocol to ```CONNECTED``` + * /network-instances/network-instance/table-connections/table-connection/config/src-protocol + * Configure destination protocol to ```BGP``` + * /network-instances/network-instance/table-connections/table-connection/config/dst-protocol + * Configure default export policy to ```ACCEPT_ROUTE``` + * /network-instances/network-instance/table-connections/table-connection/config/default-export-policy + * Disable metric propogation by setting it to ```true``` + * /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation + ##### Configure redistribution + * Configure an IPv6 route-policy definition with the name ```route-policy``` + * /routing-policy/policy-definitions/policy-definition/config/name + * For routing-policy ```route-policy``` configure a statement with the name ```statement``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/config/name + * For routing-policy ```route-policy``` statement ```statement``` set policy-result as ```ACCEPT_ROUTE``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result + ##### Configure a prefix-set for route-filtering/matching + * Configure a prefix-set with the name ```prefix-set``` and mode ```IPV6``` + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/name + * /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode + * For prefix-set ```prefix-set``` set the ip-prefix to ```loopback0 IPv6 address/mask``` and masklength to ```exact``` + * /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 + ##### Attach the prefix-set to route-policy + * For routing-policy ```route-policy``` statement ```statement``` set match options to ```ANY``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options + * For routing-policy ```route-policy``` statement ```statement``` set prefix set to ```prefix-set``` + * /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set + ##### Attach the route-policy to the redistribution export-policy + * Apply routing policy ```route-policy``` for redistribution to BGP + * /network-instances/network-instance/table-connections/table-connection/config/export-policy + +##### Configure linecard port to ATE + +* Configure IPv6 addresses on DUT Port-3 and ATE Port-3 + * /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip + * /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length +* Configure DUT Port-3 to participate in the VRF "mgmt" + * /network-instances/network-instance[name=mgmt]/interfaces/interface/config/interface +* Configure a static route on ATE device of Port-3 destined to the DUT loopback with next-hop address of DUT Port-3 with administrative-distance or preference of 220 +* Configure a IPv6 default static route on DUT in VRF "mgmt" pointing towards the IPv6 address of ATE Port-3 with Administrative Distance or Preference of 220 + * /network-instances/network-instance[name=mgmt]/protocols/protocol[identifier=STATIC, name=static]/static-routes/static/config/prefix = ::/0 + * /network-instances/network-instance[name=mgmt]/protocols/protocol[identifier=STATIC, name=static]/static-routes/static/next-hops/next-hop/index = 1 + * /network-instances/network-instance[name=mgmt]/protocols/protocol[identifier=STATIC, name=static]/static-routes/static/next-hops/next-hop[index=1]/config/next-hop = ATE Port-3 IP + * /network-instances/network-instance[name=mgmt]/protocols/protocol[identifier=STATIC, name=static]/static-routes/static/next-hops/next-hop/config/preference = 220 + +### MGT-1.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2762] +#### Testing reachability to the DUT loopback with no failures in the network +--- + +* Generate ICMP echo (ping) sourced from the ```ateNet``` network destined towards the DUT loopback1 IPv6 address +* Validate ICMP echo-reply is received by the ATE on Port-1 or Port-2 + +### MGT-1.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2762] +#### Testing BGP redundancy +--- + +* Shutdown BGP session on Port-1 +* Generate ICMP echo (ping) sourced from the ```ateNet``` network destined towards the DUT loopback1 IPv6 address +* Validate ICMP echo-reply is received by the ATE on Port-2 +* Bring up BGP session on Port-1 and shutdowm BGP on Port-2 +* Generate ICMP echo (ping) sourced from the ```ateNet``` network destined towards the DUT loopback1 IPv6 address +* Validate ICMP echo-reply is received by the ATE on Port-1 + +### MGT-1.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2762] +#### Testing failover between BGP and Static route +--- + +* Shutdown BGP session on Port-1 and Port-2 +* Generate ICMP echo (ping) sourced from the ```ateNet``` network destined towards the DUT loopback1 IPv6 address +* Validate ICMP echo-reply is received by the ATE on Port-3 +* Bring up BGP session on Port-1 and Port-2 +* Generate ICMP echo (ping) sourced from the ```ateNet``` network destined towards the DUT loopback1 IPv6 address +* Validate ICMP echo-reply is received by the ATE on Port-1 or Port-2 + +## 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/config/name: + /network-instances/network-instance/config/route-distinguisher: + /network-instances/network-instance/interfaces/interface/config/interface: + /interfaces/interface/config/name: + /interfaces/interface/config/type: + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip: + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length: + /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/peer-as: + /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/default-export-policy: + /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/default-import-policy: + /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation: + /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/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: + /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/index: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/preference: + + ## State paths: N/A + +rpcs: + gnmi: + gNMI.Set: + Replace: +``` + +## Required DUT platform + +* FFF diff --git a/feature/system/management/otg_tests/management_ha_test/management_ha_test.go b/feature/system/management/otg_tests/management_ha_test/management_ha_test.go new file mode 100644 index 00000000000..7b5c3334703 --- /dev/null +++ b/feature/system/management/otg_tests/management_ha_test/management_ha_test.go @@ -0,0 +1,450 @@ +// 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 management_ha_test + +import ( + "fmt" + "math" + "sort" + "strconv" + "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/netutil" + "github.com/openconfig/ygnmi/schemaless" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + prefixesStart = "2001:db8:1::1" + prefixP6Len = 128 + prefixesCount = 1 + pathID = 1 + defaultRoute = "0:0:0:0:0:0:0:0" + ateNetPrefix = "2001:0db8::192:0:3:1" +) + +var ( + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.1", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + mgmtVRF = map[ondatra.Vendor]string{ + ondatra.JUNIPER: "mvrf1", + ondatra.ARISTA: "mvrf1", + ondatra.CISCO: "mgmtvrf1", + ondatra.NOKIA: "mgmtvrf1", + } + loopbackIntf = map[ondatra.Vendor]int{ + ondatra.JUNIPER: 0, + ondatra.ARISTA: 1, + ondatra.CISCO: 1, + ondatra.NOKIA: 1, + } + loopbackSubIntf = map[ondatra.Vendor]int32{ + ondatra.JUNIPER: 10, + ondatra.ARISTA: 0, + ondatra.CISCO: 0, + ondatra.NOKIA: 0, + } + bgpPorts = []string{"port1", "port2"} + + lossTolerance = float64(1) +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestManagementHA1(t *testing.T) { + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + loopbackIntfName := netutil.LoopbackInterface(t, dut, loopbackIntf[dut.Vendor()]) + mgmtVRFName := mgmtVRF[dut.Vendor()] + createAndAddInterfacesToVRF(t, dut, mgmtVRFName, []string{p1.Name(), p2.Name(), p3.Name(), p4.Name(), loopbackIntfName}, []uint32{0, 0, 0, 0, uint32(loopbackSubIntf[dut.Vendor()])}) + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount4, &mgmtVRFName) + bs.WithEBGP( + t, + []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST}, + bgpPorts, + true, + true, + ) + if deviations.BgpAfiSafiInDefaultNiBeforeOtherNi(dut) { + g := bs.DUTConf.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(cfgplugins.PTBGP, "BGP").GetOrCreateBgp().GetOrCreateGlobal() + g.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST).Enabled = ygot.Bool(true) + } + bgp := bs.DUTConf.GetOrCreateNetworkInstance(mgmtVRFName).GetOrCreateProtocol(cfgplugins.PTBGP, "BGP").GetOrCreateBgp() + bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + + if deviations.SetNoPeerGroup(dut) || deviations.PeerGroupDefEbgpVrfUnsupported(dut) { + bs.DUTConf.GetOrCreateNetworkInstance(mgmtVRFName).GetOrCreateProtocol(cfgplugins.PTBGP, "BGP").GetOrCreateBgp().PeerGroup = nil + neighbors := bs.DUTConf.GetOrCreateNetworkInstance(mgmtVRFName).GetOrCreateProtocol(cfgplugins.PTBGP, "BGP").GetOrCreateBgp().Neighbor + for _, neighbor := range neighbors { + neighbor.PeerGroup = nil + } + } + + configureEmulatedNetworks(bs) + + if deviations.ExplicitEnableBGPOnDefaultVRF(dut) { + bs.DUTConf.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(cfgplugins.PTBGP, "BGP").GetOrCreateBgp().GetOrCreateGlobal().SetAs(cfgplugins.DutAS) + } + if dut.Vendor() != ondatra.NOKIA && dut.Vendor() != ondatra.JUNIPER { + bs.DUTConf.GetOrCreateNetworkInstance(mgmtVRFName).SetRouteDistinguisher(fmt.Sprintf("%d:%d", cfgplugins.DutAS, 100)) + } + bs.PushAndStart(t) + if verfied := verifyDUTBGPEstablished(t, bs.DUT, mgmtVRFName); verfied { + t.Log("DUT BGP sessions established") + } else { + t.Fatalf("BGP sessions not established") + } + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) + + configureLoopbackOnDUT(t, bs.DUT) + advertiseDUTLoopbackToATE(t, bs.DUT, bs) + configureStaticRoute(t, bs.DUT, bs.ATEPorts[2].IPv6) + configureImportExportBGPPolicy(t, bs, dut) + + t.Run("traffic received by port1 or port2", func(t *testing.T) { + createFlowV6(t, bs) + otgutils.WaitForARP(t, bs.ATE.OTG(), bs.ATETop, "IPv6") + bs.ATE.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + otgutils.LogPortMetrics(t, bs.ATE.OTG(), bs.ATETop) + lossV6 := otgutils.GetFlowLossPct(t, bs.ATE.OTG(), "v6Flow", 10*time.Second) + if lossV6 > lossTolerance { + t.Errorf("Loss percent for IPv6 Traffic: got: %f, want %f", lossV6, lossTolerance) + } + }) + + t.Run("traffic received by port2", func(t *testing.T) { + createFlowV6(t, bs) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), false) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).AdminStatus().State(), 30*time.Second, oc.Interface_AdminStatus_DOWN) + time.Sleep(3 * time.Second) + bs.ATE.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + otgutils.LogPortMetrics(t, bs.ATE.OTG(), bs.ATETop) + framesTx := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Port(bs.ATE.Port(t, "port4").ID()).Counters().OutFrames().State()) + framesRx := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Port(bs.ATE.Port(t, "port2").ID()).Counters().InFrames().State()) + lossV6 := otgutils.GetFlowLossPct(t, bs.ATE.OTG(), "v6Flow", 10*time.Second) + if lossV6 > lossTolerance || framesRx < framesTx { + t.Errorf("Frames sent/received: got: %d, want: %d", framesRx, framesTx) + } + }) + + t.Run("traffic received by port3", func(t *testing.T) { + createFlowV6(t, bs) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Enabled().Config(), false) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).AdminStatus().State(), 30*time.Second, oc.Interface_AdminStatus_DOWN) + bs.ATE.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + otgutils.LogPortMetrics(t, bs.ATE.OTG(), bs.ATETop) + framesTx := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Port(bs.ATE.Port(t, "port4").ID()).Counters().OutFrames().State()) + framesRx := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Port(bs.ATE.Port(t, "port3").ID()).Counters().InFrames().State()) + if lossPct(float64(framesTx), float64(framesRx)) > lossTolerance { + t.Errorf("Frames sent/received: got: %d, want: %d", framesRx, framesTx) + } + }) + + t.Run("traffic received by port1", func(t *testing.T) { + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), true) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).AdminStatus().State(), 30*time.Second, oc.Interface_AdminStatus_UP) + createFlowV6(t, bs) + time.Sleep(30 * time.Second) + bs.ATE.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + bs.ATE.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, bs.ATE.OTG(), bs.ATETop) + otgutils.LogPortMetrics(t, bs.ATE.OTG(), bs.ATETop) + framesTx := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Port(bs.ATE.Port(t, "port4").ID()).Counters().OutFrames().State()) + framesRx := gnmi.Get(t, bs.ATE.OTG(), gnmi.OTG().Port(bs.ATE.Port(t, "port1").ID()).Counters().InFrames().State()) + lossV6 := otgutils.GetFlowLossPct(t, bs.ATE.OTG(), "v6Flow", 10*time.Second) + if lossV6 > lossTolerance || framesRx < framesTx { + t.Errorf("Frames sent/received: got: %d, want: %d", framesRx, framesTx) + } + }) + + defer func() { + batchConfig := &gnmi.SetBatch{} + gnmi.BatchDelete(batchConfig, gnmi.OC().Interface(loopbackIntfName).Config()) + gnmi.BatchDelete(batchConfig, gnmi.OC().NetworkInstance(mgmtVRFName).Config()) + batchConfig.Set(t, dut) + }() +} + +func createFlowV6(t *testing.T, bs *cfgplugins.BGPSession) { + bs.ATETop.Flows().Clear() + + t.Log("Configuring v6 traffic flow") + v6Flow := bs.ATETop.Flows().Add().SetName("v6Flow") + v6Flow.Metrics().SetEnable(true) + v6Flow.TxRx().Device(). + SetTxNames([]string{"port4.IPv6"}). + SetRxNames([]string{"port1.BGP4.peer.rr6", "port2.BGP4.peer.rr6", "port3.IPv6"}) + v6Flow.Size().SetFixed(512) + v6Flow.Rate().SetPps(100) + v6Flow.Duration().Continuous() + e1 := v6Flow.Packet().Add().Ethernet() + e1.Src().SetValues([]string{bs.ATEPorts[3].MAC}) + v6 := v6Flow.Packet().Add().Ipv6() + v6.Src().SetValue(ateNetPrefix) + v6.Dst().Increment().SetStart(prefixesStart).SetCount(1) + icmp1 := v6Flow.Packet().Add().Icmp() + icmp1.SetEcho(gosnappi.NewFlowIcmpEcho()) + + bs.ATE.OTG().PushConfig(t, bs.ATETop) + bs.ATE.OTG().StartProtocols(t) +} + +func configureStaticRoute(t *testing.T, dut *ondatra.DUTDevice, nextHopIP string) { + mgmtVRFName := mgmtVRF[dut.Vendor()] + c := &oc.NetworkInstance_Protocol{ + Identifier: oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, + Name: ygot.String(deviations.StaticProtocolName(dut)), + } + s := c.GetOrCreateStatic(defaultRoute + "/0") + nh := s.GetOrCreateNextHop("0") + nh.NextHop = oc.UnionString(nextHopIP) + if deviations.SetMetricAsPreference(dut) { + nh.Metric = ygot.Uint32(220) + } else { + nh.Preference = ygot.Uint32(220) + } + sp := gnmi.OC().NetworkInstance(mgmtVRFName).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.Update(t, dut, sp.Config(), c) + gnmi.Replace(t, dut, sp.Static(defaultRoute+"/0").Config(), s) +} + +func configureEmulatedNetworks(bs *cfgplugins.BGPSession) { + devices := bs.ATETop.Devices().Items() + byName := func(i, j int) bool { return devices[i].Name() < devices[j].Name() } + sort.Slice(devices, byName) + for i, otgPort := range bs.ATEPorts[:len(bgpPorts)] { + ipv6 := devices[i].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := devices[i].Bgp().Ipv6Interfaces().Items()[0].Peers().Items()[0] + bgp6PeerRoute := bgp6Peer.V6Routes().Add() + bgp6PeerRoute.SetName(otgPort.Name + ".BGP4.peer.rr6") + bgp6PeerRoute.SetNextHopIpv6Address(ipv6.Address()) + bgp6PeerRoute.SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6) + bgp6PeerRoute.SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + bgp6PeerRoute.AddPath().SetPathId(pathID) + + bgp6PeerRoute.Addresses().Add().SetAddress(prefixesStart).SetPrefix(prefixP6Len).SetCount(prefixesCount) + bgp6PeerRoute.Addresses().Add().SetAddress(defaultRoute).SetPrefix(0) + } +} + +func configureLoopbackOnDUT(t *testing.T, dut *ondatra.DUTDevice) { + loopbackIntfName := netutil.LoopbackInterface(t, dut, loopbackIntf[dut.Vendor()]) + dutlo0Attrs.Subinterface = uint32(loopbackSubIntf[dut.Vendor()]) + loop := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, gnmi.OC().Interface(loopbackIntfName).Config(), loop) + t.Logf("Got DUT IPv6 loopback address: %v", dutlo0Attrs.IPv6) +} + +func createAndAddInterfacesToVRF(t *testing.T, dut *ondatra.DUTDevice, vrfname string, intfNames []string, unit []uint32) { + root := &oc.Root{} + batchConfig := &gnmi.SetBatch{} + for index, intfName := range intfNames { + i := root.GetOrCreateInterface(intfName) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.Description = ygot.String(fmt.Sprintf("Port %s", strconv.Itoa(index+1))) + if intfName == netutil.LoopbackInterface(t, dut, loopbackIntf[dut.Vendor()]) { + i.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + i.Description = ygot.String(fmt.Sprintf("Port %s", intfName)) + } + si := i.GetOrCreateSubinterface(unit[index]) + si.Enabled = ygot.Bool(true) + gnmi.BatchUpdate(batchConfig, gnmi.OC().Interface(intfName).Config(), i) + } + + mgmtNI := root.GetOrCreateNetworkInstance(vrfname) + mgmtNI.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + for index, intfName := range intfNames { + vi := mgmtNI.GetOrCreateInterface(intfName) + vi.Interface = ygot.String(intfName) + vi.Subinterface = ygot.Uint32(unit[index]) + } + gnmi.BatchReplace(batchConfig, gnmi.OC().NetworkInstance(vrfname).Config(), mgmtNI) + batchConfig.Set(t, dut) + t.Logf("Added interface %v to VRF %s", intfNames, vrfname) +} + +func verifyDUTBGPEstablished(t *testing.T, dut *ondatra.DUTDevice, ni string) bool { + nSessionState := gnmi.OC().NetworkInstance(ni).Protocol(cfgplugins.PTBGP, "BGP").Bgp().NeighborAny().SessionState().State() + watch := gnmi.WatchAll(t, dut, nSessionState, 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if !ok || state != oc.Bgp_Neighbor_SessionState_ESTABLISHED { + return false + } + return true + }) + if _, ok := watch.Await(t); !ok { + return false + } + return true +} + +func advertiseDUTLoopbackToATE(t *testing.T, dut *ondatra.DUTDevice, bs *cfgplugins.BGPSession) { + t.Helper() + mgmtVRFName := mgmtVRF[dut.Vendor()] + batchSet := &gnmi.SetBatch{} + + root := &oc.Root{} + rp := root.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition("rp") + stmt, err := pdef.AppendNewStatement("rp-stmt") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "rp-stmt", err) + } + stmt.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + prefixSet := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet("ps") + if !deviations.SkipPrefixSetMode(dut) { + prefixSet.SetMode(oc.PrefixSet_Mode_IPV6) + } + prefixSet.GetOrCreatePrefix(dutlo0Attrs.IPv6CIDR(), "exact") + + if !deviations.SkipSetRpMatchSetOptions(dut) { + stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet("ps") + + gnmi.BatchUpdate(batchSet, gnmi.OC().RoutingPolicy().Config(), rp) + if deviations.TableConnectionsUnsupported(dut) { + if deviations.RedisConnectedUnderEbgpVrfUnsupported(dut) && dut.Vendor() == ondatra.CISCO { + cliPath, err := schemaless.NewConfig[string]("", "cli") + if err != nil { + t.Fatalf("Failed to create CLI ygnmi query: %v", err) + } + cliCfg := getCiscoCLIRedisConfig("BGP", cfgplugins.DutAS, mgmtVRFName) + gnmi.BatchUpdate(batchSet, cliPath, cliCfg) + } else { + stmt.GetOrCreateConditions().SetInstallProtocolEq(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED) + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + for _, neighbor := range []string{bs.ATEPorts[0].IPv6, bs.ATEPorts[1].IPv6} { + pathV6 := gnmi.OC().NetworkInstance(mgmtVRFName).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(neighbor).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := root.GetOrCreateNetworkInstance(mgmtVRFName).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").GetOrCreateBgp().GetOrCreateNeighbor(neighbor).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policyV6.SetExportPolicy([]string{"rp"}) + gnmi.BatchUpdate(batchSet, pathV6.Config(), policyV6) + } + } + } else { + tableConn := root.GetOrCreateNetworkInstance(mgmtVRFName).GetOrCreateTableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV6) + if !deviations.SkipSettingDisableMetricPropagation(dut) { + tableConn.SetDisableMetricPropagation(false) + } + tableConn.SetDefaultImportPolicy(oc.RoutingPolicy_DefaultPolicyType_REJECT_ROUTE) + tableConn.SetImportPolicy([]string{"rp"}) + gnmi.BatchUpdate(batchSet, gnmi.OC().NetworkInstance(mgmtVRFName).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV6).Config(), tableConn) + + tableConn1 := root.GetOrCreateNetworkInstance(mgmtVRFName).GetOrCreateTableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV4) + tableConn1.SetImportPolicy([]string{"rp"}) + gnmi.BatchUpdate(batchSet, gnmi.OC().NetworkInstance(mgmtVRFName).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV4).Config(), tableConn1) + } + batchSet.Set(t, dut) +} + +func configureImportExportBGPPolicy(t *testing.T, bs *cfgplugins.BGPSession, dut *ondatra.DUTDevice) { + mgmtVRFName := mgmtVRF[dut.Vendor()] + root := &oc.Root{} + batchSet := &gnmi.SetBatch{} + + rp := root.GetOrCreateRoutingPolicy() + pdef1 := rp.GetOrCreatePolicyDefinition("importRoutePolicy") + stmt1, err := pdef1.AppendNewStatement("routePolicyStatement1") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement1", err) + } + stmt1.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + prefixSet1 := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet("ps1") + if !deviations.SkipPrefixSetMode(dut) { + prefixSet1.SetMode(oc.PrefixSet_Mode_IPV6) + } + prefixSet1.GetOrCreatePrefix(defaultRoute+"/0", "exact") + + if !deviations.SkipSetRpMatchSetOptions(bs.DUT) { + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt1.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet("ps1") + + pdef2 := rp.GetOrCreatePolicyDefinition("exportRoutePolicy") + stmt2, err := pdef2.AppendNewStatement("routePolicyStatement2") + if err != nil { + t.Fatalf("AppendNewStatement(%s) failed: %v", "routePolicyStatement2", err) + } + stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + prefixSet2 := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet("ps2") + if !deviations.SkipPrefixSetMode(dut) { + prefixSet2.SetMode(oc.PrefixSet_Mode_IPV6) + } + prefixSet2.GetOrCreatePrefix(dutlo0Attrs.IPv6CIDR(), "exact") + + if !deviations.SkipSetRpMatchSetOptions(bs.DUT) { + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY) + } + stmt2.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet("ps2") + + gnmi.BatchUpdate(batchSet, gnmi.OC().RoutingPolicy().Config(), rp) + + for _, neighbor := range []string{bs.ATEPorts[0].IPv6, bs.ATEPorts[1].IPv6} { + pathV6 := gnmi.OC().NetworkInstance(mgmtVRFName).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().Neighbor(neighbor).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).ApplyPolicy() + policyV6 := root.GetOrCreateNetworkInstance(mgmtVRFName).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").GetOrCreateBgp().GetOrCreateNeighbor(neighbor).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateApplyPolicy() + policyV6.SetImportPolicy([]string{"importRoutePolicy"}) + policyV6.SetExportPolicy([]string{"exportRoutePolicy"}) + gnmi.BatchUpdate(batchSet, pathV6.Config(), policyV6) + } + + batchSet.Set(t, bs.DUT) +} + +func lossPct(tx, rx float64) float64 { + return (math.Abs(tx-rx) * 100) / tx +} + +func getCiscoCLIRedisConfig(instanceName string, as uint32, vrf string) string { + cfg := fmt.Sprintf("router bgp %d instance %s\n", as, instanceName) + cfg = cfg + fmt.Sprintf(" vrf %s\n", vrf) + cfg = cfg + " address-family ipv6 unicast\n" + cfg = cfg + " redistribute connected\n" + return cfg +} diff --git a/feature/system/management/otg_tests/management_ha_test/metadata.textproto b/feature/system/management/otg_tests/management_ha_test/metadata.textproto new file mode 100644 index 00000000000..bbc70c53bc1 --- /dev/null +++ b/feature/system/management/otg_tests/management_ha_test/metadata.textproto @@ -0,0 +1,49 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "b569d5de-0038-43f5-b329-bedce86eec3d" +plan_id: "MGT-1" +description: "Management HA solution test" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + static_protocol_name: "STATIC" + interface_enabled: true + default_network_instance: "default" + skip_set_rp_match_set_options: true + skip_setting_disable_metric_propagation: true + set_no_peer_group: true + explicit_enable_bgp_on_default_vrf: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + static_protocol_name: "static" + skip_set_rp_match_set_options: true + skip_prefix_set_mode: true + table_connections_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + explicit_enable_bgp_on_default_vrf: true + peer_group_def_ebgp_vrf_unsupported: true + redis_connected_under_ebgp_vrf_unsupported: true + table_connections_unsupported: true + bgp_afi_safi_in_default_ni_before_other_ni: true + } +} +tags: TAGS_TRANSIT +tags: TAGS_DATACENTER_EDGE diff --git a/feature/system/ntp/feature.textproto b/feature/system/ntp/feature.textproto index a88d35bbf79..ad945baa9dd 100644 --- a/feature/system/ntp/feature.textproto +++ b/feature/system/ntp/feature.textproto @@ -11,6 +11,9 @@ # 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" diff --git a/feature/system/ntp/tests/system_ntp_test/README.md b/feature/system/ntp/tests/system_ntp_test/README.md index a0083cc0744..8307aeb317f 100644 --- a/feature/system/ntp/tests/system_ntp_test/README.md +++ b/feature/system/ntp/tests/system_ntp_test/README.md @@ -16,16 +16,29 @@ Ensure DUT can be configured as a Network Time Protocol (NTP) client. Note: [TODO]the source address of NTP need to be specified -## Config Parameter Coverage - -* /system/ntp/config/enabled -* /system/ntp/servers/server/config/address -* [TODO]/system/ntp/servers/server/config/source-address -* /system/ntp/servers/server/config/network-instance - -## Telemetry Parameter Coverage - -* /system/ntp/servers/server/state/address -* [TODO]/system/ntp/servers/server/state/source-address -* [TODO]/system/ntp/servers/server/state/port -* /system/ntp/servers/server/state/network-instance +## 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(OCPATH): Populate path from test already written. + +```yaml +paths: + ## Config paths + /system/ntp/config/enabled: + /system/ntp/servers/server/config/address: + #[TODO]/system/ntp/servers/server/config/source-address: + /system/ntp/servers/server/config/network-instance: + + ## State paths + /system/ntp/servers/server/state/address: + #[TODO]/system/ntp/servers/server/state/source-address: + #[TODO]/system/ntp/servers/server/state/port: + /system/ntp/servers/server/state/network-instance: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/system/ntp/tests/system_ntp_test/metadata.textproto b/feature/system/ntp/tests/system_ntp_test/metadata.textproto index dddf1072d80..28b530604bb 100644 --- a/feature/system/ntp/tests/system_ntp_test/metadata.textproto +++ b/feature/system/ntp/tests/system_ntp_test/metadata.textproto @@ -5,22 +5,6 @@ uuid: "9e5ec4a5-0adb-48aa-b6c2-c0d604d15934" plan_id: "OC-26.1" description: "Network Time Protocol (NTP)" testbed: TESTBED_DUT -platform_exceptions: { - platform: { - vendor: CISCO - } - deviations: { - ntp_non_default_vrf_unsupported: true - } -} -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - ntp_non_default_vrf_unsupported: true - } -} platform_exceptions: { platform: { vendor: NOKIA @@ -29,4 +13,3 @@ platform_exceptions: { ntp_non_default_vrf_unsupported: true } } - 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 4acc14ae9a9..b72e3344d12 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 @@ -99,7 +99,7 @@ func TestNtpServerConfigurability(t *testing.T) { if ntpServer == nil { t.Errorf("Missing NTP server from NTP state: %s", address) } - if got, want := testCase.vrf, ntpServer.GetNetworkInstance(); want != "" && got != want { + 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/tests/system_base_test/g_protocol_test.go b/feature/system/tests/system_base_test/g_protocol_test.go index 3db029982e0..8e901db64e0 100644 --- a/feature/system/tests/system_base_test/g_protocol_test.go +++ b/feature/system/tests/system_base_test/g_protocol_test.go @@ -18,167 +18,94 @@ package system_base_test import ( "context" - "crypto/tls" - "fmt" "testing" "time" "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/binding" - "github.com/openconfig/ondatra/knebind/creds" + "github.com/openconfig/ondatra/binding/introspect" "google.golang.org/grpc" - "google.golang.org/grpc/credentials" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" gpb "github.com/openconfig/gnmi/proto/gnmi" spb "github.com/openconfig/gnoi/system" authzpb "github.com/openconfig/gnsi/authz" gribipb "github.com/openconfig/gribi/v1/proto/service" - tpb "github.com/openconfig/kne/proto/topo" p4rtpb "github.com/p4lang/p4runtime/go/p4/v1" ) -func resolveService(t *testing.T, dut *ondatra.DUTDevice, serviceName string, wantPort uint32) string { +func dialConn(t *testing.T, dut *ondatra.DUTDevice, svc introspect.Service, wantPort uint32) *grpc.ClientConn { t.Helper() - 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) - } - if serviceName == "gnoi" || serviceName == "gnsi" { + if svc == introspect.GNOI || svc == introspect.GNSI { // Renaming service name due to gnoi and gnsi always residing on same port as gnmi. - serviceName = "gnmi" + svc = introspect.GNMI } - s, err := servDUT.Service(serviceName) - if err != nil { - t.Fatal(err) + dialer := introspect.DUTDialer(t, dut, svc) + if dialer.DevicePort != int(wantPort) { + t.Fatalf("DUT is not listening on correct port for %q: got %d, want %d", svc, dialer.DevicePort, wantPort) } - if s.GetInside() != wantPort { - t.Fatalf("DUT is not listening on correct port for %q: got %d, want %d", serviceName, s.GetInside(), wantPort) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + conn, err := dialer.Dial(ctx) + if err != nil { + t.Fatalf("grpc.Dial failed to: %q", dialer.DialTarget) } - return fmt.Sprintf("%s:%d", s.GetOutsideIp(), s.GetOutside()) - -} - -type rpcCredentials struct { - *creds.UserPass -} - -func (r *rpcCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { - return map[string]string{ - "username": "admin", - "password": "admin", - }, nil -} - -func (r *rpcCredentials) RequireTransportSecurity() bool { - return true + return conn } // TestGNMIClient validates that the DUT listens on standard gNMI Port. func TestGNMIClient(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := resolveService(t, dut, "gnmi", 9339) - credOpts := []grpc.DialOption{grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} // NOLINT - creds := &rpcCredentials{} - credOpts = append(credOpts, grpc.WithPerRPCCredentials(creds)) - t.Logf("gNMI standard port test: %q", target) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - conn, err := grpc.DialContext(ctx, target, credOpts...) - if err != nil { - t.Fatalf("grpc.Dial failed to: %q", target) - } + conn := dialConn(t, dut, introspect.GNMI, 9339) c := gpb.NewGNMIClient(conn) - if _, err := c.Get(context.Background(), &gpb.GetRequest{}); err != nil { + if _, err := c.Get(context.Background(), &gpb.GetRequest{Encoding: gpb.Encoding_JSON_IETF, Path: []*gpb.Path{{Elem: []*gpb.PathElem{}}}}); err != nil { t.Fatalf("gnmi.Get failed: %v", err) } } -// TestGNOIClient validates that the DUT listens on standard gNMI Port. +// TestGNOIClient validates that the DUT listens on standard gNOI Port. func TestGNOIClient(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := resolveService(t, dut, "gnoi", 9339) - credOpts := []grpc.DialOption{grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} // NOLINT - creds := &rpcCredentials{} - credOpts = append(credOpts, grpc.WithPerRPCCredentials(creds)) - - t.Logf("gNOI standard port test: %q", target) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - conn, err := grpc.DialContext(ctx, target, credOpts...) - if err != nil { - t.Fatalf("grpc.Dial failed to: %q", target) - } + conn := dialConn(t, dut, introspect.GNOI, 9339) c := spb.NewSystemClient(conn) - _, err = c.Ping(context.Background(), &spb.PingRequest{}) - if err != nil { - t.Fatalf("gnoi.system.Time failed: %v", err) + if _, err := c.Ping(context.Background(), &spb.PingRequest{}); err != nil { + t.Fatalf("gnoi.system.Ping failed: %v", err) } } -// TestGNSIClient validates that the DUT listens on standard gNMI Port. +// TestGNSIClient validates that the DUT listens on standard gNSI Port. func TestGNSIClient(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := resolveService(t, dut, "gnsi", 9339) - credOpts := []grpc.DialOption{grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} // NOLINT - creds := &rpcCredentials{} - credOpts = append(credOpts, grpc.WithPerRPCCredentials(creds)) - - t.Logf("gNSI standard port test: %q", target) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - conn, err := grpc.DialContext(ctx, target, credOpts...) - if err != nil { - t.Fatalf("grpc.Dial failed to: %q", target) - } + conn := dialConn(t, dut, introspect.GNSI, 9339) c := authzpb.NewAuthzClient(conn) - _, err = c.Get(context.Background(), &authzpb.GetRequest{}) + rsp, err := c.Get(context.Background(), &authzpb.GetRequest{}) if err != nil { - t.Fatalf("gnsi.authz.Get failed: %v", err) + statusError, _ := status.FromError(err) + if statusError.Code() == codes.FailedPrecondition { + t.Logf("Expected error FAILED_PRECONDITION seen for authz Get Request.") + } else { + t.Errorf("Unexpected error during authz Get Request.") + } } + t.Logf("gNSI authz get response is %s", rsp) } -// TestGRIBIClient validates that the DUT listens on standard gNMI Port. +// TestGRIBIClient validates that the DUT listens on standard gRIBI Port. func TestGRIBIClient(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := resolveService(t, dut, "gribi", 9340) - credOpts := []grpc.DialOption{grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} // NOLINT - creds := &rpcCredentials{} - credOpts = append(credOpts, grpc.WithPerRPCCredentials(creds)) - - t.Logf("gRIBI standard port test: %q", target) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - conn, err := grpc.DialContext(ctx, target, credOpts...) - if err != nil { - t.Fatalf("grpc.Dial failed to: %q", target) - } + conn := dialConn(t, dut, introspect.GRIBI, 9340) c := gribipb.NewGRIBIClient(conn) - _, err = c.Get(context.Background(), &gribipb.GetRequest{}) - if err != nil { + if _, err := c.Get(context.Background(), &gribipb.GetRequest{}); err != nil { t.Fatalf("gribi.Get failed: %v", err) } } -// TestP4RTClient validates that the DUT listens on standard gNMI Port. +// TestP4RTClient validates that the DUT listens on standard P4RT Port. func TestP4RTClient(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := resolveService(t, dut, "p4rt", 9559) - credOpts := []grpc.DialOption{grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} // NOLINT - creds := &rpcCredentials{} - credOpts = append(credOpts, grpc.WithPerRPCCredentials(creds)) - - t.Logf("P4RT standard port test: %q", target) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - conn, err := grpc.DialContext(ctx, target, credOpts...) - if err != nil { - t.Fatalf("grpc.Dial failed to: %q", target) - } + conn := dialConn(t, dut, introspect.P4RT, 9559) c := p4rtpb.NewP4RuntimeClient(conn) - _, err = c.Capabilities(context.Background(), &p4rtpb.CapabilitiesRequest{}) - if err != nil { + if _, err := c.Capabilities(context.Background(), &p4rtpb.CapabilitiesRequest{}); err != nil { t.Fatalf("p4rt.Capabilites failed: %v", err) } } diff --git a/feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/README.md b/feature/tunnel/otg_tests/tunnel_acl_based_test/README.md similarity index 66% rename from feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/README.md rename to feature/tunnel/otg_tests/tunnel_acl_based_test/README.md index 073bc96cac8..f7f4769287a 100644 --- a/feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/README.md +++ b/feature/tunnel/otg_tests/tunnel_acl_based_test/README.md @@ -14,10 +14,20 @@ Verify the DSCP value of original packet header after GRE acl based tunnel encap * verify dscp value of original packet after encapsulation. * verify that no traffic drops in all flows. -## Config Parameter coverage - -* /acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/config/set-name -* /acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/config/set-type +## 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: + /acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/config/set-name: + /acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/config/type: +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` ## Validation coverage diff --git a/feature/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto b/feature/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto new file mode 100644 index 00000000000..0e10ec8d554 --- /dev/null +++ b/feature/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto @@ -0,0 +1,8 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "5e46a336-0e6c-4b65-ac49-8b925aacb46c" +plan_id: "TUN-1.9" +description: "GRE inner packet DSCP" +testbed: TESTBED_DUT_ATE_2LINKS +tags: TAGS_AGGREGATION diff --git a/feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/tun_acl_dscp_test.go b/feature/tunnel/otg_tests/tunnel_acl_based_test/tun_acl_dscp_test.go similarity index 97% rename from feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/tun_acl_dscp_test.go rename to feature/tunnel/otg_tests/tunnel_acl_based_test/tun_acl_dscp_test.go index 2085ca9edda..d2f9d00a7aa 100644 --- a/feature/experimental/tunnel/otg_tests/tunnel_acl_based_test/tun_acl_dscp_test.go +++ b/feature/tunnel/otg_tests/tunnel_acl_based_test/tun_acl_dscp_test.go @@ -200,14 +200,14 @@ func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { srcPort := topo.Ports().Add().SetName("port1") srcDev := topo.Devices().Add().SetName(ateSrc.Name) srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) - srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(srcPort.Name()) + srcEth.Connection().SetPortName(srcPort.Name()) srcIpv4 := srcEth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4") srcIpv4.SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) t.Logf("Configuring OTG port2") dstPort := topo.Ports().Add().SetName("port2") dstDev := topo.Devices().Add().SetName(ateDst.Name) dstEth := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) - dstEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(dstPort.Name()) + dstEth.Connection().SetPortName(dstPort.Name()) dstIpv4 := dstEth.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4") dstIpv4.SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) topo.Captures().Add().SetName("grecapture").SetPortNames([]string{dstPort.Name()}).SetFormat(gosnappi.CaptureFormat.PCAP) @@ -219,7 +219,7 @@ func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { SetRxNames([]string{dstIpv4.Name()}) flowipv4.Size().SetFixed(FrameSize) flowipv4.Rate().SetPps(pps) - flowipv4.Duration().SetChoice("continuous") + flowipv4.Duration().Continuous() e1 := flowipv4.Packet().Add().Ethernet() e1.Src().SetValue(srcEth.Mac()) v4 := flowipv4.Packet().Add().Ipv4() @@ -232,7 +232,8 @@ func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { ate.OTG().StartProtocols(t) time.Sleep(30 * time.Second) // otgutils.WaitForARP(t, otg, topo, "IPv4") - t.Log(topo.Msg().GetCaptures()) + pb, _ := topo.Marshal().ToProto() + t.Log(pb.GetCaptures()) return topo } diff --git a/feature/experimental/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/README.md b/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/README.md similarity index 94% rename from feature/experimental/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/README.md rename to feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/README.md index 0bfc7dbe99f..076b0069e74 100644 --- a/feature/experimental/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/README.md +++ b/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/README.md @@ -71,4 +71,13 @@ Validate Interface based Ipv6 GRE Tunnel Config. - state/counters/out-forwarded-pkts - state/counters/out-forwarded-octets - state/counters/out-discarded-pkt - - Fragmentation and assembly counters Filter counters Output to display the traffic is spread across the different tunnel subnet ranges/NH groups/Interfaces \ No newline at end of file + - Fragmentation and assembly counters Filter counters Output to display the traffic is spread across the different tunnel subnet ranges/NH groups/Interfaces + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/metadata.textproto b/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/metadata.textproto new file mode 100644 index 00000000000..59448872569 --- /dev/null +++ b/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/metadata.textproto @@ -0,0 +1,20 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "4ba9a73c-9ed3-46fb-a9be-356542f41a3b" +plan_id: "TUN-1.4" +description: "Interface based IPv6 GRE Encapsulation" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: JUNIPER + hardware_model: "PTX10008" + hardware_model: "PTX10001-36MR" + hardware_model: "cptx" + } + deviations: { + tunnel_state_path_unsupported: true + tunnel_config_path_unsupported: true + } +} +tags: TAGS_AGGREGATION diff --git a/feature/experimental/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 similarity index 98% rename from feature/experimental/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/tunnel_interface_based_ipv6_gre_encapsulation_test.go rename to feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/tunnel_interface_based_ipv6_gre_encapsulation_test.go index 0647e95cfb2..7f926f52c5a 100644 --- a/feature/experimental/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,7 @@ 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.Logf(top.ToJson()) + t.Logf(top.Marshal().ToJson()) ate.OTG().PushConfig(t, top) ate.OTG().StartProtocols(t) time.Sleep(30 * time.Second) @@ -258,7 +258,7 @@ func configureOtgPorts(top gosnappi.Config, port *ondatra.Port, name string, mac //port1 iDutDev := top.Devices().Add().SetName(name) iDutEth := iDutDev.Ethernets().Add().SetName(name + ".Eth").SetMac(mac) - iDutEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(port.ID()) + iDutEth.Connection().SetPortName(port.ID()) iDutIpv6 := iDutEth.Ipv6Addresses().Add().SetName(name + ".IPv6") iDutIpv6.SetAddress(ipv6Address).SetGateway(ipv6Gateway).SetPrefix(uint32(ipv6Mask)) } @@ -273,7 +273,7 @@ func configureTrafficFlowsToEncasulation(t *testing.T, top gosnappi.Config, port // Flow settings. flow1ipv6.Size().SetFixed(512) flow1ipv6.Rate().SetPps(trafficRatePps) - flow1ipv6.Duration().SetChoice("continuous") + flow1ipv6.Duration().Continuous() // Ethernet header f1e2 := flow1ipv6.Packet().Add().Ethernet() f1e2.Src().SetValue(peer.MAC) @@ -281,7 +281,7 @@ func configureTrafficFlowsToEncasulation(t *testing.T, top gosnappi.Config, port // IPv6 header f1v6 := flow1ipv6.Packet().Add().Ipv6() // V6 NextHeader - f1v6.NextHeader().SetChoice("value").SetValue(6) + f1v6.NextHeader().SetValue(6) // V6 source f1v6.Src().Increment().SetStart(peer.IPv6).SetCount(200) // V6 destination diff --git a/feature/experimental/tunnel/otg_tests/tunnel_interface_based_resize_test/README.md b/feature/tunnel/otg_tests/tunnel_interface_based_resize_test/README.md similarity index 98% rename from feature/experimental/tunnel/otg_tests/tunnel_interface_based_resize_test/README.md rename to feature/tunnel/otg_tests/tunnel_interface_based_resize_test/README.md index 1bdd0384ace..24925300655 100644 --- a/feature/experimental/tunnel/otg_tests/tunnel_interface_based_resize_test/README.md +++ b/feature/tunnel/otg_tests/tunnel_interface_based_resize_test/README.md @@ -101,3 +101,12 @@ TODO: OpenConfig definition required for Tunnel protocol under interfaces/interf * state/counters/out-forwarded-pkts * state/counters/out-forwarded-octets * state/counters/out-discarded-pkts + +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/go.mod b/go.mod index 91e63f3e921..9965d7ca82f 100644 --- a/go.mod +++ b/go.mod @@ -3,84 +3,103 @@ module github.com/openconfig/featureprofiles go 1.21 require ( - cloud.google.com/go/pubsub v1.33.0 - cloud.google.com/go/storage v1.30.1 - github.com/cisco-open/go-p4 v0.1.1 - github.com/go-git/go-billy/v5 v5.4.1 - github.com/go-git/go-git/v5 v5.7.0 - github.com/golang/glog v1.1.2 + cloud.google.com/go/pubsub v1.36.1 + cloud.google.com/go/storage v1.38.0 + github.com/cisco-open/go-p4 v0.1.2 + github.com/go-git/go-billy/v5 v5.5.0 + github.com/go-git/go-git/v5 v5.11.0 + github.com/golang/glog v1.2.1 github.com/google/go-cmp v0.6.0 github.com/google/go-github/v50 v50.1.0 github.com/google/gopacket v1.1.19 - github.com/google/uuid v1.4.0 - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 - github.com/open-traffic-generator/snappi/gosnappi v0.13.0 + 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.3.0 + github.com/openconfig/containerz v0.0.0-20240620162940-e0bf23af17d6 github.com/openconfig/entity-naming v0.0.0-20230912181021-7ac806551a31 - github.com/openconfig/gnmi v0.10.0 - github.com/openconfig/gnoi v0.3.0 - github.com/openconfig/gnoigo v0.0.0-20231026010722-87413fdb22e7 - github.com/openconfig/gnsi v1.2.3 + github.com/openconfig/gnmi v0.11.0 + github.com/openconfig/gnoi v0.4.1 + github.com/openconfig/gnoigo v0.0.0-20240320202954-ebd033e3542c + github.com/openconfig/gnsi v1.6.0 github.com/openconfig/gocloser v0.0.0-20220310182203-c6c950ed3b0b - github.com/openconfig/goyang v1.4.4 + github.com/openconfig/goyang v1.4.5 github.com/openconfig/gribi v1.0.0 - github.com/openconfig/gribigo v0.0.0-20231031140438-9a293da13ff9 - github.com/openconfig/kne v0.1.14 + github.com/openconfig/gribigo v0.0.0-20240829231637-69cf06726cc3 + github.com/openconfig/kne v0.1.18 github.com/openconfig/models-ci v1.0.2-0.20231113233730-f0986391428e - github.com/openconfig/ondatra v0.4.3 - github.com/openconfig/replayer v0.0.0-20231031192218-5462382820d4 + github.com/openconfig/ondatra v0.6.1 + github.com/openconfig/replayer v0.0.0-20240110192655-4e9cf83d8d30 github.com/openconfig/testt v0.0.0-20220311054427-efbb1a32ec07 - github.com/openconfig/ygnmi v0.10.0 - github.com/openconfig/ygot v0.29.15 + github.com/openconfig/ygnmi v0.11.1 + github.com/openconfig/ygot v0.29.19 github.com/p4lang/p4runtime v1.4.0-rc.5.0.20220728214547-13f0d02a521e + github.com/pborman/uuid v1.2.1 github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a - golang.org/x/crypto v0.15.0 - golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 - golang.org/x/text v0.14.0 - google.golang.org/api v0.143.0 - google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + github.com/spf13/cobra v1.8.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.19.0 + github.com/yoheimuta/go-protoparser/v4 v4.9.0 + github.com/yuin/goldmark v1.4.13 + golang.org/x/crypto v0.31.0 + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 + golang.org/x/text v0.21.0 + google.golang.org/api v0.171.0 + google.golang.org/grpc v1.66.2 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 + k8s.io/klog/v2 v2.120.1 ) require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect - github.com/Microsoft/go-winio v0.5.2 // indirect - golang.org/x/oauth2 v0.12.0 + github.com/Microsoft/go-winio v0.6.1 // indirect + golang.org/x/oauth2 v0.21.0 ) require ( - cloud.google.com/go v0.110.9 // indirect - cloud.google.com/go/compute v1.23.2 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.4 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect - github.com/aristanetworks/arista-ceoslab-operator/v2 v2.0.2 // indirect + bitbucket.org/creachadair/stringset v0.0.14 // indirect + cel.dev/expr v0.15.0 // indirect + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // 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.1.3 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // 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-20240423153145-555b57ec207b // indirect github.com/creack/pty v1.1.18 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 // indirect + github.com/drivenets/cdnos-controller v1.7.4 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.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-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.4.1 // 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/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // 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.1 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // 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/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -89,7 +108,6 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -101,53 +119,55 @@ require ( 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/ixia-c-operator v0.3.6 // indirect + github.com/open-traffic-generator/keng-operator v0.3.28 // indirect + github.com/openconfig/attestz v0.2.0 // indirect + github.com/openconfig/gnpsi v0.3.2 // indirect github.com/openconfig/grpctunnel v0.0.0-20220819142823-6f5422b8ca70 // indirect github.com/openconfig/lemming/operator v0.2.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pborman/uuid v1.2.1 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/scrapli/scrapligo v1.1.11 // indirect github.com/scrapli/scrapligocfg v1.0.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4 // indirect - github.com/skeema/knownhosts v1.1.1 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.10.0 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/cobra v1.8.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.17.0 // indirect - github.com/srl-labs/srl-controller v0.6.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/srl-labs/srl-controller v0.6.1 // indirect github.com/srl-labs/srlinux-scrapli v0.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.opencensus.io v0.24.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.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/term v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.22.0 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.26.3 // indirect k8s.io/apimachinery v0.26.3 // indirect k8s.io/client-go v0.26.3 // indirect - k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect lukechampine.com/uint128 v1.3.0 // indirect diff --git a/go.sum b/go.sum index ce34f9de890..f5b7340ff7c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +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.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= +cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= 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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -38,18 +42,31 @@ cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= -cloud.google.com/go v0.110.9 h1:e7ITSqGFFk4rbz/JFIqZh3G4VEHguhAL4BQcFlWtU68= +cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +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/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= cloud.google.com/go/accessapproval v1.7.1/go.mod h1:JYczztsHRMK7NTXb6Xw+dwbs/WnOJxbo/2mTI+Kgg68= +cloud.google.com/go/accessapproval v1.7.2/go.mod h1:/gShiq9/kK/h8T/eEn1BTzalDvk0mZxJlhfw0p+Xuc0= +cloud.google.com/go/accessapproval v1.7.3/go.mod h1:4l8+pwIxGTNqSf4T3ds8nLO94NQf0W/KnMNuQ9PbnP8= +cloud.google.com/go/accessapproval v1.7.4/go.mod h1:/aTEh45LzplQgFYdQdwPMR9YdX0UlhBmvB84uAmQKUc= cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= cloud.google.com/go/accesscontextmanager v1.8.0/go.mod h1:uI+AI/r1oyWK99NN8cQ3UK76AMelMzgZCvJfsi2c+ps= cloud.google.com/go/accesscontextmanager v1.8.1/go.mod h1:JFJHfvuaTC+++1iL1coPiG1eu5D24db2wXCDWDjIrxo= +cloud.google.com/go/accesscontextmanager v1.8.2/go.mod h1:E6/SCRM30elQJ2PKtFMs2YhfJpZSNcJyejhuzoId4Zk= +cloud.google.com/go/accesscontextmanager v1.8.3/go.mod h1:4i/JkF2JiFbhLnnpnfoTX5vRXfhf9ukhU1ANOTALTOQ= +cloud.google.com/go/accesscontextmanager v1.8.4/go.mod h1:ParU+WbMpD34s5JFEnGAnPBYAgUHozaTmDJU7aCU9+M= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= @@ -57,24 +74,47 @@ cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6l cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= cloud.google.com/go/aiplatform v1.45.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4AotZiqjQ8MYA= +cloud.google.com/go/aiplatform v1.48.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4AotZiqjQ8MYA= +cloud.google.com/go/aiplatform v1.50.0/go.mod h1:IRc2b8XAMTa9ZmfJV1BCCQbieWWvDnP1A8znyz5N7y4= +cloud.google.com/go/aiplatform v1.51.0/go.mod h1:IRc2b8XAMTa9ZmfJV1BCCQbieWWvDnP1A8znyz5N7y4= +cloud.google.com/go/aiplatform v1.51.1/go.mod h1:kY3nIMAVQOK2XDqDPHaOuD9e+FdMA6OOpfBjsvaFSOo= +cloud.google.com/go/aiplatform v1.51.2/go.mod h1:hCqVYB3mY45w99TmetEoe8eCQEwZEp9WHxeZdcv9phw= +cloud.google.com/go/aiplatform v1.52.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.54.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.57.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= cloud.google.com/go/analytics v0.21.2/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N9RlEkYFHpQo= +cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N9RlEkYFHpQo= +cloud.google.com/go/analytics v0.21.4/go.mod h1:zZgNCxLCy8b2rKKVfC1YkC2vTrpfZmeRCySM3aUbskA= +cloud.google.com/go/analytics v0.21.5/go.mod h1:BQtOBHWTlJ96axpPPnw5CvGJ6i3Ve/qX2fTxR8qWyr8= +cloud.google.com/go/analytics v0.21.6/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= +cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= cloud.google.com/go/apigateway v1.6.1/go.mod h1:ufAS3wpbRjqfZrzpvLC2oh0MFlpRJm2E/ts25yyqmXA= +cloud.google.com/go/apigateway v1.6.2/go.mod h1:CwMC90nnZElorCW63P2pAYm25AtQrHfuOkbRSHj0bT8= +cloud.google.com/go/apigateway v1.6.3/go.mod h1:k68PXWpEs6BVDTtnLQAyG606Q3mz8pshItwPXjgv44Y= +cloud.google.com/go/apigateway v1.6.4/go.mod h1:0EpJlVGH5HwAN4VF4Iec8TAzGN1aQgbxAWGJsnPCGGY= cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= cloud.google.com/go/apigeeconnect v1.6.1/go.mod h1:C4awq7x0JpLtrlQCr8AzVIzAaYgngRqWf9S5Uhg+wWs= +cloud.google.com/go/apigeeconnect v1.6.2/go.mod h1:s6O0CgXT9RgAxlq3DLXvG8riw8PYYbU/v25jqP3Dy18= +cloud.google.com/go/apigeeconnect v1.6.3/go.mod h1:peG0HFQ0si2bN15M6QSjEW/W7Gy3NYkWGz7pFz13cbo= +cloud.google.com/go/apigeeconnect v1.6.4/go.mod h1:CapQCWZ8TCjnU0d7PobxhpOdVz/OVJ2Hr/Zcuu1xFx0= cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= cloud.google.com/go/apigeeregistry v0.7.1/go.mod h1:1XgyjZye4Mqtw7T9TsY4NW10U7BojBvG4RMD+vRDrIw= +cloud.google.com/go/apigeeregistry v0.7.2/go.mod h1:9CA2B2+TGsPKtfi3F7/1ncCCsL62NXBRfM6iPoGSM+8= +cloud.google.com/go/apigeeregistry v0.8.1/go.mod h1:MW4ig1N4JZQsXmBSwH4rwpgDonocz7FPBSw6XPGHmYw= +cloud.google.com/go/apigeeregistry v0.8.2/go.mod h1:h4v11TDGdeXJDJvImtgK2AFVvMIgGWjSb0HRnBSjcX8= cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= @@ -84,11 +124,17 @@ cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= cloud.google.com/go/appengine v1.8.1/go.mod h1:6NJXGLVhZCN9aQ/AEDvmfzKEfoYBlfB80/BHiKVputY= +cloud.google.com/go/appengine v1.8.2/go.mod h1:WMeJV9oZ51pvclqFN2PqHoGnys7rK0rz6s3Mp6yMvDo= +cloud.google.com/go/appengine v1.8.3/go.mod h1:2oUPZ1LVZ5EXi+AF1ihNAF+S8JrzQ3till5m9VQkrsk= +cloud.google.com/go/appengine v1.8.4/go.mod h1:TZ24v+wXBujtkK77CXCpjZbnuTvsFNT41MUaZ28D6vg= cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= cloud.google.com/go/area120 v0.8.1/go.mod h1:BVfZpGpB7KFVNxPiQBuHkX6Ed0rS51xIgmGyjrAfzsg= +cloud.google.com/go/area120 v0.8.2/go.mod h1:a5qfo+x77SRLXnCynFWPUZhnZGeSgvQ+Y0v1kSItkh4= +cloud.google.com/go/area120 v0.8.3/go.mod h1:5zj6pMzVTH+SVHljdSKC35sriR/CVvQZzG/Icdyriw0= +cloud.google.com/go/area120 v0.8.4/go.mod h1:jfawXjxf29wyBXr48+W+GyX/f8fflxp642D/bb9v68M= cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= @@ -98,6 +144,10 @@ cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9e cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= cloud.google.com/go/artifactregistry v1.14.1/go.mod h1:nxVdG19jTaSTu7yA7+VbWL346r3rIdkZ142BSQqhn5E= +cloud.google.com/go/artifactregistry v1.14.2/go.mod h1:Xk+QbsKEb0ElmyeMfdHAey41B+qBq3q5R5f5xD4XT3U= +cloud.google.com/go/artifactregistry v1.14.3/go.mod h1:A2/E9GXnsyXl7GUvQ/2CjHA+mVRoWAXC0brg2os+kNI= +cloud.google.com/go/artifactregistry v1.14.4/go.mod h1:SJJcZTMv6ce0LDMUnihCN7WSrI+kBSFV0KIKo8S8aYU= +cloud.google.com/go/artifactregistry v1.14.6/go.mod h1:np9LSFotNWHcjnOgh8UVK0RFPCTUGbO0ve3384xyHfE= cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= @@ -107,6 +157,12 @@ cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrd cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= cloud.google.com/go/asset v1.14.1/go.mod h1:4bEJ3dnHCqWCDbWJ/6Vn7GVI9LerSi7Rfdi03hd+WTQ= +cloud.google.com/go/asset v1.15.0/go.mod h1:tpKafV6mEut3+vN9ScGvCHXHj7FALFVta+okxFECHcg= +cloud.google.com/go/asset v1.15.1/go.mod h1:yX/amTvFWRpp5rcFq6XbCxzKT8RJUam1UoboE179jU4= +cloud.google.com/go/asset v1.15.2/go.mod h1:B6H5tclkXvXz7PD22qCA2TDxSVQfasa3iDlM89O2NXs= +cloud.google.com/go/asset v1.15.3/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.16.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= @@ -114,23 +170,45 @@ cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEar cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= cloud.google.com/go/assuredworkloads v1.11.1/go.mod h1:+F04I52Pgn5nmPG36CWFtxmav6+7Q+c5QyJoL18Lry0= +cloud.google.com/go/assuredworkloads v1.11.2/go.mod h1:O1dfr+oZJMlE6mw0Bp0P1KZSlj5SghMBvTpZqIcUAW4= +cloud.google.com/go/assuredworkloads v1.11.3/go.mod h1:vEjfTKYyRUaIeA0bsGJceFV2JKpVRgyG2op3jfa59Zs= +cloud.google.com/go/assuredworkloads v1.11.4/go.mod h1:4pwwGNwy1RP0m+y12ef3Q/8PaiWrIDQ6nD2E8kvWI9U= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= cloud.google.com/go/automl v1.13.1/go.mod h1:1aowgAHWYZU27MybSCFiukPO7xnyawv7pt3zK4bheQE= +cloud.google.com/go/automl v1.13.2/go.mod h1:gNY/fUmDEN40sP8amAX3MaXkxcqPIn7F1UIIPZpy4Mg= +cloud.google.com/go/automl v1.13.3/go.mod h1:Y8KwvyAZFOsMAPqUCfNu1AyclbC6ivCUF/MTwORymyY= +cloud.google.com/go/automl v1.13.4/go.mod h1:ULqwX/OLZ4hBVfKQaMtxMSTlPx0GqGbWN8uA/1EqCP8= cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/baremetalsolution v1.1.1/go.mod h1:D1AV6xwOksJMV4OSlWHtWuFNZZYujJknMAP4Qa27QIA= +cloud.google.com/go/baremetalsolution v1.2.0/go.mod h1:68wi9AwPYkEWIUT4SvSGS9UJwKzNpshjHsH4lzk8iOw= +cloud.google.com/go/baremetalsolution v1.2.1/go.mod h1:3qKpKIw12RPXStwQXcbhfxVj1dqQGEvcmA+SX/mUR88= +cloud.google.com/go/baremetalsolution v1.2.2/go.mod h1:O5V6Uu1vzVelYahKfwEWRMaS3AbCkeYHy3145s1FkhM= +cloud.google.com/go/baremetalsolution v1.2.3/go.mod h1:/UAQ5xG3faDdy180rCUv47e0jvpp3BFxT+Cl0PFjw5g= cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/batch v1.3.1/go.mod h1:VguXeQKXIYaeeIYbuozUmBR13AfL4SJP7IltNPS+A4A= +cloud.google.com/go/batch v1.4.1/go.mod h1:KdBmDD61K0ovcxoRHGrN6GmOBWeAOyCgKD0Mugx4Fkk= +cloud.google.com/go/batch v1.5.0/go.mod h1:KdBmDD61K0ovcxoRHGrN6GmOBWeAOyCgKD0Mugx4Fkk= +cloud.google.com/go/batch v1.5.1/go.mod h1:RpBuIYLkQu8+CWDk3dFD/t/jOCGuUpkpX+Y0n1Xccs8= +cloud.google.com/go/batch v1.6.1/go.mod h1:urdpD13zPe6YOK+6iZs/8/x2VBRofvblLpx0t57vM98= +cloud.google.com/go/batch v1.6.3/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= +cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= cloud.google.com/go/beyondcorp v0.6.1/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= +cloud.google.com/go/beyondcorp v1.0.0/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= +cloud.google.com/go/beyondcorp v1.0.1/go.mod h1:zl/rWWAFVeV+kx+X2Javly7o1EIQThU4WlkynffL/lk= +cloud.google.com/go/beyondcorp v1.0.2/go.mod h1:m8cpG7caD+5su+1eZr+TSvF6r21NdLJk4f9u4SP2Ntc= +cloud.google.com/go/beyondcorp v1.0.3/go.mod h1:HcBvnEd7eYr+HGDd5ZbuVmBYX019C6CEXBonXbCVwJo= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -145,6 +223,11 @@ cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= cloud.google.com/go/bigquery v1.52.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= +cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= +cloud.google.com/go/bigquery v1.55.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= +cloud.google.com/go/bigquery v1.56.0/go.mod h1:KDcsploXTEY7XT3fDQzMUZlpQLHzE4itubHrnmhUrZA= +cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug= +cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -152,31 +235,60 @@ cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOA cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= cloud.google.com/go/billing v1.16.0/go.mod h1:y8vx09JSSJG02k5QxbycNRrN7FGZB6F3CAcgum7jvGA= +cloud.google.com/go/billing v1.17.0/go.mod h1:Z9+vZXEq+HwH7bhJkyI4OQcR6TSbeMrjlpEjO2vzY64= +cloud.google.com/go/billing v1.17.1/go.mod h1:Z9+vZXEq+HwH7bhJkyI4OQcR6TSbeMrjlpEjO2vzY64= +cloud.google.com/go/billing v1.17.2/go.mod h1:u/AdV/3wr3xoRBk5xvUzYMS1IawOAPwQMuHgHMdljDg= +cloud.google.com/go/billing v1.17.3/go.mod h1:z83AkoZ7mZwBGT3yTnt6rSGI1OOsHSIi6a5M3mJ8NaU= +cloud.google.com/go/billing v1.17.4/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= +cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= cloud.google.com/go/binaryauthorization v1.6.1/go.mod h1:TKt4pa8xhowwffiBmbrbcxijJRZED4zrqnwZ1lKH51U= +cloud.google.com/go/binaryauthorization v1.7.0/go.mod h1:Zn+S6QqTMn6odcMU1zDZCJxPjU2tZPV1oDl45lWY154= +cloud.google.com/go/binaryauthorization v1.7.1/go.mod h1:GTAyfRWYgcbsP3NJogpV3yeunbUIjx2T9xVeYovtURE= +cloud.google.com/go/binaryauthorization v1.7.2/go.mod h1:kFK5fQtxEp97m92ziy+hbu+uKocka1qRRL8MVJIgjv0= +cloud.google.com/go/binaryauthorization v1.7.3/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= +cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= cloud.google.com/go/certificatemanager v1.7.1/go.mod h1:iW8J3nG6SaRYImIa+wXQ0g8IgoofDFRp5UMzaNk1UqI= +cloud.google.com/go/certificatemanager v1.7.2/go.mod h1:15SYTDQMd00kdoW0+XY5d9e+JbOPjp24AvF48D8BbcQ= +cloud.google.com/go/certificatemanager v1.7.3/go.mod h1:T/sZYuC30PTag0TLo28VedIRIj1KPGcOQzjWAptHa00= +cloud.google.com/go/certificatemanager v1.7.4/go.mod h1:FHAylPe/6IIKuaRmHbjbdLhGhVQ+CWHSD5Jq0k4+cCE= cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= cloud.google.com/go/channel v1.16.0/go.mod h1:eN/q1PFSl5gyu0dYdmxNXscY/4Fi7ABmeHCJNf/oHmc= +cloud.google.com/go/channel v1.17.0/go.mod h1:RpbhJsGi/lXWAUM1eF4IbQGbsfVlg2o8Iiy2/YLfVT0= +cloud.google.com/go/channel v1.17.1/go.mod h1:xqfzcOZAcP4b/hUDH0GkGg1Sd5to6di1HOJn/pi5uBQ= +cloud.google.com/go/channel v1.17.2/go.mod h1:aT2LhnftnyfQceFql5I/mP8mIbiiJS4lWqgXA815zMk= +cloud.google.com/go/channel v1.17.3/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= +cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= cloud.google.com/go/cloudbuild v1.10.1/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= +cloud.google.com/go/cloudbuild v1.13.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= +cloud.google.com/go/cloudbuild v1.14.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= +cloud.google.com/go/cloudbuild v1.14.1/go.mod h1:K7wGc/3zfvmYWOWwYTgF/d/UVJhS4pu+HAy7PL7mCsU= +cloud.google.com/go/cloudbuild v1.14.2/go.mod h1:Bn6RO0mBYk8Vlrt+8NLrru7WXlQ9/RDWz2uo5KG1/sg= +cloud.google.com/go/cloudbuild v1.14.3/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= +cloud.google.com/go/cloudbuild v1.15.0/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= cloud.google.com/go/clouddms v1.6.1/go.mod h1:Ygo1vL52Ov4TBZQquhz5fiw2CQ58gvu+PlS6PVXCpZI= +cloud.google.com/go/clouddms v1.7.0/go.mod h1:MW1dC6SOtI/tPNCciTsXtsGNEM0i0OccykPvv3hiYeM= +cloud.google.com/go/clouddms v1.7.1/go.mod h1:o4SR8U95+P7gZ/TX+YbJxehOCsM+fe6/brlrFquiszk= +cloud.google.com/go/clouddms v1.7.2/go.mod h1:Rk32TmWmHo64XqDvW7jgkFQet1tUKNVzs7oajtJT3jU= +cloud.google.com/go/clouddms v1.7.3/go.mod h1:fkN2HQQNUYInAU3NQ3vRLkV2iWs8lIdmBKOx4nrL6Hc= cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= @@ -184,6 +296,10 @@ cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQky cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= cloud.google.com/go/cloudtasks v1.11.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8oHwpmFsKspEvM= +cloud.google.com/go/cloudtasks v1.12.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8oHwpmFsKspEvM= +cloud.google.com/go/cloudtasks v1.12.2/go.mod h1:A7nYkjNlW2gUoROg1kvJrQGhJP/38UaWwsnuBDOBVUk= +cloud.google.com/go/cloudtasks v1.12.3/go.mod h1:GPVXhIOSGEaR+3xT4Fp72ScI+HjHffSS4B8+BaBB5Ys= +cloud.google.com/go/cloudtasks v1.12.4/go.mod h1:BEPu0Gtt2dU6FxZHNqqNdGqIG86qyWKBPGnsb7udGY0= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= @@ -202,28 +318,49 @@ cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IK cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute v1.23.2 h1:nWEMDhgbBkBJjfpVySqU4jgWdc22PLR0o4vEexZHers= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= cloud.google.com/go/contactcenterinsights v1.9.1/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= +cloud.google.com/go/contactcenterinsights v1.10.0/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= +cloud.google.com/go/contactcenterinsights v1.11.0/go.mod h1:hutBdImE4XNZ1NV4vbPJKSFOnQruhC5Lj9bZqWMTKiU= +cloud.google.com/go/contactcenterinsights v1.11.1/go.mod h1:FeNP3Kg8iteKM80lMwSk3zZZKVxr+PGnAId6soKuXwE= +cloud.google.com/go/contactcenterinsights v1.11.2/go.mod h1:A9PIR5ov5cRcd28KlDbmmXE8Aay+Gccer2h4wzkYFso= +cloud.google.com/go/contactcenterinsights v1.11.3/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.0/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= cloud.google.com/go/container v1.22.1/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq6NURZ83eVH4= +cloud.google.com/go/container v1.24.0/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq6NURZ83eVH4= +cloud.google.com/go/container v1.26.0/go.mod h1:YJCmRet6+6jnYYRS000T6k0D0xUXQgBSaJ7VwI8FBj4= +cloud.google.com/go/container v1.26.1/go.mod h1:5smONjPRUxeEpDG7bMKWfDL4sauswqEtnBK1/KKpR04= +cloud.google.com/go/container v1.26.2/go.mod h1:YlO84xCt5xupVbLaMY4s3XNE79MUJ+49VmkInr6HvF4= +cloud.google.com/go/container v1.27.1/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.28.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= cloud.google.com/go/containeranalysis v0.10.1/go.mod h1:Ya2jiILITMY68ZLPaogjmOMNkwsDrWBSTyBubGXO7j0= +cloud.google.com/go/containeranalysis v0.11.0/go.mod h1:4n2e99ZwpGxpNcz+YsFT1dfOHPQFGcAC8FN2M2/ne/U= +cloud.google.com/go/containeranalysis v0.11.1/go.mod h1:rYlUOM7nem1OJMKwE1SadufX0JP3wnXj844EtZAwWLY= +cloud.google.com/go/containeranalysis v0.11.2/go.mod h1:xibioGBC1MD2j4reTyV1xY1/MvKaz+fyM9ENWhmIeP8= +cloud.google.com/go/containeranalysis v0.11.3/go.mod h1:kMeST7yWFQMGjiG9K7Eov+fPNQcGhb8mXj/UcTiWw9U= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= @@ -234,42 +371,82 @@ cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3 cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= cloud.google.com/go/datacatalog v1.14.0/go.mod h1:h0PrGtlihoutNMp/uvwhawLQ9+c63Kz65UFqh49Yo+E= cloud.google.com/go/datacatalog v1.14.1/go.mod h1:d2CevwTG4yedZilwe+v3E3ZBDRMobQfSG/a6cCCN5R4= +cloud.google.com/go/datacatalog v1.16.0/go.mod h1:d2CevwTG4yedZilwe+v3E3ZBDRMobQfSG/a6cCCN5R4= +cloud.google.com/go/datacatalog v1.17.1/go.mod h1:nCSYFHgtxh2MiEktWIz71s/X+7ds/UT9kp0PC7waCzE= +cloud.google.com/go/datacatalog v1.18.0/go.mod h1:nCSYFHgtxh2MiEktWIz71s/X+7ds/UT9kp0PC7waCzE= +cloud.google.com/go/datacatalog v1.18.1/go.mod h1:TzAWaz+ON1tkNr4MOcak8EBHX7wIRX/gZKM+yTVsv+A= +cloud.google.com/go/datacatalog v1.18.2/go.mod h1:SPVgWW2WEMuWHA+fHodYjmxPiMqcOiWfhc9OD5msigk= +cloud.google.com/go/datacatalog v1.18.3/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.0/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= cloud.google.com/go/dataflow v0.9.1/go.mod h1:Wp7s32QjYuQDWqJPFFlnBKhkAtiFpMTdg00qGbnIHVw= +cloud.google.com/go/dataflow v0.9.2/go.mod h1:vBfdBZ/ejlTaYIGB3zB4T08UshH70vbtZeMD+urnUSo= +cloud.google.com/go/dataflow v0.9.3/go.mod h1:HI4kMVjcHGTs3jTHW/kv3501YW+eloiJSLxkJa/vqFE= +cloud.google.com/go/dataflow v0.9.4/go.mod h1:4G8vAkHYCSzU8b/kmsoR2lWyHJD85oMJPHMtan40K8w= cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= cloud.google.com/go/dataform v0.8.1/go.mod h1:3BhPSiw8xmppbgzeBbmDvmSWlwouuJkXsXsb8UBih9M= +cloud.google.com/go/dataform v0.8.2/go.mod h1:X9RIqDs6NbGPLR80tnYoPNiO1w0wenKTb8PxxlhTMKM= +cloud.google.com/go/dataform v0.8.3/go.mod h1:8nI/tvv5Fso0drO3pEjtowz58lodx8MVkdV2q0aPlqg= +cloud.google.com/go/dataform v0.9.1/go.mod h1:pWTg+zGQ7i16pyn0bS1ruqIE91SdL2FDMvEYu/8oQxs= cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= cloud.google.com/go/datafusion v1.7.1/go.mod h1:KpoTBbFmoToDExJUso/fcCiguGDk7MEzOWXUsJo0wsI= +cloud.google.com/go/datafusion v1.7.2/go.mod h1:62K2NEC6DRlpNmI43WHMWf9Vg/YvN6QVi8EVwifElI0= +cloud.google.com/go/datafusion v1.7.3/go.mod h1:eoLt1uFXKGBq48jy9LZ+Is8EAVLnmn50lNncLzwYokE= +cloud.google.com/go/datafusion v1.7.4/go.mod h1:BBs78WTOLYkT4GVZIXQCZT3GFpkpDN4aBY4NDX/jVlM= cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= cloud.google.com/go/datalabeling v0.8.1/go.mod h1:XS62LBSVPbYR54GfYQsPXZjTW8UxCK2fkDciSrpRFdY= +cloud.google.com/go/datalabeling v0.8.2/go.mod h1:cyDvGHuJWu9U/cLDA7d8sb9a0tWLEletStu2sTmg3BE= +cloud.google.com/go/datalabeling v0.8.3/go.mod h1:tvPhpGyS/V7lqjmb3V0TaDdGvhzgR1JoW7G2bpi2UTI= +cloud.google.com/go/datalabeling v0.8.4/go.mod h1:Z1z3E6LHtffBGrNUkKwbwbDxTiXEApLzIgmymj8A3S8= cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= cloud.google.com/go/dataplex v1.8.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= +cloud.google.com/go/dataplex v1.9.0/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= +cloud.google.com/go/dataplex v1.9.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= +cloud.google.com/go/dataplex v1.10.1/go.mod h1:1MzmBv8FvjYfc7vDdxhnLFNskikkB+3vl475/XdCDhs= +cloud.google.com/go/dataplex v1.10.2/go.mod h1:xdC8URdTrCrZMW6keY779ZT1cTOfV8KEPNsw+LTRT1Y= +cloud.google.com/go/dataplex v1.11.1/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.11.2/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.13.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataproc/v2 v2.0.1/go.mod h1:7Ez3KRHdFGcfY7GcevBbvozX+zyWGcwLJvvAMwCaoZ4= +cloud.google.com/go/dataproc/v2 v2.2.0/go.mod h1:lZR7AQtwZPvmINx5J87DSOOpTfof9LVZju6/Qo4lmcY= +cloud.google.com/go/dataproc/v2 v2.2.1/go.mod h1:QdAJLaBjh+l4PVlVZcmrmhGccosY/omC1qwfQ61Zv/o= +cloud.google.com/go/dataproc/v2 v2.2.2/go.mod h1:aocQywVmQVF4i8CL740rNI/ZRpsaaC1Wh2++BJ7HEJ4= +cloud.google.com/go/dataproc/v2 v2.2.3/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= +cloud.google.com/go/dataproc/v2 v2.3.0/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= cloud.google.com/go/dataqna v0.8.1/go.mod h1:zxZM0Bl6liMePWsHA8RMGAfmTG34vJMapbHAxQ5+WA8= +cloud.google.com/go/dataqna v0.8.2/go.mod h1:KNEqgx8TTmUipnQsScOoDpq/VlXVptUqVMZnt30WAPs= +cloud.google.com/go/dataqna v0.8.3/go.mod h1:wXNBW2uvc9e7Gl5k8adyAMnLush1KVV6lZUhB+rqNu4= +cloud.google.com/go/dataqna v0.8.4/go.mod h1:mySRKjKg5Lz784P6sCov3p1QD+RZQONRMRjzGNcFd0c= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= cloud.google.com/go/datastore v1.12.0/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= cloud.google.com/go/datastore v1.12.1/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= +cloud.google.com/go/datastore v1.13.0/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= +cloud.google.com/go/datastore v1.14.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= +cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= @@ -277,11 +454,22 @@ cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2 cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= cloud.google.com/go/datastream v1.9.1/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpdZIcZIEl8h43Q= +cloud.google.com/go/datastream v1.10.0/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpdZIcZIEl8h43Q= +cloud.google.com/go/datastream v1.10.1/go.mod h1:7ngSYwnw95YFyTd5tOGBxHlOZiL+OtpjheqU7t2/s/c= +cloud.google.com/go/datastream v1.10.2/go.mod h1:W42TFgKAs/om6x/CdXX5E4oiAsKlH+e8MTGy81zdYt0= +cloud.google.com/go/datastream v1.10.3/go.mod h1:YR0USzgjhqA/Id0Ycu1VvZe8hEWwrkjuXrGbzeDOSEA= cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= cloud.google.com/go/deploy v1.11.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCNCeyl0F2g= +cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCNCeyl0F2g= +cloud.google.com/go/deploy v1.13.1/go.mod h1:8jeadyLkH9qu9xgO3hVWw8jVr29N1mnW42gRJT8GY6g= +cloud.google.com/go/deploy v1.14.1/go.mod h1:N8S0b+aIHSEeSr5ORVoC0+/mOPUysVt8ae4QkZYolAw= +cloud.google.com/go/deploy v1.14.2/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.15.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.16.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= @@ -291,10 +479,22 @@ cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHih cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= cloud.google.com/go/dialogflow v1.38.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4hTaNltEuxXn4= +cloud.google.com/go/dialogflow v1.40.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4hTaNltEuxXn4= +cloud.google.com/go/dialogflow v1.43.0/go.mod h1:pDUJdi4elL0MFmt1REMvFkdsUTYSHq+rTCS8wg0S3+M= +cloud.google.com/go/dialogflow v1.44.0/go.mod h1:pDUJdi4elL0MFmt1REMvFkdsUTYSHq+rTCS8wg0S3+M= +cloud.google.com/go/dialogflow v1.44.1/go.mod h1:n/h+/N2ouKOO+rbe/ZnI186xImpqvCVj2DdsWS/0EAk= +cloud.google.com/go/dialogflow v1.44.2/go.mod h1:QzFYndeJhpVPElnFkUXxdlptx0wPnBWLCBT9BvtC3/c= +cloud.google.com/go/dialogflow v1.44.3/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.47.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= cloud.google.com/go/dlp v1.10.1/go.mod h1:IM8BWz1iJd8njcNcG0+Kyd9OPnqnRNkDV8j42VT5KOI= +cloud.google.com/go/dlp v1.10.2/go.mod h1:ZbdKIhcnyhILgccwVDzkwqybthh7+MplGC3kZVZsIOQ= +cloud.google.com/go/dlp v1.10.3/go.mod h1:iUaTc/ln8I+QT6Ai5vmuwfw8fqTk2kaz0FvCwhLCom0= +cloud.google.com/go/dlp v1.11.1/go.mod h1:/PA2EnioBeXTL/0hInwgj0rfsQb3lpE3R8XUJxqUNKI= cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= @@ -302,32 +502,60 @@ cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= cloud.google.com/go/documentai v1.20.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe88I1YTZT+a8E= +cloud.google.com/go/documentai v1.22.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe88I1YTZT+a8E= +cloud.google.com/go/documentai v1.22.1/go.mod h1:LKs22aDHbJv7ufXuPypzRO7rG3ALLJxzdCXDPutw4Qc= +cloud.google.com/go/documentai v1.23.0/go.mod h1:LKs22aDHbJv7ufXuPypzRO7rG3ALLJxzdCXDPutw4Qc= +cloud.google.com/go/documentai v1.23.2/go.mod h1:Q/wcRT+qnuXOpjAkvOV4A+IeQl04q2/ReT7SSbytLSo= +cloud.google.com/go/documentai v1.23.4/go.mod h1:4MYAaEMnADPN1LPN5xboDR5QVB6AgsaxgFdJhitlE2Y= +cloud.google.com/go/documentai v1.23.5/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.6/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= cloud.google.com/go/domains v0.9.1/go.mod h1:aOp1c0MbejQQ2Pjf1iJvnVyT+z6R6s8pX66KaCSDYfE= +cloud.google.com/go/domains v0.9.2/go.mod h1:3YvXGYzZG1Temjbk7EyGCuGGiXHJwVNmwIf+E/cUp5I= +cloud.google.com/go/domains v0.9.3/go.mod h1:29k66YNDLDY9LCFKpGFeh6Nj9r62ZKm5EsUJxAl84KU= +cloud.google.com/go/domains v0.9.4/go.mod h1:27jmJGShuXYdUNjyDG0SodTfT5RwLi7xmH334Gvi3fY= cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= cloud.google.com/go/edgecontainer v1.1.1/go.mod h1:O5bYcS//7MELQZs3+7mabRqoWQhXCzenBu0R8bz2rwk= +cloud.google.com/go/edgecontainer v1.1.2/go.mod h1:wQRjIzqxEs9e9wrtle4hQPSR1Y51kqN75dgF7UllZZ4= +cloud.google.com/go/edgecontainer v1.1.3/go.mod h1:Ll2DtIABzEfaxaVSbwj3QHFaOOovlDFiWVDu349jSsA= +cloud.google.com/go/edgecontainer v1.1.4/go.mod h1:AvFdVuZuVGdgaE5YvlL1faAoa1ndRR/5XhXZvPBHbsE= cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= cloud.google.com/go/essentialcontacts v1.6.2/go.mod h1:T2tB6tX+TRak7i88Fb2N9Ok3PvY3UNbUsMag9/BARh4= +cloud.google.com/go/essentialcontacts v1.6.3/go.mod h1:yiPCD7f2TkP82oJEFXFTou8Jl8L6LBRPeBEkTaO0Ggo= +cloud.google.com/go/essentialcontacts v1.6.4/go.mod h1:iju5Vy3d9tJUg0PYMd1nHhjV7xoCXaOAVabrwLaPBEM= +cloud.google.com/go/essentialcontacts v1.6.5/go.mod h1:jjYbPzw0x+yglXC890l6ECJWdYeZ5dlYACTFL0U/VuM= cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= cloud.google.com/go/eventarc v1.12.1/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8aMx3sRJiAI= +cloud.google.com/go/eventarc v1.13.0/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8aMx3sRJiAI= +cloud.google.com/go/eventarc v1.13.1/go.mod h1:EqBxmGHFrruIara4FUQ3RHlgfCn7yo1HYsu2Hpt/C3Y= +cloud.google.com/go/eventarc v1.13.2/go.mod h1:X9A80ShVu19fb4e5sc/OLV7mpFUKZMwfJFeeWhcIObM= +cloud.google.com/go/eventarc v1.13.3/go.mod h1:RWH10IAZIRcj1s/vClXkBgMHwh59ts7hSWcqD3kaclg= cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/filestore v1.7.1/go.mod h1:y10jsorq40JJnjR/lQ8AfFbbcGlw3g+Dp8oN7i7FjV4= +cloud.google.com/go/filestore v1.7.2/go.mod h1:TYOlyJs25f/omgj+vY7/tIG/E7BX369triSPzE4LdgE= +cloud.google.com/go/filestore v1.7.3/go.mod h1:Qp8WaEERR3cSkxToxFPHh/b8AACkSut+4qlCjAmKTV0= +cloud.google.com/go/filestore v1.7.4/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= +cloud.google.com/go/filestore v1.8.0/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= +cloud.google.com/go/firestore v1.12.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= +cloud.google.com/go/firestore v1.13.0/go.mod h1:QojqqOh8IntInDUSTAh0c8ZsPYAr68Ma8c5DWOy8xb8= +cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= @@ -336,6 +564,9 @@ cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1Yb cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= cloud.google.com/go/functions v1.15.1/go.mod h1:P5yNWUTkyU+LvW/S9O6V+V423VZooALQlqoXdoPz5AE= +cloud.google.com/go/functions v1.15.2/go.mod h1:CHAjtcR6OU4XF2HuiVeriEdELNcnvRZSk1Q8RMqy4lE= +cloud.google.com/go/functions v1.15.3/go.mod h1:r/AMHwBheapkkySEhiZYLDBwVJCdlRwsm4ieJu35/Ug= +cloud.google.com/go/functions v1.15.4/go.mod h1:CAsTc3VlRMVvx+XqXxKqVevguqJpnVip4DdonFsX28I= cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= @@ -345,25 +576,44 @@ cloud.google.com/go/gaming v1.10.1/go.mod h1:XQQvtfP8Rb9Rxnxm5wFVpAp9zCQkJi2bLIb cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkebackup v1.3.0/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6GoBFlWCWBU= +cloud.google.com/go/gkebackup v1.3.1/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6GoBFlWCWBU= +cloud.google.com/go/gkebackup v1.3.2/go.mod h1:OMZbXzEJloyXMC7gqdSB+EOEQ1AKcpGYvO3s1ec5ixk= +cloud.google.com/go/gkebackup v1.3.3/go.mod h1:eMk7/wVV5P22KBakhQnJxWSVftL1p4VBFLpv0kIft7I= +cloud.google.com/go/gkebackup v1.3.4/go.mod h1:gLVlbM8h/nHIs09ns1qx3q3eaXcGSELgNu1DWXYz1HI= cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= cloud.google.com/go/gkeconnect v0.8.1/go.mod h1:KWiK1g9sDLZqhxB2xEuPV8V9NYzrqTUmQR9shJHpOZw= +cloud.google.com/go/gkeconnect v0.8.2/go.mod h1:6nAVhwchBJYgQCXD2pHBFQNiJNyAd/wyxljpaa6ZPrY= +cloud.google.com/go/gkeconnect v0.8.3/go.mod h1:i9GDTrfzBSUZGCe98qSu1B8YB8qfapT57PenIb820Jo= +cloud.google.com/go/gkeconnect v0.8.4/go.mod h1:84hZz4UMlDCKl8ifVW8layK4WHlMAFeq8vbzjU0yJkw= cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= cloud.google.com/go/gkehub v0.14.1/go.mod h1:VEXKIJZ2avzrbd7u+zeMtW00Y8ddk/4V9511C9CQGTY= +cloud.google.com/go/gkehub v0.14.2/go.mod h1:iyjYH23XzAxSdhrbmfoQdePnlMj2EWcvnR+tHdBQsCY= +cloud.google.com/go/gkehub v0.14.3/go.mod h1:jAl6WafkHHW18qgq7kqcrXYzN08hXeK/Va3utN8VKg8= +cloud.google.com/go/gkehub v0.14.4/go.mod h1:Xispfu2MqnnFt8rV/2/3o73SK1snL8s9dYJ9G2oQMfc= cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= cloud.google.com/go/gkemulticloud v0.6.1/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZViRnxWK2DVknXWfw= +cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZViRnxWK2DVknXWfw= +cloud.google.com/go/gkemulticloud v1.0.1/go.mod h1:AcrGoin6VLKT/fwZEYuqvVominLriQBCKmbjtnbMjG8= +cloud.google.com/go/gkemulticloud v1.0.2/go.mod h1:+ee5VXxKb3H1l4LZAcgWB/rvI16VTNTrInWxDjAGsGo= +cloud.google.com/go/gkemulticloud v1.0.3/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= +cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= cloud.google.com/go/gsuiteaddons v1.6.1/go.mod h1:CodrdOqRZcLp5WOwejHWYBjZvfY0kOphkAKpF/3qdZY= +cloud.google.com/go/gsuiteaddons v1.6.2/go.mod h1:K65m9XSgs8hTF3X9nNTPi8IQueljSdYo9F+Mi+s4MyU= +cloud.google.com/go/gsuiteaddons v1.6.3/go.mod h1:sCFJkZoMrLZT3JTb8uJqgKPNshH2tfXeCwTFRebTq48= +cloud.google.com/go/gsuiteaddons v1.6.4/go.mod h1:rxtstw7Fx22uLOXBpsvb9DUbC+fiXs7rF4U29KHM/pE= cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= @@ -376,23 +626,37 @@ cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCta cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8= cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= -cloud.google.com/go/iam v1.1.4 h1:K6n/GZHFTtEoKT5aUG3l9diPi0VduZNQ1PfdnpkkIFk= +cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +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/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= cloud.google.com/go/iap v1.8.1/go.mod h1:sJCbeqg3mvWLqjZNsI6dfAtbbV1DL2Rl7e1mTyXYREQ= +cloud.google.com/go/iap v1.9.0/go.mod h1:01OFxd1R+NFrg78S+hoPV5PxEzv22HXaNqUUlmNHFuY= +cloud.google.com/go/iap v1.9.1/go.mod h1:SIAkY7cGMLohLSdBR25BuIxO+I4fXJiL06IBL7cy/5Q= +cloud.google.com/go/iap v1.9.2/go.mod h1:GwDTOs047PPSnwRD0Us5FKf4WDRcVvHg1q9WVkKBhdI= +cloud.google.com/go/iap v1.9.3/go.mod h1:DTdutSZBqkkOm2HEOTBzhZxh2mwwxshfD/h3yofAiCw= cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= cloud.google.com/go/ids v1.4.1/go.mod h1:np41ed8YMU8zOgv53MMMoCntLTn2lF+SUzlM+O3u/jw= +cloud.google.com/go/ids v1.4.2/go.mod h1:3vw8DX6YddRu9BncxuzMyWn0g8+ooUjI2gslJ7FH3vk= +cloud.google.com/go/ids v1.4.3/go.mod h1:9CXPqI3GedjmkjbMWCUhMZ2P2N7TUMzAkVXYEH2orYU= +cloud.google.com/go/ids v1.4.4/go.mod h1:z+WUc2eEl6S/1aZWzwtVNWoSZslgzPxAboS0lZX0HjI= cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= cloud.google.com/go/iot v1.7.1/go.mod h1:46Mgw7ev1k9KqK1ao0ayW9h0lI+3hxeanz+L1zmbbbk= +cloud.google.com/go/iot v1.7.2/go.mod h1:q+0P5zr1wRFpw7/MOgDXrG/HVA+l+cSwdObffkrpnSg= +cloud.google.com/go/iot v1.7.3/go.mod h1:t8itFchkol4VgNbHnIq9lXoOOtHNR3uAACQMYbN9N4I= +cloud.google.com/go/iot v1.7.4/go.mod h1:3TWqDVvsddYBG++nHSZmluoCAVGr1hAcabbWZNKEZLk= cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= @@ -402,54 +666,97 @@ cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63 cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= cloud.google.com/go/kms v1.11.0/go.mod h1:hwdiYC0xjnWsKQQCQQmIQnS9asjYVSK6jtXm+zFqXLM= cloud.google.com/go/kms v1.12.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= -cloud.google.com/go/kms v1.15.4 h1:gEZzC54ZBI+aeW8/jg9tgz9KR4Aa+WEDPbdGIV3iJ7A= +cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= +cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w= +cloud.google.com/go/kms v1.15.3/go.mod h1:AJdXqHxS2GlPyduM99s9iGqi2nwbviBbhV/hdmt4iOQ= cloud.google.com/go/kms v1.15.4/go.mod h1:L3Sdj6QTHK8dfwK5D1JLsAyELsNMnd3tAIwGS4ltKpc= +cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= +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/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= cloud.google.com/go/language v1.10.1/go.mod h1:CPp94nsdVNiQEt1CNjF5WkTcisLiHPyIbMhvR8H2AW0= +cloud.google.com/go/language v1.11.0/go.mod h1:uDx+pFDdAKTY8ehpWbiXyQdz8tDSYLJbQcXsCkjYyvQ= +cloud.google.com/go/language v1.11.1/go.mod h1:Xyid9MG9WOX3utvDbpX7j3tXDmmDooMyMDqgUVpH17U= +cloud.google.com/go/language v1.12.1/go.mod h1:zQhalE2QlQIxbKIZt54IASBzmZpN/aDASea5zl1l+J4= +cloud.google.com/go/language v1.12.2/go.mod h1:9idWapzr/JKXBBQ4lWqVX/hcadxB194ry20m/bTrhWc= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= cloud.google.com/go/lifesciences v0.9.1/go.mod h1:hACAOd1fFbCGLr/+weUKRAJas82Y4vrL3O5326N//Wc= +cloud.google.com/go/lifesciences v0.9.2/go.mod h1:QHEOO4tDzcSAzeJg7s2qwnLM2ji8IRpQl4p6m5Z9yTA= +cloud.google.com/go/lifesciences v0.9.3/go.mod h1:gNGBOJV80IWZdkd+xz4GQj4mbqaz737SCLHn2aRhQKM= +cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqNj+Nia7hF0Z7JA= cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= +cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= cloud.google.com/go/longrunning v0.4.2/go.mod h1:OHrnaYyLUV6oqwh0xiS7e5sLQhP1m0QU9R+WhGDMgIQ= cloud.google.com/go/longrunning v0.5.0/go.mod h1:0JNuqRShmscVAhIACGtskSAWtqtOoPkwP0YF1oVEchc= cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= +cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= +cloud.google.com/go/longrunning v0.5.3/go.mod h1:y/0ga59EYu58J6SHmmQOvekvND2qODbu8ywBBW7EK7Y= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= cloud.google.com/go/managedidentities v1.6.1/go.mod h1:h/irGhTN2SkZ64F43tfGPMbHnypMbu4RB3yl8YcuEak= +cloud.google.com/go/managedidentities v1.6.2/go.mod h1:5c2VG66eCa0WIq6IylRk3TBW83l161zkFvCj28X7jn8= +cloud.google.com/go/managedidentities v1.6.3/go.mod h1:tewiat9WLyFN0Fi7q1fDD5+0N4VUoL0SCX0OTCthZq4= +cloud.google.com/go/managedidentities v1.6.4/go.mod h1:WgyaECfHmF00t/1Uk8Oun3CQ2PGUtjc3e9Alh79wyiM= cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/maps v1.3.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9vgG/5s= +cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9vgG/5s= +cloud.google.com/go/maps v1.4.1/go.mod h1:BxSa0BnW1g2U2gNdbq5zikLlHUuHW0GFWh7sgML2kIY= +cloud.google.com/go/maps v1.5.1/go.mod h1:NPMZw1LJwQZYCfz4y+EIw+SI+24A4bpdFJqdKVr0lt4= +cloud.google.com/go/maps v1.6.1/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.2/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= cloud.google.com/go/mediatranslation v0.8.1/go.mod h1:L/7hBdEYbYHQJhX2sldtTO5SZZ1C1vkapubj0T2aGig= +cloud.google.com/go/mediatranslation v0.8.2/go.mod h1:c9pUaDRLkgHRx3irYE5ZC8tfXGrMYwNZdmDqKMSfFp8= +cloud.google.com/go/mediatranslation v0.8.3/go.mod h1:F9OnXTy336rteOEywtY7FOqCk+J43o2RF638hkOQl4Y= +cloud.google.com/go/mediatranslation v0.8.4/go.mod h1:9WstgtNVAdN53m6TQa5GjIjLqKQPXe74hwSCxUP6nj4= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= cloud.google.com/go/memcache v1.10.1/go.mod h1:47YRQIarv4I3QS5+hoETgKO40InqzLP6kpNLvyXuyaA= +cloud.google.com/go/memcache v1.10.2/go.mod h1:f9ZzJHLBrmd4BkguIAa/l/Vle6uTHzHokdnzSWOdQ6A= +cloud.google.com/go/memcache v1.10.3/go.mod h1:6z89A41MT2DVAW0P4iIRdu5cmRTsbsFn4cyiIx8gbwo= +cloud.google.com/go/memcache v1.10.4/go.mod h1:v/d8PuC8d1gD6Yn5+I3INzLR01IDn0N4Ym56RgikSI0= cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= cloud.google.com/go/metastore v1.11.1/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/MlYi/z5OAOWRA= +cloud.google.com/go/metastore v1.12.0/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/MlYi/z5OAOWRA= +cloud.google.com/go/metastore v1.13.0/go.mod h1:URDhpG6XLeh5K+Glq0NOt74OfrPKTwS62gEPZzb5SOk= +cloud.google.com/go/metastore v1.13.1/go.mod h1:IbF62JLxuZmhItCppcIfzBBfUFq0DIB9HPDoLgWrVOU= +cloud.google.com/go/metastore v1.13.2/go.mod h1:KS59dD+unBji/kFebVp8XU/quNSyo8b6N6tPGspKszA= +cloud.google.com/go/metastore v1.13.3/go.mod h1:K+wdjXdtkdk7AQg4+sXS8bRrQa9gcOr+foOMF2tqINE= cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= cloud.google.com/go/monitoring v1.15.1/go.mod h1:lADlSAlFdbqQuwwpaImhsJXu1QSdd3ojypXrFSMr2rM= +cloud.google.com/go/monitoring v1.16.0/go.mod h1:Ptp15HgAyM1fNICAojDMoNc/wUmn67mLHQfyqbw+poY= +cloud.google.com/go/monitoring v1.16.1/go.mod h1:6HsxddR+3y9j+o/cMJH6q/KJ/CBTvM/38L/1m7bTRJ4= +cloud.google.com/go/monitoring v1.16.2/go.mod h1:B44KGwi4ZCF8Rk/5n+FWeispDXoKSk9oss2QNlXJBgc= +cloud.google.com/go/monitoring v1.16.3/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= @@ -457,15 +764,27 @@ cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5Mp cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= cloud.google.com/go/networkconnectivity v1.12.1/go.mod h1:PelxSWYM7Sh9/guf8CFhi6vIqf19Ir/sbfZRUwXh92E= +cloud.google.com/go/networkconnectivity v1.13.0/go.mod h1:SAnGPes88pl7QRLUen2HmcBSE9AowVAcdug8c0RSBFk= +cloud.google.com/go/networkconnectivity v1.14.0/go.mod h1:SAnGPes88pl7QRLUen2HmcBSE9AowVAcdug8c0RSBFk= +cloud.google.com/go/networkconnectivity v1.14.1/go.mod h1:LyGPXR742uQcDxZ/wv4EI0Vu5N6NKJ77ZYVnDe69Zug= +cloud.google.com/go/networkconnectivity v1.14.2/go.mod h1:5UFlwIisZylSkGG1AdwK/WZUaoz12PKu6wODwIbFzJo= +cloud.google.com/go/networkconnectivity v1.14.3/go.mod h1:4aoeFdrJpYEXNvrnfyD5kIzs8YtHg945Og4koAjHQek= cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= cloud.google.com/go/networkmanagement v1.8.0/go.mod h1:Ho/BUGmtyEqrttTgWEe7m+8vDdK74ibQc+Be0q7Fof0= +cloud.google.com/go/networkmanagement v1.9.0/go.mod h1:UTUaEU9YwbCAhhz3jEOHr+2/K/MrBk2XxOLS89LQzFw= +cloud.google.com/go/networkmanagement v1.9.1/go.mod h1:CCSYgrQQvW73EJawO2QamemYcOb57LvrDdDU51F0mcI= +cloud.google.com/go/networkmanagement v1.9.2/go.mod h1:iDGvGzAoYRghhp4j2Cji7sF899GnfGQcQRQwgVOWnDw= +cloud.google.com/go/networkmanagement v1.9.3/go.mod h1:y7WMO1bRLaP5h3Obm4tey+NquUvB93Co1oh4wpL+XcU= cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= cloud.google.com/go/networksecurity v0.9.1/go.mod h1:MCMdxOKQ30wsBI1eI659f9kEp4wuuAueoC9AJKSPWZQ= +cloud.google.com/go/networksecurity v0.9.2/go.mod h1:jG0SeAttWzPMUILEHDUvFYdQTl8L/E/KC8iZDj85lEI= +cloud.google.com/go/networksecurity v0.9.3/go.mod h1:l+C0ynM6P+KV9YjOnx+kk5IZqMSLccdBqW6GUoF4p/0= +cloud.google.com/go/networksecurity v0.9.4/go.mod h1:E9CeMZ2zDsNBkr8axKSYm8XyTqNhiCHf1JO/Vb8mD1w= cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= @@ -473,19 +792,34 @@ cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vu cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= cloud.google.com/go/notebooks v1.9.1/go.mod h1:zqG9/gk05JrzgBt4ghLzEepPHNwE5jgPcHZRKhlC1A8= +cloud.google.com/go/notebooks v1.10.0/go.mod h1:SOPYMZnttHxqot0SGSFSkRrwE29eqnKPBJFqgWmiK2k= +cloud.google.com/go/notebooks v1.10.1/go.mod h1:5PdJc2SgAybE76kFQCWrTfJolCOUQXF97e+gteUUA6A= +cloud.google.com/go/notebooks v1.11.1/go.mod h1:V2Zkv8wX9kDCGRJqYoI+bQAaoVeE5kSiz4yYHd2yJwQ= +cloud.google.com/go/notebooks v1.11.2/go.mod h1:z0tlHI/lREXC8BS2mIsUeR3agM1AkgLiS+Isov3SS70= cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= cloud.google.com/go/optimization v1.4.1/go.mod h1:j64vZQP7h9bO49m2rVaTVoNM0vEBEN5eKPUPbZyXOrk= +cloud.google.com/go/optimization v1.5.0/go.mod h1:evo1OvTxeBRBu6ydPlrIRizKY/LJKo/drDMMRKqGEUU= +cloud.google.com/go/optimization v1.5.1/go.mod h1:NC0gnUD5MWVAF7XLdoYVPmYYVth93Q6BUzqAq3ZwtV8= +cloud.google.com/go/optimization v1.6.1/go.mod h1:hH2RYPTTM9e9zOiTaYPTiGPcGdNZVnBSBxjIAJzUkqo= +cloud.google.com/go/optimization v1.6.2/go.mod h1:mWNZ7B9/EyMCcwNl1frUGEuY6CPijSkz88Fz2vwKPOY= cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= cloud.google.com/go/orchestration v1.8.1/go.mod h1:4sluRF3wgbYVRqz7zJ1/EUNc90TTprliq9477fGobD8= +cloud.google.com/go/orchestration v1.8.2/go.mod h1:T1cP+6WyTmh6LSZzeUhvGf0uZVmJyTx7t8z7Vg87+A0= +cloud.google.com/go/orchestration v1.8.3/go.mod h1:xhgWAYqlbYjlz2ftbFghdyqENYW+JXuhBx9KsjMoGHs= +cloud.google.com/go/orchestration v1.8.4/go.mod h1:d0lywZSVYtIoSZXb0iFjv9SaL13PGyVOKDxqGxEf/qI= cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= cloud.google.com/go/orgpolicy v1.11.0/go.mod h1:2RK748+FtVvnfuynxBzdnyu7sygtoZa1za/0ZfpOs1M= cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK755wKhIIUCE= +cloud.google.com/go/orgpolicy v1.11.2/go.mod h1:biRDpNwfyytYnmCRWZWxrKF22Nkz9eNVj9zyaBdpm1o= +cloud.google.com/go/orgpolicy v1.11.3/go.mod h1:oKAtJ/gkMjum5icv2aujkP4CxROxPXsBbYGCDbPO8MM= +cloud.google.com/go/orgpolicy v1.11.4/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= +cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= @@ -493,26 +827,45 @@ cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= cloud.google.com/go/osconfig v1.12.0/go.mod h1:8f/PaYzoS3JMVfdfTubkowZYGmAhUCjjwnjqWI7NVBc= cloud.google.com/go/osconfig v1.12.1/go.mod h1:4CjBxND0gswz2gfYRCUoUzCm9zCABp91EeTtWXyz0tE= +cloud.google.com/go/osconfig v1.12.2/go.mod h1:eh9GPaMZpI6mEJEuhEjUJmaxvQ3gav+fFEJon1Y8Iw0= +cloud.google.com/go/osconfig v1.12.3/go.mod h1:L/fPS8LL6bEYUi1au832WtMnPeQNT94Zo3FwwV1/xGM= +cloud.google.com/go/osconfig v1.12.4/go.mod h1:B1qEwJ/jzqSRslvdOCI8Kdnp0gSng0xW4LOnIebQomA= cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= cloud.google.com/go/oslogin v1.10.1/go.mod h1:x692z7yAue5nE7CsSnoG0aaMbNoRJRXO4sn73R+ZqAs= +cloud.google.com/go/oslogin v1.11.0/go.mod h1:8GMTJs4X2nOAUVJiPGqIWVcDaF0eniEto3xlOxaboXE= +cloud.google.com/go/oslogin v1.11.1/go.mod h1:OhD2icArCVNUxKqtK0mcSmKL7lgr0LVlQz+v9s1ujTg= +cloud.google.com/go/oslogin v1.12.1/go.mod h1:VfwTeFJGbnakxAY236eN8fsnglLiVXndlbcNomY4iZU= +cloud.google.com/go/oslogin v1.12.2/go.mod h1:CQ3V8Jvw4Qo4WRhNPF0o+HAM4DiLuE27Ul9CX9g2QdY= +cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= cloud.google.com/go/phishingprotection v0.8.1/go.mod h1:AxonW7GovcA8qdEk13NfHq9hNx5KPtfxXNeUxTDxB6I= +cloud.google.com/go/phishingprotection v0.8.2/go.mod h1:LhJ91uyVHEYKSKcMGhOa14zMMWfbEdxG032oT6ECbC8= +cloud.google.com/go/phishingprotection v0.8.3/go.mod h1:3B01yO7T2Ra/TMojifn8EoGd4G9jts/6cIO0DgDY9J8= +cloud.google.com/go/phishingprotection v0.8.4/go.mod h1:6b3kNPAc2AQ6jZfFHioZKg9MQNybDg4ixFd4RPZZ2nE= cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= cloud.google.com/go/policytroubleshooter v1.7.1/go.mod h1:0NaT5v3Ag1M7U5r0GfDCpUFkWd9YqpubBWsQlhanRv0= +cloud.google.com/go/policytroubleshooter v1.8.0/go.mod h1:tmn5Ir5EToWe384EuboTcVQT7nTag2+DuH3uHmKd1HU= +cloud.google.com/go/policytroubleshooter v1.9.0/go.mod h1:+E2Lga7TycpeSTj2FsH4oXxTnrbHJGRlKhVZBLGgU64= +cloud.google.com/go/policytroubleshooter v1.9.1/go.mod h1:MYI8i0bCrL8cW+VHN1PoiBTyNZTstCg2WUw2eVC4c4U= +cloud.google.com/go/policytroubleshooter v1.10.1/go.mod h1:5C0rhT3TDZVxAu8813bwmTvd57Phbl8mr9F4ipOsxEs= +cloud.google.com/go/policytroubleshooter v1.10.2/go.mod h1:m4uF3f6LseVEnMV6nknlN2vYGRb+75ylQwJdnOXfnv0= cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= cloud.google.com/go/privatecatalog v0.9.1/go.mod h1:0XlDXW2unJXdf9zFz968Hp35gl/bhF4twwpXZAW50JA= +cloud.google.com/go/privatecatalog v0.9.2/go.mod h1:RMA4ATa8IXfzvjrhhK8J6H4wwcztab+oZph3c6WmtFc= +cloud.google.com/go/privatecatalog v0.9.3/go.mod h1:K5pn2GrVmOPjXz3T26mzwXLcKivfIJ9R5N79AFCF9UE= +cloud.google.com/go/privatecatalog v0.9.4/go.mod h1:SOjm93f+5hp/U3PqMZAHTtBtluqLygrDrVO8X8tYtG0= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -522,8 +875,10 @@ cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhz cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= cloud.google.com/go/pubsub v1.32.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= -cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g= cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= +cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= +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/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= @@ -537,42 +892,73 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91j cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= cloud.google.com/go/recaptchaenterprise/v2 v2.7.2/go.mod h1:kR0KjsJS7Jt1YSyWFkseQ756D45kaYNTlDPPaRAvDBU= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.0/go.mod h1:QuE8EdU9dEnesG8/kG3XuJyNsjEqMlMzg3v3scCJ46c= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.1/go.mod h1:JZYZJOeZjgSSTGP4uz7NlQ4/d1w5hGmksVgM0lbEij0= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.2/go.mod h1:kpaDBOpkwD4G0GVMzG1W6Doy1tFFC97XAV3xy+Rd/pw= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.3/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.4/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= cloud.google.com/go/recommendationengine v0.8.1/go.mod h1:MrZihWwtFYWDzE6Hz5nKcNz3gLizXVIDI/o3G1DLcrE= +cloud.google.com/go/recommendationengine v0.8.2/go.mod h1:QIybYHPK58qir9CV2ix/re/M//Ty10OxjnnhWdaKS1Y= +cloud.google.com/go/recommendationengine v0.8.3/go.mod h1:m3b0RZV02BnODE9FeSvGv1qibFo8g0OnmB/RMwYy4V8= +cloud.google.com/go/recommendationengine v0.8.4/go.mod h1:GEteCf1PATl5v5ZsQ60sTClUE0phbWmo3rQ1Js8louU= cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= cloud.google.com/go/recommender v1.10.1/go.mod h1:XFvrE4Suqn5Cq0Lf+mCP6oBHD/yRMA8XxP5sb7Q7gpA= +cloud.google.com/go/recommender v1.11.0/go.mod h1:kPiRQhPyTJ9kyXPCG6u/dlPLbYfFlkwHNRwdzPVAoII= +cloud.google.com/go/recommender v1.11.1/go.mod h1:sGwFFAyI57v2Hc5LbIj+lTwXipGu9NW015rkaEM5B18= +cloud.google.com/go/recommender v1.11.2/go.mod h1:AeoJuzOvFR/emIcXdVFkspVXVTYpliRCmKNYDnyBv6Y= +cloud.google.com/go/recommender v1.11.3/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= +cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= cloud.google.com/go/redis v1.13.1/go.mod h1:VP7DGLpE91M6bcsDdMuyCm2hIpB6Vp2hI090Mfd1tcg= +cloud.google.com/go/redis v1.13.2/go.mod h1:0Hg7pCMXS9uz02q+LoEVl5dNHUkIQv+C/3L76fandSA= +cloud.google.com/go/redis v1.13.3/go.mod h1:vbUpCKUAZSYzFcWKmICnYgRAhTFg9r+djWqFxDYXi4U= +cloud.google.com/go/redis v1.14.1/go.mod h1:MbmBxN8bEnQI4doZPC1BzADU4HGocHBk2de3SbgOkqs= cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= cloud.google.com/go/resourcemanager v1.9.1/go.mod h1:dVCuosgrh1tINZ/RwBufr8lULmWGOkPS8gL5gqyjdT8= +cloud.google.com/go/resourcemanager v1.9.2/go.mod h1:OujkBg1UZg5lX2yIyMo5Vz9O5hf7XQOSV7WxqxxMtQE= +cloud.google.com/go/resourcemanager v1.9.3/go.mod h1:IqrY+g0ZgLsihcfcmqSe+RKp1hzjXwG904B92AwBz6U= +cloud.google.com/go/resourcemanager v1.9.4/go.mod h1:N1dhP9RFvo3lUfwtfLWVxfUWq8+KUQ+XLlHLH3BoFJ0= cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= cloud.google.com/go/resourcesettings v1.6.1/go.mod h1:M7mk9PIZrC5Fgsu1kZJci6mpgN8o0IUzVx3eJU3y4Jw= +cloud.google.com/go/resourcesettings v1.6.2/go.mod h1:mJIEDd9MobzunWMeniaMp6tzg4I2GvD3TTmPkc8vBXk= +cloud.google.com/go/resourcesettings v1.6.3/go.mod h1:pno5D+7oDYkMWZ5BpPsb4SO0ewg3IXcmmrUZaMJrFic= +cloud.google.com/go/resourcesettings v1.6.4/go.mod h1:pYTTkWdv2lmQcjsthbZLNBP4QW140cs7wqA3DuqErVI= cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= cloud.google.com/go/retail v1.14.1/go.mod h1:y3Wv3Vr2k54dLNIrCzenyKG8g8dhvhncT2NcNjb/6gE= +cloud.google.com/go/retail v1.14.2/go.mod h1:W7rrNRChAEChX336QF7bnMxbsjugcOCPU44i5kbLiL8= +cloud.google.com/go/retail v1.14.3/go.mod h1:Omz2akDHeSlfCq8ArPKiBxlnRpKEBjUH386JYFLUvXo= +cloud.google.com/go/retail v1.14.4/go.mod h1:l/N7cMtY78yRnJqp5JW8emy7MB1nz8E4t2yfOmklYfg= cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/run v1.2.0/go.mod h1:36V1IlDzQ0XxbQjUx6IYbw8H3TJnWvhii963WW3B/bo= +cloud.google.com/go/run v1.3.0/go.mod h1:S/osX/4jIPZGg+ssuqh6GNgg7syixKe3YnprwehzHKU= +cloud.google.com/go/run v1.3.1/go.mod h1:cymddtZOzdwLIAsmS6s+Asl4JoXIDm/K1cpZTxV4Q5s= +cloud.google.com/go/run v1.3.2/go.mod h1:SIhmqArbjdU/D9M6JoHaAqnAMKLFtXaVdNeq04NjnVE= +cloud.google.com/go/run v1.3.3/go.mod h1:WSM5pGyJ7cfYyYbONVQBN4buz42zFqwG67Q3ch07iK4= cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= @@ -580,11 +966,18 @@ cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJe cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= cloud.google.com/go/scheduler v1.10.1/go.mod h1:R63Ldltd47Bs4gnhQkmNDse5w8gBRrhObZ54PxgR2Oo= +cloud.google.com/go/scheduler v1.10.2/go.mod h1:O3jX6HRH5eKCA3FutMw375XHZJudNIKVonSCHv7ropY= +cloud.google.com/go/scheduler v1.10.3/go.mod h1:8ANskEM33+sIbpJ+R4xRfw/jzOG+ZFE8WVLy7/yGvbc= +cloud.google.com/go/scheduler v1.10.4/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= +cloud.google.com/go/scheduler v1.10.5/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= cloud.google.com/go/secretmanager v1.11.1/go.mod h1:znq9JlXgTNdBeQk9TBW/FnR/W4uChEKGeqQWAJ8SXFw= +cloud.google.com/go/secretmanager v1.11.2/go.mod h1:MQm4t3deoSub7+WNwiC4/tRYgDBHJgJPvswqQVB1Vss= +cloud.google.com/go/secretmanager v1.11.3/go.mod h1:0bA2o6FabmShrEy328i67aV+65XoUFFSmVeLBn/51jI= +cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= @@ -593,6 +986,9 @@ cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= cloud.google.com/go/security v1.15.1/go.mod h1:MvTnnbsWnehoizHi09zoiZob0iCHVcL4AUBj76h9fXA= +cloud.google.com/go/security v1.15.2/go.mod h1:2GVE/v1oixIRHDaClVbHuPcZwAqFM28mXuAKCfMgYIg= +cloud.google.com/go/security v1.15.3/go.mod h1:gQ/7Q2JYUZZgOzqKtw9McShH+MjNvtDpL40J1cT+vBs= +cloud.google.com/go/security v1.15.4/go.mod h1:oN7C2uIZKhxCLiAAijKUCuHLZbIt/ghYEo8MqwD/Ty4= cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= @@ -600,6 +996,10 @@ cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZ cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY172h4S8aXyI9/hsQ= +cloud.google.com/go/securitycenter v1.23.1/go.mod h1:w2HV3Mv/yKhbXKwOCu2i8bCuLtNP1IMHuiYQn4HJq5s= +cloud.google.com/go/securitycenter v1.24.1/go.mod h1:3h9IdjjHhVMXdQnmqzVnM7b0wMn/1O/U20eWVpMpZjI= +cloud.google.com/go/securitycenter v1.24.2/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= +cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= @@ -612,6 +1012,10 @@ cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UV cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= cloud.google.com/go/servicedirectory v1.10.1/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= +cloud.google.com/go/servicedirectory v1.11.0/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= +cloud.google.com/go/servicedirectory v1.11.1/go.mod h1:tJywXimEWzNzw9FvtNjsQxxJ3/41jseeILgwU/QLrGI= +cloud.google.com/go/servicedirectory v1.11.2/go.mod h1:KD9hCLhncWRV5jJphwIpugKwM5bn1x0GyVVD4NO8mGg= +cloud.google.com/go/servicedirectory v1.11.3/go.mod h1:LV+cHkomRLr67YoQy3Xq2tUXBGOs5z5bPofdq7qtiAw= cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= @@ -624,10 +1028,20 @@ cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IW cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= cloud.google.com/go/shell v1.7.1/go.mod h1:u1RaM+huXFaTojTbW4g9P5emOrrmLE69KrxqQahKn4g= +cloud.google.com/go/shell v1.7.2/go.mod h1:KqRPKwBV0UyLickMn0+BY1qIyE98kKyI216sH/TuHmc= +cloud.google.com/go/shell v1.7.3/go.mod h1:cTTEz/JdaBsQAeTQ3B6HHldZudFoYBOqjteev07FbIc= +cloud.google.com/go/shell v1.7.4/go.mod h1:yLeXB8eKLxw0dpEmXQ/FjriYrBijNsONpwnWsdPqlKM= cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSqW4uH9lfUI= +cloud.google.com/go/spanner v1.49.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= +cloud.google.com/go/spanner v1.50.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= +cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8BVc3LNahq0= +cloud.google.com/go/spanner v1.53.0/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.53.1/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.54.0/go.mod h1:wZvSQVBgngF0Gq86fKup6KIYmN2be7uOKjtK97X+bQU= +cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= @@ -635,6 +1049,11 @@ cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSy cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/speech v1.17.1/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ14kGlJYo= +cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ14kGlJYo= +cloud.google.com/go/speech v1.19.1/go.mod h1:WcuaWz/3hOlzPFOVo9DUsblMIHwxP589y6ZMtaG+iAA= +cloud.google.com/go/speech v1.19.2/go.mod h1:2OYFfj+Ch5LWjsaSINuCZsre/789zlcCI3SY4oAi2oI= +cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= +cloud.google.com/go/speech v1.21.0/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -646,38 +1065,61 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +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/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= cloud.google.com/go/storagetransfer v1.10.0/go.mod h1:DM4sTlSmGiNczmV6iZyceIh2dbs+7z2Ayg6YAiQlYfA= +cloud.google.com/go/storagetransfer v1.10.1/go.mod h1:rS7Sy0BtPviWYTTJVWCSV4QrbBitgPeuK4/FKa4IdLs= +cloud.google.com/go/storagetransfer v1.10.2/go.mod h1:meIhYQup5rg9juQJdyppnA/WLQCOguxtk1pr3/vBWzA= +cloud.google.com/go/storagetransfer v1.10.3/go.mod h1:Up8LY2p6X68SZ+WToswpQbQHnJpOty/ACcMafuey8gc= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= cloud.google.com/go/talent v1.6.2/go.mod h1:CbGvmKCG61mkdjcqTcLOkb2ZN1SrQI8MDyma2l7VD24= +cloud.google.com/go/talent v1.6.3/go.mod h1:xoDO97Qd4AK43rGjJvyBHMskiEf3KulgYzcH6YWOVoo= +cloud.google.com/go/talent v1.6.4/go.mod h1:QsWvi5eKeh6gG2DlBkpMaFYZYrYUnIpo34f6/V5QykY= +cloud.google.com/go/talent v1.6.5/go.mod h1:Mf5cma696HmE+P2BWJ/ZwYqeJXEeU0UqjHFXVLadEDI= cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= cloud.google.com/go/texttospeech v1.7.1/go.mod h1:m7QfG5IXxeneGqTapXNxv2ItxP/FS0hCZBwXYqucgSk= +cloud.google.com/go/texttospeech v1.7.2/go.mod h1:VYPT6aTOEl3herQjFHYErTlSZJ4vB00Q2ZTmuVgluD4= +cloud.google.com/go/texttospeech v1.7.3/go.mod h1:Av/zpkcgWfXlDLRYob17lqMstGZ3GqlvJXqKMp2u8so= +cloud.google.com/go/texttospeech v1.7.4/go.mod h1:vgv0002WvR4liGuSd5BJbWy4nDn5Ozco0uJymY5+U74= cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= cloud.google.com/go/tpu v1.6.1/go.mod h1:sOdcHVIgDEEOKuqUoi6Fq53MKHJAtOwtz0GuKsWSH3E= +cloud.google.com/go/tpu v1.6.2/go.mod h1:NXh3NDwt71TsPZdtGWgAG5ThDfGd32X1mJ2cMaRlVgU= +cloud.google.com/go/tpu v1.6.3/go.mod h1:lxiueqfVMlSToZY1151IaZqp89ELPSrk+3HIQ5HRkbY= +cloud.google.com/go/tpu v1.6.4/go.mod h1:NAm9q3Rq2wIlGnOhpYICNI7+bpBebMJbh0yyp3aNw1Y= cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= cloud.google.com/go/trace v1.10.1/go.mod h1:gbtL94KE5AJLH3y+WVpfWILmqgc6dXcqgNXdOPAQTYk= +cloud.google.com/go/trace v1.10.2/go.mod h1:NPXemMi6MToRFcSxRl2uDnu/qAlAQ3oULUphcHGh1vA= +cloud.google.com/go/trace v1.10.3/go.mod h1:Ke1bgfc73RV3wUFml+uQp7EsDw4dGaETLxB7Iq/r4CY= +cloud.google.com/go/trace v1.10.4/go.mod h1:Nso99EDIK8Mj5/zmB+iGr9dosS/bzWCJ8wGmE6TXNWY= cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= cloud.google.com/go/translate v1.8.1/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= +cloud.google.com/go/translate v1.8.2/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= +cloud.google.com/go/translate v1.9.0/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= +cloud.google.com/go/translate v1.9.1/go.mod h1:TWIgDZknq2+JD4iRcojgeDtqGEp154HN/uL6hMvylS8= +cloud.google.com/go/translate v1.9.2/go.mod h1:E3Tc6rUTsQkVrXW6avbUhKJSr7ZE3j7zNmqzXKHqRrY= +cloud.google.com/go/translate v1.9.3/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= +cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= @@ -685,12 +1127,20 @@ cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1t cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/video v1.17.1/go.mod h1:9qmqPqw/Ib2tLqaeHgtakU+l5TcJxCJbhFXM7UJjVzU= +cloud.google.com/go/video v1.19.0/go.mod h1:9qmqPqw/Ib2tLqaeHgtakU+l5TcJxCJbhFXM7UJjVzU= +cloud.google.com/go/video v1.20.0/go.mod h1:U3G3FTnsvAGqglq9LxgqzOiBc/Nt8zis8S+850N2DUM= +cloud.google.com/go/video v1.20.1/go.mod h1:3gJS+iDprnj8SY6pe0SwLeC5BUW80NjhwX7INWEuWGU= +cloud.google.com/go/video v1.20.2/go.mod h1:lrixr5JeKNThsgfM9gqtwb6Okuqzfo4VrY2xynaViTA= +cloud.google.com/go/video v1.20.3/go.mod h1:TnH/mNZKVHeNtpamsSPygSR0iHtvrR/cW1/GDjN5+GU= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= cloud.google.com/go/videointelligence v1.11.1/go.mod h1:76xn/8InyQHarjTWsBR058SmlPCwQjgcvoW0aZykOvo= +cloud.google.com/go/videointelligence v1.11.2/go.mod h1:ocfIGYtIVmIcWk1DsSGOoDiXca4vaZQII1C85qtoplc= +cloud.google.com/go/videointelligence v1.11.3/go.mod h1:tf0NUaGTjU1iS2KEkGWvO5hRHeCkFK3nPo0/cOZhZAo= +cloud.google.com/go/videointelligence v1.11.4/go.mod h1:kPBMAYsTPFiQxMLmmjpcZUMklJp3nC9+ipJJtprccD8= cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= @@ -699,35 +1149,60 @@ cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98z cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= cloud.google.com/go/vision/v2 v2.7.2/go.mod h1:jKa8oSYBWhYiXarHPvP4USxYANYUEdEsQrloLjrSwJU= +cloud.google.com/go/vision/v2 v2.7.3/go.mod h1:V0IcLCY7W+hpMKXK1JYE0LV5llEqVmj+UJChjvA1WsM= +cloud.google.com/go/vision/v2 v2.7.4/go.mod h1:ynDKnsDN/0RtqkKxQZ2iatv3Dm9O+HfRb5djl7l4Vvw= +cloud.google.com/go/vision/v2 v2.7.5/go.mod h1:GcviprJLFfK9OLf0z8Gm6lQb6ZFUulvpZws+mm6yPLM= cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= cloud.google.com/go/vmmigration v1.7.1/go.mod h1:WD+5z7a/IpZ5bKK//YmT9E047AD+rjycCAvyMxGJbro= +cloud.google.com/go/vmmigration v1.7.2/go.mod h1:iA2hVj22sm2LLYXGPT1pB63mXHhrH1m/ruux9TwWLd8= +cloud.google.com/go/vmmigration v1.7.3/go.mod h1:ZCQC7cENwmSWlwyTrZcWivchn78YnFniEQYRWQ65tBo= +cloud.google.com/go/vmmigration v1.7.4/go.mod h1:yBXCmiLaB99hEl/G9ZooNx2GyzgsjKnw5fWcINRgD70= cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= cloud.google.com/go/vmwareengine v0.4.1/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkGHXoa7uyfTgSI0= +cloud.google.com/go/vmwareengine v1.0.0/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkGHXoa7uyfTgSI0= +cloud.google.com/go/vmwareengine v1.0.1/go.mod h1:aT3Xsm5sNx0QShk1Jc1B8OddrxAScYLwzVoaiXfdzzk= +cloud.google.com/go/vmwareengine v1.0.2/go.mod h1:xMSNjIk8/itYrz1JA8nV3Ajg4L4n3N+ugP8JKzk3OaA= +cloud.google.com/go/vmwareengine v1.0.3/go.mod h1:QSpdZ1stlbfKtyt6Iu19M6XRxjmXO+vb5a/R6Fvy2y4= cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= cloud.google.com/go/vpcaccess v1.7.1/go.mod h1:FogoD46/ZU+JUBX9D606X21EnxiszYi2tArQwLY4SXs= +cloud.google.com/go/vpcaccess v1.7.2/go.mod h1:mmg/MnRHv+3e8FJUjeSibVFvQF1cCy2MsFaFqxeY1HU= +cloud.google.com/go/vpcaccess v1.7.3/go.mod h1:YX4skyfW3NC8vI3Fk+EegJnlYFatA+dXK4o236EUCUc= +cloud.google.com/go/vpcaccess v1.7.4/go.mod h1:lA0KTvhtEOb/VOdnH/gwPuOzGgM+CWsmGu6bb4IoMKk= cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= cloud.google.com/go/webrisk v1.9.1/go.mod h1:4GCmXKcOa2BZcZPn6DCEvE7HypmEJcJkr4mtM+sqYPc= +cloud.google.com/go/webrisk v1.9.2/go.mod h1:pY9kfDgAqxUpDBOrG4w8deLfhvJmejKB0qd/5uQIPBc= +cloud.google.com/go/webrisk v1.9.3/go.mod h1:RUYXe9X/wBDXhVilss7EDLW9ZNa06aowPuinUOPCXH8= +cloud.google.com/go/webrisk v1.9.4/go.mod h1:w7m4Ib4C+OseSr2GL66m0zMBywdrVNTDKsdEsfMl7X0= cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/websecurityscanner v1.6.1/go.mod h1:Njgaw3rttgRHXzwCB8kgCYqv5/rGpFCsBOvPbYgszpg= +cloud.google.com/go/websecurityscanner v1.6.2/go.mod h1:7YgjuU5tun7Eg2kpKgGnDuEOXWIrh8x8lWrJT4zfmas= +cloud.google.com/go/websecurityscanner v1.6.3/go.mod h1:x9XANObUFR+83Cya3g/B9M/yoHVqzxPnFtgF8yYGAXw= +cloud.google.com/go/websecurityscanner v1.6.4/go.mod h1:mUiyMQ+dGpPPRkHgknIZeCzSHJ45+fY4F52nZFDHm2o= cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= cloud.google.com/go/workflows v1.11.1/go.mod h1:Z+t10G1wF7h8LgdY/EmRcQY8ptBD/nvofaL6FqlET6g= +cloud.google.com/go/workflows v1.12.0/go.mod h1:PYhSk2b6DhZ508tj8HXKaBh+OFe+xdl0dHF/tJdzPQM= +cloud.google.com/go/workflows v1.12.1/go.mod h1:5A95OhD/edtOhQd/O741NSfIMezNTbCwLM1P1tBRGHM= +cloud.google.com/go/workflows v1.12.2/go.mod h1:+OmBIgNqYJPVggnMo9nqmizW0qEXHhmnAzK/CnBqsHc= +cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= @@ -736,13 +1211,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -754,17 +1228,19 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= -github.com/aristanetworks/arista-ceoslab-operator/v2 v2.0.2 h1:KQL1evr4NM4ZQOLRs1bbmD0kYPmLRAMqvRrNSpYAph4= -github.com/aristanetworks/arista-ceoslab-operator/v2 v2.0.2/go.mod h1:/mvSt2fEmlVEU7dppip3UNz/MUt380f50dFsZRGn83o= +github.com/aristanetworks/arista-ceoslab-operator/v2 v2.1.2 h1:1aAxwwu4xyfiU1/FX2D5x/jsF/sxFVkjVhvF661isM4= +github.com/aristanetworks/arista-ceoslab-operator/v2 v2.1.2/go.mod h1:/mvSt2fEmlVEU7dppip3UNz/MUt380f50dFsZRGn83o= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= 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.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= @@ -773,21 +1249,22 @@ github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8 github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cisco-open/go-p4 v0.1.1 h1:HkgALtPr8nCp8TlRVfN002AzmG/tHo+qHkCv8OnVig4= -github.com/cisco-open/go-p4 v0.1.1/go.mod h1:iZTF7o84nki1abNEhzdEAExVnjEC3Y/+8V9h7czjE2Q= +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.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -803,10 +1280,15 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -814,15 +1296,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/drivenets/cdnos-controller v1.7.4 h1:UI6aJGfu1jny9sR1tC1+TNWVA+fuzsaedMyikqECrL4= +github.com/drivenets/cdnos-controller v1.7.4/go.mod h1:s+rGGwx3UZ8TECpeC3htXJv+3sF+VK7fFiYgesxD0vA= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE= -github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -840,6 +1323,9 @@ github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJ github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc= +github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= @@ -847,15 +1333,19 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -869,22 +1359,26 @@ github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2H github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= -github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +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.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -896,6 +1390,8 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= 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/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -903,8 +1399,10 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -936,8 +1434,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -964,6 +1463,8 @@ 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-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-pkcs11 v0.2.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -992,6 +1493,7 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +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/protobuf v3.11.4+incompatible/go.mod h1:lUQ9D1ePzbH2PrIS7ob/bjm9HXyH5WHB0Akwh7URreM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -1003,15 +1505,20 @@ github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8 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.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0/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.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +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.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -1026,18 +1533,20 @@ github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +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/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +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.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +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.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -1045,6 +1554,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ 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/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= @@ -1056,6 +1566,8 @@ 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 v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -1067,8 +1579,8 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/k-sone/critbitgo v1.4.0 h1:l71cTyBGeh6X5ATh6Fibgw3+rtNT80BA0uNNWgkPrbE= github.com/k-sone/critbitgo v1.4.0/go.mod h1:7E6pyoyADnFxlUBEKcnfS49b7SUAQGMK+OAp/UQvo0s= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kentik/patricia v1.2.0 h1:WZcp8V8GQhsya0bMZuXktEH/Wz+aBlhiMle4tExkj6M= -github.com/kentik/patricia v1.2.0/go.mod h1:6jY40ESetsbfi04/S12iJlsiS6DYL2B2W+WAcqoDHtw= +github.com/kentik/patricia v1.2.1 h1:+ZyPXnEiFLbmT1yZR0JRfRUuNXmxROXdzI8YiSpTx5w= +github.com/kentik/patricia v1.2.1/go.mod h1:6jY40ESetsbfi04/S12iJlsiS6DYL2B2W+WAcqoDHtw= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -1078,7 +1590,6 @@ github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHU github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -1099,8 +1610,6 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/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/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -1108,6 +1617,12 @@ github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4 github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 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/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -1121,68 +1636,78 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/networkop/meshnet-cni v0.3.1-0.20230525201116-d7c306c635cf h1:9Pe9L0QCovb9o82inAVQitCo3IRnG9u45lRRm8QvgbU= github.com/networkop/meshnet-cni v0.3.1-0.20230525201116-d7c306c635cf/go.mod h1:VMkJl7N6e14GTWS0AnCDrnvJOT67hwOFVUcxTzt/EtE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= -github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/open-traffic-generator/ixia-c-operator v0.3.6 h1:dablUs6FAToVDFaoIo2M+Z9UCa93KAwlj7HJqNwLwTQ= -github.com/open-traffic-generator/ixia-c-operator v0.3.6/go.mod h1:Q+ZXCinXxUKcnrJf5PJC1Q7JxUQc5ZPZA85jwVAqIRQ= -github.com/open-traffic-generator/snappi/gosnappi v0.13.0 h1:RdlbT+CIlVum6xbhiFr/IzTvQee5bMa3V4oBWa79UBw= -github.com/open-traffic-generator/snappi/gosnappi v0.13.0/go.mod h1:QjB939WFJqUq6V7RQqkY/LFCgRRzKrybHHFp7F7xdWA= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +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.3.0 h1:6SFSuZLTuncLW1xMcBG5HEvVCWh9wVuxiYb71C3yj7s= +github.com/open-traffic-generator/snappi/gosnappi v1.3.0/go.mod h1:CaE4nisXftNXdXWvTSqb4eiW2WMFIXkJsH5rqPoipcg= +github.com/openconfig/attestz v0.2.0 h1:VuksFIG1OlGnRuUpdTFAkMyAY59ITvyLbp4AtiTXV64= +github.com/openconfig/attestz v0.2.0/go.mod h1:byY6H68zm3VXmQHEb4O4OZtRtFyHEjkmzrvIljYc79Y= +github.com/openconfig/containerz v0.0.0-20240620162940-e0bf23af17d6 h1:4SPV//llewH/1v5l3+ogzUubksBGSeI+hHLXTAw2T1A= +github.com/openconfig/containerz v0.0.0-20240620162940-e0bf23af17d6/go.mod h1:Byu9uT5Yyz8XEKv9eUBcWqAoUADVvfmN8m+BGqTQPoc= github.com/openconfig/entity-naming v0.0.0-20230912181021-7ac806551a31 h1:K/9O+J20+liIof8WjquMydnebD0N1U9ItjhJYF6H4hg= github.com/openconfig/entity-naming v0.0.0-20230912181021-7ac806551a31/go.mod h1:ZRUrfwYYY+pLaOoWPad3p/8J4LLQcSqtXhBCkD2pXJc= github.com/openconfig/gnmi v0.0.0-20200414194230-1597cc0f2600/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A= github.com/openconfig/gnmi v0.0.0-20200508230933-d19cebf5e7be/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A= -github.com/openconfig/gnmi v0.10.0 h1:kQEZ/9ek3Vp2Y5IVuV2L/ba8/77TgjdXg505QXvYmg8= github.com/openconfig/gnmi v0.10.0/go.mod h1:Y9os75GmSkhHw2wX8sMsxfI7qRGAEcDh8NTa5a8vj6E= -github.com/openconfig/gnoi v0.3.0 h1:ieThHVx5rRwAt6lqKOKzoA3pcr5FE5Xs40GJ7wNqshs= -github.com/openconfig/gnoi v0.3.0/go.mod h1:bv+Cln0d052XT0KnHKAe3MekHKpSl2z5g/TJCD8gbkM= -github.com/openconfig/gnoigo v0.0.0-20231026010722-87413fdb22e7 h1:Jy48edwfOFFrJlysB/0itfEOE7hgxFT1Dm/qFasxIrg= -github.com/openconfig/gnoigo v0.0.0-20231026010722-87413fdb22e7/go.mod h1:P3ST0D9yq2XYeKh5cvkEFqcVAdLuyAxN6k2Zh7zADj4= -github.com/openconfig/gnsi v1.2.3 h1:Y/fBMQOn5xqdo9xuT7AK2YHSRejx/ws4sDOMBCHQG6w= -github.com/openconfig/gnsi v1.2.3/go.mod h1:QikTHkm468uc2rq/kVhETfyZ6FPeM+zitubrHBbB0HE= +github.com/openconfig/gnmi v0.11.0 h1:H7pLIb/o3xObu3+x0Fv9DCK7TH3FUh7mNwbYe+34hFw= +github.com/openconfig/gnmi v0.11.0/go.mod h1:9oJSQPPCpNvfMRj8e4ZoLVAw4wL8HyxXbiDlyuexCGU= +github.com/openconfig/gnoi v0.4.1 h1:sONbBqRBKjPT6voRAFqhTkUIatAajBp+YRLCWgyS4Dk= +github.com/openconfig/gnoi v0.4.1/go.mod h1:KDWxp9YvfRNR5BbiLy6uQSzHUpGhAtO8C80XXKLqNqU= +github.com/openconfig/gnoigo v0.0.0-20240320202954-ebd033e3542c h1:egPgBUBDn0XEtbz0CvE+Bh/I/3iTzwzMq5/rmtPJdQs= +github.com/openconfig/gnoigo v0.0.0-20240320202954-ebd033e3542c/go.mod h1:Se/HklUcFVcCGB66khgYouiesLRPoa4UL1ovvmE/68k= +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.6.0 h1:PfQa9Gy0lH1sHqA2L3Gj2fEh2zPMbWxMmIRQv2Nk1T8= +github.com/openconfig/gnsi v1.6.0/go.mod h1:RiHTEIb2ruIeWOOamms6vqbZtgmajDx+g5YJlF2hZ0k= 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 v0.2.2/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8= github.com/openconfig/goyang v0.2.9/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8= -github.com/openconfig/goyang v1.4.4 h1:ee0bbD60eQIjqrzH5S8yjneA6kA36UdxitokeL3+SGg= -github.com/openconfig/goyang v1.4.4/go.mod h1:sdNZi/wdTZyLNBNfgLzmmbi7kISm7FskMDKKzMY+x1M= +github.com/openconfig/goyang v1.4.5 h1:+s3p3MeiPQ/QNsC5DL3MXhCp5cv4dag3vlGKCtszsRU= +github.com/openconfig/goyang v1.4.5/go.mod h1:sdNZi/wdTZyLNBNfgLzmmbi7kISm7FskMDKKzMY+x1M= github.com/openconfig/gribi v0.1.1-0.20210423184541-ce37eb4ba92f/go.mod h1:OoH46A2kV42cIXGyviYmAlGmn6cHjGduyC2+I9d/iVs= github.com/openconfig/gribi v1.0.0 h1:xMwEg0mBD+21mOxuFOw0d9dBKuIPwJEhMUUeUulZdLg= github.com/openconfig/gribi v1.0.0/go.mod h1:VFqGH2ZPFIfnKTimP4/AQB4OK0eySW5muJNFxXAwP6k= -github.com/openconfig/gribigo v0.0.0-20231031140438-9a293da13ff9 h1:AXdc2tqHWnYr4GlzYcs50wpQ7NLv6YWckHlgOHZ2qgo= -github.com/openconfig/gribigo v0.0.0-20231031140438-9a293da13ff9/go.mod h1:Fue9aTSqtMM9AxH9p3wOEAPGec5uBRurxLmtcjTHhWg= +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 h1:t6SvvdfWCMlw0XPlsdxO8EgO+q/fXnTevDjdYREKFwU= github.com/openconfig/grpctunnel v0.0.0-20220819142823-6f5422b8ca70/go.mod h1:OmTWe7RyZj2CIzIgy4ovEBzCLBJzRvWSZmn7u02U9gU= -github.com/openconfig/kne v0.1.14 h1:3xHy2bP+rr+2/2uFqliWXGjMPR7umO6mvFXh/TA2aJE= -github.com/openconfig/kne v0.1.14/go.mod h1:gMhrUKk6aveDaLSo2yi/25tDm9pSlmgRkG8IP45CGqs= -github.com/openconfig/lemming v0.3.2-0.20230914210403-c6484d12af0a h1:JNiu6/3IWtESSq6N+dH65MYaeiDi5CF1Jck5YGvf3JE= -github.com/openconfig/lemming v0.3.2-0.20230914210403-c6484d12af0a/go.mod h1:fC8o1NYR9yEmDmoIVaCZQY+iP9RSxujYzckUGSkpWD8= +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/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.4.3 h1:M6moqZRuA87aXNbHkZgj/U+Jj4WOjzgTC/EM1ARqt+E= -github.com/openconfig/ondatra v0.4.3/go.mod h1:WEZ0twDTtctPYDJ6D0zeyKwdXfEHRpKV7At0xPHMrBs= -github.com/openconfig/replayer v0.0.0-20231031192218-5462382820d4 h1:ohE/OcV38lEOt7IHOtFzjYVzzoCgMYp3sqJBKmVwuw0= -github.com/openconfig/replayer v0.0.0-20231031192218-5462382820d4/go.mod h1:qWxkJVt7w/SWhrTyxwAWGHUG1nT2hqDRtL74u5SuMh0= +github.com/openconfig/ondatra v0.6.1 h1:/N3mm4iJX3G8HcASu+qAKcJc/lJqKEaD8MFd6aRVWqc= +github.com/openconfig/ondatra v0.6.1/go.mod h1:ol5PMSLtZJEYPrTwTFpRCyQvAFeA5vQUTAEYFiAlXz0= +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.10.0 h1:KZGbdfC8ErNL3nTj9mzmizbc0QlsZUAK60/UxyShv8M= -github.com/openconfig/ygnmi v0.10.0/go.mod h1:JKQ8HVkxH27Q8hHQHrb5uRj2uoyZp2CmJ6JbivXtD3g= +github.com/openconfig/ygnmi v0.11.1 h1:tIHlAinvOX+8jA2YTk4ACb7IOQe1PXsjT41Ci7E2KUk= +github.com/openconfig/ygnmi v0.11.1/go.mod h1:naCxQR+/wBItM82ilJXWgapCRkrx8bphBmUHXJmRhuQ= github.com/openconfig/ygot v0.6.0/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs= github.com/openconfig/ygot v0.10.4/go.mod h1:oCQNdXnv7dWc8scTDgoFkauv1wwplJn5HspHcjlxSAQ= github.com/openconfig/ygot v0.13.2/go.mod h1:kJN0yCXIH07dOXvNBEFm3XxXdnDD5NI6K99tnD5x49c= -github.com/openconfig/ygot v0.29.15 h1:8lmf+f8KzQjr6Q8JwdlCVgHBTcjeyg+D8Z8bBOT4FnA= -github.com/openconfig/ygot v0.29.15/go.mod h1:GDi97RDhYa2MGyYQXSk/RbSV3OgtuOCXBPfeOKD5pQQ= +github.com/openconfig/ygot v0.29.19 h1:3bbAWbCBVjyjHgeROvT38LQ7pAxcjtm7C2vNVj/rvEE= +github.com/openconfig/ygot v0.29.19/go.mod h1:8/FXt4tc5wSfYDEJbGGumxmxwJ55Xuv12oO/jCyEins= 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= github.com/p4lang/p4runtime v1.4.0-rc.5.0.20220728214547-13f0d02a521e h1:AfZKoikDXbZ7zWvO/lvCRzLo7i6lM+gNleYVMxPiWyQ= github.com/p4lang/p4runtime v1.4.0-rc.5.0.20220728214547-13f0d02a521e/go.mod h1:m9laObIMXM9N1ElGXijc66/MSM5eheZJLRLxg/TG+fU= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -1191,8 +1716,8 @@ github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwp 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.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -1205,6 +1730,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1213,8 +1740,10 @@ github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQg github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= @@ -1224,16 +1753,16 @@ github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a/go.mod h1 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= -github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= -github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +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/scrapli/scrapligo v1.0.0/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= @@ -1248,28 +1777,29 @@ github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4 h1:FHUL2HofY github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4/go.mod h1:CJYqpTg9u5VPCoD0VEl9E68prCIiWQD8m457k098DdQ= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= -github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +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.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +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.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= -github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= -github.com/srl-labs/srl-controller v0.6.0 h1:tavIPfNRcqAdqc7cvduMCCMk9JoJbqedn6LXny856tU= -github.com/srl-labs/srl-controller v0.6.0/go.mod h1:PedxdPZPtDcC+wDOKhG6uXR4xgkHxb4JhW1cXNk/eaY= +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/srl-labs/srl-controller v0.6.1 h1:hHduqG41wglpeVPD85RALTwWWcS+NqvU8V1pHJMQIZo= +github.com/srl-labs/srl-controller v0.6.1/go.mod h1:PedxdPZPtDcC+wDOKhG6uXR4xgkHxb4JhW1cXNk/eaY= github.com/srl-labs/srlinux-scrapli v0.6.0 h1:YQjckD+a7f6u2M+k4SmJUrDa7BFvoOTb2mMbPe6hLZM= github.com/srl-labs/srlinux-scrapli v0.6.0/go.mod h1:8hCoel3XaSyZD8hxSs8Pij/uZqaccd57mfeHgc0oJhM= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -1277,6 +1807,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1288,30 +1819,34 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/wenovus/gobgp/v3 v3.0.0-20230831013712-6d33842cbf42 h1:jse5eORjbrlTIOPzOO3cpm4feJ16ZCntxzAHSdcWuy4= -github.com/wenovus/gobgp/v3 v3.0.0-20230831013712-6d33842cbf42/go.mod h1:P+5INtnzris2TTpWI4m1/RwqCUhniEqc/SOZw5CQCMo= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yoheimuta/go-protoparser/v4 v4.9.0 h1:zHRXzRjkOamwMkPu7bpiCtOpxHkM9c8zxQOvW99eWlo= +github.com/yoheimuta/go-protoparser/v4 v4.9.0/go.mod h1:AHNNnSWnb0UoL4QgHPiOAg2BniQceFscPI5X/BZNHl8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/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.1/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/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +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.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1321,18 +1856,42 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +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.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +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= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1348,13 +1907,21 @@ golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1370,8 +1937,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= -golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1416,6 +1983,9 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1480,8 +2050,15 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1513,8 +2090,12 @@ golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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= @@ -1532,8 +2113,12 @@ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1592,6 +2177,7 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1621,8 +2207,14 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1635,8 +2227,14 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= -golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1655,15 +2253,19 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1682,6 +2284,7 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1730,6 +2333,9 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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= @@ -1737,8 +2343,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -1811,16 +2418,21 @@ google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZ google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= google.golang.org/api v0.125.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= -google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA= -google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= +google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1959,13 +2571,31 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= +google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= -google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= +google.golang.org/genproto v0.0.0-20230821184602-ccc8af3d0e93/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= +google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +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-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -1973,9 +2603,28 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go. google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= @@ -1983,8 +2632,24 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920183334-c177e329c48b/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2028,11 +2693,17 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -2051,8 +2722,11 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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= @@ -2073,6 +2747,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -2092,8 +2767,8 @@ k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOGNOA7ki4n6qNYoFAgMlNvg= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1EoULdbQBcAeNJkY= diff --git a/internal/args/args.go b/internal/args/args.go index 4dff1f6208f..1afbf3aa1f3 100644 --- a/internal/args/args.go +++ b/internal/args/args.go @@ -24,19 +24,35 @@ import ( // Global test flags. var ( - NumControllerCards = flag.Int("arg_num_controller_cards", -1, "The expected number of controller cards. Some devices with a single controller report 0, which is a valid expected value. Expectation is not checked for values < 0.") - NumLinecards = flag.Int("arg_num_linecards", -1, "The expected number of linecards. Some devices with a single linecard report 0, which is a valid expected value. Expectation is not checked for values < 0.") - NumFabrics = flag.Int("arg_num_fabrics", -1, "The expected number of fabrics. Some devices with a single fabric report 0, which is a valid expected value. Expectation is not checked for values < 0.") - P4RTNodeName1 = flag.String("arg_p4rt_node_name_1", "", "The P4RT Node Name for the first FAP. Test that reserves ports in the same FAP should configure this P4RT Node. The value will only be used if deviation ExplicitP4RTNodeComponent is applied.") - P4RTNodeName2 = flag.String("arg_p4rt_node_name_2", "", "The P4RT Node Name for the second FAP. Test that reserves ports in two different FAPs should configure this P4RT Node in addition to the Node defined in P4RTNodeName1. The value will only be used if deviation ExplicitP4RTNodeComponent is applied.") - FullConfigReplaceTime = flag.Duration("arg_full_config_replace_time", 0, "Time taken for gNMI set operation to complete full configuration replace. Expected duration is in nanoseconds. Expectation is not checked when value is 0.") - SubsetConfigReplaceTime = flag.Duration("arg_subset_config_replace_time", 0, "Time taken for gNMI set operation to modify a subset of configuration. Expected duration is in nanoseconds. Expectation is not checked when value is 0.") - QoSBaseConfigPresent = flag.Bool("arg_qos_baseconfig_present", true, "QoS Counter subtest in gNMI-1.10 requires related base config to be loaded. Use this flag to skip the when base config is not loaded.") - LACPBaseConfigPresent = flag.Bool("arg_lacp_baseconfig_present", true, "LACP subtest in gNMI-1.10 requires related base config to be loaded. Use this flag to skip the test when base config is not loaded.") - TempSensorNamePattern = flag.String("arg_temp_sensor_name_pattern", "", "There is no component type specifically for temperature sensors. So, we use the name pattern to find them.") - SwitchChipNamePattern = flag.String("arg_switchchip_name_pattern", "", "There is no component type specifically for SwitchChip components. So, we use the name pattern to find them.") - FanNamePattern = flag.String("arg_fan_name_pattern", "", "This name pattern is used to filter out Fan components.") - FabricChipNamePattern = flag.String("arg_fabricChip_name_pattern", "", "This name pattern is used to filter out FabricChip components.") - CheckInterfacesInBinding = flag.Bool("arg_check_interfaces_in_binding", true, "GNOI tests perform interface status validation based on all interfaces. This can cause flakiness in testing environments where only connectivity of interfaces in binding is guaranteed.") - ConvergencePathChange = flag.Uint64("arg_convergence_path_change", 250, "Traffic loss expected during path change set as 250 ms") + NumControllerCards = flag.Int("arg_num_controller_cards", -1, "The expected number of controller cards. Some devices with a single controller report 0, which is a valid expected value. Expectation is not checked for values < 0.") + NumLinecards = flag.Int("arg_num_linecards", -1, "The expected number of linecards. Some devices with a single linecard report 0, which is a valid expected value. Expectation is not checked for values < 0.") + NumFabrics = flag.Int("arg_num_fabrics", -1, "The expected number of fabrics. Some devices with a single fabric report 0, which is a valid expected value. Expectation is not checked for values < 0.") + NumFans = flag.Int("arg_num_fans", 0, "The expected number of fans (default is 0, meaning the device is not expected to have fans so none are validated).") + NumFanTrays = flag.Int("arg_num_fan_trays", 0, "The expected number of fan trays (default is 0, meaning the device is not expected to have fan trays so none are validated).") + FullConfigReplaceTime = flag.Duration("arg_full_config_replace_time", 0, "Time taken for gNMI set operation to complete full configuration replace. Expected duration is in nanoseconds. Expectation is not checked when value is 0.") + SubsetConfigReplaceTime = flag.Duration("arg_subset_config_replace_time", 0, "Time taken for gNMI set operation to modify a subset of configuration. Expected duration is in nanoseconds. Expectation is not checked when value is 0.") + QoSBaseConfigPresent = flag.Bool("arg_qos_baseconfig_present", true, "QoS Counter subtest in gNMI-1.10 requires related base config to be loaded. Use this flag to skip the when base config is not loaded.") + LACPBaseConfigPresent = flag.Bool("arg_lacp_baseconfig_present", true, "LACP subtest in gNMI-1.10 requires related base config to be loaded. Use this flag to skip the test when base config is not loaded.") + TempSensorNamePattern = flag.String("arg_temp_sensor_name_pattern", "", "There is no component type specifically for temperature sensors. So, we use the name pattern to find them.") + SwitchChipNamePattern = flag.String("arg_switchchip_name_pattern", "", "There is no component type specifically for SwitchChip components. So, we use the name pattern to find them.") + FanNamePattern = flag.String("arg_fan_name_pattern", "", "This name pattern is used to filter out Fan components.") + FabricChipNamePattern = flag.String("arg_fabricChip_name_pattern", "", "This name pattern is used to filter out FabricChip components.") + CheckInterfacesInBinding = flag.Bool("arg_check_interfaces_in_binding", true, "GNOI tests perform interface status validation based on all interfaces. This can cause flakiness in testing environments where only connectivity of interfaces in binding is guaranteed.") + ConvergencePathChange = flag.Uint64("arg_convergence_path_change", 250, "Traffic loss expected during path change set as 250 ms") + DefaultVRFIPv4Count = flag.Int("arg_default_vrf_ipv4_count", 1064, "In gRIBI scaling tests, the number of IPv4 entries to install in default network instance for recursive lookup") + DefaultVRFIPv4NHSize = flag.Int("arg_default_vrf_ipv4_nh_size", 8, "In gRIBI scaling tests, the number of next-hops in each next-hop-group installed in default network instance") + DefaultVRFIPv4NHGWeightSum = flag.Int("arg_default_vrf_ipv4_nhg_weight_sum", 64, "In gRIBI scaling tests, the sum of weights to assign to next-hops within a next-hop-group in the default network instance") + DefaultVRFIPv4NHCount = flag.Int("arg_default_vrf_ipv4_nh_count", 16, "In gRIBI scaling tests, the number of next-hops to install in default network instance") + NonDefaultVRFIPv4Count = flag.Int("arg_non_default_vrf_ipv4_count", 32000, "In gRIBI scaling tests, the number of IPv4 entries to install in non-default VRF") + NonDefaultVRFIPv4NHGCount = flag.Int("arg_non_default_vrf_ipv4_nhg_count", 1000, "In gRIBI scaling tests, the number of next-hop-groups to install to be referenced from IPv4 entries in non-default VRFs") + NonDefaultVRFIPv4NHSize = flag.Int("arg_non_default_vrf_ipv4_nh_size", 8, "In gRIBI scaling tests, the number of next-hops in each next-hop-group referenced from IPv4 entries in non-default VRFs") + NonDefaultVRFIPv4NHGWeightSum = flag.Int("arg_non_default_vrf_ipv4_nhg_weight_sum", 32, "In gRIBI scaling tests, the sum of weights to assign to next-hops within a next-hop-group referenced from IPv4 entries in non-default VRFs") + DecapEncapCount = flag.Int("arg_decap_encap_count", 64, "In gRIBI scaling tests, number of next-hop-groups with decap+encap next-hops") + DefaultVRFPrimarySubifCount = flag.Int("arg_default_vrf_primary_subif_count", 64, "In gRIBI scaling tests, number of subinterfaces to use for \"primary\" (i.e. non-backup) next-hop forwarding. Set such that DefaultVRFPrimarySubifCount <= (DefaultVRFIPv4NHCount - DefaultVRFIPv4NHSize)") + + V4TunnelCount = flag.Int("arg_v4_tunnel_count", 20000, "In gRIBI scaling tests, the number of tunnel IPs.") + V4TunnelNHGCount = flag.Int("arg_v4_tunnel_nhg_count", 256, "In gRIBI scaling tests, the number of next-hop-groups associated to the v4 tunnels.") + V4TunnelNHGSplitCount = flag.Int("arg_v4_tunnel_nhg_split_count", 2, "In gRIBI scaling tests, the number of next-hop per next-hop-group for the v4 tunnels.") + EgressNHGSplitCount = flag.Int("arg_egress_nhg_split_count", 16, "In gRIBI scaling tests, the number of next-hop per next-hop-group for the egress traffic.") + V4ReEncapNHGCount = flag.Int("arg_v4_re_encap_nhg_count", 256, "In gRIBI scaling tests, the number of next-hop-groups for re-encapping v4 tunnels.") ) diff --git a/internal/attrs/attrs.go b/internal/attrs/attrs.go index e09b579018a..d1444ebd7af 100644 --- a/internal/attrs/attrs.go +++ b/internal/attrs/attrs.go @@ -34,15 +34,18 @@ import ( // and for an ATETopology. All fields are optional; only those that are // non-empty will be set when configuring an interface. type Attributes struct { - IPv4 string - IPv6 string - MAC string - Name string // Interface name, only applied to ATE ports. - Desc string // Description, only applied to DUT interfaces. - IPv4Len uint8 // Prefix length for IPv4. - IPv6Len uint8 // Prefix length for IPv6. - MTU uint16 - ID uint32 // /interfaces/interface/state/id p4rt interface id + IPv4 string + IPv4Sec string // Secondary IPv4 address + IPv6 string + MAC string + Name string // Interface name, only applied to ATE ports. + Desc string // Description, only applied to DUT interfaces. + Subinterface uint32 //Subinterface + IPv4Len uint8 // Prefix length for IPv4. + IPv4LenSec uint8 // Prefix length for Secondary IPv4 address. + IPv6Len uint8 // Prefix length for IPv6. + MTU uint16 + ID uint32 // /interfaces/interface/state/id p4rt interface id } // IPv4CIDR constructs the IPv4 CIDR notation with the given prefix @@ -74,7 +77,7 @@ func (a *Attributes) ConfigOCInterface(intf *oc.Interface, dut *ondatra.DUTDevic e.MacAddress = ygot.String(a.MAC) } - s := intf.GetOrCreateSubinterface(0) + s := intf.GetOrCreateSubinterface(a.Subinterface) if a.IPv4 != "" { s4 := s.GetOrCreateIpv4() if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { @@ -89,6 +92,18 @@ func (a *Attributes) ConfigOCInterface(intf *oc.Interface, dut *ondatra.DUTDevic } } + if a.IPv4Sec != "" { + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + a4 := s4.GetOrCreateAddress(a.IPv4Sec) + if a.IPv4LenSec > 0 { + a4.PrefixLength = ygot.Uint8(a.IPv4LenSec) + a4.Type = oc.IfIp_Ipv4AddressType_SECONDARY + } + } + if a.IPv6 != "" { s6 := s.GetOrCreateIpv6() if a.MTU > 0 { @@ -130,11 +145,11 @@ func (a *Attributes) AddToATE(top *ondatra.ATETopology, ap *ondatra.Port, peer * } // AddToOTG adds basic elements to a gosnappi configuration -func (a *Attributes) AddToOTG(top gosnappi.Config, ap *ondatra.Port, peer *Attributes) { +func (a *Attributes) AddToOTG(top gosnappi.Config, ap *ondatra.Port, peer *Attributes) gosnappi.Device { top.Ports().Add().SetName(ap.ID()) dev := top.Devices().Add().SetName(a.Name) eth := dev.Ethernets().Add().SetName(a.Name + ".Eth").SetMac(a.MAC) - eth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(ap.ID()) + eth.Connection().SetPortName(ap.ID()) if a.MTU > 0 { eth.SetMtu(uint32(a.MTU)) @@ -147,4 +162,6 @@ func (a *Attributes) AddToOTG(top gosnappi.Config, ap *ondatra.Port, peer *Attri ip := eth.Ipv6Addresses().Add().SetName(dev.Name() + ".IPv6") ip.SetAddress(a.IPv6).SetGateway(peer.IPv6).SetPrefix(uint32(a.IPv6Len)) } + + return dev } diff --git a/internal/cfgplugins/bgp.go b/internal/cfgplugins/bgp.go new file mode 100644 index 00000000000..4b57c046f82 --- /dev/null +++ b/internal/cfgplugins/bgp.go @@ -0,0 +1,556 @@ +// 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 ( + "sort" + "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" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +// PortCount of topology +type PortCount int + +const ( + // RPLPermitAll policy + RPLPermitAll = "PERMIT-ALL" + + // DutAS dut AS + DutAS = uint32(65501) + // AteAS1 for ATE port1 + AteAS1 = uint32(65511) + // AteAS2 for ATE port2 + AteAS2 = uint32(65512) + // AteAS3 for ATE port3 + AteAS3 = uint32(65513) + // AteAS4 for ATE port4 + AteAS4 = uint32(65514) + + // BGPPeerGroup1 for ATE port1 + BGPPeerGroup1 = "BGP-PEER-GROUP1" + // BGPPeerGroup2 for ATE port2 + BGPPeerGroup2 = "BGP-PEER-GROUP2" + // BGPPeerGroup3 for ATE port3 + BGPPeerGroup3 = "BGP-PEER-GROUP3" + // BGPPeerGroup4 for ATE port4 + BGPPeerGroup4 = "BGP-PEER-GROUP4" + + // PTBGP is shorthand for the long oc protocol type constant + PTBGP = oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP +) + +var ( + plenIPv4 = uint8(30) + plenIPv6 = uint8(126) + + dutPort1 = &attrs.Attributes{ + Name: "port1", + IPv4: "192.0.2.1", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:1", + IPv6Len: plenIPv6, + } + dutPort2 = &attrs.Attributes{ + Name: "port2", + IPv4: "192.0.2.5", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:5", + IPv6Len: plenIPv6, + } + dutPort3 = &attrs.Attributes{ + Name: "port3", + IPv4: "192.0.2.9", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:9", + IPv6Len: plenIPv6, + } + dutPort4 = &attrs.Attributes{ + Name: "port4", + IPv4: "192.0.2.13", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:d", + IPv6Len: plenIPv6, + } + + atePort1 = &attrs.Attributes{ + Name: "port1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:2", + IPv6Len: plenIPv6, + } + atePort2 = &attrs.Attributes{ + Name: "port2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:6", + IPv6Len: plenIPv6, + } + atePort3 = &attrs.Attributes{ + Name: "port3", + MAC: "02:00:03:01:01:01", + IPv4: "192.0.2.10", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:a", + IPv6Len: plenIPv6, + } + atePort4 = &attrs.Attributes{ + Name: "port4", + MAC: "02:00:04:01:01:01", + IPv4: "192.0.2.14", + IPv4Len: plenIPv4, + IPv6: "2001:0db8::192:0:2:e", + IPv6Len: plenIPv6, + } + + bgpName = "BGP" + + // PortCount2 use this for topology of 2 ports + PortCount2 PortCount = 2 + // PortCount4 use this for topology of 4 ports + PortCount4 PortCount = 4 +) + +// BGPSession is a convenience wrapper around the dut, ate, ports, and topology we're using. +type BGPSession struct { + DUT *ondatra.DUTDevice + ATE *ondatra.ATEDevice + OndatraDUTPorts []*ondatra.Port + OndatraATEPorts []*ondatra.Port + ATEIntfs []gosnappi.Device + + DUTConf *oc.Root + ATETop gosnappi.Config + + DUTPorts []*attrs.Attributes + ATEPorts []*attrs.Attributes + afiTypes []oc.E_BgpTypes_AFI_SAFI_TYPE + networkInstance string +} + +// NewBGPSession creates a new BGPSession using the default global config, and +// configures the interfaces on the dut and the ate based in given topology port count. +// Only supports 2 and 4 port DUT-ATE topology +func NewBGPSession(t *testing.T, pc PortCount, ni *string) *BGPSession { + conf := &BGPSession{ + DUT: ondatra.DUT(t, "dut"), + DUTConf: &oc.Root{}, + DUTPorts: []*attrs.Attributes{dutPort1, dutPort2}, + ATEPorts: []*attrs.Attributes{atePort1, atePort2}, + OndatraDUTPorts: make([]*ondatra.Port, int(pc)), + OndatraATEPorts: make([]*ondatra.Port, int(pc)), + ATEIntfs: make([]gosnappi.Device, int(pc)), + } + + if pc == PortCount4 { + conf.DUTPorts = append(conf.DUTPorts, dutPort3, dutPort4) + conf.ATEPorts = append(conf.ATEPorts, atePort3, atePort4) + } + + for i := 0; i < int(pc); i++ { + conf.OndatraDUTPorts[i] = conf.DUT.Port(t, "port"+strconv.Itoa(i+1)) + conf.DUTPorts[i].ConfigOCInterface(conf.DUTConf.GetOrCreateInterface(conf.OndatraDUTPorts[i].Name()), conf.DUT) + } + + if ate, ok := ondatra.ATEs(t)["ate"]; ok { + conf.ATE = ate + conf.ATETop = gosnappi.NewConfig() + for i := 0; i < int(pc); i++ { + conf.OndatraATEPorts[i] = conf.ATE.Port(t, "port"+strconv.Itoa(i+1)) + conf.ATEIntfs[i] = conf.ATEPorts[i].AddToOTG(conf.ATETop, conf.OndatraATEPorts[i], conf.DUTPorts[i]) + } + } + + if ni == nil { + fptest.ConfigureDefaultNetworkInstance(t, conf.DUT) + conf.networkInstance = deviations.DefaultNetworkInstance(conf.DUT) + } else { + conf.networkInstance = *ni + } + + return conf +} + +// WithEBGP adds eBGP specific config +func (bs *BGPSession) WithEBGP(t *testing.T, afiTypes []oc.E_BgpTypes_AFI_SAFI_TYPE, bgpPorts []string, isSamePG, isSameAS bool) *BGPSession { + for _, afiType := range afiTypes { + if afiType != oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST && afiType != oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST { + t.Fatalf("Unsupported AFI type: %v", afiType) + } + } + bs.afiTypes = afiTypes + + asNumbers := []uint32{AteAS1, AteAS2, AteAS3, AteAS4} + if isSameAS { + asNumbers = []uint32{AteAS1, AteAS1, AteAS1, AteAS1} + } + + devices := bs.ATETop.Devices().Items() + byName := func(i, j int) bool { return devices[i].Name() < devices[j].Name() } + sort.Slice(devices, byName) + for i, otgPort := range bs.ATEPorts { + if !containsValue(bgpPorts, otgPort.Name) { + continue + } + bgp := devices[i].Bgp().SetRouterId(otgPort.IPv4) + + for _, afiType := range afiTypes { + switch afiType { + case oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST: + ipv4 := devices[i].Ethernets().Items()[0].Ipv4Addresses().Items()[0] + bgp4Peer := bgp.Ipv4Interfaces().Add().SetIpv4Name(ipv4.Name()).Peers().Add().SetName(devices[i].Name() + ".BGP4.peer") + bgp4Peer.SetPeerAddress(ipv4.Gateway()) + bgp4Peer.SetAsNumber(asNumbers[i]) + bgp4Peer.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) + case oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST: + ipv6 := devices[i].Ethernets().Items()[0].Ipv6Addresses().Items()[0] + bgp6Peer := bgp.Ipv6Interfaces().Add().SetIpv6Name(ipv6.Name()).Peers().Add().SetName(devices[i].Name() + ".BGP6.peer") + bgp6Peer.SetPeerAddress(ipv6.Gateway()) + bgp6Peer.SetAsNumber(asNumbers[i]) + bgp6Peer.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + } + } + } + + niProtocol := bs.DUTConf.GetOrCreateNetworkInstance(bs.networkInstance).GetOrCreateProtocol(PTBGP, bgpName) + neighborConfig := bs.buildNeigborConfig(isSamePG, isSameAS, bgpPorts) + niProtocol.Bgp = BuildBGPOCConfig(t, bs.DUT, dutPort1.IPv4, afiTypes, neighborConfig) + + err := bs.configureRoutingPolicy() + if err != nil { + t.Fatalf("Failed to configure routing policy: %v", err) + } + + return bs +} + +func (bs *BGPSession) configureRoutingPolicy() error { + rp := bs.DUTConf.GetOrCreateRoutingPolicy() + pdef := rp.GetOrCreatePolicyDefinition(RPLPermitAll) + stmt, err := pdef.AppendNewStatement("20") + if err != nil { + return err + } + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + return nil +} + +// PushAndStart calls PushDUT and PushAndStartATE to send config to both devices +func (bs *BGPSession) PushAndStart(t testing.TB) error { + t.Helper() + if err := bs.PushDUT(t); err != nil { + return err + } + bs.PushAndStartATE(t) + return nil +} + +// PushDUT replaces DUT config with s.dutConf. Only interfaces and the ISIS protocol are written +func (bs *BGPSession) PushDUT(t testing.TB) error { + fptest.WriteQuery(t, "Updating Config", gnmi.OC().Config(), bs.DUTConf) + res := gnmi.Update(t, bs.DUT, gnmi.OC().Config(), bs.DUTConf) + if res == nil { + t.Fatal("Failed to set DUT config: gnmi.Update returned nil result; check `Updating Config` for update request") + } + + if deviations.ExplicitInterfaceInDefaultVRF(bs.DUT) { + for i := 0; i < len(bs.DUTPorts); i++ { + fptest.AssignToNetworkInstance(t, bs.DUT, bs.OndatraDUTPorts[i].Name(), bs.networkInstance, 0) + } + } + if deviations.ExplicitPortSpeed(bs.DUT) { + for i := 0; i < len(bs.DUTPorts); i++ { + fptest.SetPortSpeed(t, bs.OndatraDUTPorts[i]) + } + } + return nil +} + +// PushAndStartATE pushes the ATETop to the ATE and starts protocols on it. +func (bs *BGPSession) PushAndStartATE(t testing.TB) { + t.Helper() + otg := bs.ATE.OTG() + otg.PushConfig(t, bs.ATETop) + otg.StartProtocols(t) + + for _, afiType := range bs.afiTypes { + switch afiType { + case oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST: + otgutils.WaitForARP(t.(*testing.T), otg, bs.ATETop, "IPv4") + case oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST: + otgutils.WaitForARP(t.(*testing.T), otg, bs.ATETop, "IPv6") + } + } +} + +// VerifyDUTBGPEstablished verifies on DUT BGP peer establishment +func VerifyDUTBGPEstablished(t *testing.T, dut *ondatra.DUTDevice) { + dni := deviations.DefaultNetworkInstance(dut) + nSessionState := gnmi.OC().NetworkInstance(dni).Protocol(PTBGP, bgpName).Bgp().NeighborAny().SessionState().State() + watch := gnmi.WatchAll(t, dut, nSessionState, 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if !ok || state != oc.Bgp_Neighbor_SessionState_ESTABLISHED { + return false + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established: got %v", val) + } + t.Log("DUT BGP sessions established") +} + +// VerifyOTGBGPEstablished verifies on OTG BGP peer establishment +func VerifyOTGBGPEstablished(t *testing.T, ate *ondatra.ATEDevice) { + pSessionState := gnmi.OTG().BgpPeerAny().SessionState().State() + watch := gnmi.WatchAll(t, ate.OTG(), pSessionState, 2*time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + state, ok := val.Val() + if !ok || state != otgtelemetry.BgpPeer_SessionState_ESTABLISHED { + return false + } + return true + }) + if val, ok := watch.Await(t); !ok { + t.Fatalf("BGP sessions not established: got %v", val) + } + t.Log("OTG BGP sessions established") +} + +// NeighborConfig to hold neighbor specific config +type NeighborConfig struct { + Name string + IPv4Neighbor string + IPv6Neighbor string + PeerGroup string + AS uint32 +} + +// BgpNeighbor holds BGP Peer information. +type BgpNeighbor struct { + LocalAS uint32 + PeerAS uint32 + Neighborip string + IsV4 bool + PeerGrp string +} + +// buildNeigborConfig builds neighbor config based on given flags +func (bs *BGPSession) buildNeigborConfig(isSamePG, isSameAS bool, bgpPorts []string) []*NeighborConfig { + nc1 := &NeighborConfig{ + Name: "port1", + IPv4Neighbor: atePort1.IPv4, + IPv6Neighbor: atePort1.IPv6, + PeerGroup: BGPPeerGroup1, + AS: AteAS1, + } + nc2 := &NeighborConfig{ + Name: "port2", + IPv4Neighbor: atePort2.IPv4, + IPv6Neighbor: atePort2.IPv6, + PeerGroup: BGPPeerGroup2, + AS: AteAS2, + } + nc3 := &NeighborConfig{ + Name: "port3", + IPv4Neighbor: atePort3.IPv4, + IPv6Neighbor: atePort3.IPv6, + PeerGroup: BGPPeerGroup3, + AS: AteAS3, + } + nc4 := &NeighborConfig{ + Name: "port4", + IPv4Neighbor: atePort4.IPv4, + IPv6Neighbor: atePort4.IPv6, + PeerGroup: BGPPeerGroup4, + AS: AteAS4, + } + ncAll := []*NeighborConfig{nc1, nc2, nc3, nc4} + + var validNC []*NeighborConfig + for _, nc := range ncAll[:len(bs.DUTPorts)] { + if containsValue(bgpPorts, nc.Name) { + validNC = append(validNC, nc) + } + } + + if isSamePG { + for _, nc := range validNC { + nc.PeerGroup = BGPPeerGroup1 + } + } + if isSameAS { + for _, nc := range validNC { + nc.AS = AteAS1 + } + } + + return validNC +} + +// BuildBGPOCConfig builds the BGP OC config applying global, neighbors and peer-group config +func BuildBGPOCConfig(t *testing.T, dut *ondatra.DUTDevice, routerID string, afiTypes []oc.E_BgpTypes_AFI_SAFI_TYPE, neighborConfig []*NeighborConfig) *oc.NetworkInstance_Protocol_Bgp { + afiSafiGlobal := map[oc.E_BgpTypes_AFI_SAFI_TYPE]*oc.NetworkInstance_Protocol_Bgp_Global_AfiSafi{} + for _, afiType := range afiTypes { + afiSafiGlobal[afiType] = &oc.NetworkInstance_Protocol_Bgp_Global_AfiSafi{ + AfiSafiName: afiType, + Enabled: ygot.Bool(true), + } + } + + global := &oc.NetworkInstance_Protocol_Bgp_Global{ + As: ygot.Uint32(DutAS), + RouterId: ygot.String(routerID), + AfiSafi: afiSafiGlobal, + } + + neighbors := make(map[string]*oc.NetworkInstance_Protocol_Bgp_Neighbor) + peerGroups := make(map[string]*oc.NetworkInstance_Protocol_Bgp_PeerGroup) + var neighbor string + for _, nc := range neighborConfig { + for _, afiType := range afiTypes { + switch afiType { + case oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST: + neighbor = nc.IPv4Neighbor + case oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST: + neighbor = nc.IPv6Neighbor + default: + t.Fatalf("Unsupported AFI type: %v", afiType) + } + + neighbors[neighbor] = &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + PeerAs: ygot.Uint32(nc.AS), + PeerGroup: ygot.String(nc.PeerGroup), + NeighborAddress: ygot.String(neighbor), + AfiSafi: map[oc.E_BgpTypes_AFI_SAFI_TYPE]*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi{ + afiType: { + AfiSafiName: afiType, + Enabled: ygot.Bool(true), + }, + }, + } + + peerGroups[nc.PeerGroup] = getPeerGroup(nc.PeerGroup, dut, afiTypes) + } + } + + return &oc.NetworkInstance_Protocol_Bgp{ + Global: global, + Neighbor: neighbors, + PeerGroup: peerGroups, + } +} + +// getPeerGroup build peer-config +func getPeerGroup(pgn string, dut *ondatra.DUTDevice, afiType []oc.E_BgpTypes_AFI_SAFI_TYPE) *oc.NetworkInstance_Protocol_Bgp_PeerGroup { + bgp := &oc.NetworkInstance_Protocol_Bgp{} + pg := bgp.GetOrCreatePeerGroup(pgn) + + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + // policy under peer group + rpl := pg.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{RPLPermitAll}) + rpl.SetImportPolicy([]string{RPLPermitAll}) + return pg + } + + // policy under peer group AFI + for _, afi := range afiType { + afisafi := pg.GetOrCreateAfiSafi(afi) + afisafi.Enabled = ygot.Bool(true) + rpl := afisafi.GetOrCreateApplyPolicy() + rpl.SetExportPolicy([]string{RPLPermitAll}) + rpl.SetImportPolicy([]string{RPLPermitAll}) + } + return pg +} + +func containsValue[T comparable](slice []T, value T) bool { + for _, v := range slice { + if v == value { + return true + } + } + return false +} + +// 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) +} + +// VerifyBGPCapabilities function is used to Verify BGP capabilities like route refresh as32 and mpbgp. +func VerifyBGPCapabilities(t *testing.T, dut *ondatra.DUTDevice, nbrs []*BgpNeighbor) { + t.Log("Verifying BGP capabilities") + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + for _, nbr := range nbrs { + nbrPath := statePath.Neighbor(nbr.Neighborip) + + 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 _, c := range gnmi.Get(t, dut, nbrPath.SupportedCapabilities().State()) { + capabilities[c] = true + } + for c, present := range capabilities { + if !present { + t.Errorf("Capability not reported: %v", c) + } + } + } +} + +// 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) + } + } +} diff --git a/internal/cfgplugins/interface.go b/internal/cfgplugins/interface.go new file mode 100644 index 00000000000..fdef68680b5 --- /dev/null +++ b/internal/cfgplugins/interface.go @@ -0,0 +1,175 @@ +// 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 cfgplugins + +import ( + "math" + "testing" + + "github.com/openconfig/featureprofiles/internal/components" + "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" +) + +const ( + targetOutputPowerdBm = -10 + targetOutputPowerTolerancedBm = 1 + targetFrequencyMHz = 193100000 + targetFrequencyToleranceMHz = 100000 +) + +// InterfaceConfig configures the interface with the given port. +func InterfaceConfig(t *testing.T, dut *ondatra.DUTDevice, dp *ondatra.Port) { + t.Helper() + d := &oc.Root{} + i := d.GetOrCreateInterface(dp.Name()) + i.Enabled = ygot.Bool(true) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + gnmi.Replace(t, dut, gnmi.OC().Interface(dp.Name()).Config(), i) + ocComponent := components.OpticalChannelComponentFromPort(t, dut, dp) + t.Logf("Got opticalChannelComponent from port: %s", ocComponent) + gnmi.Update(t, dut, gnmi.OC().Component(ocComponent).Name().Config(), ocComponent) + gnmi.Replace(t, dut, gnmi.OC().Component(ocComponent).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(targetOutputPowerdBm), + Frequency: ygot.Uint64(targetFrequencyMHz), + }) +} + +// ValidateInterfaceConfig validates the output power and frequency for the given port. +func ValidateInterfaceConfig(t *testing.T, dut *ondatra.DUTDevice, dp *ondatra.Port) { + t.Helper() + ocComponent := components.OpticalChannelComponentFromPort(t, dut, dp) + t.Logf("Got opticalChannelComponent from port: %s", ocComponent) + + outputPower := gnmi.Get(t, dut, gnmi.OC().Component(ocComponent).OpticalChannel().TargetOutputPower().State()) + if math.Abs(float64(outputPower)-float64(targetOutputPowerdBm)) > targetOutputPowerTolerancedBm { + t.Fatalf("Output power is not within expected tolerance, got: %v want: %v tolerance: %v", outputPower, targetOutputPowerdBm, targetOutputPowerTolerancedBm) + } + + frequency := gnmi.Get(t, dut, gnmi.OC().Component(ocComponent).OpticalChannel().Frequency().State()) + if math.Abs(float64(frequency)-float64(targetFrequencyMHz)) > targetFrequencyToleranceMHz { + t.Fatalf("Frequency is not within expected tolerance, got: %v want: %v tolerance: %v", frequency, targetFrequencyMHz, targetFrequencyToleranceMHz) + } +} + +// ToggleInterface toggles the interface. +func ToggleInterface(t *testing.T, dut *ondatra.DUTDevice, intf string, isEnabled bool) { + d := &oc.Root{} + i := d.GetOrCreateInterface(intf) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.Enabled = ygot.Bool(isEnabled) + gnmi.Replace(t, dut, gnmi.OC().Interface(intf).Config(), i) +} + +// ConfigOpticalChannel configures the optical channel. +func ConfigOpticalChannel(t *testing.T, dut *ondatra.DUTDevice, och string, frequency uint64, targetOpticalPower float64, operationalMode uint16) { + gnmi.Update(t, dut, gnmi.OC().Component(och).Name().Config(), och) + gnmi.Replace(t, dut, gnmi.OC().Component(och).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + OperationalMode: ygot.Uint16(operationalMode), + Frequency: ygot.Uint64(frequency), + TargetOutputPower: ygot.Float64(targetOpticalPower), + }) +} + +// ConfigOTNChannel configures the OTN channel. +func ConfigOTNChannel(t *testing.T, dut *ondatra.DUTDevice, och string, otnIndex, ethIndex uint32) { + t.Helper() + if deviations.OTNChannelTribUnsupported(dut) { + gnmi.Replace(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex).Config(), &oc.TerminalDevice_Channel{ + Description: ygot.String("OTN Logical Channel"), + Index: ygot.Uint32(otnIndex), + LogicalChannelType: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_OTN, + Assignment: map[uint32]*oc.TerminalDevice_Channel_Assignment{ + 0: { + Index: ygot.Uint32(1), + OpticalChannel: ygot.String(och), + Description: ygot.String("OTN to Optical Channel"), + Allocation: ygot.Float64(400), + AssignmentType: oc.Assignment_AssignmentType_OPTICAL_CHANNEL, + }, + }, + }) + } else { + gnmi.Replace(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex).Config(), &oc.TerminalDevice_Channel{ + Description: ygot.String("OTN Logical Channel"), + Index: ygot.Uint32(otnIndex), + LogicalChannelType: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_OTN, + TribProtocol: oc.TransportTypes_TRIBUTARY_PROTOCOL_TYPE_PROT_400GE, + Assignment: map[uint32]*oc.TerminalDevice_Channel_Assignment{ + 0: { + Index: ygot.Uint32(0), + OpticalChannel: ygot.String(och), + Description: ygot.String("OTN to Optical Channel"), + Allocation: ygot.Float64(400), + AssignmentType: oc.Assignment_AssignmentType_OPTICAL_CHANNEL, + }, + 1: { + Index: ygot.Uint32(1), + LogicalChannel: ygot.Uint32(ethIndex), + Description: ygot.String("OTN to ETH"), + Allocation: ygot.Float64(400), + AssignmentType: oc.Assignment_AssignmentType_LOGICAL_CHANNEL, + }, + }, + }) + } +} + +// ConfigETHChannel configures the ETH channel. +func ConfigETHChannel(t *testing.T, dut *ondatra.DUTDevice, interfaceName, transceiverName string, otnIndex, ethIndex uint32) { + t.Helper() + var ingress = &oc.TerminalDevice_Channel_Ingress{} + if !deviations.EthChannelIngressParametersUnsupported(dut) { + ingress = &oc.TerminalDevice_Channel_Ingress{ + Interface: ygot.String(interfaceName), + Transceiver: ygot.String(transceiverName), + } + } + var assignment = map[uint32]*oc.TerminalDevice_Channel_Assignment{} + if deviations.EthChannelAssignmentCiscoNumbering(dut) { + assignment = map[uint32]*oc.TerminalDevice_Channel_Assignment{ + 0: { + Index: ygot.Uint32(1), + LogicalChannel: ygot.Uint32(otnIndex), + Description: ygot.String("ETH to OTN"), + Allocation: ygot.Float64(400), + AssignmentType: oc.Assignment_AssignmentType_LOGICAL_CHANNEL, + }, + } + } else { + assignment = map[uint32]*oc.TerminalDevice_Channel_Assignment{ + 0: { + Index: ygot.Uint32(0), + LogicalChannel: ygot.Uint32(otnIndex), + Description: ygot.String("ETH to OTN"), + Allocation: ygot.Float64(400), + AssignmentType: oc.Assignment_AssignmentType_LOGICAL_CHANNEL, + }, + } + } + channel := &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, + RateClass: oc.TransportTypes_TRIBUTARY_RATE_CLASS_TYPE_TRIB_RATE_400G, + Ingress: ingress, + Assignment: assignment, + } + gnmi.Replace(t, dut, gnmi.OC().TerminalDevice().Channel(ethIndex).Config(), channel) +} diff --git a/internal/cfgplugins/sflow.go b/internal/cfgplugins/sflow.go index 51b57546fa4..99229eac17a 100644 --- a/internal/cfgplugins/sflow.go +++ b/internal/cfgplugins/sflow.go @@ -15,7 +15,11 @@ package cfgplugins import ( + "fmt" + "testing" + "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" @@ -26,7 +30,7 @@ import ( // configuration including any deviations for the device. // If sfglobal is nil, default values are provided. // The SFlow configuration is returned to give the caller an option to override default values. -func NewSFlowGlobalCfg(batch *gnmi.SetBatch, newcfg *oc.Sampling_Sflow, d *ondatra.DUTDevice) *oc.Sampling_Sflow { +func NewSFlowGlobalCfg(t *testing.T, batch *gnmi.SetBatch, newcfg *oc.Sampling_Sflow, d *ondatra.DUTDevice, ni, intfName string, srcAddrV4 string, srcAddrV6 string) *oc.Sampling_Sflow { c := new(oc.Sampling_Sflow) if newcfg == nil { @@ -35,12 +39,12 @@ func NewSFlowGlobalCfg(batch *gnmi.SetBatch, newcfg *oc.Sampling_Sflow, d *ondat c.IngressSamplingRate = ygot.Uint32(1000000) // c.EgressSamplingRate = ygot.Uint32(1000000), TODO: verify if EgressSamplingRate is a required DUT feature c.Dscp = ygot.Uint8(8) - coll := new(oc.Sampling_Sflow_Collector) - coll.SetAddress("192.0.2.129") - coll.SetPort(6343) - coll.SetSourceAddress("192.0.2.5") - coll.SetNetworkInstance(deviations.DefaultNetworkInstance(d)) - c.AppendCollector(coll) + c.GetOrCreateInterface(d.Port(t, "port1").Name()).Enabled = ygot.Bool(true) + c.GetOrCreateInterface(d.Port(t, "port2").Name()).Enabled = ygot.Bool(true) + coll := NewSFlowCollector(t, batch, nil, d, ni, intfName, srcAddrV4, srcAddrV6) + for _, col := range coll { + c.AppendCollector(col) + } } else { *c = *newcfg } @@ -52,19 +56,55 @@ func NewSFlowGlobalCfg(batch *gnmi.SetBatch, newcfg *oc.Sampling_Sflow, d *ondat // NewSFlowCollector creates a collector to be appended to SFlowConfig. // If sfc is nil, default values are provided. -func NewSFlowCollector(batch *gnmi.SetBatch, newcfg *oc.Sampling_Sflow_Collector, d *ondatra.DUTDevice) *oc.Sampling_Sflow_Collector { - c := new(oc.Sampling_Sflow_Collector) +func NewSFlowCollector(t *testing.T, batch *gnmi.SetBatch, newcfg *oc.Sampling_Sflow_Collector, d *ondatra.DUTDevice, ni, intfName string, srcAddrV4 string, srcAddrV6 string) []*oc.Sampling_Sflow_Collector { + var coll []*oc.Sampling_Sflow_Collector if newcfg == nil { - c.SetAddress("192.0.2.129") - c.SetPort(6343) - c.SetSourceAddress("192.0.2.5") + intf := gnmi.Get[*oc.Interface](t, d, gnmi.OC().Interface(intfName).State()) + + cV4 := new(oc.Sampling_Sflow_Collector) + cV4.SetAddress("192.0.2.129") + cV4.SetPort(6343) + + if deviations.SflowSourceAddressUpdateUnsupported(d) { + sFlowSourceAddressCli := "" + switch d.Vendor() { + case ondatra.ARISTA: + sFlowSourceAddressCli = fmt.Sprintf("sflow vrf %s source-interface %s", ni, intf.GetName()) + } + if sFlowSourceAddressCli != "" { + helpers.GnmiCLIConfig(t, d, sFlowSourceAddressCli) + } + } else { + cV4.SetSourceAddress(srcAddrV4) + } + cV4.SetNetworkInstance(ni) + coll = append(coll, cV4) + + cV6 := new(oc.Sampling_Sflow_Collector) + cV6.SetAddress("2001:db8::129") + cV6.SetPort(6343) + if deviations.SflowSourceAddressUpdateUnsupported(d) { + sFlowSourceAddressCli := "" + switch d.Vendor() { + case ondatra.ARISTA: + sFlowSourceAddressCli = fmt.Sprintf("sflow vrf %s source-interface %s", ni, intf.GetName()) + } + if sFlowSourceAddressCli != "" { + helpers.GnmiCLIConfig(t, d, sFlowSourceAddressCli) + } + } else { + cV6.SetSourceAddress(srcAddrV6) + } + cV6.SetNetworkInstance(ni) + coll = append(coll, cV6) } else { - *c = *newcfg + coll = append(coll, newcfg) } - c.SetNetworkInstance(normalizeNIName("DEFAULT", d)) - gnmi.BatchReplace(batch, gnmi.OC().Sampling().Sflow().Collector(c.GetAddress(), c.GetPort()).Config(), c) + for _, c := range coll { + gnmi.BatchReplace(batch, gnmi.OC().Sampling().Sflow().Collector(c.GetAddress(), c.GetPort()).Config(), c) + } - return c + return coll } diff --git a/internal/cfgplugins/staticroute.go b/internal/cfgplugins/staticroute.go index 45cffcdf2ff..73022d52a41 100644 --- a/internal/cfgplugins/staticroute.go +++ b/internal/cfgplugins/staticroute.go @@ -26,32 +26,34 @@ import ( // StaticRouteCfg defines commonly used attributes for setting a static route type StaticRouteCfg struct { - NIName string - Prefix string - Nexthop string + NetworkInstance string + Prefix string + NextHops map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union } // NewStaticRouteCfg provides OC configuration for a static route for a specific NetworkInstance, -// Prefix and nexthop. +// Prefix and NextHops. // // Configuration deviations are applied based on the ondatra device passed in. -func NewStaticRouteCfg(batch *gnmi.SetBatch, newcfg *StaticRouteCfg, d *ondatra.DUTDevice) (*oc.NetworkInstance_Protocol, error) { - if newcfg == nil { - return nil, errors.New("newcfg must be defined") +func NewStaticRouteCfg(batch *gnmi.SetBatch, cfg *StaticRouteCfg, d *ondatra.DUTDevice) (*oc.NetworkInstance_Protocol_Static, error) { + if cfg == nil { + return nil, errors.New("cfg must be defined") } - niName := normalizeNIName(newcfg.NIName, d) + ni := normalizeNIName(cfg.NetworkInstance, d) c := &oc.NetworkInstance_Protocol{ Identifier: oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, Name: ygot.String(deviations.StaticProtocolName(d)), } - staticroute := c.GetOrCreateStatic(newcfg.Prefix) - nh := staticroute.GetOrCreateNextHop("0") - nh.NextHop = oc.UnionString(newcfg.Nexthop) - gnmi.BatchReplace(batch, - gnmi.OC().NetworkInstance(niName).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(d)).Config(), - c) - - return c, nil + s := c.GetOrCreateStatic(cfg.Prefix) + for k, v := range cfg.NextHops { + nh := s.GetOrCreateNextHop(k) + nh.NextHop = v + } + sp := gnmi.OC().NetworkInstance(ni).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(d)) + gnmi.BatchUpdate(batch, sp.Config(), c) + gnmi.BatchReplace(batch, sp.Static(cfg.Prefix).Config(), s) + + return s, nil } diff --git a/internal/check/check_test.go b/internal/check/check_test.go index 529b0512abf..c021aaf7629 100644 --- a/internal/check/check_test.go +++ b/internal/check/check_test.go @@ -62,9 +62,9 @@ func newFakeGNMI(ctx context.Context) (*fakeGNMI, error) { if err != nil { return nil, err } - conn, err := grpc.DialContext(ctx, agent.Address(), grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.NewClient(agent.Address(), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - return nil, fmt.Errorf("DialContext(%s): %w", agent.Address(), err) + return nil, fmt.Errorf("NewClient(%s): %w", agent.Address(), err) } client, err := ygnmi.NewClient(gpb.NewGNMIClient(conn)) diff --git a/internal/cntrsrv/README.md b/internal/cntrsrv/README.md new file mode 100644 index 00000000000..c43946516a1 --- /dev/null +++ b/internal/cntrsrv/README.md @@ -0,0 +1,11 @@ +# `cntr` Container Image + +This directory contains the source code for a binary which can be run as a +container on a network device to run the `feature/container` tests. It exposes +a gRPC API that can be interacted with through ONDATRA in order to validate +connectivity to specific gRPC services. + +The `build` directory contains a `Dockerfile` for building the container. + +The `proto/cntr` directory contains a protobuf that defines the gRPC API +exposed by the container for test purposes. diff --git a/internal/cntrsrv/build/Dockerfile.cntr b/internal/cntrsrv/build/Dockerfile.cntr new file mode 100644 index 00000000000..5face02fee0 --- /dev/null +++ b/internal/cntrsrv/build/Dockerfile.cntr @@ -0,0 +1,6 @@ +FROM us-west1-docker.pkg.dev/gep-kne/arista/ceos:ga + +COPY cntrsrv /usr/bin +COPY ./cntr.service /etc/systemd/system + +RUN systemctl enable cntr diff --git a/internal/cntrsrv/build/Dockerfile.local b/internal/cntrsrv/build/Dockerfile.local new file mode 100644 index 00000000000..997c221adf2 --- /dev/null +++ b/internal/cntrsrv/build/Dockerfile.local @@ -0,0 +1,5 @@ +FROM alpine:3.16 + +COPY cntrsrv / + +CMD ["./cntrsrv"] diff --git a/internal/cntrsrv/build/cntr.service b/internal/cntrsrv/build/cntr.service new file mode 100644 index 00000000000..379cb793a1d --- /dev/null +++ b/internal/cntrsrv/build/cntr.service @@ -0,0 +1,13 @@ +[Unit] +Description="CNTR" + +[Service] +Type=simple +Restart=always +RestartSec=10 +User=root +ExecStart=/usr/bin/cntrsrv +StandardOutput=append:/var/log/cntr.log + +[Install] +WantedBy=multi-user.target diff --git a/feature/experimental/hierarchical_gribi_entries/traffic_balancing_according_to_weights/feature.textproto b/internal/cntrsrv/build/cntr_build.sh old mode 100644 new mode 100755 similarity index 53% rename from feature/experimental/hierarchical_gribi_entries/traffic_balancing_according_to_weights/feature.textproto rename to internal/cntrsrv/build/cntr_build.sh index a18ae203662..9da4d016218 --- a/feature/experimental/hierarchical_gribi_entries/traffic_balancing_according_to_weights/feature.textproto +++ b/internal/cntrsrv/build/cntr_build.sh @@ -1,4 +1,6 @@ -# Copyright 2022 Google LLC +#!/bin/bash + +# 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. @@ -12,11 +14,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -id { - name: "experimental_hierarchical_gribi_entries_traffic_balancing_according_to_weights" - version: 1 -} +# exit when a command fails +set -e + +case $1 in + "static") + echo "Building static binary" + CGO_ENABLED=0 go build ... + ;; + *) + echo "Building dynamic binary" + go build -ldflags '-s -w -I /lib64/ld-linux-x86-64.so.2 -extldflags=-Wl,--dynamic-linker,/lib64/ld-linux-x86-64.so.2,--strip-all' ... + ;; +esac + +docker build -t cntr:latest -f Dockerfile.cntr . -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight" -} +echo "docker build complete. Have a nice day." diff --git a/internal/cntrsrv/cntrsrv.go b/internal/cntrsrv/cntrsrv.go new file mode 100644 index 00000000000..bbbf4c2f47f --- /dev/null +++ b/internal/cntrsrv/cntrsrv.go @@ -0,0 +1,191 @@ +/* + 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. +*/ + +// Binary cntrserver implements the Cntr (Container) service which can be used to test base +// functionalities of a container hosting device. It implements a service that can: +// - dial a remote address as specified by the Dial RPC +// - respond to a ping request with a specified timestamp. +// +// By running the CNTR server on one machine, A, one can validate: +// - An external client can connect to a gRPC service running on A. +// +// By running the CNTR server on two machines, A and B, one can: +// - Validate that A can dial B via gRPC by calling the Dial RPC on A with B's address. +// - Validate that A can send an RPC to B via gRPC by calling the Dial RPC on A with B's address and ping. +package main + +import ( + "context" + "crypto/tls" + "flag" // NOLINT + "fmt" + "net" + + "github.com/openconfig/gnmi/testing/fake/testing/grpc/config" + "github.com/openconfig/ondatra/knebind/creds" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" + "k8s.io/klog/v2" + + cpb "github.com/openconfig/featureprofiles/internal/cntrsrv/proto/cntr" + gpb "github.com/openconfig/gnmi/proto/gnmi" + spb "github.com/openconfig/gribi/v1/proto/service" +) + +var ( + // timeFn is the function that returns a timestamp for Ping. It can be overloaded in order to + // have deterministic output for unit testing. + timeFn = timestamppb.Now + + // port is the port that this CNTR server should listen on. + port = flag.Uint("port", 60061, "port for CNTR service to listen on.") +) + +// C is the container for the CNTR server implementation. +type C struct { + *cpb.UnimplementedCntrServer +} + +// Ping implements the Ping RPC. It responds with a PingResponse corresponding to the timeFn timestamp. +func (c *C) Ping(_ context.Context, _ *cpb.PingRequest) (*cpb.PingResponse, error) { + return &cpb.PingResponse{ + Timestamp: timeFn(), + }, nil +} + +// rpcCredentials stores the per-RPC username and password used for authentication. +type rpcCredentials struct { + *creds.UserPass +} + +func (r *rpcCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { + return map[string]string{ + "username": "admin", + "password": "admin", + }, nil +} + +func (r *rpcCredentials) RequireTransportSecurity() bool { + return true +} + +// Dial connects to the remote gRPC CNTR server hosted at the address in the request proto. +func (c *C) Dial(ctx context.Context, req *cpb.DialRequest) (*cpb.DialResponse, error) { + conn, err := grpc.NewClient(req.GetAddr(), + grpc.WithPerRPCCredentials(&rpcCredentials{}), + grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, // NOLINT + }))) + if err != nil { + klog.Infof("error dialling target at %s, %v", req.GetAddr(), err) + return nil, err + } + defer conn.Close() + + if req.GetPing() != nil { + cl := cpb.NewCntrClient(conn) + pr, err := cl.Ping(ctx, &cpb.PingRequest{}) + if err != nil { + return nil, err + } + return &cpb.DialResponse{ + Response: &cpb.DialResponse_Pong{Pong: pr}, + }, nil + } + + switch req.GetSrv() { + case cpb.Service_ST_GNMI: + cl := gpb.NewGNMIClient(conn) + cr, err := cl.Capabilities(ctx, &gpb.CapabilityRequest{}) + if err != nil { + return nil, err + } + a, err := anypb.New(cr) + if err != nil { + return nil, status.Errorf(codes.Internal, "can't marshal %s to any", prototext.Format(cr)) + } + return &cpb.DialResponse{ + Response: &cpb.DialResponse_GnmiResponse{ + GnmiResponse: a, + }, + }, nil + case cpb.Service_ST_GRIBI: + cl := spb.NewGRIBIClient(conn) + gr, err := cl.Get(ctx, &spb.GetRequest{ + NetworkInstance: &spb.GetRequest_All{ + All: &spb.Empty{}, + }, + Aft: spb.AFTType_ALL, + }) + if err != nil { + return nil, err + } + msg, err := gr.Recv() + if err != nil { + return nil, err + } + a, err := anypb.New(msg) + if err != nil { + return nil, status.Errorf(codes.Internal, "can't marshal %s to any", prototext.Format(msg)) + } + return &cpb.DialResponse{ + Response: &cpb.DialResponse_GribiResponse{ + GribiResponse: a, + }, + }, nil + default: + klog.Warningf("No action was specified in request, dial-only performed, %v", req) + } + + return &cpb.DialResponse{}, nil +} + +// startServer starts a CNTR server listening on the specified port on localhost. +func startServer(port uint) func() { + tls, err := config.WithSelfTLSCert() + if err != nil { + klog.Fatalf("cannot generate self-signed cert, %v", err) + } + + srv := grpc.NewServer(tls) + s := &C{} + cpb.RegisterCntrServer(srv, s) + + lis, err := net.Listen("tcp", fmt.Sprintf("[::]:%d", port)) + if err != nil { + klog.Exitf("cannot start listening, got err: %v", err) + } + + klog.Infof("cntr server listening on %s", lis.Addr().String()) + + go srv.Serve(lis) + return srv.Stop +} + +func main() { + klog.InitFlags(nil) + flag.Parse() + + stop := startServer(*port) + defer stop() + + select {} +} diff --git a/internal/cntrsrv/cntrsrv_test.go b/internal/cntrsrv/cntrsrv_test.go new file mode 100644 index 00000000000..4c358180e88 --- /dev/null +++ b/internal/cntrsrv/cntrsrv_test.go @@ -0,0 +1,294 @@ +/* + 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. +*/ + +package main + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/gnmi/testing/fake/testing/grpc/config" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" + + cpb "github.com/openconfig/featureprofiles/internal/cntrsrv/proto/cntr" + gpb "github.com/openconfig/gnmi/proto/gnmi" + spb "github.com/openconfig/gribi/v1/proto/service" +) + +func newClient(t *testing.T, port uint) (cpb.CntrClient, func()) { + conn, err := grpc.NewClient(fmt.Sprintf("localhost:%d", port), + grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, // NOLINT + }))) + if err != nil { + t.Fatalf("cannot dial, %v", err) + } + + return cpb.NewCntrClient(conn), func() { conn.Close() } +} + +type badMode int64 + +const ( + _ badMode = iota + PingError +) + +type badServer struct { + mode badMode + *cpb.UnimplementedCntrServer +} + +func (b *badServer) Ping(_ context.Context, _ *cpb.PingRequest) (*cpb.PingResponse, error) { + switch b.mode { + case PingError: + return nil, status.Errorf(codes.Internal, "can't generate a ping") + default: + return &cpb.PingResponse{}, nil + } +} + +func buildBadServer(t *testing.T, mode badMode) func(uint) func() { + return func(port uint) func() { + tls, err := config.WithSelfTLSCert() + if err != nil { + t.Fatalf("cannot create server, %v", err) + } + srv := grpc.NewServer(tls) + s := &badServer{mode: mode} + cpb.RegisterCntrServer(srv, s) + + lis, err := net.Listen("tcp", fmt.Sprintf("[::]:%d", port)) + if err != nil { + t.Fatalf("cannot listen on port %d, got err: %v", port, err) + } + go srv.Serve(lis) + return srv.Stop + } +} + +type gRIBIServer struct { + *spb.UnimplementedGRIBIServer +} + +func (g *gRIBIServer) Get(_ *spb.GetRequest, stream spb.GRIBI_GetServer) error { + if err := stream.Send(&spb.GetResponse{}); err != nil { + return status.Errorf(codes.Internal, "can't send") + } + return nil +} + +func buildGRIBIServer(t *testing.T) func(uint) func() { + return func(port uint) func() { + tls, err := config.WithSelfTLSCert() + if err != nil { + t.Fatalf("cannot create server, %v", err) + } + srv := grpc.NewServer(tls) + s := &gRIBIServer{} + spb.RegisterGRIBIServer(srv, s) + lis, err := net.Listen("tcp", fmt.Sprintf("[::]:%d", port)) + if err != nil { + t.Fatalf("cannot listen on port %d, got err: %v", port, err) + } + go srv.Serve(lis) + return srv.Stop + } +} + +type gNMIServer struct { + *gpb.UnimplementedGNMIServer +} + +func (g *gNMIServer) Capabilities(context.Context, *gpb.CapabilityRequest) (*gpb.CapabilityResponse, error) { + return &gpb.CapabilityResponse{ + GNMIVersion: "demo", + }, nil +} + +func buildGNMIServer(t *testing.T) func(uint) func() { + return func(port uint) func() { + tls, err := config.WithSelfTLSCert() + if err != nil { + t.Fatalf("cannot create server, %v", err) + } + srv := grpc.NewServer(tls) + s := &gNMIServer{} + gpb.RegisterGNMIServer(srv, s) + lis, err := net.Listen("tcp", fmt.Sprintf("[::]:%d", port)) + if err != nil { + t.Fatalf("cannot listen on port %d, got err: %v", port, err) + } + go srv.Serve(lis) + return srv.Stop + } +} + +func TestDial(t *testing.T) { + timeFn = func() *timestamppb.Timestamp { + return ×tamppb.Timestamp{ + Seconds: 42, + Nanos: 42, + } + } + + tests := []struct { + desc string + inServer func(uint) func() + inServerPort uint + inRemote func(uint) func() + inRemotePort uint + inReq *cpb.DialRequest + wantResp *cpb.DialResponse + wantErr bool + }{{ + desc: "self-dial", + inServer: startServer, + inServerPort: 60061, + inReq: &cpb.DialRequest{ + Addr: "localhost:60061", + }, + wantResp: &cpb.DialResponse{}, + }, { + desc: "timeout", + inServer: startServer, + inServerPort: 60061, + inReq: &cpb.DialRequest{ + Request: &cpb.DialRequest_Ping{ + Ping: &cpb.PingRequest{}, + }, + Addr: "localhost:6666", + }, + wantErr: true, + }, { + desc: "self-dial and ping", + inServer: startServer, + inServerPort: 60061, + inReq: &cpb.DialRequest{ + Addr: "localhost:60061", + Request: &cpb.DialRequest_Ping{ + Ping: &cpb.PingRequest{}, + }, + }, + wantResp: &cpb.DialResponse{ + Response: &cpb.DialResponse_Pong{ + Pong: &cpb.PingResponse{ + Timestamp: ×tamppb.Timestamp{ + Seconds: 42, + Nanos: 42, + }, + }, + }, + }, + }, { + desc: "bad target server", + inServer: startServer, + inServerPort: 60061, + inRemote: buildBadServer(t, PingError), + inRemotePort: 60062, + inReq: &cpb.DialRequest{ + Addr: "localhost:60062", + Request: &cpb.DialRequest_Ping{ + Ping: &cpb.PingRequest{}, + }, + }, + wantErr: true, + }, { + desc: "gribi server", + inServer: startServer, + inServerPort: 60061, + inRemote: buildGRIBIServer(t), + inRemotePort: 9339, + inReq: &cpb.DialRequest{ + Addr: "localhost:9339", + Request: &cpb.DialRequest_Srv{ + Srv: cpb.Service_ST_GRIBI, + }, + }, + wantResp: &cpb.DialResponse{ + Response: &cpb.DialResponse_GribiResponse{ + GribiResponse: func() *anypb.Any { + a, err := anypb.New(&spb.GetResponse{}) + if err != nil { + t.Fatalf("cannot create gRIBI response, %v", err) + } + return a + }(), + }, + }, + }, { + desc: "gnmi server", + inServer: startServer, + inServerPort: 60061, + inRemote: buildGNMIServer(t), + inRemotePort: 9340, + inReq: &cpb.DialRequest{ + Addr: "localhost:9340", + Request: &cpb.DialRequest_Srv{ + Srv: cpb.Service_ST_GNMI, + }, + }, + wantResp: &cpb.DialResponse{ + Response: &cpb.DialResponse_GnmiResponse{ + GnmiResponse: func() *anypb.Any { + a, err := anypb.New(&gpb.CapabilityResponse{ + GNMIVersion: "demo", + }) + if err != nil { + t.Fatalf("cannot create gNMI response, %v", err) + } + return a + }(), + }, + }, + }} + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + stop := tt.inServer(tt.inServerPort) + defer stop() + if tt.inRemote != nil { + stopRemote := tt.inRemote(tt.inRemotePort) + defer stopRemote() + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + client, stopC := newClient(t, 60061) + defer stopC() + + got, err := client.Dial(ctx, tt.inReq) + if (err != nil) != tt.wantErr { + t.Fatalf("did not get expected error, got: %v, wantErr? %v", err, tt.wantErr) + } + t.Logf("got err: %v", err) + if diff := cmp.Diff(got, tt.wantResp, protocmp.Transform()); diff != "" { + t.Fatalf("did not get expected response, diff(-got,+want):\n%s", diff) + } + }) + } +} diff --git a/internal/cntrsrv/proto/cntr/cntr.pb.go b/internal/cntrsrv/proto/cntr/cntr.pb.go new file mode 100644 index 00000000000..f90e3d5984f --- /dev/null +++ b/internal/cntrsrv/proto/cntr/cntr.pb.go @@ -0,0 +1,548 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.21.9 +// source: cntr.proto + +package cntr + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Service enumerates the services that the dialler can connect to +// in an RPC-content-aware manner. +type Service int32 + +const ( + Service_ST_UNSPECIFIED Service = 0 + // gNMI indicates that a gNMI check should be initiated, particularly the + // Capabilities RPC should be sent to the target. + Service_ST_GNMI Service = 1 + // gRIBI indictes that gRIBI check should be initiated, particularly the + // Get RPC should be sent to the target. + Service_ST_GRIBI Service = 2 +) + +// Enum value maps for Service. +var ( + Service_name = map[int32]string{ + 0: "ST_UNSPECIFIED", + 1: "ST_GNMI", + 2: "ST_GRIBI", + } + Service_value = map[string]int32{ + "ST_UNSPECIFIED": 0, + "ST_GNMI": 1, + "ST_GRIBI": 2, + } +) + +func (x Service) Enum() *Service { + p := new(Service) + *p = x + return p +} + +func (x Service) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Service) Descriptor() protoreflect.EnumDescriptor { + return file_cntr_proto_enumTypes[0].Descriptor() +} + +func (Service) Type() protoreflect.EnumType { + return &file_cntr_proto_enumTypes[0] +} + +func (x Service) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Service.Descriptor instead. +func (Service) EnumDescriptor() ([]byte, []int) { + return file_cntr_proto_rawDescGZIP(), []int{0} +} + +type DialRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Address for the container to dial. + Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` + // Types that are assignable to Request: + // *DialRequest_Ping + // *DialRequest_Srv + Request isDialRequest_Request `protobuf_oneof:"request"` +} + +func (x *DialRequest) Reset() { + *x = DialRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_cntr_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DialRequest) ProtoMessage() {} + +func (x *DialRequest) ProtoReflect() protoreflect.Message { + mi := &file_cntr_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DialRequest.ProtoReflect.Descriptor instead. +func (*DialRequest) Descriptor() ([]byte, []int) { + return file_cntr_proto_rawDescGZIP(), []int{0} +} + +func (x *DialRequest) GetAddr() string { + if x != nil { + return x.Addr + } + return "" +} + +func (m *DialRequest) GetRequest() isDialRequest_Request { + if m != nil { + return m.Request + } + return nil +} + +func (x *DialRequest) GetPing() *PingRequest { + if x, ok := x.GetRequest().(*DialRequest_Ping); ok { + return x.Ping + } + return nil +} + +func (x *DialRequest) GetSrv() Service { + if x, ok := x.GetRequest().(*DialRequest_Srv); ok { + return x.Srv + } + return Service_ST_UNSPECIFIED +} + +type isDialRequest_Request interface { + isDialRequest_Request() +} + +type DialRequest_Ping struct { + // The payload of a PingRequest, if specified, the container dials and then + // uses the Ping RPC to send a request. + Ping *PingRequest `protobuf:"bytes,2,opt,name=ping,proto3,oneof"` +} + +type DialRequest_Srv struct { + // Service to be initiated towards the target. + Srv Service `protobuf:"varint,3,opt,name=srv,proto3,enum=openconfig.featureprofiles.cntr.Service,oneof"` +} + +func (*DialRequest_Ping) isDialRequest_Request() {} + +func (*DialRequest_Srv) isDialRequest_Request() {} + +type DialResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Response: + // *DialResponse_Pong + // *DialResponse_GribiResponse + // *DialResponse_GnmiResponse + Response isDialResponse_Response `protobuf_oneof:"response"` +} + +func (x *DialResponse) Reset() { + *x = DialResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_cntr_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DialResponse) ProtoMessage() {} + +func (x *DialResponse) ProtoReflect() protoreflect.Message { + mi := &file_cntr_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DialResponse.ProtoReflect.Descriptor instead. +func (*DialResponse) Descriptor() ([]byte, []int) { + return file_cntr_proto_rawDescGZIP(), []int{1} +} + +func (m *DialResponse) GetResponse() isDialResponse_Response { + if m != nil { + return m.Response + } + return nil +} + +func (x *DialResponse) GetPong() *PingResponse { + if x, ok := x.GetResponse().(*DialResponse_Pong); ok { + return x.Pong + } + return nil +} + +func (x *DialResponse) GetGribiResponse() *anypb.Any { + if x, ok := x.GetResponse().(*DialResponse_GribiResponse); ok { + return x.GribiResponse + } + return nil +} + +func (x *DialResponse) GetGnmiResponse() *anypb.Any { + if x, ok := x.GetResponse().(*DialResponse_GnmiResponse); ok { + return x.GnmiResponse + } + return nil +} + +type isDialResponse_Response interface { + isDialResponse_Response() +} + +type DialResponse_Pong struct { + // The ping response returned from the remote system, populated when the + // request specifies a PingRequest. + Pong *PingResponse `protobuf:"bytes,2,opt,name=pong,proto3,oneof"` +} + +type DialResponse_GribiResponse struct { + // The gRIBI message sent in response to the gRIBI Get RPC. Contains only + // the first message, and is populated only when the Service in the request + // is set to GRIBI. + GribiResponse *anypb.Any `protobuf:"bytes,3,opt,name=gribi_response,json=gribiResponse,proto3,oneof"` +} + +type DialResponse_GnmiResponse struct { + // The gNMI message sent in response to the gNMI Capabilities RPC. Populated + // only when the Service in the request is set to GNMI. + GnmiResponse *anypb.Any `protobuf:"bytes,4,opt,name=gnmi_response,json=gnmiResponse,proto3,oneof"` +} + +func (*DialResponse_Pong) isDialResponse_Response() {} + +func (*DialResponse_GribiResponse) isDialResponse_Response() {} + +func (*DialResponse_GnmiResponse) isDialResponse_Response() {} + +type PingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_cntr_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_cntr_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_cntr_proto_rawDescGZIP(), []int{2} +} + +type PingResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Timestamp at which the ping response was sent. + Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_cntr_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_cntr_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_cntr_proto_rawDescGZIP(), []int{3} +} + +func (x *PingResponse) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +var File_cntr_proto protoreflect.FileDescriptor + +var file_cntr_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x63, 0x6e, 0x74, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x6f, 0x70, + 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, 0x6e, 0x74, 0x72, 0x1a, 0x19, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, + 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae, 0x01, 0x0a, 0x0b, 0x44, 0x69, + 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x42, 0x0a, + 0x04, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, 0x6e, 0x74, 0x72, 0x2e, 0x50, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x69, 0x6e, + 0x67, 0x12, 0x3c, 0x0a, 0x03, 0x73, 0x72, 0x76, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, 0x6e, 0x74, 0x72, + 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x00, 0x52, 0x03, 0x73, 0x72, 0x76, 0x42, + 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xdb, 0x01, 0x0a, 0x0c, 0x44, + 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x70, + 0x6f, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, 0x6e, 0x74, 0x72, 0x2e, 0x50, 0x69, 0x6e, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6f, 0x6e, 0x67, + 0x12, 0x3d, 0x0a, 0x0e, 0x67, 0x72, 0x69, 0x62, 0x69, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x48, 0x00, + 0x52, 0x0d, 0x67, 0x72, 0x69, 0x62, 0x69, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3b, 0x0a, 0x0d, 0x67, 0x6e, 0x6d, 0x69, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x48, 0x00, 0x52, 0x0c, + 0x67, 0x6e, 0x6d, 0x69, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0a, 0x0a, 0x08, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0d, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x48, 0x0a, 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2a, 0x38, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x0e, + 0x53, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x5f, 0x47, 0x4e, 0x4d, 0x49, 0x10, 0x01, 0x12, 0x0c, 0x0a, + 0x08, 0x53, 0x54, 0x5f, 0x47, 0x52, 0x49, 0x42, 0x49, 0x10, 0x02, 0x32, 0xd0, 0x01, 0x0a, 0x04, + 0x43, 0x6e, 0x74, 0x72, 0x12, 0x63, 0x0a, 0x04, 0x44, 0x69, 0x61, 0x6c, 0x12, 0x2c, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, 0x6e, 0x74, 0x72, 0x2e, 0x44, + 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x70, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, 0x6e, 0x74, 0x72, 0x2e, 0x44, 0x69, 0x61, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x04, 0x50, 0x69, 0x6e, + 0x67, 0x12, 0x2c, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, + 0x6e, 0x74, 0x72, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2d, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x63, 0x6e, 0x74, + 0x72, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x49, + 0x50, 0x01, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x2f, 0x6f, 0x70, + 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2f, 0x63, 0x6e, 0x74, 0x72, 0x73, 0x72, 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x63, 0x6e, 0x74, 0x72, 0x3b, 0x63, 0x6e, 0x74, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_cntr_proto_rawDescOnce sync.Once + file_cntr_proto_rawDescData = file_cntr_proto_rawDesc +) + +func file_cntr_proto_rawDescGZIP() []byte { + file_cntr_proto_rawDescOnce.Do(func() { + file_cntr_proto_rawDescData = protoimpl.X.CompressGZIP(file_cntr_proto_rawDescData) + }) + return file_cntr_proto_rawDescData +} + +var file_cntr_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_cntr_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_cntr_proto_goTypes = []interface{}{ + (Service)(0), // 0: openconfig.featureprofiles.cntr.Service + (*DialRequest)(nil), // 1: openconfig.featureprofiles.cntr.DialRequest + (*DialResponse)(nil), // 2: openconfig.featureprofiles.cntr.DialResponse + (*PingRequest)(nil), // 3: openconfig.featureprofiles.cntr.PingRequest + (*PingResponse)(nil), // 4: openconfig.featureprofiles.cntr.PingResponse + (*anypb.Any)(nil), // 5: google.protobuf.Any + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp +} +var file_cntr_proto_depIdxs = []int32{ + 3, // 0: openconfig.featureprofiles.cntr.DialRequest.ping:type_name -> openconfig.featureprofiles.cntr.PingRequest + 0, // 1: openconfig.featureprofiles.cntr.DialRequest.srv:type_name -> openconfig.featureprofiles.cntr.Service + 4, // 2: openconfig.featureprofiles.cntr.DialResponse.pong:type_name -> openconfig.featureprofiles.cntr.PingResponse + 5, // 3: openconfig.featureprofiles.cntr.DialResponse.gribi_response:type_name -> google.protobuf.Any + 5, // 4: openconfig.featureprofiles.cntr.DialResponse.gnmi_response:type_name -> google.protobuf.Any + 6, // 5: openconfig.featureprofiles.cntr.PingResponse.timestamp:type_name -> google.protobuf.Timestamp + 1, // 6: openconfig.featureprofiles.cntr.Cntr.Dial:input_type -> openconfig.featureprofiles.cntr.DialRequest + 3, // 7: openconfig.featureprofiles.cntr.Cntr.Ping:input_type -> openconfig.featureprofiles.cntr.PingRequest + 2, // 8: openconfig.featureprofiles.cntr.Cntr.Dial:output_type -> openconfig.featureprofiles.cntr.DialResponse + 4, // 9: openconfig.featureprofiles.cntr.Cntr.Ping:output_type -> openconfig.featureprofiles.cntr.PingResponse + 8, // [8:10] is the sub-list for method output_type + 6, // [6:8] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_cntr_proto_init() } +func file_cntr_proto_init() { + if File_cntr_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_cntr_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DialRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cntr_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DialResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cntr_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cntr_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_cntr_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*DialRequest_Ping)(nil), + (*DialRequest_Srv)(nil), + } + file_cntr_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*DialResponse_Pong)(nil), + (*DialResponse_GribiResponse)(nil), + (*DialResponse_GnmiResponse)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_cntr_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_cntr_proto_goTypes, + DependencyIndexes: file_cntr_proto_depIdxs, + EnumInfos: file_cntr_proto_enumTypes, + MessageInfos: file_cntr_proto_msgTypes, + }.Build() + File_cntr_proto = out.File + file_cntr_proto_rawDesc = nil + file_cntr_proto_goTypes = nil + file_cntr_proto_depIdxs = nil +} diff --git a/internal/cntrsrv/proto/cntr/cntr.proto b/internal/cntrsrv/proto/cntr/cntr.proto new file mode 100644 index 00000000000..5da4b0e5f5c --- /dev/null +++ b/internal/cntrsrv/proto/cntr/cntr.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package openconfig.featureprofiles.cntr; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +option java_multiple_files = true; +option go_package="github.co/openconfig/featureprofiles/internal/cntrsrv/proto/cntr;cntr"; + +// Service Cntr is a CoNTaineR service that runs on a network device +// that is exposed to ONDATRA tests, it allows in-container behaviours +// to be triggered via a gRPC API. +service Cntr { + // Dial instructs the container to dial the target specified in the + // DialRequest message. + rpc Dial(DialRequest) returns (DialResponse); + // Ping provides a means for a container to respond to an external gRPC probe. + rpc Ping(PingRequest) returns (PingResponse); +} + +// Service enumerates the services that the dialler can connect to +// in an RPC-content-aware manner. +enum Service { + ST_UNSPECIFIED = 0; + // gNMI indicates that a gNMI check should be initiated, particularly the + // Capabilities RPC should be sent to the target. + ST_GNMI = 1; + // gRIBI indictes that gRIBI check should be initiated, particularly the + // Get RPC should be sent to the target. + ST_GRIBI = 2; +} + +message DialRequest { + // Address for the container to dial. + string addr = 1; + oneof request { + // The payload of a PingRequest, if specified, the container dials and then + // uses the Ping RPC to send a request. + PingRequest ping = 2; + // Service to be initiated towards the target. + Service srv = 3; + } +} + +message DialResponse { + oneof response { + // The ping response returned from the remote system, populated when the + // request specifies a PingRequest. + PingResponse pong = 2; + // The gRIBI message sent in response to the gRIBI Get RPC. Contains only + // the first message, and is populated only when the Service in the request + // is set to GRIBI. + google.protobuf.Any gribi_response = 3; + // The gNMI message sent in response to the gNMI Capabilities RPC. Populated + // only when the Service in the request is set to GNMI. + google.protobuf.Any gnmi_response = 4; + } +} + +message PingRequest {} + +message PingResponse { + // Timestamp at which the ping response was sent. + google.protobuf.Timestamp timestamp = 1; +} diff --git a/internal/cntrsrv/proto/cntr/cntr_grpc.pb.go b/internal/cntrsrv/proto/cntr/cntr_grpc.pb.go new file mode 100644 index 00000000000..f3828baaa02 --- /dev/null +++ b/internal/cntrsrv/proto/cntr/cntr_grpc.pb.go @@ -0,0 +1,143 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package cntr + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// CntrClient is the client API for Cntr service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CntrClient interface { + // Dial instructs the container to dial the target specified in the + // DialRequest message. + Dial(ctx context.Context, in *DialRequest, opts ...grpc.CallOption) (*DialResponse, error) + // Ping provides a means for a container to respond to an external gRPC probe. + Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) +} + +type cntrClient struct { + cc grpc.ClientConnInterface +} + +func NewCntrClient(cc grpc.ClientConnInterface) CntrClient { + return &cntrClient{cc} +} + +func (c *cntrClient) Dial(ctx context.Context, in *DialRequest, opts ...grpc.CallOption) (*DialResponse, error) { + out := new(DialResponse) + err := c.cc.Invoke(ctx, "/openconfig.featureprofiles.cntr.Cntr/Dial", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cntrClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) { + out := new(PingResponse) + err := c.cc.Invoke(ctx, "/openconfig.featureprofiles.cntr.Cntr/Ping", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CntrServer is the server API for Cntr service. +// All implementations must embed UnimplementedCntrServer +// for forward compatibility +type CntrServer interface { + // Dial instructs the container to dial the target specified in the + // DialRequest message. + Dial(context.Context, *DialRequest) (*DialResponse, error) + // Ping provides a means for a container to respond to an external gRPC probe. + Ping(context.Context, *PingRequest) (*PingResponse, error) + mustEmbedUnimplementedCntrServer() +} + +// UnimplementedCntrServer must be embedded to have forward compatible implementations. +type UnimplementedCntrServer struct { +} + +func (UnimplementedCntrServer) Dial(context.Context, *DialRequest) (*DialResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Dial not implemented") +} +func (UnimplementedCntrServer) Ping(context.Context, *PingRequest) (*PingResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedCntrServer) mustEmbedUnimplementedCntrServer() {} + +// UnsafeCntrServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CntrServer will +// result in compilation errors. +type UnsafeCntrServer interface { + mustEmbedUnimplementedCntrServer() +} + +func RegisterCntrServer(s grpc.ServiceRegistrar, srv CntrServer) { + s.RegisterService(&Cntr_ServiceDesc, srv) +} + +func _Cntr_Dial_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CntrServer).Dial(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/openconfig.featureprofiles.cntr.Cntr/Dial", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CntrServer).Dial(ctx, req.(*DialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Cntr_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CntrServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/openconfig.featureprofiles.cntr.Cntr/Ping", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CntrServer).Ping(ctx, req.(*PingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Cntr_ServiceDesc is the grpc.ServiceDesc for Cntr service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Cntr_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "openconfig.featureprofiles.cntr.Cntr", + HandlerType: (*CntrServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Dial", + Handler: _Cntr_Dial_Handler, + }, + { + MethodName: "Ping", + Handler: _Cntr_Ping_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "cntr.proto", +} diff --git a/internal/components/components.go b/internal/components/components.go index 3e666357082..e13fd064046 100644 --- a/internal/components/components.go +++ b/internal/components/components.go @@ -22,6 +22,7 @@ import ( "testing" "time" + "github.com/openconfig/featureprofiles/internal/deviations" tpb "github.com/openconfig/gnoi/types" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" @@ -57,6 +58,28 @@ func FindComponentsByType(t *testing.T, dut *ondatra.DUTDevice, cType oc.E_Platf return s } +// 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 { + 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 +} + // 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()) @@ -148,9 +171,9 @@ func (y Y) FindByType(ctx context.Context, want oc.Component_Type_Union) ([]stri return names, nil } -// FindStandbyRP gets a list of two components and finds out the active and standby rp. -func FindStandbyRP(t *testing.T, dut *ondatra.DUTDevice, supervisors []string) (string, string) { - var activeRP, standbyRP string +// FindStandbyControllerCard gets a list of two components and finds out the active and standby controller_cards. +func FindStandbyControllerCard(t *testing.T, dut *ondatra.DUTDevice, supervisors []string) (string, string) { + var activeCC, standbyCC string for _, supervisor := range supervisors { watch := gnmi.Watch(t, dut, gnmi.OC().Component(supervisor).RedundantRole().State(), 10*time.Minute, func(val *ygnmi.Value[oc.E_Platform_ComponentRedundantRole]) bool { return val.IsPresent() @@ -161,17 +184,41 @@ func FindStandbyRP(t *testing.T, dut *ondatra.DUTDevice, supervisors []string) ( role := gnmi.Get(t, dut, gnmi.OC().Component(supervisor).RedundantRole().State()) t.Logf("Component(supervisor).RedundantRole().Get(t): %v, Role: %v", supervisor, role) if role == standbyController { - standbyRP = supervisor + standbyCC = supervisor } else if role == activeController { - activeRP = supervisor + activeCC = supervisor } else { t.Fatalf("Expected controller %s to be active or standby, got %v", supervisor, role) } } - if standbyRP == "" || activeRP == "" { - t.Fatalf("Expected non-empty activeRP and standbyRP, got activeRP: %v, standbyRP: %v", activeRP, standbyRP) + if standbyCC == "" || activeCC == "" { + t.Fatalf("Expected non-empty activeCC and standbyCC, got activeCC: %v, standbyCC: %v", activeCC, standbyCC) } - t.Logf("Detected activeRP: %v, standbyRP: %v", activeRP, standbyRP) + t.Logf("Detected activeCC: %v, standbyCC: %v", activeCC, standbyCC) + + return standbyCC, activeCC +} - return standbyRP, activeRP +// OpticalChannelComponentFromPort finds the optical channel component for a port. +func OpticalChannelComponentFromPort(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port) string { + t.Helper() + + if deviations.MissingPortToOpticalChannelMapping(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + transceiverName := gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()) + return fmt.Sprintf("%s-Optical0", transceiverName) + default: + t.Fatal("Manual Optical channel name required when deviation missing_port_to_optical_channel_component_mapping applied.") + } + } + transceiverName := gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()) + if transceiverName == "" { + t.Fatalf("Associated Transceiver for Interface (%v) not found!", p.Name()) + } + opticalChannelName := gnmi.Get(t, dut, gnmi.OC().Component(transceiverName).Transceiver().Channel(0).AssociatedOpticalChannel().State()) + if opticalChannelName == "" { + t.Fatalf("Associated Optical Channel for Transceiver (%v) not found!", transceiverName) + } + return opticalChannelName } diff --git a/internal/core/core.go b/internal/core/core.go index 438c646b297..2d3b7ac0210 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -20,6 +20,7 @@ package core import ( "bytes" "context" + "errors" "fmt" "regexp" "sync" @@ -30,7 +31,6 @@ import ( "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/binding" "github.com/openconfig/ondatra/eventlis" - "google.golang.org/grpc" fpb "github.com/openconfig/gnoi/file" opb "github.com/openconfig/ondatra/proto" @@ -87,7 +87,7 @@ func newChecker(dut binding.DUT) (*checker, error) { if _, ok := vendorCoreFileNamePattern[dutVendor]; !ok { return nil, fmt.Errorf("add support for vendor %v in var vendorCoreFileNamePattern", dutVendor) } - gClients, err := dut.DialGNOI(context.Background(), grpc.WithBlock()) + gClients, err := dut.DialGNOI(context.Background()) if err != nil { return nil, err } @@ -212,7 +212,7 @@ func registerAfter(_ *eventlis.AfterTestsEvent) error { glog.Infof(msg) ondatra.Report().AddSuiteProperty("validator.core.end", report) if foundCores { - return fmt.Errorf(msg) + return errors.New(msg) } return nil } diff --git a/internal/deviations/README.md b/internal/deviations/README.md index 670bdbe9226..a42d37cfd59 100644 --- a/internal/deviations/README.md +++ b/internal/deviations/README.md @@ -1,21 +1,57 @@ -## Guidelines to add deviations to FNT tests +# Guidelines to add deviations to FNT tests -### Adding Deviations +## When to use deviations -* Add the deviation to the `Deviations` message in the [proto/metadata.proto](https://github.com/openconfig/featureprofiles/blob/main/proto/metadata.proto) file. +1. Deviations may be created to use alternate OC or use CLI instead of OC to + achieve the operational intent described in the README. +2. Deviations should not be created which change the operational intent. See + below for guidance on changing operational intent. +3. Deviations may be created to change which OC path is used for telemetry or + use an implementation's native yang path.  Deviations for telemetry + should not introduce a depedency on CLI output. +4. As with any pull request (PR), the CODEOWNERs must review and approve (or + delegate if appropriate). +5. The CODEOWNERs must ensure the README and code reflects the agreed to + operational support goal.  This may be done via offline discussions or + directly via approvals in the github PR. - ``` +See [Deviation Examples](#deviation-examples) for more information. + +## When not to use a deviation + +Deviations should not be used to skip configuration or skip validations. If the +feature is not supported and there is no workaround to achieve +the functionality, then the test should fail for that platform. + +If the README is in error, the README can be updated and code can be changed +(without introducing deviation) with approval from the CODEOWNERs. + +If the intent of the README needs to be changed (not due to an error, but a +change in the feature request), the CODEOWNER must ensure all parties are +notified. The CODEOWNER must determine if the change is late or early in the +development cycle. If late (development is underway and/or nearly complete), it +is recommended to create a new test which represents the change. If early in +the feature request (development has not started or is very early stage), then +the existing README and code may be updated. + +## Adding Deviations + +* Add the deviation to the `Deviations` message in the + [proto/metadata.proto](https://github.com/openconfig/featureprofiles/blob/main/proto/metadata.proto) + file. + +```go message Deviations { ... // Device does not support fragmentation bit for traceroute. bool traceroute_fragmentation = 2; ... } - ``` +``` * Run `make proto/metadata_go_proto/metadata.pb.go` from your featureprofiles root directory to generate the Go code for the added proto fields. - ``` +```shell $ make proto/metadata_go_proto/metadata.pb.go mkdir -p proto/metadata_go_proto # Set directory to hold symlink @@ -30,36 +66,57 @@ go list -f '{{ .Dir }} protobuf-import/{{ .Path }}' -m github.com/openconfig/ondatra | xargs -L1 -- ln -s protoc -I='protobuf-import' --proto_path=proto --go_out=./ --go_opt=Mmetadata.proto=proto/metadata_go_proto metadata.proto goimports -w proto/metadata_go_proto/metadata.pb.go - ``` - -* Add the accessor function for this deviation to the [internal/deviations/deviations.go](https://github.com/openconfig/featureprofiles/blob/main/internal/deviations/deviations.go) file. This function will need to accept a parameter `dut` of type `*ondatra.DUTDevice` to lookup the deviation value for a specific dut. This accessor function must call `lookupDUTDeviations` and return the deviation value. Test code will use this function to access deviations. - * If the default value of the deviation is the same as the default value for the proto field, the accessor method can directly call the `Get*()` function for the deviation field. For example, the boolean `traceroute_fragmentation` deviation, which has a default value of `false`, will have an accessor method with the single line `return lookupDUTDeviations(dut).GetTracerouteFragmentation()`. - - ``` - // TraceRouteFragmentation returns if the device does not support fragmentation bit for traceroute. - // Default value is false. - func TraceRouteFragmentation(dut *ondatra.DUTDevice) bool { - return lookupDUTDeviations(dut).GetTracerouteFragmentation() - } - ``` - - * If the default value of deviation is not the same as the default value of the proto field, the accessor method can add a check and return the required default value. For example, the accessor method for the float `hierarchical_weight_resolution_tolerance` deviation, which has a default value of `0`, will call the `GetHierarchicalWeightResolutionTolerance()` to check the value set in `metadata.textproto` and return the default value `0.2` if applicable. - - ``` - // HierarchicalWeightResolutionTolerance returns the allowed tolerance for BGP traffic flow while comparing for pass or fail conditions. - // Default minimum value is 0.2. Anything less than 0.2 will be set to 0.2. - func HierarchicalWeightResolutionTolerance(dut *ondatra.DUTDevice) float64 { - hwrt := lookupDUTDeviations(dut).GetHierarchicalWeightResolutionTolerance() - if minHWRT := 0.2; hwrt < minHWRT { +``` + +* Add the accessor function for this deviation to the + [internal/deviations/deviations.go](https://github.com/openconfig/featureprofiles/blob/main/internal/deviations/deviations.go) + file. This function will need to accept a parameter `dut` of type + `*ondatra.DUTDevice` to lookup the deviation value for a specific dut. This + accessor function must call `lookupDUTDeviations` and return the deviation + value. Test code will use this function to access deviations. + * If the default value of the deviation is the same as the default value for + the proto field, the accessor method can directly call the `Get*()` function + for the deviation field. For example, the boolean `traceroute_fragmentation` + deviation, which has a default value of `false`, will have an accessor + method with the single line `return + lookupDUTDeviations(dut).GetTracerouteFragmentation()`. + + ```go + // TraceRouteFragmentation returns if the device does not support fragmentation bit for traceroute. + // Default value is false. + func TraceRouteFragmentation(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetTracerouteFragmentation() + } + ``` + + * If the default value of deviation is not the same as the default value of + the proto field, the accessor method can add a check and return the required + default value. For example, the accessor method for the float + `hierarchical_weight_resolution_tolerance` deviation, which has a default + value of `0`, will call the `GetHierarchicalWeightResolutionTolerance()` to + check the value set in `metadata.textproto` and return the default value + `0.2` if applicable. + + ```go + // HierarchicalWeightResolutionTolerance returns the allowed tolerance for BGP traffic flow while comparing for pass or fail conditions. + // Default minimum value is 0.2. Anything less than 0.2 will be set to 0.2. + func HierarchicalWeightResolutionTolerance(dut *ondatra.DUTDevice) float64 { + hwrt := lookupDUTDeviations(dut).GetHierarchicalWeightResolutionTolerance() + if minHWRT := 0.2; hwrt < minHWRT { return minHWRT - } - return hwrt - } - ``` - -* Set the deviation value in the `metadata.textproto` file in the same folder as the test. For example, the deviations used in the test `feature/gnoi/system/tests/traceroute_test/traceroute_test.go` will be set in the file `feature/gnoi/system/tests/traceroute_test/metadata.textproto`. List all the vendor and optionally also hardware model regex that this deviation is applicable for. - - ``` + } + return hwrt + } + ``` + +* Set the deviation value in the `metadata.textproto` file in the same folder as + the test. For example, the deviations used in the test + `feature/gnoi/system/tests/traceroute_test/traceroute_test.go` will be set in + the file `feature/gnoi/system/tests/traceroute_test/metadata.textproto`. List + all the vendor and optionally also hardware model regex that this deviation is + applicable for. + + ```go ... platform_exceptions: { platform: { @@ -73,30 +130,69 @@ ... ``` -* To access the deviation from the test call the accessor function for the deviation. Pass the dut to this accessor. +* To access the deviation from the test call the accessor function for the + deviation. Pass the dut to this accessor. - ``` + ```go if deviations.TraceRouteFragmentation(dut) { ... } ``` -* Example PRs - https://github.com/openconfig/featureprofiles/pull/1649 and - https://github.com/openconfig/featureprofiles/pull/1668 +* Example PRs - and + + +## Removing Deviations -### Removing Deviations +* Once a deviation is no longer required and removed from all tests, delete the + deviation by removing them from the following files: -* Once a deviation is no longer required and removed from all tests, delete the deviation by removing them from the following files: + * metadata.textproto - Remove the deviation field from all metadata.textproto + in all tests. - * metadata.textproto - Remove the deviation field from all metadata.textproto in all tests. + * Remove the accessor method from + [deviations.go](https://github.com/openconfig/featureprofiles/blob/main/internal/deviations/deviations.go) - * [deviations.go](https://github.com/openconfig/featureprofiles/blob/main/internal/deviations/deviations.go) - Remove the accessor method for this deviation. + * Remove the field number from + [metadata.proto](https://github.com/openconfig/featureprofiles/blob/main/proto/metadata.proto) + by adding the `reserved n` to the `Deviations` message. Ref: + - * [metadata.proto](https://github.com/openconfig/featureprofiles/blob/main/proto/metadata.proto) - Remove the deviation field from the `Deviations` message and reserve the deleted field number by adding the `reserved n` to the `Deviations` message. -Ref: https://protobuf.dev/programming-guides/proto3/#deleting +* Run `make proto/metadata_go_proto/metadata.pb.go` from your featureprofiles + root directory to update the Go code for the removed proto fields. -* Run `make proto/metadata_go_proto/metadata.pb.go` from your featureprofiles root directory to update the Go code for the removed proto fields. +## Deviation examples + +```go +conf := configureDUT(dut) // returns *oc.Root + +if deviations.AlternateOCEnabled(t, dut) { + switch dut.Vendor() { + case ondatra.VENDOR_X: + conf.SetAlternateOC(val) + } +} else { + conf.SetRequiredOC(val) +} +``` + +```go +conf := configureDUT(dut) // returns *oc.Root + +if deviations.RequiredOCNotSupported(t, dut) { + switch dut.Vendor() { + case ondatra.VENDOR_X: + configureDeviceUsingCli(t, dut, vendorXConfig) + } +} +``` ## Notes -* If you run into issues with the `make proto/metadata_go_proto/metadata.pb.go` you may need to check if the `protoc` module is installed in your environment. Also depending on your Go version you may need to update your PATH and GOPATH. -* After running the `make proto/metadata_go_proto/metadata.pb.go` script, a `protobuf-import/` folder will be added in your current directory. Keep an eye out for this in case you use `git add .` to add modified files since this folder should not be part of your PR. + +* If you run into issues with the `make proto/metadata_go_proto/metadata.pb.go` + you may need to check if the `protoc` module is installed in your environment. + Also depending on your Go version you may need to update your PATH and GOPATH. +* After running the `make proto/metadata_go_proto/metadata.pb.go` script, a + `protobuf-import/` folder will be added in your current directory. Keep an eye + out for this in case you use `git add .` to add modified files since this + folder should not be part of your PR. diff --git a/internal/deviations/deviations.go b/internal/deviations/deviations.go index 8f7e638c68b..fb8797c656d 100644 --- a/internal/deviations/deviations.go +++ b/internal/deviations/deviations.go @@ -31,8 +31,8 @@ // Requirements for deviations: // // - Deviations may only use OpenConfig compliant behavior. -// - Deviations should be small in scope, typically affecting one sub-test, one -// OpenConfig path or small OpenConfig sub-tree. +// - Deviations should be small in scope, typically affecting one subtest, one +// OpenConfig path or small OpenConfig subtree. // // If a device could not pass without deviation, that is considered non-compliant // behavior. Ideally, a device should pass both with and without a deviation which means @@ -49,6 +49,7 @@ import ( log "github.com/golang/glog" "github.com/openconfig/featureprofiles/internal/metadata" + mpb "github.com/openconfig/featureprofiles/proto/metadata_go_proto" "github.com/openconfig/ondatra" ) @@ -56,12 +57,12 @@ import ( func lookupDeviations(dvc *ondatra.Device) (*mpb.Metadata_PlatformExceptions, error) { var matchedPlatformException *mpb.Metadata_PlatformExceptions - for _, platformExceptions := range metadata.Get().PlatformExceptions { - if platformExceptions.GetPlatform().Vendor.String() == "" { + for _, platformExceptions := range metadata.Get().GetPlatformExceptions() { + if platformExceptions.GetPlatform().GetVendor().String() == "" { return nil, fmt.Errorf("vendor should be specified in textproto %v", platformExceptions) } - if dvc.Vendor().String() != platformExceptions.GetPlatform().Vendor.String() { + if dvc.Vendor().String() != platformExceptions.GetPlatform().GetVendor().String() { continue } @@ -131,7 +132,7 @@ func GRIBIMACOverrideStaticARPStaticRoute(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetGribiMacOverrideStaticArpStaticRoute() } -// AggregateAtomicUpdate returns if device requires that aggregate Port-Channel and its members be defined in a single gNMI Update transaction at /interfaces. +// AggregateAtomicUpdate returns if device requires that aggregate Port-Channel and its members be defined in a single gNMI Update transaction at /interfaces, // Otherwise lag-type will be dropped, and no member can be added to the aggregate. // Full OpenConfig compliant devices should pass both with and without this deviation. func AggregateAtomicUpdate(dut *ondatra.DUTDevice) bool { @@ -146,12 +147,6 @@ func DefaultNetworkInstance(dut *ondatra.DUTDevice) string { return "DEFAULT" } -// ExplicitP4RTNodeComponent returns if device does not report P4RT node names in the component hierarchy. -// Fully compliant devices should report the PORT hardware components with the INTEGRATED_CIRCUIT components as their parents, as the P4RT node names. -func ExplicitP4RTNodeComponent(dut *ondatra.DUTDevice) bool { - return lookupDUTDeviations(dut).GetExplicitP4RtNodeComponent() -} - // ISISRestartSuppressUnsupported returns whether the device should skip isis restart-suppress check. func ISISRestartSuppressUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetIsisRestartSuppressUnsupported() @@ -185,17 +180,12 @@ func StaticProtocolName(dut *ondatra.DUTDevice) string { return "DEFAULT" } -// UseVendorNativeACLConfig returns whether a device requires native model to configure ACL, specifically for RT-1.4. -func UseVendorNativeACLConfig(dut *ondatra.DUTDevice) bool { - return lookupDUTDeviations(dut).GetUseVendorNativeAclConfig() -} - // SwitchChipIDUnsupported returns whether the device supports id leaf for SwitchChip components. func SwitchChipIDUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetSwitchChipIdUnsupported() } -// BackplaneFacingCapacityUnsupported returns whether the device supports backplane-facing-capacity leaves for some of the components. +// BackplaneFacingCapacityUnsupported returns whether the device supports backplane-facing-capacity leaves for some components. func BackplaneFacingCapacityUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBackplaneFacingCapacityUnsupported() } @@ -354,7 +344,7 @@ func InstallOSForStandbyRP(dut *ondatra.DUTDevice) bool { } // GNOIStatusWithEmptySubcomponent returns if the response of gNOI reboot status is a single value (not a list), -// the device requires explict component path to account for a situation when there is more than one active reboot requests. +// the device requires explicit component path to account for a situation when there is more than one active reboot requests. func GNOIStatusWithEmptySubcomponent(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetGnoiStatusEmptySubcomponent() } @@ -377,6 +367,11 @@ func ExplicitInterfaceInDefaultVRF(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetExplicitInterfaceInDefaultVrf() } +// RibWecmp returns if device requires CLI knob to enable wecmp feature. +func RibWecmp(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetRibWecmp() +} + // InterfaceConfigVRFBeforeAddress returns if vrf should be configured before IP address when configuring interface. func InterfaceConfigVRFBeforeAddress(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetInterfaceConfigVrfBeforeAddress() @@ -392,16 +387,6 @@ func QOSDroppedOctets(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetQosDroppedOctets() } -// ExplicitGRIBIUnderNetworkInstance returns if device requires gribi-protocol to be enabled under network-instance. -func ExplicitGRIBIUnderNetworkInstance(dut *ondatra.DUTDevice) bool { - return lookupDUTDeviations(dut).GetExplicitGribiUnderNetworkInstance() -} - -// SkipBGPTestPasswordMismatch retuns if BGP TestPassword mismatch subtest should be skipped. -func SkipBGPTestPasswordMismatch(dut *ondatra.DUTDevice) bool { - return lookupDUTDeviations(dut).GetSkipBgpTestPasswordMismatch() -} - // BGPMD5RequiresReset returns if device requires a BGP session reset to utilize a new MD5 key. func BGPMD5RequiresReset(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBgpMd5RequiresReset() @@ -448,7 +433,7 @@ func GNOIFabricComponentRebootUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetGnoiFabricComponentRebootUnsupported() } -// NtpNonDefaultVrfUnsupported returns true if the device does not support ntp nondefault vrf. +// NtpNonDefaultVrfUnsupported returns true if the device does not support ntp non-default vrf. // Default value is false. func NtpNonDefaultVrfUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetNtpNonDefaultVrfUnsupported() @@ -506,7 +491,7 @@ func SetNativeUser(dut *ondatra.DUTDevice) bool { } // P4RTGdpRequiresDot1QSubinterface returns true for devices that require configuring -// subinterface with tagged vlan for p4rt packet in. +// subinterface with tagged vlan for P4RT packet in. func P4RTGdpRequiresDot1QSubinterface(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetP4RtGdpRequiresDot1QSubinterface() } @@ -609,7 +594,7 @@ func QOSQueueRequiresID(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetQosQueueRequiresId() } -// BgpLlgrOcUndefined returns true if device should does not support OC path to disable BGP LLGR. +// BgpLlgrOcUndefined returns true if device does not support OC path to disable BGP LLGR. func BgpLlgrOcUndefined(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBgpLlgrOcUndefined() } @@ -620,7 +605,7 @@ func QOSBufferAllocationConfigRequired(dut *ondatra.DUTDevice) bool { } // BGPGlobalExtendedNextHopEncodingUnsupported returns true for devices that do not support configuring -// BGP ExtendedNextHopEncoding at thee global level. +// BGP ExtendedNextHopEncoding at the global level. func BGPGlobalExtendedNextHopEncodingUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBgpGlobalExtendedNextHopEncodingUnsupported() } @@ -685,3 +670,597 @@ func SkipPlqInterfaceOperStatusCheck(dut *ondatra.DUTDevice) bool { func BGPExplicitPrefixLimitReceived(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBgpExplicitPrefixLimitReceived() } + +// BGPMissingOCMaxPrefixesConfiguration returns true for devices that does not configure BGP +// maximum routes correctly when max-prefixes OC leaf is configured. +func BGPMissingOCMaxPrefixesConfiguration(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpMissingOcMaxPrefixesConfiguration() +} + +// SkipBgpSessionCheckWithoutAfisafi returns if device needs to skip checking AFI-SAFI disable. +func SkipBgpSessionCheckWithoutAfisafi(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipBgpSessionCheckWithoutAfisafi() +} + +// MismatchedHardwareResourceNameInComponent returns true for devices that have separate +// naming conventions for hardware resource name in /system/ tree and /components/ tree. +func MismatchedHardwareResourceNameInComponent(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMismatchedHardwareResourceNameInComponent() +} + +// MissingHardwareResourceTelemetryBeforeConfig returns true for devices that don't +// support telemetry for hardware resources before used-threshold-upper configuration. +func MissingHardwareResourceTelemetryBeforeConfig(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMissingHardwareResourceTelemetryBeforeConfig() +} + +// GNOISubcomponentRebootStatusUnsupported returns true for devices that do not support subcomponent reboot status check. +func GNOISubcomponentRebootStatusUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetGnoiSubcomponentRebootStatusUnsupported() +} + +// SkipNonBgpRouteExportCheck returns true for devices that exports routes from all +// protocols to BGP if the export-policy is ACCEPT. +func SkipNonBgpRouteExportCheck(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipNonBgpRouteExportCheck() +} + +// ISISMetricStyleTelemetryUnsupported returns true for devices that do not support state path +// /network-instances/network-instance/protocols/protocol/isis/levels/level/state/metric-style +func ISISMetricStyleTelemetryUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisMetricStyleTelemetryUnsupported() +} + +// StaticRouteNextHopInterfaceRefUnsupported returns if device does not support Interface-ref under static-route next-hop +func StaticRouteNextHopInterfaceRefUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticRouteNextHopInterfaceRefUnsupported() +} + +// SkipStaticNexthopCheck returns if device needs index starting from non-zero +func SkipStaticNexthopCheck(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipStaticNexthopCheck() +} + +// Ipv6RouterAdvertisementConfigUnsupported returns true for devices which don't support Ipv6 RouterAdvertisement configuration +func Ipv6RouterAdvertisementConfigUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIpv6RouterAdvertisementConfigUnsupported() +} + +// PrefixLimitExceededTelemetryUnsupported is to skip checking prefix limit telemetry flag. +func PrefixLimitExceededTelemetryUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPrefixLimitExceededTelemetryUnsupported() +} + +// SkipSettingAllowMultipleAS return true if device needs to skip setting allow-multiple-as while configuring eBGP +func SkipSettingAllowMultipleAS(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipSettingAllowMultipleAs() +} + +// SkipPbfWithDecapEncapVrf return true if device needs to skip test with which has PBF with decap encap VRF as action +func SkipPbfWithDecapEncapVrf(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipPbfWithDecapEncapVrf() +} + +// TTLCopyUnsupported returns true for devices which does not support TTL copy. +func TTLCopyUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetTtlCopyUnsupported() +} + +// GribiDecapMixedPlenUnsupported returns true if devices does not support +// programming with mixed prefix length. +func GribiDecapMixedPlenUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetGribiDecapMixedPlenUnsupported() +} + +// SkipIsisSetLevel return true if device needs to skip setting isis-actions set-level while configuring routing-policy statement action +func SkipIsisSetLevel(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipIsisSetLevel() +} + +// SkipIsisSetMetricStyleType return true if device needs to skip setting isis-actions set-metric-style-type while configuring routing-policy statement action +func SkipIsisSetMetricStyleType(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipIsisSetMetricStyleType() +} + +// SkipSetRpMatchSetOptions return true if device needs to skip setting match-prefix-set match-set-options while configuring routing-policy statement condition +func SkipSetRpMatchSetOptions(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipSetRpMatchSetOptions() +} + +// SkipSettingDisableMetricPropagation return true if device needs to skip setting disable-metric-propagation while configuring table-connection +func SkipSettingDisableMetricPropagation(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipSettingDisableMetricPropagation() +} + +// BGPConditionsMatchCommunitySetUnsupported returns true if device doesn't support bgp-conditions/match-community-set leaf +func BGPConditionsMatchCommunitySetUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpConditionsMatchCommunitySetUnsupported() +} + +// PfRequireMatchDefaultRule returns true for device which requires match condition for ether type v4 and v6 for default rule with network-instance default-vrf in policy-forwarding. +func PfRequireMatchDefaultRule(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPfRequireMatchDefaultRule() +} + +// MissingPortToOpticalChannelMapping returns true for devices missing component tree mapping from hardware port to optical channel. +func MissingPortToOpticalChannelMapping(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMissingPortToOpticalChannelComponentMapping() +} + +// SkipContainerOp returns true if gNMI container OP needs to be skipped. +// Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 +func SkipContainerOp(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipContainerOp() +} + +// ReorderCallsForVendorCompatibilty returns true if call needs to be updated/added/deleted. +// Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 +func ReorderCallsForVendorCompatibilty(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetReorderCallsForVendorCompatibilty() +} + +// AddMissingBaseConfigViaCli returns true if missing base config needs to be added using CLI. +// Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 +func AddMissingBaseConfigViaCli(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetAddMissingBaseConfigViaCli() +} + +// SkipMacaddressCheck returns true if mac address for an interface via gNMI needs to be skipped. +// Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 +func SkipMacaddressCheck(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipMacaddressCheck() +} + +// BGPRibOcPathUnsupported returns true if BGP RIB OC telemetry path is not supported. +func BGPRibOcPathUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpRibOcPathUnsupported() +} + +// SkipPrefixSetMode return true if device needs to skip setting prefix-set mode while configuring prefix-set routing-policy +func SkipPrefixSetMode(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipPrefixSetMode() +} + +// SetMetricAsPreference returns true for devices which set metric as +// preference for static next-hop +func SetMetricAsPreference(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSetMetricAsPreference() +} + +// IPv6StaticRouteWithIPv4NextHopRequiresStaticARP returns true if devices don't support having an +// IPv6 static Route with an IPv4 address as next hop and requires configuring a static ARP entry. +// Arista: https://partnerissuetracker.corp.google.com/issues/316593298 +func IPv6StaticRouteWithIPv4NextHopRequiresStaticARP(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIpv6StaticRouteWithIpv4NextHopRequiresStaticArp() +} + +// PfRequireSequentialOrderPbrRules returns true for device requires policy-forwarding rules to be in sequential order in the gNMI set-request. +func PfRequireSequentialOrderPbrRules(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPfRequireSequentialOrderPbrRules() +} + +// MissingStaticRouteNextHopMetricTelemetry returns true for devices missing +// static route next-hop metric telemetry. +// Arista: https://partnerissuetracker.corp.google.com/issues/321010782 +func MissingStaticRouteNextHopMetricTelemetry(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMissingStaticRouteNextHopMetricTelemetry() +} + +// UnsupportedStaticRouteNextHopRecurse returns true for devices that don't support recursive +// resolution of static route next hop. +// Arista: https://partnerissuetracker.corp.google.com/issues/314449182 +func UnsupportedStaticRouteNextHopRecurse(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetUnsupportedStaticRouteNextHopRecurse() +} + +// MissingStaticRouteDropNextHopTelemetry returns true for devices missing +// static route telemetry with DROP next hop. +// Arista: https://partnerissuetracker.corp.google.com/issues/330619816 +func MissingStaticRouteDropNextHopTelemetry(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMissingStaticRouteDropNextHopTelemetry() +} + +// MissingZROpticalChannelTunableParametersTelemetry returns true for devices missing 400ZR +// optical-channel tunable parameters telemetry: min/max/avg. +// Arista: https://partnerissuetracker.corp.google.com/issues/319314781 +func MissingZROpticalChannelTunableParametersTelemetry(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMissingZrOpticalChannelTunableParametersTelemetry() +} + +// PLQReflectorStatsUnsupported returns true for devices that does not support packet link qualification(PLQ) reflector packet sent/received stats. +func PLQReflectorStatsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPlqReflectorStatsUnsupported() +} + +// PLQGeneratorCapabilitiesMaxMTU returns supported max_mtu for devices that does not support packet link qualification(PLQ) Generator max_mtu to be at least >= 8184. +func PLQGeneratorCapabilitiesMaxMTU(dut *ondatra.DUTDevice) uint32 { + return lookupDUTDeviations(dut).GetPlqGeneratorCapabilitiesMaxMtu() +} + +// PLQGeneratorCapabilitiesMaxPPS returns supported max_pps for devices that does not support packet link qualification(PLQ) Generator max_pps to be at least >= 100000000. +func PLQGeneratorCapabilitiesMaxPPS(dut *ondatra.DUTDevice) uint64 { + return lookupDUTDeviations(dut).GetPlqGeneratorCapabilitiesMaxPps() +} + +// BgpExtendedCommunityIndexUnsupported return true if BGP extended community index is not supported. +func BgpExtendedCommunityIndexUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpExtendedCommunityIndexUnsupported() +} + +// BgpCommunitySetRefsUnsupported return true if BGP community set refs is not supported. +func BgpCommunitySetRefsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpCommunitySetRefsUnsupported() +} + +// TableConnectionsUnsupported returns true if Table Connections are unsupported. +func TableConnectionsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetTableConnectionsUnsupported() +} + +// UseVendorNativeTagSetConfig returns whether a device requires native model to configure tag-set +func UseVendorNativeTagSetConfig(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetUseVendorNativeTagSetConfig() +} + +// SkipBgpSendCommunityType return true if device needs to skip setting BGP send-community-type +func SkipBgpSendCommunityType(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipBgpSendCommunityType() +} + +// BgpActionsSetCommunityMethodUnsupported return true if BGP actions set-community method is unsupported +func BgpActionsSetCommunityMethodUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpActionsSetCommunityMethodUnsupported() + +} + +// SetNoPeerGroup Ensure that no BGP configurations exists under PeerGroups. +func SetNoPeerGroup(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSetNoPeerGroup() +} + +// BgpCommunityMemberIsAString returns true if device community member is not a list +func BgpCommunityMemberIsAString(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpCommunityMemberIsAString() +} + +// IPv4StaticRouteWithIPv6NextHopUnsupported unsupported ipv4 with ipv6 nexthop +func IPv4StaticRouteWithIPv6NextHopUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIpv4StaticRouteWithIpv6NhUnsupported() +} + +// IPv6StaticRouteWithIPv4NextHopUnsupported unsupported ipv6 with ipv4 nexthop +func IPv6StaticRouteWithIPv4NextHopUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIpv6StaticRouteWithIpv4NhUnsupported() +} + +// StaticRouteWithDropNhUnsupported unsupported drop nexthop +func StaticRouteWithDropNhUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticRouteWithDropNh() +} + +// StaticRouteWithExplicitMetric set explicit metric +func StaticRouteWithExplicitMetric(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticRouteWithExplicitMetric() +} + +// BgpDefaultPolicyUnsupported return true if BGP default-import/export-policy is not supported. +func BgpDefaultPolicyUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpDefaultPolicyUnsupported() +} + +// ExplicitEnableBGPOnDefaultVRF return true if BGP needs to be explicitly enabled on default VRF +func ExplicitEnableBGPOnDefaultVRF(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetExplicitEnableBgpOnDefaultVrf() +} + +// RoutingPolicyTagSetEmbedded returns true if the implementation does not support tag-set(s) as a +// separate entity, but embeds it in the policy statement +func RoutingPolicyTagSetEmbedded(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetRoutingPolicyTagSetEmbedded() +} + +// SkipAfiSafiPathForBgpMultipleAs return true if device do not support afi/safi path to enable allow multiple-as for eBGP +func SkipAfiSafiPathForBgpMultipleAs(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipAfiSafiPathForBgpMultipleAs() +} + +// CommunityMemberRegexUnsupported return true if device do not support community member regex +func CommunityMemberRegexUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetCommunityMemberRegexUnsupported() +} + +// SamePolicyAttachedToAllAfis returns true if same import policy has to be applied for all AFIs +func SamePolicyAttachedToAllAfis(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSamePolicyAttachedToAllAfis() +} + +// SkipSettingStatementForPolicy return true if device do not support afi/safi path to enable allow multiple-as for eBGP +func SkipSettingStatementForPolicy(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipSettingStatementForPolicy() +} + +// SkipCheckingAttributeIndex return true if device do not return bgp attribute for the bgp session specifying the index +func SkipCheckingAttributeIndex(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipCheckingAttributeIndex() +} + +// FlattenPolicyWithMultipleStatements return true if devices does not support policy-chaining +func FlattenPolicyWithMultipleStatements(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetFlattenPolicyWithMultipleStatements() +} + +// SlaacPrefixLength128 for Slaac generated IPv6 link local address +func SlaacPrefixLength128(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSlaacPrefixLength128() +} + +// DefaultRoutePolicyUnsupported returns true if default route policy is not supported +func DefaultRoutePolicyUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetDefaultRoutePolicyUnsupported() +} + +// CommunityMatchWithRedistributionUnsupported is set to true for devices that do not support matching community at the redistribution attach point. +func CommunityMatchWithRedistributionUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetCommunityMatchWithRedistributionUnsupported() +} + +// BgpMaxMultipathPathsUnsupported returns true if the device does not support +// bgp max multipaths. +func BgpMaxMultipathPathsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpMaxMultipathPathsUnsupported() +} + +// MultipathUnsupportedNeighborOrAfisafi returns true if the device does not +// support multipath under neighbor or afisafi. +func MultipathUnsupportedNeighborOrAfisafi(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMultipathUnsupportedNeighborOrAfisafi() +} + +// ModelNameUnsupported returns true if /components/components/state/model-name +// is not supported for any component type. +func ModelNameUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetModelNameUnsupported() +} + +// InstallPositionAndInstallComponentUnsupported returns true if install +// position and install component are not supported. +func InstallPositionAndInstallComponentUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetInstallPositionAndInstallComponentUnsupported() +} + +// EncapTunnelShutBackupNhgZeroTraffic returns true when encap tunnel is shut then zero traffic flows to back-up NHG +func EncapTunnelShutBackupNhgZeroTraffic(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetEncapTunnelShutBackupNhgZeroTraffic() +} + +// MaxEcmpPaths supported for isis max ecmp path +func MaxEcmpPaths(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMaxEcmpPaths() +} + +// WecmpAutoUnsupported returns true if wecmp auto is not supported +func WecmpAutoUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetWecmpAutoUnsupported() +} + +// RoutingPolicyChainingUnsupported returns true if policy chaining is unsupported +func RoutingPolicyChainingUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetRoutingPolicyChainingUnsupported() +} + +// ISISLoopbackRequired returns true if isis loopback is required. +func ISISLoopbackRequired(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisLoopbackRequired() +} + +// WeightedEcmpFixedPacketVerification returns true if fixed packet is used in traffic flow +func WeightedEcmpFixedPacketVerification(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetWeightedEcmpFixedPacketVerification() +} + +// OverrideDefaultNhScale returns true if default NextHop scale needs to be modified +// else returns false +func OverrideDefaultNhScale(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetOverrideDefaultNhScale() +} + +// BgpExtendedCommunitySetUnsupported returns true if set bgp extended community is unsupported +func BgpExtendedCommunitySetUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpExtendedCommunitySetUnsupported() +} + +// BgpSetExtCommunitySetRefsUnsupported returns true if bgp set ext community refs is unsupported +func BgpSetExtCommunitySetRefsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpSetExtCommunitySetRefsUnsupported() +} + +// BgpDeleteLinkBandwidthUnsupported returns true if bgp delete link bandwidth is unsupported +func BgpDeleteLinkBandwidthUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpDeleteLinkBandwidthUnsupported() +} + +// QOSInQueueDropCounterUnsupported returns true if /qos/interfaces/interface/input/queues/queue/state/dropped-pkts +// is not supported for any component type. +func QOSInQueueDropCounterUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetQosInqueueDropCounterUnsupported() +} + +// BgpExplicitExtendedCommunityEnable returns true if explicit extended community enable is needed +func BgpExplicitExtendedCommunityEnable(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpExplicitExtendedCommunityEnable() +} + +// MatchTagSetConditionUnsupported returns true if match tag set condition is not supported +func MatchTagSetConditionUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMatchTagSetConditionUnsupported() +} + +// PeerGroupDefEbgpVrfUnsupported returns true if peer group definition under ebgp vrf is unsupported +func PeerGroupDefEbgpVrfUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPeerGroupDefEbgpVrfUnsupported() +} + +// RedisConnectedUnderEbgpVrfUnsupported returns true if redistribution of routes under ebgp vrf is unsupported +func RedisConnectedUnderEbgpVrfUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetRedisConnectedUnderEbgpVrfUnsupported() +} + +// BgpAfiSafiInDefaultNiBeforeOtherNi returns true if certain AFI SAFIs are configured in default network instance before other network instances +func BgpAfiSafiInDefaultNiBeforeOtherNi(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpAfiSafiInDefaultNiBeforeOtherNi() +} + +// DefaultImportExportPolicyUnsupported returns true when device +// does not support default import export policy. +func DefaultImportExportPolicyUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetDefaultImportExportPolicyUnsupported() +} + +// CommunityInvertAnyUnsupported returns true when device +// does not support community invert any. +func CommunityInvertAnyUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetCommunityInvertAnyUnsupported() +} + +// Ipv6RouterAdvertisementIntervalUnsupported returns true for devices which don't support Ipv6 RouterAdvertisement interval configuration +func Ipv6RouterAdvertisementIntervalUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIpv6RouterAdvertisementIntervalUnsupported() +} + +// DecapNHWithNextHopNIUnsupported returns true if Decap NH with NextHopNetworkInstance is unsupported +func DecapNHWithNextHopNIUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetDecapNhWithNexthopNiUnsupported() +} + +// SflowSourceAddressUpdateUnsupported returns true if sflow source address update is unsupported +func SflowSourceAddressUpdateUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSflowSourceAddressUpdateUnsupported() +} + +// LinkLocalMaskLen returns true if linklocal mask length is not 64 +func LinkLocalMaskLen(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetLinkLocalMaskLen() +} + +// UseParentComponentForTemperatureTelemetry returns true if parent component supports temperature telemetry +func UseParentComponentForTemperatureTelemetry(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetUseParentComponentForTemperatureTelemetry() +} + +// ComponentMfgDateUnsupported returns true if component's mfg-date leaf is unsupported +func ComponentMfgDateUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetComponentMfgDateUnsupported() +} + +// InterfaceCountersUpdateDelayed returns true if telemetry for interface counters +// does not return the latest counter values. +func InterfaceCountersUpdateDelayed(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetInterfaceCountersUpdateDelayed() +} + +// OTNChannelTribUnsupported returns true if TRIB parameter is unsupported under OTN channel configuration +func OTNChannelTribUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetOtnChannelTribUnsupported() +} + +// EthChannelIngressParametersUnsupported returns true if ingress parameters are unsupported under ETH channel configuration +func EthChannelIngressParametersUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetEthChannelIngressParametersUnsupported() +} + +// EthChannelAssignmentCiscoNumbering returns true if eth channel assignment index starts from 1 instead of 0 +func EthChannelAssignmentCiscoNumbering(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetEthChannelAssignmentCiscoNumbering() +} + +// ChassisGetRPCUnsupported returns true if a Healthz Get RPC against the Chassis component is unsupported +func ChassisGetRPCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetChassisGetRpcUnsupported() +} + +// PowerDisableEnableLeafRefValidation returns true if definition of leaf-ref is not supported. +func PowerDisableEnableLeafRefValidation(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPowerDisableEnableLeafRefValidation() +} + +// SSHServerCountersUnsupported is to skip checking ssh server counters. +func SSHServerCountersUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSshServerCountersUnsupported() +} + +// OperationalModeUnsupported returns true if operational-mode leaf is unsupported +func OperationalModeUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetOperationalModeUnsupported() +} + +// BgpSessionStateIdleInPassiveMode returns true if BGP session state idle is not supported instead of active in passive mode. +func BgpSessionStateIdleInPassiveMode(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpSessionStateIdleInPassiveMode() +} + +// EnableMultipathUnderAfiSafi returns true for devices that do not support multipath under /global path and instead support under global/afi/safi path. +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() +} + +// CiscoPreFECBERInactiveValue returns true if a non-zero pre-fec-ber value is to be used for Cisco +func CiscoPreFECBERInactiveValue(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetCiscoPreFecBerInactiveValue() +} + +// BgpExtendedNextHopEncodingLeafUnsupported return true if bgp extended next hop encoding leaf is unsupported +// Cisco supports the extended nexthop encoding set to true by default that is exercised in the Script where the extended-nexthop-encoding +// a bool value is set to true. +func BgpExtendedNextHopEncodingLeafUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpExtendedNextHopEncodingLeafUnsupported() +} + +// BgpAfiSafiWildcardNotSupported return true if bgp afi/safi wildcard query is not supported. +// For example, this yang path query includes the wildcard key `afi-safi-name=`: +// `/network-instances/network-instance[name=DEFAULT]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor[neighbor-address=192.0.2.2]/afi-safis/afi-safi[afi-safi-name=]`. +// Use of this deviation is permitted if a query using an explicit key is supported (such as +// `oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST`). +func BgpAfiSafiWildcardNotSupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpAfiSafiWildcardNotSupported() +} + +// EnableTableConnections Admin Enable Table Connections in SRL native +func EnableTableConnections(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetEnableTableConnections() +} + +// NoZeroSuppression returns true if device wants to remove zero suppression +func NoZeroSuppression(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetNoZeroSuppression() +} + +// IsisInterfaceLevelPassiveUnsupported returns true for devices that do not support passive leaf +func IsisInterfaceLevelPassiveUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisInterfaceLevelPassiveUnsupported() +} + +// IsisDisSysidUnsupported returns true for devices that do not support dis-system-id leaf +func IsisDisSysidUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisDisSysidUnsupported() +} + +// IsisDatabaseOverloadsUnsupported returns true for devices that do not support database-overloads leaf +func IsisDatabaseOverloadsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisDatabaseOverloadsUnsupported() +} + +// BgpSetMedV7Unsupported returns true if devices which are not +// supporting bgp set med union type in OC. +func BgpSetMedV7Unsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpSetMedV7Unsupported() +} diff --git a/internal/encap_frr/base.go b/internal/encap_frr/base.go new file mode 100644 index 00000000000..30a0203e072 --- /dev/null +++ b/internal/encap_frr/base.go @@ -0,0 +1,578 @@ +// 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 base contains utility functions for encap frr using repair VRF. +package base + +import ( + "context" + "strconv" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + plenIPv4 = 30 + magicIP = "192.168.1.1" + magicMAC = "02:00:00:00:00:01" + maskLen24 = "24" + maskLen32 = "32" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niTEVRF111 = "TE_VRF_111" + niTEVRF222 = "TE_VRF_222" + niRepairVrf = "REPAIR_VRF" + ipv4OuterSrc111Addr = "198.51.100.111" + ipv4OuterSrc222Addr = "198.51.100.222" + 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.0.113.2" + gribiIPv4EntryVRF2221 = "203.0.113.100" + gribiIPv4EntryVRF2222 = "203.0.113.101" + gribiIPv4EntryEncapVRF = "138.0.11.0" + noMatchEncapDest = "20.0.0.1" +) + +var ( + dutPort2DummyIP = attrs.Attributes{ + Desc: "dutPort2", + IPv4Sec: "192.0.2.33", + IPv4LenSec: plenIPv4, + } + + otgPort2DummyIP = attrs.Attributes{ + Desc: "otgPort2", + IPv4: "192.0.2.34", + IPv4Len: plenIPv4, + } + + dutPort3DummyIP = attrs.Attributes{ + Desc: "dutPort3", + IPv4Sec: "192.0.2.37", + IPv4LenSec: plenIPv4, + } + + otgPort3DummyIP = attrs.Attributes{ + Desc: "otgPort3", + IPv4: "192.0.2.38", + IPv4Len: plenIPv4, + } + + dutPort4DummyIP = attrs.Attributes{ + Desc: "dutPort4", + IPv4Sec: "192.0.2.41", + IPv4LenSec: plenIPv4, + } + + otgPort4DummyIP = attrs.Attributes{ + Desc: "otgPort4", + IPv4: "192.0.2.42", + IPv4Len: plenIPv4, + } + + dutPort5DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.45", + IPv4LenSec: plenIPv4, + } + + otgPort5DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.46", + IPv4Len: plenIPv4, + } + dutPort6DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.49", + IPv4LenSec: plenIPv4, + } + + otgPort6DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.50", + IPv4Len: plenIPv4, + } + dutPort7DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.53", + IPv4LenSec: plenIPv4, + } + + otgPort7DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.54", + IPv4Len: plenIPv4, + } +) + +func programAftWithDummyIP(t *testing.T, dut *ondatra.DUTDevice, client *fluent.GRIBIClient) { + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port2").Name()). + WithIPAddress(otgPort2DummyIP.IPv4), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port3").Name()). + WithIPAddress(otgPort3DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF1+"/"+maskLen32).WithNextHopGroup(11), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port4").Name()). + WithIPAddress(otgPort4DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF2+"/"+maskLen32).WithNextHopGroup(12), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port5").Name()). + WithIPAddress(otgPort5DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF3+"/"+maskLen32).WithNextHopGroup(13), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port6").Name()). + WithIPAddress(otgPort6DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF4+"/"+maskLen32).WithNextHopGroup(14), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port7").Name()). + WithIPAddress(otgPort7DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF5+"/"+maskLen32).WithNextHopGroup(15), + ) +} + +// configStaticArp configures static arp entries +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 +} + +// StaticARPWithSpecificIP configures secondary IPs and static ARP. +func StaticARPWithSpecificIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2DummyIP.NewOCInterface(p2.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), dutPort3DummyIP.NewOCInterface(p3.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), dutPort4DummyIP.NewOCInterface(p4.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), dutPort5DummyIP.NewOCInterface(p5.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p6.Name()).Config(), dutPort6DummyIP.NewOCInterface(p6.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p7.Name()).Config(), dutPort7DummyIP.NewOCInterface(p7.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), configStaticArp(p2.Name(), otgPort2DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), configStaticArp(p3.Name(), otgPort3DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), configStaticArp(p4.Name(), otgPort4DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), configStaticArp(p5.Name(), otgPort5DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p6.Name()).Config(), configStaticArp(p6.Name(), otgPort6DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p7.Name()).Config(), configStaticArp(p7.Name(), otgPort7DummyIP.IPv4, magicMAC)) +} + +// StaticARPWithMagicUniversalIP programs the static ARP with magic universal IP +func StaticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + sb := &gnmi.SetBatch{} + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + portList := []*ondatra.Port{p2, p3, p4, p5, p6, p7} + for idx, p := range portList { + s := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(magicIP + "/32"), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + strconv.Itoa(idx): { + Index: ygot.String(strconv.Itoa(idx)), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(p.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(p.Name()).Config(), configStaticArp(p.Name(), magicIP, magicMAC)) + } + sb.Set(t, dut) +} + +// ConfigureBaseGribiRoutes programs the base gribi routes for encap FRR using repair VRF +func ConfigureBaseGribiRoutes(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, client *fluent.GRIBIClient) { + t.Helper() + + // Programming AFT entries for prefixes in DEFAULT VRF + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port2").Name()).WithIPAddress(magicIP), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port3").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port4").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port5").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port6").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port7").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + ) + } else if deviations.GRIBIMACOverrideWithStaticARP(dut) { + programAftWithDummyIP(t, dut, client) + } else { + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port2").Name()), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port3").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port4").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port5").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port6").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port7").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + ) + } + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + client.Modify().AddEntry(t, + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF1+"/"+maskLen32).WithNextHopGroup(11), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF2+"/"+maskLen32).WithNextHopGroup(12), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF3+"/"+maskLen32).WithNextHopGroup(13), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF4+"/"+maskLen32).WithNextHopGroup(14), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF5+"/"+maskLen32).WithNextHopGroup(15), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList := []string{gribiIPv4EntryDefVRF1, gribiIPv4EntryDefVRF2, gribiIPv4EntryDefVRF3, gribiIPv4EntryDefVRF4, gribiIPv4EntryDefVRF5} + for ip := range defaultVRFIPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2000).WithDecapsulateHeader(fluent.IPinIP).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2000).AddNextHop(2000, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in TE_VRF_222 + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3).WithIPAddress(gribiIPv4EntryDefVRF3), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2).AddNextHop(3, 1).WithBackupNHG(2000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF222).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF2221+"/"+maskLen32).WithNextHopGroup(2), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(5).WithIPAddress(gribiIPv4EntryDefVRF5), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(4).AddNextHop(5, 1).WithBackupNHG(2000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF222).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF2222+"/"+maskLen32).WithNextHopGroup(4), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF222IPList := []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF2222} + for ip := range teVRF222IPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF222IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1000).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2221). + WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1000).AddNextHop(1000, 1).WithBackupNHG(2000), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1001).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2222). + WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1001).AddNextHop(1001, 1).WithBackupNHG(2000), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3000).WithNextHopNetworkInstance(niRepairVrf), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3000).AddNextHop(3000, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in TE_VRF_111 + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1).WithIPAddress(gribiIPv4EntryDefVRF1), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2).WithIPAddress(gribiIPv4EntryDefVRF2), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(1).AddNextHop(1, 1).AddNextHop(2, 3).WithBackupNHG(3000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF111).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(4).WithIPAddress(gribiIPv4EntryDefVRF4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3).AddNextHop(4, 1).WithBackupNHG(3000), + fluent.IPv4Entry().WithNetworkInstance(niTEVRF111).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(3), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1000), + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(1001), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + teVRF111IPList := []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112} + for ip := range teVRF111IPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(teVRF111IPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + + // Programming AFT entries for backup NHG + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(2001).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(2001).AddNextHop(2001, 1), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + // Programming AFT entries for prefixes in ENCAP_TE_VRF_A + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(101).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1111). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(102).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc111Addr, gribiIPv4EntryVRF1112). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(101).AddNextHop(101, 1).AddNextHop(102, 3).WithBackupNHG(2001), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfA).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(101), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) +} + +// TestCase is a struct to hold the parameters for FRR test cases. +type TestCase struct { + Desc string + DownPortList []string + CapturePortList []string + EncapHeaderOuterIPList []string + EncapHeaderInnerIPList []string + TrafficDestIP string + LoadBalancePercent []float64 + TestID string +} + +// TestCases returns a list of base test cases for FRR tests. +func TestCases(atePortNamelist []string, ipv4InnerDst string) []*TestCase { + cases := []*TestCase{ + { + Desc: "Test-1: primary encap unviable but backup encap viable for single tunnel", + DownPortList: []string{"port2", "port3", "port4"}, + CapturePortList: []string{atePortNamelist[4], atePortNamelist[5]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF1112}, + EncapHeaderInnerIPList: []string{ipv4InnerDst, ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0.25, 0.75, 0, 0}, + TestID: "primarySingle", + }, { + Desc: "Test-2: primary and backup encap unviable for single tunnel", + DownPortList: []string{"port2", "port3", "port4", "port5"}, + CapturePortList: []string{atePortNamelist[5], atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF1112}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0.75, 0, 0.25}, + TestID: "primaryBackupSingle", + }, { + Desc: "Test-3: primary encap unviable with backup to routing for single tunnel", + DownPortList: []string{"port2", "port3", "port4"}, + CapturePortList: []string{atePortNamelist[5], atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF1112}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0.75, 0, 0.25}, + TestID: "primaryBackupRoutingSingle", + }, { + Desc: "Test-4: primary encap unviable but backup encap viable for all tunnels", + DownPortList: []string{"port2", "port3", "port4", "port6"}, + CapturePortList: []string{atePortNamelist[4], atePortNamelist[6]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF2222}, + EncapHeaderInnerIPList: []string{ipv4InnerDst, ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0.25, 0, 0.75, 0}, + TestID: "primaryAll", + }, { + Desc: "Test-5: primary and backup encap unviable for all tunnels", + DownPortList: []string{"port2", "port3", "port4", "port5", "port6", "port7"}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "primaryBackupAll", + }, { + Desc: "Test-6: primary encap unviable with backup to routing for all tunnels", + DownPortList: []string{"port2", "port3", "port4", "port6"}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "primaryBackupRoutingAll", + }, { + Desc: "Test-7: no match in encap VRF", + DownPortList: []string{}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{noMatchEncapDest}, + TrafficDestIP: noMatchEncapDest, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "encapNoMatch", + }, + } + + return cases +} + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} diff --git a/internal/encapfrr/base.go b/internal/encapfrr/base.go new file mode 100644 index 00000000000..8274906bc99 --- /dev/null +++ b/internal/encapfrr/base.go @@ -0,0 +1,425 @@ +// 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 encapfrr contains utility functions for encap frr using repair VRF. +package encapfrr + +import ( + "context" + "strconv" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + plenIPv4 = 30 + magicIP = "192.168.1.1" + magicMAC = "02:00:00:00:00:01" + maskLen32 = "32" + 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.0.113.2" + gribiIPv4EntryVRF2221 = "203.0.113.100" + gribiIPv4EntryVRF2222 = "203.0.113.101" + gribiIPv4EntryEncapVRF = "138.0.11.0" + noMatchEncapDest = "20.0.0.1" +) + +var ( + dutPort2DummyIP = attrs.Attributes{ + Desc: "dutPort2", + IPv4Sec: "192.0.2.33", + IPv4LenSec: plenIPv4, + } + + otgPort2DummyIP = attrs.Attributes{ + Desc: "otgPort2", + IPv4: "192.0.2.34", + IPv4Len: plenIPv4, + } + + dutPort3DummyIP = attrs.Attributes{ + Desc: "dutPort3", + IPv4Sec: "192.0.2.37", + IPv4LenSec: plenIPv4, + } + + otgPort3DummyIP = attrs.Attributes{ + Desc: "otgPort3", + IPv4: "192.0.2.38", + IPv4Len: plenIPv4, + } + + dutPort4DummyIP = attrs.Attributes{ + Desc: "dutPort4", + IPv4Sec: "192.0.2.41", + IPv4LenSec: plenIPv4, + } + + otgPort4DummyIP = attrs.Attributes{ + Desc: "otgPort4", + IPv4: "192.0.2.42", + IPv4Len: plenIPv4, + } + + dutPort5DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.45", + IPv4LenSec: plenIPv4, + } + + otgPort5DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.46", + IPv4Len: plenIPv4, + } + dutPort6DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.49", + IPv4LenSec: plenIPv4, + } + + otgPort6DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.50", + IPv4Len: plenIPv4, + } + dutPort7DummyIP = attrs.Attributes{ + Desc: "dutPort5", + IPv4Sec: "192.0.2.53", + IPv4LenSec: plenIPv4, + } + + otgPort7DummyIP = attrs.Attributes{ + Desc: "otgPort5", + IPv4: "192.0.2.54", + IPv4Len: plenIPv4, + } +) + +func programAftWithDummyIP(t *testing.T, dut *ondatra.DUTDevice, client *fluent.GRIBIClient) { + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port2").Name()). + WithIPAddress(otgPort2DummyIP.IPv4), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port3").Name()). + WithIPAddress(otgPort3DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF1+"/"+maskLen32).WithNextHopGroup(11), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port4").Name()). + WithIPAddress(otgPort4DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF2+"/"+maskLen32).WithNextHopGroup(12), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port5").Name()). + WithIPAddress(otgPort5DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF3+"/"+maskLen32).WithNextHopGroup(13), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port6").Name()). + WithIPAddress(otgPort6DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF4+"/"+maskLen32).WithNextHopGroup(14), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port7").Name()). + WithIPAddress(otgPort7DummyIP.IPv4), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF5+"/"+maskLen32).WithNextHopGroup(15), + ) +} + +// configStaticArp configures static arp entries +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 +} + +// StaticARPWithSpecificIP configures secondary IPs and static ARP. +func StaticARPWithSpecificIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2DummyIP.NewOCInterface(p2.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), dutPort3DummyIP.NewOCInterface(p3.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), dutPort4DummyIP.NewOCInterface(p4.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), dutPort5DummyIP.NewOCInterface(p5.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p6.Name()).Config(), dutPort6DummyIP.NewOCInterface(p6.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p7.Name()).Config(), dutPort7DummyIP.NewOCInterface(p7.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), configStaticArp(p2.Name(), otgPort2DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p3.Name()).Config(), configStaticArp(p3.Name(), otgPort3DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p4.Name()).Config(), configStaticArp(p4.Name(), otgPort4DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p5.Name()).Config(), configStaticArp(p5.Name(), otgPort5DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p6.Name()).Config(), configStaticArp(p6.Name(), otgPort6DummyIP.IPv4, magicMAC)) + gnmi.Update(t, dut, gnmi.OC().Interface(p7.Name()).Config(), configStaticArp(p7.Name(), otgPort7DummyIP.IPv4, magicMAC)) +} + +// StaticARPWithMagicUniversalIP programs the static ARP with magic universal IP +func StaticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + sb := &gnmi.SetBatch{} + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + p5 := dut.Port(t, "port5") + p6 := dut.Port(t, "port6") + p7 := dut.Port(t, "port7") + portList := []*ondatra.Port{p2, p3, p4, p5, p6, p7} + for idx, p := range portList { + s := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(magicIP + "/32"), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + strconv.Itoa(idx): { + Index: ygot.String(strconv.Itoa(idx)), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(p.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(p.Name()).Config(), configStaticArp(p.Name(), magicIP, magicMAC)) + } + sb.Set(t, dut) +} + +// ConfigureBaseGribiRoutes programs the base gribi routes for encap FRR using repair VRF +func ConfigureBaseGribiRoutes(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, client *fluent.GRIBIClient) { + t.Helper() + + // Programming AFT entries for prefixes in DEFAULT VRF + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port2").Name()).WithIPAddress(magicIP), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port3").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port4").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port5").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port6").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port7").Name()).WithIPAddress(magicIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + ) + } else if deviations.GRIBIMACOverrideWithStaticARP(dut) { + programAftWithDummyIP(t, dut, client) + } else { + client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(11).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port2").Name()), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(12).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port3").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(11).AddNextHop(11, 1).AddNextHop(12, 3), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(13).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port4").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(12).AddNextHop(13, 2), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(14).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port5").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(13).AddNextHop(14, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(15).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port6").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(14).AddNextHop(15, 1), + + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(16).WithMacAddress(magicMAC).WithInterfaceRef(dut.Port(t, "port7").Name()), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(15).AddNextHop(16, 1), + ) + } + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + client.Modify().AddEntry(t, + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF1+"/"+maskLen32).WithNextHopGroup(11), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF2+"/"+maskLen32).WithNextHopGroup(12), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF3+"/"+maskLen32).WithNextHopGroup(13), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF4+"/"+maskLen32).WithNextHopGroup(14), + fluent.IPv4Entry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryDefVRF5+"/"+maskLen32).WithNextHopGroup(15), + ) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList := []string{gribiIPv4EntryDefVRF1, gribiIPv4EntryDefVRF2, gribiIPv4EntryDefVRF3, gribiIPv4EntryDefVRF4, gribiIPv4EntryDefVRF5} + for ip := range defaultVRFIPList { + chk.HasResult(t, client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } +} + +// TestCase is a struct to hold the parameters for FRR test cases. +type TestCase struct { + Desc string + DownPortList []string + CapturePortList []string + EncapHeaderOuterIPList []string + EncapHeaderInnerIPList []string + TrafficDestIP string + LoadBalancePercent []float64 + TestID string +} + +// TestCases returns a list of base test cases for FRR tests. +func TestCases(atePortNamelist []string, ipv4InnerDst string) []*TestCase { + cases := []*TestCase{ + { + Desc: "Test-1: primary encap unviable but backup encap viable for single tunnel", + DownPortList: []string{"port2", "port3", "port4"}, + CapturePortList: []string{atePortNamelist[4], atePortNamelist[5]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF1112}, + EncapHeaderInnerIPList: []string{ipv4InnerDst, ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0.25, 0.75, 0, 0}, + TestID: "primarySingle", + }, { + Desc: "Test-2: primary and backup encap unviable for single tunnel", + DownPortList: []string{"port2", "port3", "port4", "port5"}, + CapturePortList: []string{atePortNamelist[5], atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF1112}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0.75, 0, 0.25}, + TestID: "primaryBackupSingle", + }, { + Desc: "Test-3: primary encap unviable with backup to routing for single tunnel", + DownPortList: []string{"port2", "port3", "port4"}, + CapturePortList: []string{atePortNamelist[5], atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF1112}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0.75, 0, 0.25}, + TestID: "primaryBackupRoutingSingle", + }, { + Desc: "Test-4: primary encap unviable but backup encap viable for all tunnels", + DownPortList: []string{"port2", "port3", "port4", "port6"}, + CapturePortList: []string{atePortNamelist[4], atePortNamelist[6]}, + EncapHeaderOuterIPList: []string{gribiIPv4EntryVRF2221, gribiIPv4EntryVRF2222}, + EncapHeaderInnerIPList: []string{ipv4InnerDst, ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0.25, 0, 0.75, 0}, + TestID: "primaryAll", + }, { + Desc: "Test-5: primary and backup encap unviable for all tunnels", + DownPortList: []string{"port2", "port3", "port4", "port5", "port6", "port7"}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "primaryBackupAll", + }, { + Desc: "Test-6: primary encap unviable with backup to routing for all tunnels", + DownPortList: []string{"port2", "port3", "port4", "port6"}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{ipv4InnerDst}, + TrafficDestIP: ipv4InnerDst, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "primaryBackupRoutingAll", + }, { + Desc: "Test-7: no match in encap VRF", + DownPortList: []string{}, + CapturePortList: []string{atePortNamelist[7]}, + EncapHeaderOuterIPList: []string{}, + EncapHeaderInnerIPList: []string{noMatchEncapDest}, + TrafficDestIP: noMatchEncapDest, + LoadBalancePercent: []float64{0, 0, 0, 0, 0, 0, 1}, + TestID: "encapNoMatch", + }, + } + + return cases +} + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} diff --git a/internal/fptest/config.go b/internal/fptest/config.go new file mode 100644 index 00000000000..bb976eea456 --- /dev/null +++ b/internal/fptest/config.go @@ -0,0 +1,205 @@ +package fptest + +import ( + "flag" + "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" +) + +var ( + // Some devices require the config to be pruned for these to work. We are still undecided + // whether they should be deviations; pending OpenConfig clarifications. + pruneComponents = flag.Bool("prune_components", true, "Prune components that are not ports. Use this to preserve the breakout-mode settings.") + pruneLLDP = flag.Bool("prune_lldp", true, "Prune LLDP config.") + setEthernetFromState = flag.Bool("set_ethernet_from_state", true, "Set interface/ethernet config from state, mostly to get the port-speed settings correct.") + + // This has no known effect except to reduce logspam while debugging. + pruneQoS = flag.Bool("prune_qos", true, "Prune QoS config.") + + // Experimental flags that will likely become a deviation. + cannotConfigurePortSpeed = flag.Bool("cannot_config_port_speed", false, "Some devices depending on the type of line card may not allow changing port speed, while still supporting the port speed leaf.") + + // Flags to ensure test passes without any dependency to the device config + baseOCConfigIsPresent = flag.Bool("base_oc_config_is_present", false, "No OC config is loaded on router, so Get config on the root returns no data.") +) + +// GetDeviceConfig gets a full config from a device but refurbishes it enough so it can be +// pushed out again. Ideally, we should be able to push the config we get from the same +// device without modification, but this is not explicitly defined in OpenConfig. +func GetDeviceConfig(t testing.TB, dev gnmi.DeviceOrOpts) *oc.Root { + t.Helper() + + // Gets all the config (read-write) paths from root, not the state (read-only) paths. + config := gnmi.Get[*oc.Root](t, dev, gnmi.OC().Config()) + WriteQuery(t, "Untouched", gnmi.OC().Config(), config) + + // load the base oc config from the device state when no oc config is loaded + if !*baseOCConfigIsPresent { + if ondatra.DUT(t, "dut").Vendor() == ondatra.CISCO { + intfsState := gnmi.GetAll(t, dev, gnmi.OC().InterfaceAny().State()) + for _, intf := range intfsState { + ygot.PruneConfigFalse(oc.SchemaTree["Interface"], intf) + config.DeleteInterface(intf.GetName()) + if intf.GetName() == "Loopback0" || intf.GetName() == "PTP0/RP1/CPU0/0" || intf.GetName() == "Null0" || intf.GetName() == "PTP0/RP0/CPU0/0" { + continue + } + intf.ForwardingViable = nil + intf.Mtu = nil + intf.HoldTime = nil + if intf.Subinterface != nil { + if intf.Subinterface[0].Ipv6 != nil { + intf.Subinterface[0].Ipv6.Autoconf = nil + } + } + config.AppendInterface(intf) + } + vrfsStates := gnmi.GetAll(t, dev, gnmi.OC().NetworkInstanceAny().State()) + for _, vrf := range vrfsStates { + // only needed for containerOp + if vrf.GetName() == "**iid" { + continue + } + if vrf.GetName() == "DEFAULT" { + config.NetworkInstance = nil + vrf.Interface = nil + for _, ni := range config.NetworkInstance { + ni.Mpls = nil + } + } + ygot.PruneConfigFalse(oc.SchemaTree["NetworkInstance"], vrf) + vrf.Table = nil + vrf.RouteLimit = nil + vrf.Mpls = nil + for _, intf := range vrf.Interface { + intf.AssociatedAddressFamilies = nil + } + for _, protocol := range vrf.Protocol { + for _, routes := range protocol.Static { + routes.Description = nil + } + } + config.AppendNetworkInstance(vrf) + } + } + } + + if *pruneComponents { + for cname, component := range config.Component { + // Keep the port components in order to preserve the breakout-mode config. + if component.GetPort() == nil { + delete(config.Component, cname) + continue + } + // Need to prune subcomponents that may have a leafref to a component that was + // pruned. + component.Subcomponent = nil + } + } + + if *setEthernetFromState { + for iname, iface := range config.Interface { + if iface.GetEthernet() == nil { + continue + } + // Ethernet config may not contain meaningful values if it wasn't explicitly + // configured, so use its current state for the config, but prune non-config leaves. + intf := gnmi.Get(t, dev, gnmi.OC().Interface(iname).State()) + e := intf.GetEthernet() + if len(intf.GetHardwarePort()) != 0 { + breakout := config.GetComponent(intf.GetHardwarePort()).GetPort().GetBreakoutMode() + e := intf.GetEthernet() + // Set port speed to unknown for non breakout interfaces + if breakout.GetGroup(1) == nil && e != nil { + e.SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_UNKNOWN) + } + } + ygot.PruneConfigFalse(oc.SchemaTree["Interface_Ethernet"], e) + if e.PortSpeed != 0 && e.PortSpeed != oc.IfEthernet_ETHERNET_SPEED_SPEED_UNKNOWN { + iface.Ethernet = e + } + // need to set mac address for mgmt interface to nil + if intf.GetName() == "MgmtEth0/RP0/CPU0/0" || intf.GetName() == "MgmtEth0/RP1/CPU0/0" && deviations.SkipMacaddressCheck(ondatra.DUT(t, "dut")) { + e.MacAddress = nil + } + // need to set mac address for bundle interface to nil + if iface.Ethernet.AggregateId != nil && deviations.SkipMacaddressCheck(ondatra.DUT(t, "dut")) { + iface.Ethernet.MacAddress = nil + continue + } + } + } + + if !*cannotConfigurePortSpeed { + for _, iface := range config.Interface { + if iface.GetEthernet() == nil { + continue + } + iface.GetEthernet().PortSpeed = oc.IfEthernet_ETHERNET_SPEED_UNSET + iface.GetEthernet().DuplexMode = oc.Ethernet_DuplexMode_UNSET + iface.GetEthernet().EnableFlowControl = nil + } + } + + if *pruneLLDP && config.Lldp != nil { + config.Lldp.ChassisId = nil + config.Lldp.ChassisIdType = oc.Lldp_ChassisIdType_UNSET + } + + if *pruneQoS { + config.Qos = nil + } + + pruneUnsupportedPaths(config) + + WriteQuery(t, "Touched", gnmi.OC().Config(), config) + return config +} + +// CopyDeviceConfig returns a deep copy of a device config but refurbishes it enough so it can be +// pushed out again +func CopyDeviceConfig(t testing.TB, dut *ondatra.DUTDevice, config *oc.Root) *oc.Root { + if deviations.SkipMacaddressCheck(dut) { + *setEthernetFromState = false + } + + o, err := ygot.DeepCopy(config) + if err != nil { + t.Fatalf("Cannot copy baseConfig: %v", err) + } + + copy := o.(*oc.Root) + + if *setEthernetFromState { + setEthernetFromBase(t, config, copy) + } + + return copy +} + +func pruneUnsupportedPaths(config *oc.Root) { + for _, ni := range config.NetworkInstance { + ni.Fdb = nil + } +} + +// setEthernetFromBase merges the ethernet config from the interfaces in base config into +// the destination config. +func setEthernetFromBase(t testing.TB, base *oc.Root, config *oc.Root) { + t.Helper() + + for iname, iface := range config.Interface { + eb := base.GetInterface(iname).GetEthernet() + ec := iface.GetOrCreateEthernet() + if eb == nil || ec == nil { + continue + } + if err := ygot.MergeStructInto(ec, eb); err != nil { + t.Errorf("Cannot merge %s ethernet: %v", iname, err) + } + } +} diff --git a/internal/fptest/log.go b/internal/fptest/log.go index 520bc6d4224..498380f3911 100644 --- a/internal/fptest/log.go +++ b/internal/fptest/log.go @@ -15,6 +15,7 @@ package fptest import ( + "flag" "fmt" "log" "os" @@ -24,8 +25,6 @@ import ( "time" "unicode" - "flag" - "github.com/openconfig/featureprofiles/internal/check" "github.com/openconfig/ondatra" "github.com/openconfig/ygnmi/ygnmi" @@ -106,7 +105,7 @@ func LogQuery(t testing.TB, what string, query LoggableQuery, obj ygot.GoStruct) logQuery(t, what, query, obj, true) } -// WriteQuery is like LogQuery but only writes to test outputs dir so it +// WriteQuery is like LogQuery but only writes to test output dir, so it // does not pollute the test log. func WriteQuery(t testing.TB, what string, query LoggableQuery, obj ygot.GoStruct) { t.Helper() diff --git a/internal/fptest/networkinstance.go b/internal/fptest/networkinstance.go index a9168d79a3b..07f85d80cba 100644 --- a/internal/fptest/networkinstance.go +++ b/internal/fptest/networkinstance.go @@ -15,12 +15,10 @@ package fptest import ( - "context" "fmt" "testing" "github.com/openconfig/featureprofiles/internal/deviations" - gpb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -55,50 +53,3 @@ func AssignToNetworkInstance(t testing.TB, d *ondatra.DUTDevice, i string, ni st gnmi.Update(t, d, gnmi.OC().NetworkInstance(ni).Config(), netInst) } } - -// EnableGRIBIUnderNetworkInstance enables GRIBI protocol under network instance. -func EnableGRIBIUnderNetworkInstance(t testing.TB, d *ondatra.DUTDevice, ni string) { - t.Helper() - if ni == "" { - t.Fatalf("Network instance not provided for gRIBI protocol definition") - } - - switch d.Vendor() { - case ondatra.NOKIA: - gpbSetRequest := &gpb.SetRequest{ - Prefix: &gpb.Path{ - Origin: "srl", - }, - Update: []*gpb.Update{{ - Path: &gpb.Path{ - Elem: []*gpb.PathElem{ - { - Name: "network-instance", - Key: map[string]string{"name": ni}, - }, - { - Name: "protocols", - }, - { - Name: "gribi", - }, - { - Name: "admin-state", - }, - }, - }, - Val: &gpb.TypedValue{ - Value: &gpb.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte(`"enable"`), - }, - }, - }}, - } - gnmiClient := d.RawAPIs().GNMI(t) - if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { - t.Fatalf("Enabling Gribi on network-instance %s failed with unexpected error: %v", ni, err) - } - default: - t.Fatalf("Vendor %s does not support 'deviation_explicit_gribi_under_network_instance'", d.Vendor()) - } -} diff --git a/internal/fptest/runtests.go b/internal/fptest/runtests.go index d6aa487f6c8..14d819c91d0 100644 --- a/internal/fptest/runtests.go +++ b/internal/fptest/runtests.go @@ -74,6 +74,8 @@ func testbedPathFromMetadata() (string, error) { mpb.Metadata_TESTBED_DUT_ATE_4LINKS: "atedut_4.testbed", mpb.Metadata_TESTBED_DUT_ATE_9LINKS_LAG: "atedut_9_lag.testbed", mpb.Metadata_TESTBED_DUT_DUT_ATE_2LINKS: "dutdutate.testbed", + mpb.Metadata_TESTBED_DUT_ATE_8LINKS: "atedut_8.testbed", + mpb.Metadata_TESTBED_DUT_400ZR: "dut_400zr.testbed", } testbedFile, ok := testbedToFile[testbed] if !ok { diff --git a/internal/gnoi/gnoi.go b/internal/gnoi/gnoi.go new file mode 100644 index 00000000000..cf7892ecea6 --- /dev/null +++ b/internal/gnoi/gnoi.go @@ -0,0 +1,134 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gnoi provides utilities for interacting with the gNOI API. +package gnoi + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/system" + gpb "github.com/openconfig/gnmi/proto/gnmi" + spb "github.com/openconfig/gnoi/system" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +var ( + daemonProcessNames = map[ondatra.Vendor]map[Daemon]string{ + ondatra.ARISTA: { + GRIBI: "Gribi", + OCAGENT: "Octa", + P4RT: "P4Runtime", + ROUTING: "Bgp-main", + }, + ondatra.CISCO: { + GRIBI: "emsd", + OCAGENT: "emsd", + P4RT: "emsd", + ROUTING: "emsd", + }, + ondatra.JUNIPER: { + GRIBI: "rpd", + P4RT: "p4-switch", + ROUTING: "rpd", + }, + ondatra.NOKIA: { + GRIBI: "sr_grpc_server", + OCAGENT: "sr_oc_mgmt_serv", + P4RT: "sr_grpc_server", + ROUTING: "sr_bgp_mgr", + }, + } +) + +// Daemon is the type of the daemon on the device. +type Daemon string + +const ( + // GRIBI is the gRIBI daemon. + GRIBI Daemon = "GRIBI" + // OCAGENT is the OpenConfig agent daemon. + OCAGENT Daemon = "OCAGENT" + // P4RT is the P4RT daemon. + P4RT Daemon = "P4RT" + // ROUTING is the routing daemon. + ROUTING Daemon = "ROUTING" +) + +// signal type of termination request +const ( + SigTerm = spb.KillProcessRequest_SIGNAL_TERM + SigKill = spb.KillProcessRequest_SIGNAL_KILL + SigHup = spb.KillProcessRequest_SIGNAL_HUP + SigAbort = spb.KillProcessRequest_SIGNAL_ABRT + SigUnspecified = spb.KillProcessRequest_SIGNAL_UNSPECIFIED +) + +// KillProcess terminates the daemon on the DUT. +func KillProcess(t *testing.T, dut *ondatra.DUTDevice, daemon Daemon, signal spb.KillProcessRequest_Signal, restart bool, waitForRestart bool) { + t.Helper() + + daemonName, err := FetchProcessName(dut, daemon) + if err != nil { + t.Fatalf("Daemon %s not defined for vendor %s", daemon, dut.Vendor().String()) + } + pid := system.FindProcessIDByName(t, dut, daemonName) + if pid == 0 { + t.Fatalf("process %s not found on device", daemonName) + } + + gnoiClient := dut.RawAPIs().GNOI(t) + killProcessRequest := &spb.KillProcessRequest{ + Signal: signal, + Name: daemonName, + Pid: uint32(pid), + Restart: restart, + } + gnoiClient.System().KillProcess(context.Background(), killProcessRequest) + + if waitForRestart { + gnmi.WatchAll( + t, + dut.GNMIOpts().WithYGNMIOpts(ygnmi.WithSubscriptionMode(gpb.SubscriptionMode_ON_CHANGE)), + gnmi.OC().System().ProcessAny().State(), + time.Minute, + func(p *ygnmi.Value[*oc.System_Process]) bool { + val, ok := p.Val() + if !ok { + return false + } + return val.GetName() == daemonName && val.GetPid() != pid + }, + ) + } +} + +// FetchProcessName returns the name of the daemon on the DUT based on the vendor. +func FetchProcessName(dut *ondatra.DUTDevice, daemon Daemon) (string, error) { + daemons, ok := daemonProcessNames[dut.Vendor()] + if !ok { + return "", fmt.Errorf("unsupported vendor: %s", dut.Vendor().String()) + } + d, ok := daemons[daemon] + if !ok { + return "", fmt.Errorf("daemon %s not defined for vendor %s", daemon, dut.Vendor().String()) + } + return d, nil +} diff --git a/internal/gribi/gribi.go b/internal/gribi/gribi.go index 699ef907617..a4e84b36354 100644 --- a/internal/gribi/gribi.go +++ b/internal/gribi/gribi.go @@ -208,6 +208,12 @@ func NHEntry(nhIndex uint64, address, instance string, expectedResult fluent.Pro nh = nh.WithIPinIP(opt.Src, opt.Dest). WithNextHopNetworkInstance(opt.VrfName) } + case "Encap": + nh = nh.WithEncapsulateHeader(fluent.IPinIP) + for _, opt := range opts { + nh = nh.WithIPinIP(opt.Src, opt.Dest). + WithNextHopNetworkInstance(opt.VrfName) + } case "VRFOnly": for _, opt := range opts { nh = nh.WithNextHopNetworkInstance(opt.VrfName) @@ -295,6 +301,29 @@ func (c *Client) AddIPv4(t testing.TB, prefix string, nhgIndex uint64, instance, ) } +// AddIPv6 adds an IPv6Entry mapping a prefix to a given next hop group index within a given network instance. +func (c *Client) AddIPv6(t testing.TB, prefix string, nhgIndex uint64, instance, nhgInstance string, expectedResult fluent.ProgrammingResult) { + t.Helper() + ipv6Entry := fluent.IPv6Entry().WithPrefix(prefix). + WithNetworkInstance(instance). + WithNextHopGroup(nhgIndex) + if nhgInstance != "" && nhgInstance != instance { + ipv6Entry.WithNextHopGroupNetworkInstance(nhgInstance) + } + c.fluentC.Modify().AddEntry(t, ipv6Entry) + if err := c.AwaitTimeout(context.Background(), t, timeout); err != nil { + t.Fatalf("Error waiting to add IPv6: %v", err) + } + chk.HasResult(t, c.fluentC.Results(t), + fluent.OperationResult(). + WithIPv6Operation(prefix). + WithOperationType(constants.Add). + WithProgrammingResult(expectedResult). + AsResult(), + chk.IgnoreOperationID(), + ) +} + // DeleteIPv4 deletes an IPv4Entry within a network instance, given the route's prefix func (c *Client) DeleteIPv4(t testing.TB, prefix string, instance string, expectedResult fluent.ProgrammingResult) { t.Helper() @@ -313,6 +342,24 @@ func (c *Client) DeleteIPv4(t testing.TB, prefix string, instance string, expect ) } +// DeleteIPv6 deletes an IPv6Entry within a network instance, given the route's prefix +func (c *Client) DeleteIPv6(t testing.TB, prefix string, instance string, expectedResult fluent.ProgrammingResult) { + t.Helper() + ipv6Entry := fluent.IPv6Entry().WithPrefix(prefix).WithNetworkInstance(instance) + c.fluentC.Modify().DeleteEntry(t, ipv6Entry) + if err := c.AwaitTimeout(context.Background(), t, timeout); err != nil { + t.Fatalf("Error waiting to delete IPv6: %v", err) + } + chk.HasResult(t, c.fluentC.Results(t), + fluent.OperationResult(). + WithIPv6Operation(prefix). + WithOperationType(constants.Delete). + WithProgrammingResult(expectedResult). + AsResult(), + chk.IgnoreOperationID(), + ) +} + // FlushAll flushes all the gribi entries func (c *Client) FlushAll(t testing.TB) { if err := FlushAll(c.fluentC); err != nil { diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go index cb2fefb4228..a0a13183ca1 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -16,6 +16,7 @@ package helpers import ( + "context" "fmt" "sort" "strings" @@ -35,7 +36,7 @@ import ( // When CheckInterfacesInBinding is set to true, all interfaces that are not defined in binding file are excluded. func FetchOperStatusUPIntfs(t *testing.T, dut *ondatra.DUTDevice, checkInterfacesInBinding bool) []string { t.Helper() - intfsOperStatusUP := []string{} + var intfsOperStatusUP []string intfs := gnmi.GetAll(t, dut, gnmi.OC().InterfaceAny().Name().State()) bindedIntf := make(map[string]bool) for _, port := range dut.Ports() { @@ -121,3 +122,35 @@ func GNMINotifString(n *gpb.Notification) string { } return build.String() } + +// GnmiCLIConfig sets config built with buildCliConfigRequest. +func GnmiCLIConfig(t testing.TB, dut *ondatra.DUTDevice, config string) { + gnmiClient := dut.RawAPIs().GNMI(t) + gpbSetRequest, err := buildCliConfigRequest(config) + if err != nil { + t.Fatalf("Cannot build a gNMI SetRequest: %v", err) + } + + t.Log("gnmiClient Set CLI config") + if _, err = gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmiClient.Set() with unexpected error: %v", err) + } +} + +// buildCliConfigRequest Build config with Origin set to cli and Ascii encoded config. +func buildCliConfigRequest(config string) (*gpb.SetRequest, error) { + 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, nil +} diff --git a/internal/iputil/iputil.go b/internal/iputil/iputil.go new file mode 100644 index 00000000000..a9a291eab9b --- /dev/null +++ b/internal/iputil/iputil.go @@ -0,0 +1,43 @@ +// 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 iputil provides utilities for IPv4/IPv6 related utils +package iputil + +import ( + "encoding/binary" + "fmt" + "net" +) + +// GenerateIPs creates list of n IPs using ipBlock +func GenerateIPs(ipBlock string, n int) []string { + var entries []string + _, netCIDR, err := net.ParseCIDR(ipBlock) + if err != nil { + return entries + } + netMask := binary.BigEndian.Uint32(netCIDR.Mask) + firstIP := binary.BigEndian.Uint32(netCIDR.IP) + lastIP := (firstIP & netMask) | (netMask ^ 0xffffffff) + + for i := firstIP; i <= lastIP && n > 0; i++ { + ip := make(net.IP, 4) + binary.BigEndian.PutUint32(ip, i) + entries = append(entries, fmt.Sprint(ip)) + n-- + } + + return entries +} diff --git a/internal/iputil/iputil_test.go b/internal/iputil/iputil_test.go new file mode 100644 index 00000000000..6e77738492b --- /dev/null +++ b/internal/iputil/iputil_test.go @@ -0,0 +1,45 @@ +package iputil + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestGenerateIPs(t *testing.T) { + tests := []struct { + name string + prefix string + count int + want []string + }{{ + name: "IPv4/24", + prefix: "192.168.0.0/24", + count: 3, + want: []string{"192.168.0.0", "192.168.0.1", "192.168.0.2"}, + }, { + name: "IPv4/31", + prefix: "192.168.0.0/31", + count: 3, + want: []string{"192.168.0.0", "192.168.0.1"}, + }, { + name: "Invalid prefix", + prefix: "192.168.0.0/24/24", + count: 3, + want: nil, + }, { + name: "Invalid count", + prefix: "192.168.0.0/24", + count: 0, + want: nil, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GenerateIPs(tt.prefix, tt.count) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("GenerateIPs() returned diff (-want +got):\n%s", diff) + } + }) + } +} diff --git a/feature/experimental/isis/otg_tests/internal/session/session.go b/internal/isissession/isissession.go similarity index 96% rename from feature/experimental/isis/otg_tests/internal/session/session.go rename to internal/isissession/isissession.go index c574676393e..3fc1dae70a8 100644 --- a/feature/experimental/isis/otg_tests/internal/session/session.go +++ b/internal/isissession/isissession.go @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package session is deprecated and scoped only to be used with +// Package isissession is deprecated and scoped only to be used with // feature/experimental/isis/ate_tests/*. Do not use elsewhere. -package session +package isissession import ( "context" @@ -24,6 +24,7 @@ import ( "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/ondatra" @@ -57,10 +58,8 @@ const ( ) var ( - // DUTNET is the Network Entity Title for the DUT - DUTNET = fmt.Sprintf("%v.%v.00", DUTAreaAddress, DUTSysID) // DUTISISAttrs has attributes for the DUT ISIS connection on port1 - DUTISISAttrs = &Attributes{ + DUTISISAttrs = &attrs.Attributes{ Desc: "DUT to ATE with IS-IS", IPv4: "192.0.2.1", IPv6: "2001:db8::1", @@ -68,7 +67,7 @@ var ( IPv6Len: pLen6, } // ATEISISAttrs has attributes for the ATE ISIS connection on port1 - ATEISISAttrs = &Attributes{ + ATEISISAttrs = &attrs.Attributes{ Name: "port1", Desc: "ATE to DUT with IS-IS", MAC: "02:11:01:00:00:01", @@ -78,7 +77,7 @@ var ( IPv6Len: pLen6, } // DUTTrafficAttrs has attributes for the DUT end of the traffic connection (port2) - DUTTrafficAttrs = &Attributes{ + DUTTrafficAttrs = &attrs.Attributes{ Desc: "DUT to ATE secondary link", IPv4: "192.0.2.5", IPv6: "2001:db8::5", @@ -86,7 +85,7 @@ var ( IPv6Len: pLen6, } // ATETrafficAttrs has attributes for the ATE end of the traffic connection (port2) - ATETrafficAttrs = &Attributes{ + ATETrafficAttrs = &attrs.Attributes{ Name: "port2", Desc: "ATE to DUT secondary link", MAC: "02:12:01:00:00:01", @@ -201,8 +200,8 @@ func New(t testing.TB) (*TestSession, error) { s.DUTPort2 = s.DUT.Port(t, "port2") s.DUTConf = &oc.Root{} // configure dut ports - DUTISISAttrs.ConfigInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort1.Name()), s.DUT) - DUTTrafficAttrs.ConfigInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort2.Name()), s.DUT) + DUTISISAttrs.ConfigOCInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort1.Name()), s.DUT) + DUTTrafficAttrs.ConfigOCInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort2.Name()), 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 new file mode 100644 index 00000000000..862d5381fa6 --- /dev/null +++ b/internal/otg_helpers/otg_config_helpers/otgconfighelpers.go @@ -0,0 +1,115 @@ +// Package otgconfighelpers provides helper functions to setup Protocol configurations on traffic generators. +package otgconfighelpers + +import ( + "testing" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/ondatra" +) + +/* +Port is a struct to hold aggregate/physical port data. + +Usage for Aggregate port: + + agg1 = &otgconfighelpers.Port{ + Name: "Port-Channel1", + AggMAC: "02:00:01:01:01:07", + Interfaces: []*otgconfighelpers.InterfaceProperties{interface1, interface2, interface3, interface4, interface5, interface6}, + MemberPorts: []string{"port1", "port2"}, + LagID: 1, + Islag: true, + } + +Usage for Physical port: + + phy1 = &otgconfighelpers.Port{ + Name: "port1", + Interfaces: []*otgconfighelpers.InterfaceProperties{interface1, interface2, interface3, interface4, interface5, interface6}, + } + +Interface properties has attributes required for creating interfaces/subinterfaces on the port.. + + interface1 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.12", + IPv4Gateway: "169.254.0.11", + Name: "Port-Channel1.20", + Vlan: 20, + IPv4Len: 29, + Mac: "02:00:01:01:01:08", + } +*/ +type Port struct { + Name string + AggMAC string + Interfaces []*InterfaceProperties + MemberPorts []string + LagID uint32 + Islag bool +} + +// InterfaceProperties is a struct to hold interface data. +type InterfaceProperties struct { + IPv4 string + IPv4Gateway string + IPv6 string + IPv6Gateway string + Name string + Vlan uint32 + IPv4Len uint32 + IPv6Len uint32 + 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) + } else { + top.Ports().Add().SetName(a.Name) + } + for _, intf := range a.Interfaces { + ConfigureOtgInterface(t, top, intf, a) + } +} + +// ConfigureOtgLag configures the aggregate port. +func ConfigureOtgLag(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, a *Port) { + 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) + } +} + +// ConfigureOtgLagMemberPort configures the member port in the LAG. +func ConfigureOtgLagMemberPort(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) { + dev := top.Devices().Add().SetName(intf.Name + ".Dev") + 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) + } + // VLAN configuration + if intf.Vlan != 0 { + eth.Vlans().Add().SetName(intf.Name + ".vlan").SetId(intf.Vlan) + } + // IP address configuration + if intf.IPv4 != "" { + eth.Ipv4Addresses().Add().SetName(intf.Name + ".IPv4").SetAddress(intf.IPv4).SetGateway(intf.IPv4Gateway).SetPrefix(intf.IPv4Len) + } + if intf.IPv6 != "" { + eth.Ipv6Addresses().Add().SetName(intf.Name + ".IPv6").SetAddress(intf.IPv6).SetGateway(intf.IPv6Gateway).SetPrefix(intf.IPv6Len) + } +} diff --git a/internal/otg_helpers/otg_config_helpers/otgflowhelpers.go b/internal/otg_helpers/otg_config_helpers/otgflowhelpers.go new file mode 100644 index 00000000000..23ffc578c2d --- /dev/null +++ b/internal/otg_helpers/otg_config_helpers/otgflowhelpers.go @@ -0,0 +1,215 @@ +package otgconfighelpers + +import ( + "github.com/open-traffic-generator/snappi/gosnappi" +) + +// Iana Ethertype is the IANA Ethertype for MPLS, IPv4 and IPv6. +const ( + IanaMPLSEthertype = 34887 + IanaIPv4Ethertype = 2048 + IanaIPv6Ethertype = 34525 +) + +/* +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() +*/ +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 +} + +// GREFlowParams is a struct to hold Ethernet traffic parameters. +type GREFlowParams struct { + Protocol uint32 +} + +// VLANFlowParams is a struct to hold VLAN traffic parameters. +type VLANFlowParams struct { + VLANId uint32 +} + +// EthFlowParams is a struct to hold Ethernet traffic parameters. +type EthFlowParams struct { + SrcMAC string + DstMAC string + SrcMACCount uint32 +} + +// IPv4FlowParams is a struct to hold IPv4 traffic parameters. +type IPv4FlowParams struct { + IPv4Src string + IPv4Dst string + IPv4SrcCount uint32 + IPv4DstCount uint32 + TTL uint32 +} + +// IPv6FlowParams is a struct to hold IPv6 traffic parameters. +type IPv6FlowParams struct { + IPv6Src string + IPv6Dst string + IPv6SrcCount uint32 + IPv6DstCount uint32 + HopLimit uint32 +} + +// TCPFlowParams is a struct to hold TCP traffic parameters. +type TCPFlowParams struct { + TCPSrcPort uint32 + TCPDstPort uint32 + TCPSrcCount uint32 + TCPDstCount uint32 +} + +// UDPFlowParams is a struct to hold UDP traffic parameters. +type UDPFlowParams struct { + UDPSrcPort uint32 + UDPDstPort uint32 + UDPSrcCount uint32 + UDPDstCount uint32 +} + +// MPLSFlowParams is a struct to hold MPLS traffic parameters. +type MPLSFlowParams struct { + MPLSLabel uint32 + MPLSExp uint32 +} + +// 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) +} + +// 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) +} + +// AddGREHeader adds a GRE header to the flow. +func (f *Flow) AddGREHeader() { + greHdr := f.flow.Packet().Add().Gre() + switch f.GREFlow.Protocol { + case IanaMPLSEthertype: + greHdr.Protocol().SetValue(IanaMPLSEthertype) + case IanaIPv4Ethertype: + greHdr.Protocol().SetValue(IanaIPv4Ethertype) + case IanaIPv6Ethertype: + greHdr.Protocol().SetValue(IanaIPv6Ethertype) + default: + greHdr.Protocol().SetValue(IanaIPv4Ethertype) + } +} + +// AddVlanHeader adds a VLAN header to the flow. +func (f *Flow) AddVlanHeader() { + 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) +} + +// AddIPv4Header adds an IPv4 header to the flow. +func (f *Flow) AddIPv4Header() { + ipv4Hdr := f.flow.Packet().Add().Ipv4() + if f.IPv4Flow.IPv4SrcCount != 0 { + ipv4Hdr.Src().Increment().SetStart(f.IPv4Flow.IPv4Src).SetCount(f.IPv4Flow.IPv4SrcCount) + } else { + ipv4Hdr.Src().SetValue(f.IPv4Flow.IPv4Src) + } + if f.IPv4Flow.IPv4DstCount != 0 { + ipv4Hdr.Dst().Increment().SetStart(f.IPv4Flow.IPv4Dst).SetCount(f.IPv4Flow.IPv4DstCount) + } else { + ipv4Hdr.Dst().SetValue(f.IPv4Flow.IPv4Dst) + } + if f.IPv4Flow.TTL != 0 { + ipv4Hdr.TimeToLive().SetValue(f.IPv4Flow.TTL) + } +} + +// AddIPv6Header adds an IPv6 header to the flow. +func (f *Flow) AddIPv6Header() { + ipv6Hdr := f.flow.Packet().Add().Ipv6() + if f.IPv6Flow.IPv6SrcCount != 0 { + ipv6Hdr.Src().Increment().SetStart(f.IPv6Flow.IPv6Src).SetCount(f.IPv6Flow.IPv6SrcCount) + } else { + ipv6Hdr.Src().SetValue(f.IPv6Flow.IPv6Src) + } + if f.IPv6Flow.IPv6DstCount != 0 { + ipv6Hdr.Dst().Increment().SetStart(f.IPv6Flow.IPv6Dst).SetCount(f.IPv6Flow.IPv6DstCount) + } else { + ipv6Hdr.Dst().SetValue(f.IPv6Flow.IPv6Dst) + } + if f.IPv6Flow.HopLimit != 0 { + ipv6Hdr.HopLimit().SetValue(f.IPv6Flow.HopLimit) + } +} + +// AddTCPHeader adds a TCP header to the flow. +func (f *Flow) AddTCPHeader() { + tcpHdr := f.flow.Packet().Add().Tcp() + if f.TCPFlow.TCPSrcCount != 0 { + tcpHdr.SrcPort().Increment().SetStart(f.TCPFlow.TCPSrcPort).SetCount(f.TCPFlow.TCPSrcCount) + } else { + tcpHdr.SrcPort().SetValue(f.TCPFlow.TCPSrcPort) + } + if f.TCPFlow.TCPDstCount != 0 { + tcpHdr.DstPort().Increment().SetStart(f.TCPFlow.TCPDstPort).SetCount(f.TCPFlow.TCPDstCount) + } else { + tcpHdr.DstPort().SetValue(f.TCPFlow.TCPDstPort) + } +} + +// AddUDPHeader adds a UDP header to the flow. +func (f *Flow) AddUDPHeader() { + udpHdr := f.flow.Packet().Add().Udp() + if f.UDPFlow.UDPSrcCount != 0 { + udpHdr.SrcPort().Increment().SetStart(f.UDPFlow.UDPSrcPort).SetCount(f.UDPFlow.UDPSrcCount) + } else { + udpHdr.SrcPort().SetValue(f.UDPFlow.UDPSrcPort) + } + if f.UDPFlow.UDPDstCount != 0 { + udpHdr.DstPort().Increment().SetStart(f.UDPFlow.UDPDstPort).SetCount(f.UDPFlow.UDPDstCount) + } else { + udpHdr.DstPort().SetValue(f.UDPFlow.UDPDstPort) + } +} diff --git a/internal/otg_helpers/otg_validation_helpers/otgvalidationhelpers.go b/internal/otg_helpers/otg_validation_helpers/otgvalidationhelpers.go new file mode 100644 index 00000000000..bbac0d33fe8 --- /dev/null +++ b/internal/otg_helpers/otg_validation_helpers/otgvalidationhelpers.go @@ -0,0 +1,102 @@ +// Package otgvalidationhelpers provides helper functions to validate OTG attributes for OTG tests. +package otgvalidationhelpers + +import ( + "fmt" + "testing" + "time" + + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ygnmi/ygnmi" +) + +/* +OTGValidation is a struct to hold OTG validation parameters. + + params := &OTGValidation{ + Interface: &InterfaceParams{Names: []string{"Interface1", "Interface2"}, Ports: []string{"Port1", "Port2"}}, + Flow: &FlowParams{Name: "flow1", TolerancePct: 0.5}, + } + + if err := params.ValidatePortIsActive(t, ate); err != nil { + t.Errorf("ValidatePortIsActive(): got err: %q, want nil", err) + } + if err := params.IsIPv4Interfaceresolved(t, ate); err != nil { + t.Errorf("IsIPv4Interfaceresolved(): got err: %q, want nil", err) + } + if err := params.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } +*/ +type OTGValidation struct { + Interface *InterfaceParams + Flow *FlowParams +} + +// InterfaceParams is a struct to hold OTG interface parameters. +type InterfaceParams struct { + Names []string + Ports []string +} + +// FlowParams is a struct to hold OTG flow parameters. +type FlowParams struct { + Name string + TolerancePct float32 +} + +// IsIPv4Interfaceresolved validates that the IPv4 interface is resolved based on the interface configured using otgconfighelpers. +func (v *OTGValidation) IsIPv4Interfaceresolved(t *testing.T, ate *ondatra.ATEDevice) error { + for _, intf := range v.Interface.Names { + val1, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().Interface(intf+".Eth").Ipv4NeighborAny().LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + return val.IsPresent() + }).Await(t) + if !ok { + return fmt.Errorf(`IPv4 %s gateway not resolved`, intf) + } + t.Logf(`IPv4 %s gateway resolved to: %s`, intf, val1) + } + return nil +} + +// IsIPv6Interfaceresolved validates that the IPv6 interface is resolved based on the interface configured using otgconfighelpers. +func (v *OTGValidation) IsIPv6Interfaceresolved(t *testing.T, ate *ondatra.ATEDevice) error { + for _, intf := range v.Interface.Names { + val1, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().Interface(intf+".Eth").Ipv6NeighborAny().LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + return val.IsPresent() + }).Await(t) + if !ok { + return fmt.Errorf(`IPv6 %s gateway not resolved`, intf) + } + t.Logf(`IPv6 %s gateway resolved to: %s`, intf, val1) + } + return nil +} + +// ValidateLossOnFlows validates the percentage of traffic loss on the flows. +func (v *OTGValidation) ValidateLossOnFlows(t *testing.T, ate *ondatra.ATEDevice) error { + 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) + if lossPct > v.Flow.TolerancePct { + return fmt.Errorf("Get(traffic loss for flow %q): got %v percent, want < %v percent", v.Flow.Name, lossPct, v.Flow.TolerancePct) + } + t.Logf("Flow %q, inPkts %d, outPkts %d, lossPct %v", v.Flow.Name, inPkts, outPkts, lossPct) + return nil +} + +// 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) + } + } + return nil +} diff --git a/internal/otgutils/actions.go b/internal/otgutils/actions.go index 0f140bfdd57..9ed8f136f98 100644 --- a/internal/otgutils/actions.go +++ b/internal/otgutils/actions.go @@ -37,11 +37,21 @@ func GetFlowStats(t testing.TB, otg *otg.OTG, flowName string, timeout time.Dura txPkts := gnmi.Get(t, otg, gnmi.OTG().Flow(flowName).Counters().OutPkts().State()) rxPkts, _ := gnmi.Watch(t, otg, gnmi.OTG().Flow(flowName).Counters().InPkts().State(), timeout, func(val *ygnmi.Value[uint64]) bool { - rxPackets, ok := val.Val() - return ok && rxPackets == txPkts + rxPackets, present := val.Val() + return present && rxPackets == txPkts }).Await(t) + if rxPkts == nil { + return txPkts, 0 + } rx, _ := rxPkts.Val() return txPkts, rx } + +// GetFlowLossPct checks to see if all the flows are completely stopped and +// returns the loss percentage for the given flow +func GetFlowLossPct(t testing.TB, otg *otg.OTG, flowName string, timeout time.Duration) (lossPct float64) { + tx, rx := GetFlowStats(t, otg, flowName, timeout) + return (float64(tx) - float64(rx)) * 100 / float64(tx) +} diff --git a/internal/otgutils/arp.go b/internal/otgutils/arp.go index ee5c0d1440e..7ac5512a36a 100644 --- a/internal/otgutils/arp.go +++ b/internal/otgutils/arp.go @@ -22,14 +22,14 @@ func WaitForARP(t *testing.T, otg *otg.OTG, c gosnappi.Config, ipType string) { for _, intf := range intfs { switch ipType { case "IPv4": - got, ok := gnmi.WatchAll(t, otg, gnmi.OTG().Interface(intf).Ipv4NeighborAny().LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + got, ok := gnmi.WatchAll(t, otg, gnmi.OTG().Interface(intf).Ipv4NeighborAny().LinkLayerAddress().State(), 2*time.Minute, func(val *ygnmi.Value[string]) bool { return val.IsPresent() }).Await(t) if !ok { t.Fatalf("Did not receive OTG Neighbor entry for interface %s, last got: %v", intf, got) } case "IPv6": - got, ok := gnmi.WatchAll(t, otg, gnmi.OTG().Interface(intf).Ipv6NeighborAny().LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + got, ok := gnmi.WatchAll(t, otg, gnmi.OTG().Interface(intf).Ipv6NeighborAny().LinkLayerAddress().State(), 2*time.Minute, func(val *ygnmi.Value[string]) bool { return val.IsPresent() }).Await(t) if !ok { diff --git a/internal/p4rtutils/p4rtutils.go b/internal/p4rtutils/p4rtutils.go index 33e8c8bc9be..2972aeaef95 100644 --- a/internal/p4rtutils/p4rtutils.go +++ b/internal/p4rtutils/p4rtutils.go @@ -25,12 +25,10 @@ import ( "github.com/cisco-open/go-p4/p4rt_client" "github.com/golang/glog" - "github.com/openconfig/featureprofiles/internal/args" - "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" - p4_v1 "github.com/p4lang/p4runtime/go/p4/v1" + p4V1 "github.com/p4lang/p4runtime/go/p4/v1" ) // Some hardcoding to simplify things @@ -53,7 +51,7 @@ var ( // ACLWbbIngressTableEntryInfo defines struct for wbb acl table type ACLWbbIngressTableEntryInfo struct { - Type p4_v1.Update_Type + Type p4V1.Update_Type IsIpv4 uint8 IsIpv6 uint8 EtherType uint16 @@ -67,40 +65,40 @@ type ACLWbbIngressTableEntryInfo struct { } // Filling up P4RT Structs is a bit cumbersome, wrap things to simplify -func aclWbbIngressTableEntryGet(info *ACLWbbIngressTableEntryInfo) *p4_v1.Update { +func aclWbbIngressTableEntryGet(info *ACLWbbIngressTableEntryInfo) *p4V1.Update { if info == nil { glog.Fatal("Nil info") } - matchFields := []*p4_v1.FieldMatch{} + var matchFields []*p4V1.FieldMatch if info.IsIpv4 > 0 { - matchFields = append(matchFields, &p4_v1.FieldMatch{ + matchFields = append(matchFields, &p4V1.FieldMatch{ FieldId: WbbMatchMap["is_ipv4"], - FieldMatchType: &p4_v1.FieldMatch_Optional_{ - Optional: &p4_v1.FieldMatch_Optional{ - Value: []byte{byte(info.IsIpv4)}, + FieldMatchType: &p4V1.FieldMatch_Optional_{ + Optional: &p4V1.FieldMatch_Optional{ + Value: []byte{info.IsIpv4}, }, }, }) } if info.IsIpv6 > 0 { - matchFields = append(matchFields, &p4_v1.FieldMatch{ + matchFields = append(matchFields, &p4V1.FieldMatch{ FieldId: WbbMatchMap["is_ipv6"], - FieldMatchType: &p4_v1.FieldMatch_Optional_{ - Optional: &p4_v1.FieldMatch_Optional{ - Value: []byte{byte(info.IsIpv6)}, + FieldMatchType: &p4V1.FieldMatch_Optional_{ + Optional: &p4V1.FieldMatch_Optional{ + Value: []byte{info.IsIpv6}, }, }, }) } if info.EtherTypeMask > 0 { - matchFields = append(matchFields, &p4_v1.FieldMatch{ + matchFields = append(matchFields, &p4V1.FieldMatch{ FieldId: WbbMatchMap["ether_type"], - FieldMatchType: &p4_v1.FieldMatch_Ternary_{ - Ternary: &p4_v1.FieldMatch_Ternary{ + FieldMatchType: &p4V1.FieldMatch_Ternary_{ + Ternary: &p4V1.FieldMatch_Ternary{ Value: []byte{ byte(info.EtherType >> 8), byte(info.EtherType & 0xFF), @@ -115,22 +113,22 @@ func aclWbbIngressTableEntryGet(info *ACLWbbIngressTableEntryInfo) *p4_v1.Update } if info.TTLMask > 0 { - matchFields = append(matchFields, &p4_v1.FieldMatch{ + matchFields = append(matchFields, &p4V1.FieldMatch{ FieldId: WbbMatchMap["ttl"], - FieldMatchType: &p4_v1.FieldMatch_Ternary_{ - Ternary: &p4_v1.FieldMatch_Ternary{ - Value: []byte{byte(info.TTL)}, - Mask: []byte{byte(info.TTLMask)}, + FieldMatchType: &p4V1.FieldMatch_Ternary_{ + Ternary: &p4V1.FieldMatch_Ternary{ + Value: []byte{info.TTL}, + Mask: []byte{info.TTLMask}, }, }, }) } if info.OuterVlanIDMask > 0 { - matchFields = append(matchFields, &p4_v1.FieldMatch{ + matchFields = append(matchFields, &p4V1.FieldMatch{ FieldId: WbbMatchMap["outer_vlan_id"], - FieldMatchType: &p4_v1.FieldMatch_Ternary_{ - Ternary: &p4_v1.FieldMatch_Ternary{ + FieldMatchType: &p4V1.FieldMatch_Ternary_{ + Ternary: &p4V1.FieldMatch_Ternary{ Value: []byte{ byte((info.OuterVlanID >> 8) & 0xF), byte(info.OuterVlanID & 0xFF), @@ -144,16 +142,16 @@ func aclWbbIngressTableEntryGet(info *ACLWbbIngressTableEntryInfo) *p4_v1.Update }) } - update := &p4_v1.Update{ + update := &p4V1.Update{ Type: info.Type, - Entity: &p4_v1.Entity{ - Entity: &p4_v1.Entity_TableEntry{ - TableEntry: &p4_v1.TableEntry{ + Entity: &p4V1.Entity{ + Entity: &p4V1.Entity_TableEntry{ + TableEntry: &p4V1.TableEntry{ TableId: WbbTableMap["acl_wbb_ingress_table"], Match: matchFields, - Action: &p4_v1.TableAction{ - Type: &p4_v1.TableAction_Action{ - Action: &p4_v1.Action{ + Action: &p4V1.TableAction{ + Type: &p4V1.TableAction_Action{ + Action: &p4V1.Action{ ActionId: WbbActionsMap["acl_wbb_ingress_trap"], }, }, @@ -174,8 +172,8 @@ func aclWbbIngressTableEntryGet(info *ACLWbbIngressTableEntryInfo) *p4_v1.Update } // ACLWbbIngressTableEntryGet returns acl table updates -func ACLWbbIngressTableEntryGet(infoList []*ACLWbbIngressTableEntryInfo) []*p4_v1.Update { - var updates []*p4_v1.Update +func ACLWbbIngressTableEntryGet(infoList []*ACLWbbIngressTableEntryInfo) []*p4V1.Update { + var updates []*p4V1.Update for _, info := range infoList { updates = append(updates, aclWbbIngressTableEntryGet(info)) @@ -184,21 +182,10 @@ func ACLWbbIngressTableEntryGet(infoList []*ACLWbbIngressTableEntryInfo) []*p4_v return updates } -func explicitP4RTNodes() map[string]string { - return map[string]string{ - "port1": *args.P4RTNodeName1, - "port2": *args.P4RTNodeName2, - } -} - // P4RTNodesByPort returns a map of : for the reserved ondatra // ports using the component and the interface OC tree. func P4RTNodesByPort(t testing.TB, dut *ondatra.DUTDevice) map[string]string { t.Helper() - if deviations.ExplicitP4RTNodeComponent(dut) { - return explicitP4RTNodes() - } - ports := make(map[string][]string) // :[] for _, p := range dut.Ports() { hp := gnmi.Lookup(t, dut, gnmi.OC().Interface(p.Name()).HardwarePort().State()) diff --git a/internal/rundata/local.go b/internal/rundata/local.go index 1e22bcb90b1..52ad488dd94 100644 --- a/internal/rundata/local.go +++ b/internal/rundata/local.go @@ -16,6 +16,7 @@ package rundata import ( "errors" + "flag" "fmt" "os" "path/filepath" @@ -24,9 +25,7 @@ import ( "strings" "time" - "flag" - - gitv5 "github.com/go-git/go-git/v5" + gitV5 "github.com/go-git/go-git/v5" "github.com/golang/glog" ) @@ -49,7 +48,7 @@ func buildInfo(m map[string]string) { } // gitOrigin returns the fetch URL of the "origin" remote. -func gitOrigin(repo *gitv5.Repository) (string, error) { +func gitOrigin(repo *gitV5.Repository) (string, error) { origin, err := repo.Remote("origin") if err != nil { return "", err @@ -62,7 +61,7 @@ func gitOrigin(repo *gitv5.Repository) (string, error) { } // gitHead returns the commit hash and the commit timestamp at HEAD. -func gitHead(repo *gitv5.Repository) (string, time.Time, error) { +func gitHead(repo *gitV5.Repository) (string, time.Time, error) { var zero time.Time head, err := repo.Head() if err != nil { @@ -77,7 +76,7 @@ func gitHead(repo *gitv5.Repository) (string, time.Time, error) { // gitInfoWithRepo populates the git properties from a given git repo // and returns the path to the working directory. -func gitInfoWithRepo(m map[string]string, repo *gitv5.Repository) string { +func gitInfoWithRepo(m map[string]string, repo *gitV5.Repository) string { wt, err := repo.Worktree() if err != nil { return "" @@ -117,7 +116,7 @@ func gitInfo(m map[string]string) string { if err != nil { return "" } - repo, err := gitv5.PlainOpenWithOptions(cwd, &gitv5.PlainOpenOptions{ + repo, err := gitV5.PlainOpenWithOptions(cwd, &gitV5.PlainOpenOptions{ DetectDotGit: true, }) if err != nil { @@ -128,18 +127,18 @@ func gitInfo(m map[string]string) string { // fpPath returns the package path of a test file path under the // featureprofiles repo. -func fpPath(testpath string) string { +func fpPath(testPath string) string { const part = "/featureprofiles/" - i := strings.LastIndex(testpath, part) + i := strings.LastIndex(testPath, part) if i < 0 { return "" } i += len(part) - j := strings.LastIndexByte(testpath, '/') + j := strings.LastIndexByte(testPath, '/') if j < 0 || j < i { return "" } - return testpath[i:j] + return testPath[i:j] } // testPath detects the relative path of the test to the base of the diff --git a/internal/rundata/local_test.go b/internal/rundata/local_test.go index 87a1a0e3f13..4670fedcb43 100644 --- a/internal/rundata/local_test.go +++ b/internal/rundata/local_test.go @@ -15,6 +15,7 @@ package rundata import ( + "flag" "fmt" "os/exec" "runtime/debug" @@ -22,10 +23,8 @@ import ( "testing" "time" - "flag" - "github.com/go-git/go-billy/v5/memfs" - gitv5 "github.com/go-git/go-git/v5" + gitV5 "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" @@ -55,10 +54,10 @@ func TestBuildInfo(t *testing.T) { } } -func newGitRepo() (*gitv5.Repository, error) { +func newGitRepo() (*gitV5.Repository, error) { // Use repo.Storer to get the object storer, and // repo.Worktree().Filesystem to get the worktree. - return gitv5.Init( + return gitV5.Init( memory.NewStorage(), memfs.New(), ) @@ -80,7 +79,7 @@ var commitSignature = &object.Signature{ When: time.Now().Round(time.Second), // Git only keeps time in seconds. } -func addCommit(repo *gitv5.Repository) (plumbing.Hash, error) { +func addCommit(repo *gitV5.Repository) (plumbing.Hash, error) { var emptyHash plumbing.Hash wt, err := repo.Worktree() @@ -97,7 +96,7 @@ func addCommit(repo *gitv5.Repository) (plumbing.Hash, error) { f.Close() wt.Add("foo") - return wt.Commit("commit message", &gitv5.CommitOptions{ + return wt.Commit("commit message", &gitV5.CommitOptions{ Author: commitSignature, }) } @@ -317,7 +316,7 @@ func TestGitInfo(t *testing.T) { got := make(map[string]string) gotWd := gitInfo(got) - if gotWd != string(wantWd) { + if gotWd != wantWd { t.Errorf("gitInfo got %q, want %q", gotWd, wantWd) } t.Log(got) diff --git a/internal/rundata/rundata.go b/internal/rundata/rundata.go index c41853a414b..21aa7333ea5 100644 --- a/internal/rundata/rundata.go +++ b/internal/rundata/rundata.go @@ -51,13 +51,12 @@ package rundata import ( "context" + "flag" "fmt" "sort" "strings" "time" - "flag" - "github.com/openconfig/featureprofiles/internal/metadata" "github.com/openconfig/ondatra/binding" ) diff --git a/internal/samplestream/samplestream.go b/internal/samplestream/samplestream.go new file mode 100644 index 00000000000..ed8160c6286 --- /dev/null +++ b/internal/samplestream/samplestream.go @@ -0,0 +1,83 @@ +// Package samplestream provides utilities for creating gNMI Subscriptions in SAMPLE mode. +package samplestream + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/openconfig/ondatra" + "github.com/openconfig/ygnmi/ygnmi" + + gpb "github.com/openconfig/gnmi/proto/gnmi" +) + +const ( + intervalTolerance = time.Second +) + +// SampleStream represents a gNMI Subscription with SAMPLE mode. +type SampleStream[T any] struct { + dataMu sync.Mutex // Lock that protects the received data and the next channel. + lastVal *ygnmi.Value[T] // Holds the last received sample. + data []*ygnmi.Value[T] // Data received from gNMI call. + cancel context.CancelFunc // Cancels the subscription. + interval time.Duration // Configured interval for the SAMPLE mode stream. +} + +// New creates a new SampleStream. +func New[T any](t *testing.T, dut *ondatra.DUTDevice, q ygnmi.SingletonQuery[T], interval time.Duration) *SampleStream[T] { + ctx, cancel := context.WithCancel(context.Background()) + s := &SampleStream[T]{ + dataMu: sync.Mutex{}, + cancel: cancel, + interval: interval, + } + + c, err := ygnmi.NewClient(dut.RawAPIs().GNMI(t), ygnmi.WithTarget(dut.ID())) + if err != nil { + t.Fatalf("unable to connect to gNMI on %s: %v", dut.ID(), err) + } + ygnmi.Watch(ctx, c, q, func(v *ygnmi.Value[T]) error { + s.dataMu.Lock() + defer s.dataMu.Unlock() + if !v.IsPresent() { + return ygnmi.Continue + } + s.data = append(s.data, v) + s.lastVal = v + return ygnmi.Continue + }, ygnmi.WithSubscriptionMode(gpb.SubscriptionMode_SAMPLE), ygnmi.WithSampleInterval(interval)) + return s +} + +// Next returns the next sample received within the sample interval. +// If no sample is received within the interval, nil is returned. +func (s *SampleStream[T]) Next() *ygnmi.Value[T] { + time.Sleep(s.interval + intervalTolerance) + s.dataMu.Lock() + defer s.dataMu.Unlock() + return s.lastVal +} + +// Nexts calls Next() count times and returns the slice of returned samples. +func (s *SampleStream[T]) Nexts(count int) []*ygnmi.Value[T] { + var nexts []*ygnmi.Value[T] + for i := 0; i < count; i++ { + nexts = append(nexts, s.Next()) + } + return nexts +} + +// All returns the list of values that has been received thus far. +func (s *SampleStream[T]) All() []*ygnmi.Value[T] { + s.dataMu.Lock() + defer s.dataMu.Unlock() + return s.data +} + +// Close closes the gnmi subscription. +func (s *SampleStream[T]) Close() { + s.cancel() +} diff --git a/internal/security/acctz/acctz.go b/internal/security/acctz/acctz.go new file mode 100644 index 00000000000..6f475e14e1e --- /dev/null +++ b/internal/security/acctz/acctz.go @@ -0,0 +1,1021 @@ +// 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 acctz provides helper APIs to simplify writing acctz test cases. +package acctz + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "strconv" + "testing" + "time" + + gnmipb "github.com/openconfig/gnmi/proto/gnmi" + systempb "github.com/openconfig/gnoi/system" + acctzpb "github.com/openconfig/gnsi/acctz" + authzpb "github.com/openconfig/gnsi/authz" + cpb "github.com/openconfig/gnsi/credentialz" + gribi "github.com/openconfig/gribi/v1/proto/service" + tpb "github.com/openconfig/kne/proto/topo" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/binding" + "github.com/openconfig/ondatra/binding/introspect" + ondatragnmi "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + p4pb "github.com/p4lang/p4runtime/go/p4/v1" + "golang.org/x/crypto/ssh" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/types/known/anypb" +) + +const ( + successUsername = "acctztestuser" + successPassword = "verysecurepassword" + failUsername = "bilbo" + failPassword = "baggins" + failRoleName = "acctz-fp-test-fail" + successCliCommand = "show version" + failCliCommand = "show version" + shellCommand = "uname -a" + gnmiCapabilitiesPath = "/gnmi.gNMI/Capabilities" + gnoiPingPath = "/gnoi.system.System/Ping" + gnsiGetPath = "/gnsi.authz.v1.Authz/Get" + gribiGetPath = "/gribi.gRIBI/Get" + p4rtCapabilitiesPath = "/p4.v1.P4Runtime/Capabilities" + defaultSSHPort = 22 + ipProto = 6 +) + +var gRPCClientAddr net.Addr + +func setupUserPassword(t *testing.T, dut *ondatra.DUTDevice, username, password string) { + request := &cpb.RotateAccountCredentialsRequest{ + Request: &cpb.RotateAccountCredentialsRequest_Password{ + Password: &cpb.PasswordRequest{ + Accounts: []*cpb.PasswordRequest_Account{ + { + Account: username, + Password: &cpb.PasswordRequest_Password{ + Value: &cpb.PasswordRequest_Password_Plaintext{ + Plaintext: password, + }, + }, + Version: "v1.0", + CreatedOn: uint64(time.Now().Unix()), + }, + }, + }, + }, + } + + credzClient := dut.RawAPIs().GNSI(t).Credentialz() + credzRotateClient, err := credzClient.RotateAccountCredentials(context.Background()) + if err != nil { + t.Fatalf("Failed fetching credentialz rotate account credentials client, error: %s", err) + } + err = credzRotateClient.Send(request) + if err != nil { + t.Fatalf("Failed sending credentialz rotate account credentials request, error: %s", err) + } + _, err = credzRotateClient.Recv() + if err != nil { + t.Fatalf("Failed receiving credentialz rotate account credentials response, error: %s", err) + } + err = credzRotateClient.Send(&cpb.RotateAccountCredentialsRequest{ + Request: &cpb.RotateAccountCredentialsRequest_Finalize{ + Finalize: request.GetFinalize(), + }, + }) + if err != nil { + t.Fatalf("Failed sending credentialz rotate account credentials finalize request, error: %s", err) + } + + // Brief sleep for finalize to get processed. + time.Sleep(time.Second) +} + +func nokiaFailCliRole(t *testing.T) *gnmipb.SetRequest { + failRoleData, err := json.Marshal([]any{ + map[string]any{ + "services": []string{"cli"}, + "cli": map[string][]string{ + "deny-command-list": {failCliCommand}, + }, + }, + }) + if err != nil { + t.Fatalf("Error with json marshal: %v", err) + } + + return &gnmipb.SetRequest{ + Prefix: &gnmipb.Path{ + Origin: "native", + }, + Replace: []*gnmipb.Update{ + { + Path: &gnmipb.Path{ + Elem: []*gnmipb.PathElem{ + {Name: "system"}, + {Name: "aaa"}, + {Name: "authorization"}, + {Name: "role", Key: map[string]string{"rolename": failRoleName}}, + }, + }, + Val: &gnmipb.TypedValue{ + Value: &gnmipb.TypedValue_JsonIetfVal{ + JsonIetfVal: failRoleData, + }, + }, + }, + }, + } +} + +// SetupUsers Setup users for acctz tests and optionally configure cli role for denied commands. +func SetupUsers(t *testing.T, dut *ondatra.DUTDevice, configureFailCliRole bool) { + auth := &oc.System_Aaa_Authentication{} + successUser := auth.GetOrCreateUser(successUsername) + successUser.SetRole(oc.AaaTypes_SYSTEM_DEFINED_ROLES_SYSTEM_ROLE_ADMIN) + failUser := auth.GetOrCreateUser(failUsername) + if configureFailCliRole { + var SetRequest *gnmipb.SetRequest + + // Create failure cli role in native. + switch dut.Vendor() { + case ondatra.NOKIA: + SetRequest = nokiaFailCliRole(t) + } + + gnmiClient := dut.RawAPIs().GNMI(t) + if _, err := gnmiClient.Set(context.Background(), SetRequest); err != nil { + t.Fatalf("Unexpected error configuring role: %v", err) + } + + failUser.SetRole(oc.UnionString(failRoleName)) + } + ondatragnmi.Update(t, dut, ondatragnmi.OC().System().Aaa().Authentication().Config(), auth) + setupUserPassword(t, dut, successUsername, successPassword) + setupUserPassword(t, dut, failUsername, failPassword) +} + +func getGrpcTarget(t *testing.T, dut *ondatra.DUTDevice, service introspect.Service) string { + dialTarget := introspect.DUTDialer(t, dut, service).DialTarget + resolvedTarget, err := net.ResolveTCPAddr("tcp", dialTarget) + if err != nil { + t.Fatalf("Failed resolving %s target %s", service, dialTarget) + } + t.Logf("Target for %s service: %s", service, resolvedTarget) + return resolvedTarget.String() +} + +func getSSHTarget(t *testing.T, dut *ondatra.DUTDevice) string { + var serviceDUT interface { + Service(string) (*tpb.Service, error) + } + + var target string + err := binding.DUTAs(dut.RawAPIs().BindingDUT(), &serviceDUT) + if err != nil { + t.Log("DUT does not support `Service` function, will attempt to resolve dut name field.") + + // Suppose ssh could be not 22 in some cases but don't think this is exposed by introspect. + dialTarget := fmt.Sprintf("%s:%d", dut.Name(), defaultSSHPort) + resolvedTarget, err := net.ResolveTCPAddr("tcp", dialTarget) + if err != nil { + t.Fatalf("Failed resolving ssh target %s", dialTarget) + } + target = resolvedTarget.String() + } else { + dutSSHService, err := serviceDUT.Service("ssh") + if err != nil { + t.Fatal(err) + } + target = fmt.Sprintf("%s:%d", dutSSHService.GetOutsideIp(), dutSSHService.GetOutside()) + } + + t.Logf("Target for ssh service: %s", target) + return target +} + +func dialGrpc(t *testing.T, target string) *grpc.ClientConn { + conn, err := grpc.NewClient( + target, + grpc.WithTransportCredentials( + credentials.NewTLS( + &tls.Config{ + InsecureSkipVerify: true, + }, + ), + ), + grpc.WithContextDialer(func(ctx context.Context, a string) (net.Conn, error) { + dst, err := net.ResolveTCPAddr("tcp", a) + if err != nil { + return nil, err + } + c, err := net.DialTCP("tcp", nil, dst) + if err != nil { + return nil, err + } + gRPCClientAddr = c.LocalAddr() + return c, err + })) + if err != nil { + t.Fatalf("Got unexpected error dialing gRPC target %q, error: %v", target, err) + } + + return conn +} + +func dialSSH(t *testing.T, username, password, target string) (*ssh.Client, io.WriteCloser) { + conn, err := ssh.Dial( + "tcp", + target, + &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + ssh.KeyboardInteractive( + func(user, instruction string, questions []string, echos []bool) ([]string, error) { + answers := make([]string, len(questions)) + for i := range answers { + answers[i] = password + } + return answers, nil + }, + ), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }) + if err != nil { + t.Fatalf("Got unexpected error dialing ssh target %s, error: %v", target, err) + } + + sess, err := conn.NewSession() + if err != nil { + t.Fatalf("Failed creating ssh session, error: %s", err) + } + + w, err := sess.StdinPipe() + if err != nil { + t.Fatal(err) + } + + term := ssh.TerminalModes{ + ssh.ECHO: 0, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + + err = sess.RequestPty( + "xterm", + 40, + 80, + term, + ) + if err != nil { + t.Fatal(err) + } + + err = sess.Shell() + if err != nil { + t.Fatal(err) + } + + return conn, w +} + +func getHostPortInfo(t *testing.T, address string) (string, uint32) { + ip, port, err := net.SplitHostPort(address) + if err != nil { + t.Fatal(err) + } + portNumber, err := strconv.Atoi(port) + if err != nil { + t.Fatal(err) + } + return ip, uint32(portNumber) +} + +// SendGnmiRPCs Setup gNMI test RPCs (successful and failed) to be used in the acctz client tests. +func SendGnmiRPCs(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we just use introspection + // but that won't get us v4 and v6, it will just get us whatever is configured in binding, + // so while the test asks for v4 and v6 we'll just be doing it for whatever we get. + target := getGrpcTarget(t, dut, introspect.GNMI) + + var records []*acctzpb.RecordResponse + grpcConn := dialGrpc(t, target) + gnmiClient := gnmipb.NewGNMIClient(grpcConn) + ctx := context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", failUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", failPassword) + + // Send an unsuccessful gNMI capabilities request (bad creds in context). + _, err := gnmiClient.Capabilities(ctx, &gnmipb.CapabilityRequest{}) + if err != nil { + t.Logf("Got expected error fetching capabilities with bad creds, error: %s", err) + } else { + t.Fatal("Did not get expected error fetching capabilities with bad creds.") + } + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GNMI, + RpcName: gnmiCapabilitiesPath, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_DENY, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_UNSPECIFIED, + }, + User: &acctzpb.UserDetail{ + Identity: failUsername, + }, + }, + }) + + // Send a successful gNMI capabilities request. + ctx = context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", successUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", successPassword) + req := &gnmipb.CapabilityRequest{} + payload, err := anypb.New(req) + if err != nil { + t.Fatal("Failed creating anypb payload.") + } + _, err = gnmiClient.Capabilities(ctx, req) + if err != nil { + t.Fatalf("Error fetching capabilities, error: %s", err) + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, gRPCClientAddr.String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GNMI, + RpcName: gnmiCapabilitiesPath, + Payload: &acctzpb.GrpcService_ProtoVal{ + ProtoVal: payload, + }, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_PERMIT, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_SUCCESS, + Cause: "authentication_method: local", + }, + User: &acctzpb.UserDetail{ + Identity: successUsername, + }, + }, + }) + + return records +} + +// SendGnoiRPCs Setup gNOI test RPCs (successful and failed) to be used in the acctz client tests. +func SendGnoiRPCs(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we just use introspection + // but that won't get us v4 and v6, it will just get us whatever is configured in binding, + // so while the test asks for v4 and v6 we'll just be doing it for whatever we get. + target := getGrpcTarget(t, dut, introspect.GNOI) + + var records []*acctzpb.RecordResponse + grpcConn := dialGrpc(t, target) + gnoiSystemClient := systempb.NewSystemClient(grpcConn) + ctx := context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", failUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", failPassword) + + // Send an unsuccessful gNOI system time request (bad creds in context), we don't + // care about receiving on it, just want to make the request. + gnoiSystemPingClient, err := gnoiSystemClient.Ping(ctx, &systempb.PingRequest{ + Destination: "127.0.0.1", + Count: 1, + }) + if err != nil { + t.Fatalf("Got unexpected error getting gnoi system time client, error: %s", err) + } + + _, err = gnoiSystemPingClient.Recv() + if err != nil { + t.Logf("Got expected error getting gnoi system time with bad creds, error: %s", err) + } + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GNOI, + RpcName: gnoiPingPath, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_DENY, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_UNSPECIFIED, + }, + User: &acctzpb.UserDetail{ + Identity: failUsername, + }, + }, + }) + + // Send a successful gNOI ping request. + ctx = context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", successUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", successPassword) + req := &systempb.PingRequest{ + Destination: "127.0.0.1", + Count: 1, + } + payload, err := anypb.New(req) + if err != nil { + t.Fatal("Failed creating anypb payload.") + } + gnoiSystemPingClient, err = gnoiSystemClient.Ping(ctx, req) + if err != nil { + t.Fatalf("Error fetching gnoi system time, error: %s", err) + } + _, err = gnoiSystemPingClient.Recv() + if err != nil { + t.Fatalf("Got unexpected error getting gnoi system time, error: %s", err) + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, gRPCClientAddr.String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GNOI, + RpcName: gnoiPingPath, + Payload: &acctzpb.GrpcService_ProtoVal{ + ProtoVal: payload, + }, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_PERMIT, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_SUCCESS, + Cause: "authentication_method: local", + }, + User: &acctzpb.UserDetail{ + Identity: successUsername, + }, + }, + }) + + return records +} + +// SendGnsiRPCs Setup gNSI test RPCs (successful and failed) to be used in the acctz client tests. +func SendGnsiRPCs(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we just use introspection + // but that won't get us v4 and v6, it will just get us whatever is configured in binding, + // so while the test asks for v4 and v6 we'll just be doing it for whatever we get. + target := getGrpcTarget(t, dut, introspect.GNSI) + + var records []*acctzpb.RecordResponse + grpcConn := dialGrpc(t, target) + authzClient := authzpb.NewAuthzClient(grpcConn) + ctx := context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", failUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", failPassword) + + // Send an unsuccessful gNSI authz get request (bad creds in context), we don't + // care about receiving on it, just want to make the request. + _, err := authzClient.Get(ctx, &authzpb.GetRequest{}) + if err != nil { + t.Logf("Got expected error fetching authz policy with bad creds, error: %s", err) + } else { + t.Fatal("Did not get expected error fetching authz policy with bad creds.") + } + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GNSI, + RpcName: gnsiGetPath, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_DENY, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_UNSPECIFIED, + }, + User: &acctzpb.UserDetail{ + Identity: failUsername, + }, + }, + }) + + // Send a successful gNSI authz get request. + ctx = context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", successUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", successPassword) + req := &authzpb.GetRequest{} + payload, err := anypb.New(req) + if err != nil { + t.Fatal("Failed creating anypb payload.") + } + _, err = authzClient.Get(ctx, &authzpb.GetRequest{}) + if err != nil { + t.Fatalf("Error fetching authz policy, error: %s", err) + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, gRPCClientAddr.String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GNSI, + RpcName: gnsiGetPath, + Payload: &acctzpb.GrpcService_ProtoVal{ + ProtoVal: payload, + }, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_PERMIT, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_SUCCESS, + Cause: "authentication_method: local", + }, + User: &acctzpb.UserDetail{ + Identity: successUsername, + }, + }, + }) + + return records +} + +// SendGribiRPCs Setup gRIBI test RPCs (successful and failed) to be used in the acctz client tests. +func SendGribiRPCs(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we just use introspection + // but that won't get us v4 and v6, it will just get us whatever is configured in binding, + // so while the test asks for v4 and v6 we'll just be doing it for whatever we get. + target := getGrpcTarget(t, dut, introspect.GRIBI) + + var records []*acctzpb.RecordResponse + grpcConn := dialGrpc(t, target) + gribiClient := gribi.NewGRIBIClient(grpcConn) + ctx := context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", failUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", failPassword) + + // Send an unsuccessful gRIBI get request (bad creds in context), we don't + // care about receiving on it, just want to make the request. + gribiGetClient, err := gribiClient.Get( + ctx, + &gribi.GetRequest{ + NetworkInstance: &gribi.GetRequest_All{}, + Aft: gribi.AFTType_IPV4, + }, + ) + if err != nil { + t.Fatalf("Got unexpected error during gribi get request, error: %s", err) + } + _, err = gribiGetClient.Recv() + if err != nil { + t.Logf("Got expected error during gribi recv request, error: %s", err) + } + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GRIBI, + RpcName: gribiGetPath, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_DENY, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_UNSPECIFIED, + }, + User: &acctzpb.UserDetail{ + Identity: failUsername, + }, + }, + }) + + // Send a successful gRIBI get request. + ctx = context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", successUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", successPassword) + req := &gribi.GetRequest{ + NetworkInstance: &gribi.GetRequest_All{}, + Aft: gribi.AFTType_IPV4, + } + payload, err := anypb.New(req) + if err != nil { + t.Fatal("Failed creating anypb payload.") + } + gribiGetClient, err = gribiClient.Get(ctx, req) + if err != nil { + t.Fatalf("Got unexpected error during gribi get request, error: %s", err) + } + _, err = gribiGetClient.Recv() + if err != nil { + // Having no messages, we get an EOF so this is not a failure. + if !errors.Is(err, io.EOF) { + t.Fatalf("Got unexpected error during gribi recv request, error: %s", err) + } + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, gRPCClientAddr.String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_GRIBI, + RpcName: gribiGetPath, + Payload: &acctzpb.GrpcService_ProtoVal{ + ProtoVal: payload, + }, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_PERMIT, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_SUCCESS, + Cause: "authentication_method: local", + }, + User: &acctzpb.UserDetail{ + Identity: successUsername, + }, + }, + }) + + return records +} + +// SendP4rtRPCs Setup P4RT test RPCs (successful and failed) to be used in the acctz client tests. +func SendP4rtRPCs(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we just use introspection + // but that won't get us v4 and v6, it will just get us whatever is configured in binding, + // so while the test asks for v4 and v6 we'll just be doing it for whatever we get. + target := getGrpcTarget(t, dut, introspect.P4RT) + + var records []*acctzpb.RecordResponse + grpcConn := dialGrpc(t, target) + ctx := context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", failUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", failPassword) + p4rtclient := p4pb.NewP4RuntimeClient(grpcConn) + _, err := p4rtclient.Capabilities(ctx, &p4pb.CapabilitiesRequest{}) + if err != nil { + t.Logf("Got expected error getting p4rt capabilities with no creds, error: %s", err) + } else { + t.Fatal("Did not get expected error fetching pr4t capabilities with no creds.") + } + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_P4RT, + RpcName: p4rtCapabilitiesPath, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_DENY, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_UNSPECIFIED, + }, + User: &acctzpb.UserDetail{ + Identity: failUsername, + }, + }, + }) + + ctx = context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "username", successUsername) + ctx = metadata.AppendToOutgoingContext(ctx, "password", successPassword) + req := &p4pb.CapabilitiesRequest{} + payload, err := anypb.New(req) + if err != nil { + t.Fatal("Failed creating anypb payload.") + } + _, err = p4rtclient.Capabilities(ctx, req) + if err != nil { + t.Fatalf("Error fetching p4rt capabilities, error: %s", err) + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, gRPCClientAddr.String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_GrpcService{ + GrpcService: &acctzpb.GrpcService{ + ServiceType: acctzpb.GrpcService_GRPC_SERVICE_TYPE_P4RT, + RpcName: p4rtCapabilitiesPath, + Payload: &acctzpb.GrpcService_ProtoVal{ + ProtoVal: payload, + }, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_PERMIT, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_ONCE, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_SUCCESS, + Cause: "authentication_method: local", + }, + User: &acctzpb.UserDetail{ + Identity: successUsername, + }, + }, + }) + + return records +} + +// SendSuccessCliCommand Setup test CLI command (successful) to be used in the acctz client tests. +func SendSuccessCliCommand(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we use this workaround + // because ssh isn't exposed in introspection. + target := getSSHTarget(t, dut) + + var records []*acctzpb.RecordResponse + + sshConn, w := dialSSH(t, successUsername, successPassword, target) + defer func() { + // Give things a second to percolate then close the connection. + time.Sleep(3 * time.Second) + err := sshConn.Close() + if err != nil { + t.Logf("Error closing tcp(ssh) connection, will ignore, error: %s", err) + } + }() + + _, err := w.Write([]byte(fmt.Sprintf("%s\n", successCliCommand))) + if err != nil { + t.Fatalf("Failed sending cli command, error: %s", err) + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, sshConn.LocalAddr().String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_CmdService{ + CmdService: &acctzpb.CommandService{ + ServiceType: acctzpb.CommandService_CMD_SERVICE_TYPE_CLI, + Cmd: successCliCommand, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_PERMIT, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_OPERATION, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_SUCCESS, + Cause: "authentication_method: local", + }, + User: &acctzpb.UserDetail{ + Identity: successUsername, + }, + }, + }) + + return records +} + +// SendFailCliCommand Setup test CLI command (failed) to be used in the acctz client tests. +func SendFailCliCommand(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we use this workaround + // because ssh isn't exposed in introspection. + target := getSSHTarget(t, dut) + + var records []*acctzpb.RecordResponse + sshConn, w := dialSSH(t, failUsername, failPassword, target) + + defer func() { + // Give things a second to percolate then close the connection. + time.Sleep(3 * time.Second) + err := sshConn.Close() + if err != nil { + t.Logf("Error closing tcp(ssh) connection, will ignore, error: %s", err) + } + }() + + _, err := w.Write([]byte(fmt.Sprintf("%s\n", failCliCommand))) + if err != nil { + t.Fatalf("Failed sending cli command, error: %s", err) + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, sshConn.LocalAddr().String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_CmdService{ + CmdService: &acctzpb.CommandService{ + ServiceType: acctzpb.CommandService_CMD_SERVICE_TYPE_CLI, + Cmd: failCliCommand, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_DENY, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_OPERATION, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_SUCCESS, + Cause: "authentication_method: local", + }, + User: &acctzpb.UserDetail{ + Identity: failUsername, + Role: failRoleName, + }, + }, + }) + + return records +} + +// SendShellCommand Setup test shell command (successful) to be used in the acctz client tests. +func SendShellCommand(t *testing.T, dut *ondatra.DUTDevice) []*acctzpb.RecordResponse { + // Per https://github.com/openconfig/featureprofiles/issues/2637, waiting to see what the + // "best"/"preferred" way is to get the v4/v6 of the dut. For now, we use this workaround + // because ssh isn't exposed in introspection. + target := getSSHTarget(t, dut) + + var records []*acctzpb.RecordResponse + shellUsername := successUsername + shellPassword := successPassword + + switch dut.Vendor() { + case ondatra.NOKIA: + // Assuming linuxadmin is present and ssh'ing directly via this user gets us to shell + // straight away so this is easy button to trigger a shell record. + shellUsername = "linuxadmin" + shellPassword = "NokiaSrl1!" + } + + sshConn, w := dialSSH(t, shellUsername, shellPassword, target) + defer func() { + // Give things a second to percolate then close the connection. + time.Sleep(3 * time.Second) + err := sshConn.Close() + if err != nil { + t.Logf("Error closing tcp(ssh) connection, will ignore, error: %s", err) + } + }() + + // This might not work for other vendors, so probably we can have a switch here and pass + // the writer to func per vendor if needed. + _, err := w.Write([]byte(fmt.Sprintf("%s\n", shellCommand))) + if err != nil { + t.Fatalf("Failed sending cli command, error: %s", err) + } + + // Remote from the perspective of the router. + remoteIP, remotePort := getHostPortInfo(t, sshConn.LocalAddr().String()) + localIP, localPort := getHostPortInfo(t, target) + + records = append(records, &acctzpb.RecordResponse{ + ServiceRequest: &acctzpb.RecordResponse_CmdService{ + CmdService: &acctzpb.CommandService{ + ServiceType: acctzpb.CommandService_CMD_SERVICE_TYPE_SHELL, + Cmd: shellCommand, + Authz: &acctzpb.AuthzDetail{ + Status: acctzpb.AuthzDetail_AUTHZ_STATUS_PERMIT, + }, + }, + }, + SessionInfo: &acctzpb.SessionInfo{ + Status: acctzpb.SessionInfo_SESSION_STATUS_OPERATION, + LocalAddress: localIP, + LocalPort: localPort, + RemoteAddress: remoteIP, + RemotePort: remotePort, + IpProto: ipProto, + Authn: &acctzpb.AuthnDetail{ + Type: acctzpb.AuthnDetail_AUTHN_TYPE_UNSPECIFIED, + Status: acctzpb.AuthnDetail_AUTHN_STATUS_UNSPECIFIED, + }, + User: &acctzpb.UserDetail{ + Identity: shellUsername, + }, + }, + }) + + return records +} diff --git a/internal/security/authz/authz.go b/internal/security/authz/authz.go index 5411cc241f3..75cd230f305 100644 --- a/internal/security/authz/authz.go +++ b/internal/security/authz/authz.go @@ -28,12 +28,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/featureprofiles/internal/security/gnxi" - "github.com/openconfig/gnsi/authz" "github.com/openconfig/ondatra" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/status" + + authzpb "github.com/openconfig/gnsi/authz" ) // Spiffe is an struct to save an Spiffe id and its svid. @@ -115,15 +116,15 @@ func (p *AuthorizationPolicy) Rotate(t *testing.T, dut *ondatra.DUTDevice, creat if err != nil { t.Fatalf("Could not marshal the policy %s", prettyPrint(policy)) } - autzRotateReq := &authz.RotateAuthzRequest_UploadRequest{ - UploadRequest: &authz.UploadRequest{ + autzRotateReq := &authzpb.RotateAuthzRequest_UploadRequest{ + UploadRequest: &authzpb.UploadRequest{ Version: version, CreatedOn: createdOn, Policy: string(policy), }, } t.Logf("Sending Authz.Rotate request on device: \n %s", prettyPrint(autzRotateReq)) - err = rotateStream.Send(&authz.RotateAuthzRequest{RotateRequest: autzRotateReq, ForceOverwrite: forcOverwrite}) + err = rotateStream.Send(&authzpb.RotateAuthzRequest{RotateRequest: autzRotateReq, ForceOverwrite: forcOverwrite}) if err != nil { t.Fatalf("Error while uploading prob request reply %v", err) } @@ -137,8 +138,8 @@ func (p *AuthorizationPolicy) Rotate(t *testing.T, dut *ondatra.DUTDevice, creat if !cmp.Equal(p, tempPolicy) { t.Fatalf("Policy after upload (temporary) is not the same as the one upload, diff is: %v", cmp.Diff(p, tempPolicy)) } - finalizeRotateReq := &authz.RotateAuthzRequest_FinalizeRotation{FinalizeRotation: &authz.FinalizeRequest{}} - err = rotateStream.Send(&authz.RotateAuthzRequest{RotateRequest: finalizeRotateReq}) + finalizeRotateReq := &authzpb.RotateAuthzRequest_FinalizeRotation{FinalizeRotation: &authzpb.FinalizeRequest{}} + err = rotateStream.Send(&authzpb.RotateAuthzRequest{RotateRequest: finalizeRotateReq}) t.Logf("Sending Authz.Rotate FinalizeRotation request: \n%s", prettyPrint(finalizeRotateReq)) if err != nil { t.Fatalf("Error while finalizing rotate request %v", err) @@ -158,13 +159,13 @@ func NewAuthorizationPolicy(name string) *AuthorizationPolicy { } // Get read the applied policy from device dut. this is test api and fails the test when it fails. -func Get(t testing.TB, dut *ondatra.DUTDevice) (*authz.GetResponse, *AuthorizationPolicy) { +func Get(t testing.TB, dut *ondatra.DUTDevice) (*authzpb.GetResponse, *AuthorizationPolicy) { t.Logf("Performing Authz.Get request on device %s", dut.Name()) gnsiC, err := dut.RawAPIs().BindingDUT().DialGNSI(context.Background()) if err != nil { t.Fatalf("Could not connect gnsi %v", err) } - resp, err := gnsiC.Authz().Get(context.Background(), &authz.GetRequest{}) + resp, err := gnsiC.Authz().Get(context.Background(), &authzpb.GetRequest{}) if err != nil { t.Fatalf("Authz.Get request is failed on device %s: %v", dut.Name(), err) } @@ -218,13 +219,13 @@ func (o *HardVerify) isVerifyOpt() {} // Verify uses prob to validate if the user access for a certain rpc is expected. // It also execute the rpc when HardVerif is passed and verifies if it matches the expectation. func Verify(t testing.TB, dut *ondatra.DUTDevice, spiffe *Spiffe, rpc *gnxi.RPC, opts ...verifyOpt) { - expectedRes := authz.ProbeResponse_ACTION_PERMIT + expectedRes := authzpb.ProbeResponse_ACTION_PERMIT expectedExecErr := codes.OK hardVerify := false for _, opt := range opts { switch opt.(type) { case *ExceptDeny: - expectedRes = authz.ProbeResponse_ACTION_DENY + expectedRes = authzpb.ProbeResponse_ACTION_DENY expectedExecErr = codes.PermissionDenied case *HardVerify: hardVerify = true @@ -236,9 +237,9 @@ func Verify(t testing.TB, dut *ondatra.DUTDevice, spiffe *Spiffe, rpc *gnxi.RPC, if err != nil { t.Fatalf("Could not connect gnsi %v", err) } - resp, err := gnsiC.Authz().Probe(context.Background(), &authz.ProbeRequest{User: spiffe.ID, Rpc: rpc.Path}) + resp, err := gnsiC.Authz().Probe(context.Background(), &authzpb.ProbeRequest{User: spiffe.ID, Rpc: rpc.Path}) if err != nil { - t.Fatalf("Prob Request %s failed on dut %s", prettyPrint(&authz.ProbeRequest{User: spiffe.ID, Rpc: rpc.Path}), dut.Name()) + t.Fatalf("Prob Request %s failed on dut %s", prettyPrint(&authzpb.ProbeRequest{User: spiffe.ID, Rpc: rpc.Path}), dut.Name()) } if resp.GetAction() != expectedRes { diff --git a/internal/security/credz/credz.go b/internal/security/credz/credz.go new file mode 100644 index 00000000000..4625c51afb0 --- /dev/null +++ b/internal/security/credz/credz.go @@ -0,0 +1,663 @@ +// 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 credz provides helper APIs to simplify writing credentialz test cases. +package credz + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "os" + "os/exec" + "strings" + "testing" + "time" + + cpb "github.com/openconfig/gnsi/credentialz" + tpb "github.com/openconfig/kne/proto/topo" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/binding" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "golang.org/x/crypto/ssh" +) + +const ( + lowercase = "abcdefghijklmnopqrstuvwxyz" + uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + digits = "0123456789" + symbols = "!@#$%^&*(){}[]\\|:;\"'" + space = " " + userKey = "testuser" + dutKey = "dut" + caKey = "ca" + minPasswordLength = 24 + maxPasswordLength = 32 + defaultSSHPort = 22 +) + +var ( + charClasses = []string{lowercase, uppercase, digits, symbols, space} +) + +// PrettyPrint prints rpc requests/responses in a pretty format. +func PrettyPrint(i interface{}) string { + s, _ := json.MarshalIndent(i, "", "\t") + return string(s) +} + +// SetupUser setup user for credentialz tests. +func SetupUser(t *testing.T, dut *ondatra.DUTDevice, username string) { + auth := &oc.System_Aaa_Authentication{} + user := auth.GetOrCreateUser(username) + user.SetRole(oc.AaaTypes_SYSTEM_DEFINED_ROLES_SYSTEM_ROLE_ADMIN) + gnmi.Update(t, dut, gnmi.OC().System().Aaa().Authentication().Config(), auth) +} + +// GeneratePassword creates a password with following restrictions: +// - Must be 24-32 characters long. +// - Must use 4 of the 5 character classes ([a-z], [A-Z], [0-9], [!@#$%^&*(){}[]\|:;'"], [ ]). +func GeneratePassword() string { + // Create random length between 24-32 characters long. + length := minPasswordLength + rand.Intn(maxPasswordLength-minPasswordLength+1) + + // Randomly select 4 out of 5 character classes by shuffling the list. + rand.Shuffle(len(charClasses), func(i, j int) { + charClasses[i], charClasses[j] = charClasses[j], charClasses[i] + }) + selectedClasses := charClasses[:4] + + var password strings.Builder + + // Add one random character from each selected class. + for _, class := range selectedClasses { + password.WriteByte(class[rand.Intn(len(class))]) + } + + // Fill remaining characters for the password. + for password.Len() < length { + classIndex := rand.Intn(len(charClasses)) + class := charClasses[classIndex] + password.WriteByte(class[rand.Intn(len(class))]) + } + + return password.String() +} + +func sendHostParametersRequest(t *testing.T, dut *ondatra.DUTDevice, request *cpb.RotateHostParametersRequest) { + credzClient := dut.RawAPIs().GNSI(t).Credentialz() + credzRotateClient, err := credzClient.RotateHostParameters(context.Background()) + if err != nil { + t.Fatalf("Failed fetching credentialz rotate host parameters client, error: %s", err) + } + t.Logf("Sending credentialz rotate host request: %s", PrettyPrint(request)) + err = credzRotateClient.Send(request) + if err != nil { + t.Fatalf("Failed sending credentialz rotate host parameters request, error: %s", err) + } + _, err = credzRotateClient.Recv() + if err != nil { + t.Fatalf("Failed receiving credentialz rotate host parameters response, error: %s", err) + } + err = credzRotateClient.Send(&cpb.RotateHostParametersRequest{ + Request: &cpb.RotateHostParametersRequest_Finalize{ + Finalize: request.GetFinalize(), + }, + }) + if err != nil { + t.Fatalf("Failed sending credentialz rotate host parameters finalize request, error: %s", err) + } + // Brief sleep for finalize to get processed. + time.Sleep(time.Second) +} + +func sendAccountCredentialsRequest(t *testing.T, dut *ondatra.DUTDevice, request *cpb.RotateAccountCredentialsRequest) { + credzClient := dut.RawAPIs().GNSI(t).Credentialz() + credzRotateClient, err := credzClient.RotateAccountCredentials(context.Background()) + if err != nil { + t.Fatalf("Failed fetching credentialz rotate account credentials client, error: %s", err) + } + t.Logf("Sending credentialz rotate account request: %s", PrettyPrint(request)) + err = credzRotateClient.Send(request) + if err != nil { + t.Fatalf("Failed sending credentialz rotate account credentials request, error: %s", err) + } + _, err = credzRotateClient.Recv() + if err != nil { + t.Fatalf("Failed receiving credentialz rotate account credentials response, error: %s", err) + } + err = credzRotateClient.Send(&cpb.RotateAccountCredentialsRequest{ + Request: &cpb.RotateAccountCredentialsRequest_Finalize{ + Finalize: request.GetFinalize(), + }, + }) + if err != nil { + t.Fatalf("Failed sending credentialz rotate account credentials finalize request, error: %s", err) + } + // Brief sleep for finalize to get processed. + time.Sleep(time.Second) +} + +// RotateUserPassword apply password for the specified username on the dut. +func RotateUserPassword(t *testing.T, dut *ondatra.DUTDevice, username, password, version string, createdOn uint64) { + request := &cpb.RotateAccountCredentialsRequest{ + Request: &cpb.RotateAccountCredentialsRequest_Password{ + Password: &cpb.PasswordRequest{ + Accounts: []*cpb.PasswordRequest_Account{ + { + Account: username, + Password: &cpb.PasswordRequest_Password{ + Value: &cpb.PasswordRequest_Password_Plaintext{ + Plaintext: password, + }, + }, + Version: version, + CreatedOn: createdOn, + }, + }, + }, + }, + } + + sendAccountCredentialsRequest(t, dut, request) +} + +// RotateAuthorizedPrincipal apply authorized principal for the specified username on the dut. +func RotateAuthorizedPrincipal(t *testing.T, dut *ondatra.DUTDevice, username, userPrincipal string) { + request := &cpb.RotateAccountCredentialsRequest{ + Request: &cpb.RotateAccountCredentialsRequest_User{ + User: &cpb.AuthorizedUsersRequest{ + Policies: []*cpb.UserPolicy{ + { + Account: username, + AuthorizedPrincipals: &cpb.UserPolicy_SshAuthorizedPrincipals{ + AuthorizedPrincipals: []*cpb.UserPolicy_SshAuthorizedPrincipal{ + { + AuthorizedUser: userPrincipal, + }, + }, + }, + Version: "v1.0", + CreatedOn: uint64(time.Now().Unix()), + }, + }, + }, + }, + } + + sendAccountCredentialsRequest(t, dut, request) +} + +// RotateAuthorizedKey read user key contents from the specified directory & apply it as authorized key on the dut. +func RotateAuthorizedKey(t *testing.T, dut *ondatra.DUTDevice, dir, username, version string, createdOn uint64) { + var keyContents []*cpb.AccountCredentials_AuthorizedKey + + if dir != "" { + data, err := os.ReadFile(fmt.Sprintf("%s/%s.pub", dir, userKey)) + if err != nil { + t.Fatalf("Failed reading private key contents, error: %s", err) + } + keyContents = append(keyContents, &cpb.AccountCredentials_AuthorizedKey{ + AuthorizedKey: data, + KeyType: cpb.KeyType_KEY_TYPE_ED25519, + }) + } + request := &cpb.RotateAccountCredentialsRequest{ + Request: &cpb.RotateAccountCredentialsRequest_Credential{ + Credential: &cpb.AuthorizedKeysRequest{ + Credentials: []*cpb.AccountCredentials{ + { + Account: username, + AuthorizedKeys: keyContents, + Version: version, + CreatedOn: createdOn, + }, + }, + }, + }, + } + + sendAccountCredentialsRequest(t, dut, request) +} + +// RotateTrustedUserCA read CA key contents from the specified directory & apply it on the dut. +func RotateTrustedUserCA(t *testing.T, dut *ondatra.DUTDevice, dir string) { + var keyContents []*cpb.PublicKey + + if dir != "" { + data, err := os.ReadFile(fmt.Sprintf("%s/%s.pub", dir, caKey)) + if err != nil { + t.Fatalf("Failed reading ca public key contents, error: %s", err) + } + keyContents = append(keyContents, &cpb.PublicKey{ + PublicKey: data, + KeyType: cpb.KeyType_KEY_TYPE_ED25519, + }) + } + request := &cpb.RotateHostParametersRequest{ + Request: &cpb.RotateHostParametersRequest_SshCaPublicKey{ + SshCaPublicKey: &cpb.CaPublicKeyRequest{ + SshCaPublicKeys: keyContents, + Version: "v1.0", + CreatedOn: uint64(time.Now().Unix()), + }, + }, + } + + sendHostParametersRequest(t, dut, request) +} + +// RotateAuthenticationTypes apply specified host authentication types on the dut. +func RotateAuthenticationTypes(t *testing.T, dut *ondatra.DUTDevice, authTypes []cpb.AuthenticationType) { + request := &cpb.RotateHostParametersRequest{ + Request: &cpb.RotateHostParametersRequest_AuthenticationAllowed{ + AuthenticationAllowed: &cpb.AllowedAuthenticationRequest{ + AuthenticationTypes: authTypes, + }, + }, + } + + sendHostParametersRequest(t, dut, request) +} + +// RotateAuthenticationArtifacts read dut key/certificate contents from the specified directory & apply it as host authentication artifacts on the dut. +func RotateAuthenticationArtifacts(t *testing.T, dut *ondatra.DUTDevice, keyDir, certDir, version string, createdOn uint64) { + var artifactContents []*cpb.ServerKeysRequest_AuthenticationArtifacts + + if keyDir != "" { + data, err := os.ReadFile(fmt.Sprintf("%s/%s", keyDir, dutKey)) + if err != nil { + t.Fatalf("Failed reading host private key, error: %s", err) + } + artifactContents = append(artifactContents, &cpb.ServerKeysRequest_AuthenticationArtifacts{ + PrivateKey: data, + }) + } + + if certDir != "" { + data, err := os.ReadFile(fmt.Sprintf("%s/%s-cert.pub", certDir, dutKey)) + if err != nil { + t.Fatalf("Failed reading host signed certificate, error: %s", err) + } + artifactContents = append(artifactContents, &cpb.ServerKeysRequest_AuthenticationArtifacts{ + Certificate: data, + }) + } + + request := &cpb.RotateHostParametersRequest{ + Request: &cpb.RotateHostParametersRequest_ServerKeys{ + ServerKeys: &cpb.ServerKeysRequest{ + AuthArtifacts: artifactContents, + Version: version, + CreatedOn: createdOn, + }, + }, + } + + sendHostParametersRequest(t, dut, request) +} + +// RotateAuthorizedPrincipalCheck apply specified authorized principal tool on the dut. +func RotateAuthorizedPrincipalCheck(t *testing.T, dut *ondatra.DUTDevice, tool cpb.AuthorizedPrincipalCheckRequest_Tool) { + request := &cpb.RotateHostParametersRequest{ + Request: &cpb.RotateHostParametersRequest_AuthorizedPrincipalCheck{ + AuthorizedPrincipalCheck: &cpb.AuthorizedPrincipalCheckRequest{ + Tool: tool, + }, + }, + } + + sendHostParametersRequest(t, dut, request) +} + +// GetRejectTelemetry retrieve ssh reject telemetry counters from the dut. +func GetRejectTelemetry(t *testing.T, dut *ondatra.DUTDevice) (uint64, uint64) { + sshCounters := gnmi.Get(t, dut, gnmi.OC().System().SshServer().Counters().State()) + return sshCounters.GetAccessRejects(), sshCounters.GetLastAccessReject() +} + +// GetAcceptTelemetry retrieve ssh accept telemetry counters from the dut. +func GetAcceptTelemetry(t *testing.T, dut *ondatra.DUTDevice) (uint64, uint64) { + sshCounters := gnmi.Get(t, dut, gnmi.OC().System().SshServer().Counters().State()) + return sshCounters.GetAccessAccepts(), sshCounters.GetLastAccessAccept() +} + +// GetDutTarget returns ssh target for the dut to be used in credentialz tests. +func GetDutTarget(t *testing.T, dut *ondatra.DUTDevice) string { + var serviceDUT interface { + Service(string) (*tpb.Service, error) + } + err := binding.DUTAs(dut.RawAPIs().BindingDUT(), &serviceDUT) + if err != nil { + t.Log("DUT does not support `Service` function, will attempt to use dut name field") + return fmt.Sprintf("%s:%d", dut.Name(), defaultSSHPort) + } + dutSSHService, err := serviceDUT.Service("ssh") + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf("%s:%d", dutSSHService.GetOutsideIp(), dutSSHService.GetOutside()) +} + +// GetDutPublicKey retrieve single host public key from the dut. +func GetDutPublicKey(t *testing.T, dut *ondatra.DUTDevice) []byte { + credzClient := dut.RawAPIs().GNSI(t).Credentialz() + req := &cpb.GetPublicKeysRequest{} + response, err := credzClient.GetPublicKeys(context.Background(), req) + if err != nil { + t.Fatalf("Failed fetching fetching credentialz public keys, error: %s", err) + } + if len(response.PublicKeys) < 1 { + return nil + } + return response.PublicKeys[0].PublicKey +} + +// CreateSSHKeyPair creates ssh keypair with a filename of keyName in the specified directory. +// Keypairs can be created for ca/dut/testuser as per individual credentialz test requirements. +func CreateSSHKeyPair(t *testing.T, dir, keyName string) { + sshCmd := exec.Command( + "ssh-keygen", + "-t", "ed25519", + "-f", keyName, + "-C", keyName, + "-q", "-N", "", + ) + sshCmd.Dir = dir + err := sshCmd.Run() + if err != nil { + t.Fatalf("Failed generating %s key pair, error: %s", keyName, err) + } +} + +// CreateUserCertificate creates ssh user certificate in the specified directory. +func CreateUserCertificate(t *testing.T, dir, userPrincipal string) { + userCertCmd := exec.Command( + "ssh-keygen", + "-s", caKey, + "-I", userKey, + "-n", userPrincipal, + "-V", "+52w", + fmt.Sprintf("%s.pub", userKey), + ) + userCertCmd.Dir = dir + err := userCertCmd.Run() + if err != nil { + t.Fatalf("Failed generating user cert, error: %s", err) + } +} + +// 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) + 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 + "-h", // create host (not user) certificate + "-n", "dut.test.com", // principal(s) + "-V", "+52w", // validity + fmt.Sprintf("%s.pub", dutKey), + ) + cmd.Dir = dir + err = cmd.Run() + if err != nil { + t.Fatalf("Failed generating dut cert, error: %s", err) + } +} + +func createHibaKeysCopy(t *testing.T, dir string) { + keyFiles := []string{ + "ca", + "ca.pub", + "hosts/dut", + "hosts/dut.pub", + "hosts/dut-cert.pub", + "users/testuser", + "users/testuser.pub", + "users/testuser-cert.pub", + } + err := os.Mkdir(fmt.Sprintf("%s/hosts", dir), 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) + if err != nil { + t.Fatalf("Failed ensuring users dir in temp dir, error: %s", err) + } + + for _, keyFile := range keyFiles { + var input []byte + input, err = os.ReadFile(keyFile) + if err != nil { + t.Errorf("Error reading file %v, error: %s", keyFile, err) + return + } + err = os.WriteFile(fmt.Sprintf("%s/%s", dir, 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) { + caCmd := exec.Command( + "hiba-ca.sh", + "-c", + "-d", dir, // 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", + "-c", + "-d", dir, + "-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", + "-c", + "-d", dir, + "-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", + "-i", + "-f", fmt.Sprintf("%s/policy/identities/prod", dir), + "domain", "example.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", + ) + 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, + "-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, + "-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, + "-s", + "-u", + "-I", userKey, + "-H", "shell", + ) + createUserCertCmd.Dir = dir + err = createUserCertCmd.Run() + if err != nil { + t.Fatalf("Failed creating user certificate, error: %s", err) + } +} + +// CreateHibaKeys creates/copies hiba granted keys/certificates in the specified directory. +// If hiba tool is not installed on the testbed, ensure following files (generated after executing steps +// from https://github.com/google/hiba/blob/main/CA.md) are present in the test directory : +// feature/security/gnsi/credentialz/tests/hiba_authentication/ca, +// feature/security/gnsi/credentialz/tests/hiba_authentication/ca.pub, +// feature/security/gnsi/credentialz/tests/hiba_authentication/hosts/dut, +// feature/security/gnsi/credentialz/tests/hiba_authentication/hosts/dut.pub, +// feature/security/gnsi/credentialz/tests/hiba_authentication/hosts/dut-cert.pub, +// 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) { + 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) + } else { + createHibaKeysGen(t, dir) + } +} + +// 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(), + }, + ) +} + +// SSHWithCertificate dials ssh with user certificate to be used in credentialz tests. +func SSHWithCertificate(t *testing.T, target, username, dir string) (*ssh.Client, 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, + &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(certificateSigner), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }, + ) +} + +// 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) { + 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, + &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }, + ) +} diff --git a/internal/security/gen/generate.go b/internal/security/gen/generate.go index 93e5ada0d7f..5797703d400 100644 --- a/internal/security/gen/generate.go +++ b/internal/security/gen/generate.go @@ -37,7 +37,6 @@ import ( gribipb "github.com/openconfig/gribi/v1/proto/service" bpb "github.com/openconfig/gnoi/bgp" - cpb "github.com/openconfig/gnoi/cert" dpb "github.com/openconfig/gnoi/diag" frpb "github.com/openconfig/gnoi/factory_reset" fpb "github.com/openconfig/gnoi/file" @@ -70,7 +69,6 @@ var ( "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.cert": cpb.File_cert_cert_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(), diff --git a/internal/security/gnxi/rpcexec.go b/internal/security/gnxi/rpcexec.go index 424818b246a..866be871b57 100644 --- a/internal/security/gnxi/rpcexec.go +++ b/internal/security/gnxi/rpcexec.go @@ -6,8 +6,6 @@ import ( "strings" "time" - "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnsi/authz" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ygnmi/ygnmi" @@ -16,7 +14,9 @@ import ( "google.golang.org/grpc/status" gpb "github.com/openconfig/gnmi/proto/gnmi" - gribi "github.com/openconfig/gribi/v1/proto/service" + spb "github.com/openconfig/gnoi/system" + authzpb "github.com/openconfig/gnsi/authz" + grpb "github.com/openconfig/gribi/v1/proto/service" ) // AllRPC implements a sample request for service * to validate if authz works as expected. @@ -102,51 +102,6 @@ func GnoiBgpClearBGPNeighbor(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.D return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.bgp.BGP/ClearBGPNeighbor is not implemented") } -// GnoiCertificatemanagementAllRPC implements a sample request for service /gnoi.certificate.CertificateManagement/* to validate if authz works as expected. -func GnoiCertificatemanagementAllRPC(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/* is not implemented") -} - -// GnoiCertificatemanagementCanGenerateCSR implements a sample request for service /gnoi.certificate.CertificateManagement/CanGenerateCSR to validate if authz works as expected. -func GnoiCertificatemanagementCanGenerateCSR(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/CanGenerateCSR is not implemented") -} - -// GnoiCertificatemanagementGenerateCSR implements a sample request for service /gnoi.certificate.CertificateManagement/GenerateCSR to validate if authz works as expected. -func GnoiCertificatemanagementGenerateCSR(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/GenerateCSR is not implemented") -} - -// GnoiCertificatemanagementGetCertificates implements a sample request for service /gnoi.certificate.CertificateManagement/GetCertificates to validate if authz works as expected. -func GnoiCertificatemanagementGetCertificates(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/GetCertificates is not implemented") -} - -// GnoiCertificatemanagementInstall implements a sample request for service /gnoi.certificate.CertificateManagement/Install to validate if authz works as expected. -func GnoiCertificatemanagementInstall(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/Install is not implemented") -} - -// GnoiCertificatemanagementLoadCertificate implements a sample request for service /gnoi.certificate.CertificateManagement/LoadCertificate to validate if authz works as expected. -func GnoiCertificatemanagementLoadCertificate(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/LoadCertificate is not implemented") -} - -// GnoiCertificatemanagementLoadCertificateAuthorityBundle implements a sample request for service /gnoi.certificate.CertificateManagement/LoadCertificateAuthorityBundle to validate if authz works as expected. -func GnoiCertificatemanagementLoadCertificateAuthorityBundle(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/LoadCertificateAuthorityBundle is not implemented") -} - -// GnoiCertificatemanagementRevokeCertificates implements a sample request for service /gnoi.certificate.CertificateManagement/RevokeCertificates to validate if authz works as expected. -func GnoiCertificatemanagementRevokeCertificates(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/RevokeCertificates is not implemented") -} - -// GnoiCertificatemanagementRotate implements a sample request for service /gnoi.certificate.CertificateManagement/Rotate to validate if authz works as expected. -func GnoiCertificatemanagementRotate(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { - return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.certificate.CertificateManagement/Rotate is not implemented") -} - // GnoiDiagAllRPC implements a sample request for service /gnoi.diag.Diag/* to validate if authz works as expected. func GnoiDiagAllRPC(_ context.Context, _ *ondatra.DUTDevice, _ []grpc.DialOption, _ ...any) error { return status.Errorf(codes.Unimplemented, "exec function for RPC /gnoi.diag.Diag/* is not implemented") @@ -408,7 +363,7 @@ func GnoiSystemTime(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.Dia if err != nil { return err } - _, err = gnoiC.System().Time(ctx, &system.TimeRequest{}) + _, err = gnoiC.System().Time(ctx, &spb.TimeRequest{}) return err } @@ -423,7 +378,7 @@ func GnoiSystemPing(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.Dia if err != nil { return err } - pingC, err := gnoiC.System().Ping(ctx, &system.PingRequest{Destination: "192.0.2.1"}) + pingC, err := gnoiC.System().Ping(ctx, &spb.PingRequest{Destination: "192.0.2.1"}) if err != nil { return err @@ -468,7 +423,7 @@ func GnsiAuthzGet(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.DialO if err != nil { return err } - _, err = gnsiC.Authz().Get(ctx, &authz.GetRequest{}) + _, err = gnsiC.Authz().Get(ctx, &authzpb.GetRequest{}) return err } @@ -478,7 +433,7 @@ func GnsiAuthzProbe(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.Dia if err != nil { return err } - _, err = gnsiC.Authz().Probe(ctx, &authz.ProbeRequest{User: "dummy", Rpc: "*"}) + _, err = gnsiC.Authz().Probe(ctx, &authzpb.ProbeRequest{User: "dummy", Rpc: "*"}) return err } @@ -493,9 +448,9 @@ func GnsiAuthzRotate(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.Di return err } // TODO: send valid policy for postive cases - err = gnsiCStream.Send(&authz.RotateAuthzRequest{ - RotateRequest: &authz.RotateAuthzRequest_UploadRequest{ - UploadRequest: &authz.UploadRequest{ + err = gnsiCStream.Send(&authzpb.RotateAuthzRequest{ + RotateRequest: &authzpb.RotateAuthzRequest_UploadRequest{ + UploadRequest: &authzpb.UploadRequest{ Version: "0.0", CreatedOn: uint64(time.Now().Nanosecond()), Policy: "", @@ -507,7 +462,7 @@ func GnsiAuthzRotate(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.Di } _, err = gnsiCStream.Recv() // invalid policy is expected since the empty policy is not allowed - if strings.Contains(err.Error(), "invalid policy") { + if strings.Contains(err.Error(), "invalid policy") || status.Code(err) == codes.InvalidArgument { return nil } return err @@ -599,7 +554,7 @@ func GribiFlush(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.DialOpt if err != nil { return err } - _, err = gribiC.Flush(ctx, &gribi.FlushRequest{Election: &gribi.FlushRequest_Id{Id: &gribi.Uint128{Low: 1}}}) + _, err = gribiC.Flush(ctx, &grpb.FlushRequest{Election: &grpb.FlushRequest_Id{Id: &grpb.Uint128{Low: 1}}}) return err } @@ -609,9 +564,9 @@ func GribiGet(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.DialOptio if err != nil { return err } - getReq := gribi.GetRequest{ - NetworkInstance: &gribi.GetRequest_All{}, - Aft: gribi.AFTType_ALL, + getReq := grpb.GetRequest{ + NetworkInstance: &grpb.GetRequest_All{}, + Aft: grpb.AFTType_ALL, } getSteram, err := gribiC.Get(ctx, &getReq) if err != nil { @@ -634,9 +589,9 @@ func GribiModify(ctx context.Context, dut *ondatra.DUTDevice, opts []grpc.DialOp if err != nil { return err } - err = mStream.Send(&gribi.ModifyRequest{ - Params: &gribi.SessionParameters{Redundancy: gribi.SessionParameters_SINGLE_PRIMARY, - Persistence: gribi.SessionParameters_PRESERVE}, + err = mStream.Send(&grpb.ModifyRequest{ + Params: &grpb.SessionParameters{Redundancy: grpb.SessionParameters_SINGLE_PRIMARY, + Persistence: grpb.SessionParameters_PRESERVE}, }) if err != nil { return err diff --git a/internal/security/gnxi/rpcs.go b/internal/security/gnxi/rpcs.go index 71552e1b1ce..5f44cb8bf54 100644 --- a/internal/security/gnxi/rpcs.go +++ b/internal/security/gnxi/rpcs.go @@ -3,110 +3,101 @@ package gnxi type rpcs struct { - AllRPC *RPC - GnmiAllRPC *RPC - GnmiGet *RPC - GnmiSet *RPC - GnmiSubscribe *RPC - GnmiCapabilities *RPC - GnoiBgpAllRPC *RPC - GnoiBgpClearBGPNeighbor *RPC - GnoiCertificatemanagementAllRPC *RPC - GnoiCertificatemanagementCanGenerateCSR *RPC - GnoiCertificatemanagementGenerateCSR *RPC - GnoiCertificatemanagementGetCertificates *RPC - GnoiCertificatemanagementInstall *RPC - GnoiCertificatemanagementLoadCertificate *RPC - GnoiCertificatemanagementLoadCertificateAuthorityBundle *RPC - GnoiCertificatemanagementRevokeCertificates *RPC - GnoiCertificatemanagementRotate *RPC - GnoiDiagAllRPC *RPC - GnoiDiagGetBERTResult *RPC - GnoiDiagStopBERT *RPC - GnoiDiagStartBERT *RPC - GnoiFactoryresetAllRPC *RPC - GnoiFactoryresetStart *RPC - GnoiFileAllRPC *RPC - GnoiFilePut *RPC - GnoiFileRemove *RPC - GnoiFileStat *RPC - GnoiFileTransferToRemote *RPC - GnoiFileGet *RPC - GnoiHealthzAcknowledge *RPC - GnoiHealthzAllRPC *RPC - GnoiHealthzArtifact *RPC - GnoiHealthzCheck *RPC - GnoiHealthzList *RPC - GnoiHealthzGet *RPC - GnoiLayer2AllRPC *RPC - GnoiLayer2ClearLLDPInterface *RPC - GnoiLayer2ClearSpanningTree *RPC - GnoiLayer2PerformBERT *RPC - GnoiLayer2SendWakeOnLAN *RPC - GnoiLayer2ClearNeighborDiscovery *RPC - GnoiLinkqualificationCreate *RPC - GnoiMplsAllRPC *RPC - GnoiMplsClearLSPCounters *RPC - GnoiMplsMPLSPing *RPC - GnoiMplsClearLSP *RPC - GnoiOtdrAllRPC *RPC - GnoiWavelengthrouterAdjustSpectrum *RPC - GnoiWavelengthrouterAllRPC *RPC - GnoiWavelengthrouterCancelAdjustPSD *RPC - GnoiWavelengthrouterCancelAdjustSpectrum *RPC - GnoiOsActivate *RPC - GnoiOsAllRPC *RPC - GnoiOsVerify *RPC - GnoiOsInstall *RPC - GnoiOtdrInitiate *RPC - GnoiLinkqualificationAllRPC *RPC - GnoiLinkqualificationCapabilities *RPC - GnoiLinkqualificationDelete *RPC - GnoiLinkqualificationGet *RPC - GnoiLinkqualificationList *RPC - GnoiSystemAllRPC *RPC - GnoiSystemCancelReboot *RPC - GnoiSystemKillProcess *RPC - GnoiSystemReboot *RPC - GnoiSystemRebootStatus *RPC - GnoiSystemSetPackage *RPC - GnoiSystemSwitchControlProcessor *RPC - GnoiSystemTime *RPC - GnoiSystemTraceroute *RPC - GnoiSystemPing *RPC - GnoiWavelengthrouterAdjustPSD *RPC - GnsiAcctzAllRPC *RPC - GnsiAcctzRecordSubscribe *RPC - GnsiAuthzAllRPC *RPC - GnsiAuthzGet *RPC - GnsiAuthzProbe *RPC - GnsiAuthzRotate *RPC - GnsiCertzAddProfile *RPC - GnsiCertzAllRPC *RPC - GnsiCertzCanGenerateCSR *RPC - GnsiCertzDeleteProfile *RPC - GnsiCertzGetProfileList *RPC - GnsiCertzRotate *RPC - GnsiCredentialzAllRPC *RPC - GnsiCredentialzCanGenerateKey *RPC - GnsiCredentialzGetPublicKeys *RPC - GnsiCredentialzRotateHostParameters *RPC - GnsiCredentialzRotateAccountCredentials *RPC - GnsiPathzAllRPC *RPC - GnsiPathzGet *RPC - GnsiPathzProbe *RPC - GnsiPathzRotate *RPC - GribiAllRPC *RPC - GribiFlush *RPC - GribiGet *RPC - GribiModify *RPC - P4P4runtimeAllRPC *RPC - P4P4runtimeCapabilities *RPC - P4P4runtimeGetForwardingPipelineConfig *RPC - P4P4runtimeRead *RPC - P4P4runtimeSetForwardingPipelineConfig *RPC - P4P4runtimeStreamChannel *RPC - P4P4runtimeWrite *RPC + AllRPC *RPC + GnmiAllRPC *RPC + GnmiGet *RPC + GnmiSet *RPC + GnmiSubscribe *RPC + GnmiCapabilities *RPC + GnoiBgpAllRPC *RPC + GnoiBgpClearBGPNeighbor *RPC + GnoiDiagAllRPC *RPC + GnoiDiagGetBERTResult *RPC + GnoiDiagStopBERT *RPC + GnoiDiagStartBERT *RPC + GnoiFactoryresetAllRPC *RPC + GnoiFactoryresetStart *RPC + GnoiFileAllRPC *RPC + GnoiFilePut *RPC + GnoiFileRemove *RPC + GnoiFileStat *RPC + GnoiFileTransferToRemote *RPC + GnoiFileGet *RPC + GnoiHealthzAcknowledge *RPC + GnoiHealthzAllRPC *RPC + GnoiHealthzArtifact *RPC + GnoiHealthzCheck *RPC + GnoiHealthzList *RPC + GnoiHealthzGet *RPC + GnoiLayer2AllRPC *RPC + GnoiLayer2ClearLLDPInterface *RPC + GnoiLayer2ClearSpanningTree *RPC + GnoiLayer2PerformBERT *RPC + GnoiLayer2SendWakeOnLAN *RPC + GnoiLayer2ClearNeighborDiscovery *RPC + GnoiLinkqualificationCreate *RPC + GnoiMplsAllRPC *RPC + GnoiMplsClearLSPCounters *RPC + GnoiMplsMPLSPing *RPC + GnoiMplsClearLSP *RPC + GnoiOtdrAllRPC *RPC + GnoiWavelengthrouterAdjustSpectrum *RPC + GnoiWavelengthrouterAllRPC *RPC + GnoiWavelengthrouterCancelAdjustPSD *RPC + GnoiWavelengthrouterCancelAdjustSpectrum *RPC + GnoiOsActivate *RPC + GnoiOsAllRPC *RPC + GnoiOsVerify *RPC + GnoiOsInstall *RPC + GnoiOtdrInitiate *RPC + GnoiLinkqualificationAllRPC *RPC + GnoiLinkqualificationCapabilities *RPC + GnoiLinkqualificationDelete *RPC + GnoiLinkqualificationGet *RPC + GnoiLinkqualificationList *RPC + GnoiSystemAllRPC *RPC + GnoiSystemCancelReboot *RPC + GnoiSystemKillProcess *RPC + GnoiSystemReboot *RPC + GnoiSystemRebootStatus *RPC + GnoiSystemSetPackage *RPC + GnoiSystemSwitchControlProcessor *RPC + GnoiSystemTime *RPC + GnoiSystemTraceroute *RPC + GnoiSystemPing *RPC + GnoiWavelengthrouterAdjustPSD *RPC + GnsiAcctzAllRPC *RPC + GnsiAcctzRecordSubscribe *RPC + GnsiAuthzAllRPC *RPC + GnsiAuthzGet *RPC + GnsiAuthzProbe *RPC + GnsiAuthzRotate *RPC + GnsiCertzAddProfile *RPC + GnsiCertzAllRPC *RPC + GnsiCertzCanGenerateCSR *RPC + GnsiCertzDeleteProfile *RPC + GnsiCertzGetProfileList *RPC + GnsiCertzRotate *RPC + GnsiCredentialzAllRPC *RPC + GnsiCredentialzCanGenerateKey *RPC + GnsiCredentialzGetPublicKeys *RPC + GnsiCredentialzRotateHostParameters *RPC + GnsiCredentialzRotateAccountCredentials *RPC + GnsiPathzAllRPC *RPC + GnsiPathzGet *RPC + GnsiPathzProbe *RPC + GnsiPathzRotate *RPC + GribiAllRPC *RPC + GribiFlush *RPC + GribiGet *RPC + GribiModify *RPC + P4P4runtimeAllRPC *RPC + P4P4runtimeCapabilities *RPC + P4P4runtimeGetForwardingPipelineConfig *RPC + P4P4runtimeRead *RPC + P4P4runtimeSetForwardingPipelineConfig *RPC + P4P4runtimeStreamChannel *RPC + P4P4runtimeWrite *RPC } var ( @@ -167,69 +158,6 @@ var ( Path: "/gnoi.bgp.BGP/ClearBGPNeighbor", Exec: GnoiBgpClearBGPNeighbor, } - gnoicertificateCertificateManagementALL = &RPC{ - Name: "*", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.*", - Path: "/gnoi.certificate.CertificateManagement/*", - Exec: GnoiCertificatemanagementAllRPC, - } - gnoicertificateCertificateManagementCanGenerateCSR = &RPC{ - Name: "CanGenerateCSR", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.CanGenerateCSR", - Path: "/gnoi.certificate.CertificateManagement/CanGenerateCSR", - Exec: GnoiCertificatemanagementCanGenerateCSR, - } - gnoicertificateCertificateManagementGenerateCSR = &RPC{ - Name: "GenerateCSR", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.GenerateCSR", - Path: "/gnoi.certificate.CertificateManagement/GenerateCSR", - Exec: GnoiCertificatemanagementGenerateCSR, - } - gnoicertificateCertificateManagementGetCertificates = &RPC{ - Name: "GetCertificates", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.GetCertificates", - Path: "/gnoi.certificate.CertificateManagement/GetCertificates", - Exec: GnoiCertificatemanagementGetCertificates, - } - gnoicertificateCertificateManagementInstall = &RPC{ - Name: "Install", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.Install", - Path: "/gnoi.certificate.CertificateManagement/Install", - Exec: GnoiCertificatemanagementInstall, - } - gnoicertificateCertificateManagementLoadCertificate = &RPC{ - Name: "LoadCertificate", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.LoadCertificate", - Path: "/gnoi.certificate.CertificateManagement/LoadCertificate", - Exec: GnoiCertificatemanagementLoadCertificate, - } - gnoicertificateCertificateManagementLoadCertificateAuthorityBundle = &RPC{ - Name: "LoadCertificateAuthorityBundle", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.LoadCertificateAuthorityBundle", - Path: "/gnoi.certificate.CertificateManagement/LoadCertificateAuthorityBundle", - Exec: GnoiCertificatemanagementLoadCertificateAuthorityBundle, - } - gnoicertificateCertificateManagementRevokeCertificates = &RPC{ - Name: "RevokeCertificates", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.RevokeCertificates", - Path: "/gnoi.certificate.CertificateManagement/RevokeCertificates", - Exec: GnoiCertificatemanagementRevokeCertificates, - } - gnoicertificateCertificateManagementRotate = &RPC{ - Name: "Rotate", - Service: "gnoi.certificate.CertificateManagement", - FQN: "gnoi.certificate.CertificateManagement.Rotate", - Path: "/gnoi.certificate.CertificateManagement/Rotate", - Exec: GnoiCertificatemanagementRotate, - } gnoidiagALL = &RPC{ Name: "*", Service: "gnoi.diag.Diag", @@ -851,123 +779,105 @@ var ( GnmiCapabilities: gnmiCapabilities, GnoiBgpAllRPC: gnoibgpALL, GnoiBgpClearBGPNeighbor: gnoibgpClearBGPNeighbor, - GnoiCertificatemanagementAllRPC: gnoicertificateCertificateManagementALL, - GnoiCertificatemanagementCanGenerateCSR: gnoicertificateCertificateManagementCanGenerateCSR, - GnoiCertificatemanagementGenerateCSR: gnoicertificateCertificateManagementGenerateCSR, - GnoiCertificatemanagementGetCertificates: gnoicertificateCertificateManagementGetCertificates, - GnoiCertificatemanagementInstall: gnoicertificateCertificateManagementInstall, - GnoiCertificatemanagementLoadCertificate: gnoicertificateCertificateManagementLoadCertificate, - GnoiCertificatemanagementLoadCertificateAuthorityBundle: gnoicertificateCertificateManagementLoadCertificateAuthorityBundle, - GnoiCertificatemanagementRevokeCertificates: gnoicertificateCertificateManagementRevokeCertificates, - GnoiCertificatemanagementRotate: gnoicertificateCertificateManagementRotate, - GnoiDiagAllRPC: gnoidiagALL, - GnoiDiagGetBERTResult: gnoidiagGetBERTResult, - GnoiDiagStopBERT: gnoidiagStopBERT, - GnoiDiagStartBERT: gnoidiagStartBERT, - GnoiFactoryresetAllRPC: gnoifactory_resetFactoryResetALL, - GnoiFactoryresetStart: gnoifactory_resetFactoryResetStart, - GnoiFileAllRPC: gnoifileALL, - GnoiFilePut: gnoifilePut, - GnoiFileRemove: gnoifileRemove, - GnoiFileStat: gnoifileStat, - GnoiFileTransferToRemote: gnoifileTransferToRemote, - GnoiFileGet: gnoifileGet, - GnoiHealthzAcknowledge: gnoihealthzAcknowledge, - GnoiHealthzAllRPC: gnoihealthzALL, - GnoiHealthzArtifact: gnoihealthzArtifact, - GnoiHealthzCheck: gnoihealthzCheck, - GnoiHealthzList: gnoihealthzList, - GnoiHealthzGet: gnoihealthzGet, - GnoiLayer2AllRPC: gnoilayer2ALL, - GnoiLayer2ClearLLDPInterface: gnoilayer2ClearLLDPInterface, - GnoiLayer2ClearSpanningTree: gnoilayer2ClearSpanningTree, - GnoiLayer2PerformBERT: gnoilayer2PerformBERT, - GnoiLayer2SendWakeOnLAN: gnoilayer2SendWakeOnLAN, - GnoiLayer2ClearNeighborDiscovery: gnoilayer2ClearNeighborDiscovery, - GnoiLinkqualificationCreate: gnoipacket_link_qualificationLinkQualificationCreate, - GnoiMplsAllRPC: gnoimplsALL, - GnoiMplsClearLSPCounters: gnoimplsClearLSPCounters, - GnoiMplsMPLSPing: gnoimplsMPLSPing, - GnoiMplsClearLSP: gnoimplsClearLSP, - GnoiOtdrAllRPC: gnoiopticalOTDRALL, - GnoiWavelengthrouterAdjustSpectrum: gnoiopticalWavelengthRouterAdjustSpectrum, - GnoiWavelengthrouterAllRPC: gnoiopticalWavelengthRouterALL, - GnoiWavelengthrouterCancelAdjustPSD: gnoiopticalWavelengthRouterCancelAdjustPSD, - GnoiWavelengthrouterCancelAdjustSpectrum: gnoiopticalWavelengthRouterCancelAdjustSpectrum, - GnoiOsActivate: gnoiosActivate, - GnoiOsAllRPC: gnoiosALL, - GnoiOsVerify: gnoiosVerify, - GnoiOsInstall: gnoiosInstall, - GnoiOtdrInitiate: gnoiopticalOTDRInitiate, - GnoiLinkqualificationAllRPC: gnoipacket_link_qualificationLinkQualificationALL, - GnoiLinkqualificationCapabilities: gnoipacket_link_qualificationLinkQualificationCapabilities, - GnoiLinkqualificationDelete: gnoipacket_link_qualificationLinkQualificationDelete, - GnoiLinkqualificationGet: gnoipacket_link_qualificationLinkQualificationGet, - GnoiLinkqualificationList: gnoipacket_link_qualificationLinkQualificationList, - GnoiSystemAllRPC: gnoisystemALL, - GnoiSystemCancelReboot: gnoisystemCancelReboot, - GnoiSystemKillProcess: gnoisystemKillProcess, - GnoiSystemReboot: gnoisystemReboot, - GnoiSystemRebootStatus: gnoisystemRebootStatus, - GnoiSystemSetPackage: gnoisystemSetPackage, - GnoiSystemSwitchControlProcessor: gnoisystemSwitchControlProcessor, - GnoiSystemTime: gnoisystemTime, - GnoiSystemTraceroute: gnoisystemTraceroute, - GnoiSystemPing: gnoisystemPing, - GnoiWavelengthrouterAdjustPSD: gnoiopticalWavelengthRouterAdjustPSD, - GnsiAcctzAllRPC: gnsiacctzv1AcctzALL, - GnsiAcctzRecordSubscribe: gnsiacctzv1AcctzRecordSubscribe, - GnsiAuthzAllRPC: gnsiauthzv1AuthzALL, - GnsiAuthzGet: gnsiauthzv1AuthzGet, - GnsiAuthzProbe: gnsiauthzv1AuthzProbe, - GnsiAuthzRotate: gnsiauthzv1AuthzRotate, - GnsiCertzAddProfile: gnsicertzv1CertzAddProfile, - GnsiCertzAllRPC: gnsicertzv1CertzALL, - GnsiCertzCanGenerateCSR: gnsicertzv1CertzCanGenerateCSR, - GnsiCertzDeleteProfile: gnsicertzv1CertzDeleteProfile, - GnsiCertzGetProfileList: gnsicertzv1CertzGetProfileList, - GnsiCertzRotate: gnsicertzv1CertzRotate, - GnsiCredentialzAllRPC: gnsicredentialzv1CredentialzALL, - GnsiCredentialzCanGenerateKey: gnsicredentialzv1CredentialzCanGenerateKey, - GnsiCredentialzGetPublicKeys: gnsicredentialzv1CredentialzGetPublicKeys, - GnsiCredentialzRotateHostParameters: gnsicredentialzv1CredentialzRotateHostParameters, - GnsiCredentialzRotateAccountCredentials: gnsicredentialzv1CredentialzRotateAccountCredentials, - GnsiPathzAllRPC: gnsipathzv1PathzALL, - GnsiPathzGet: gnsipathzv1PathzGet, - GnsiPathzProbe: gnsipathzv1PathzProbe, - GnsiPathzRotate: gnsipathzv1PathzRotate, - GribiAllRPC: gribiALL, - GribiFlush: gribiFlush, - GribiGet: gribiGet, - GribiModify: gribiModify, - P4P4runtimeAllRPC: p4v1P4RuntimeALL, - P4P4runtimeCapabilities: p4v1P4RuntimeCapabilities, - P4P4runtimeGetForwardingPipelineConfig: p4v1P4RuntimeGetForwardingPipelineConfig, - P4P4runtimeRead: p4v1P4RuntimeRead, - P4P4runtimeSetForwardingPipelineConfig: p4v1P4RuntimeSetForwardingPipelineConfig, - P4P4runtimeStreamChannel: p4v1P4RuntimeStreamChannel, - P4P4runtimeWrite: p4v1P4RuntimeWrite, + GnoiDiagAllRPC: gnoidiagALL, + GnoiDiagGetBERTResult: gnoidiagGetBERTResult, + GnoiDiagStopBERT: gnoidiagStopBERT, + GnoiDiagStartBERT: gnoidiagStartBERT, + GnoiFactoryresetAllRPC: gnoifactory_resetFactoryResetALL, + GnoiFactoryresetStart: gnoifactory_resetFactoryResetStart, + GnoiFileAllRPC: gnoifileALL, + GnoiFilePut: gnoifilePut, + GnoiFileRemove: gnoifileRemove, + GnoiFileStat: gnoifileStat, + GnoiFileTransferToRemote: gnoifileTransferToRemote, + GnoiFileGet: gnoifileGet, + GnoiHealthzAcknowledge: gnoihealthzAcknowledge, + GnoiHealthzAllRPC: gnoihealthzALL, + GnoiHealthzArtifact: gnoihealthzArtifact, + GnoiHealthzCheck: gnoihealthzCheck, + GnoiHealthzList: gnoihealthzList, + GnoiHealthzGet: gnoihealthzGet, + GnoiLayer2AllRPC: gnoilayer2ALL, + GnoiLayer2ClearLLDPInterface: gnoilayer2ClearLLDPInterface, + GnoiLayer2ClearSpanningTree: gnoilayer2ClearSpanningTree, + GnoiLayer2PerformBERT: gnoilayer2PerformBERT, + GnoiLayer2SendWakeOnLAN: gnoilayer2SendWakeOnLAN, + GnoiLayer2ClearNeighborDiscovery: gnoilayer2ClearNeighborDiscovery, + GnoiLinkqualificationCreate: gnoipacket_link_qualificationLinkQualificationCreate, + GnoiMplsAllRPC: gnoimplsALL, + GnoiMplsClearLSPCounters: gnoimplsClearLSPCounters, + GnoiMplsMPLSPing: gnoimplsMPLSPing, + GnoiMplsClearLSP: gnoimplsClearLSP, + GnoiOtdrAllRPC: gnoiopticalOTDRALL, + GnoiWavelengthrouterAdjustSpectrum: gnoiopticalWavelengthRouterAdjustSpectrum, + GnoiWavelengthrouterAllRPC: gnoiopticalWavelengthRouterALL, + GnoiWavelengthrouterCancelAdjustPSD: gnoiopticalWavelengthRouterCancelAdjustPSD, + GnoiWavelengthrouterCancelAdjustSpectrum: gnoiopticalWavelengthRouterCancelAdjustSpectrum, + GnoiOsActivate: gnoiosActivate, + GnoiOsAllRPC: gnoiosALL, + GnoiOsVerify: gnoiosVerify, + GnoiOsInstall: gnoiosInstall, + GnoiOtdrInitiate: gnoiopticalOTDRInitiate, + GnoiLinkqualificationAllRPC: gnoipacket_link_qualificationLinkQualificationALL, + GnoiLinkqualificationCapabilities: gnoipacket_link_qualificationLinkQualificationCapabilities, + GnoiLinkqualificationDelete: gnoipacket_link_qualificationLinkQualificationDelete, + GnoiLinkqualificationGet: gnoipacket_link_qualificationLinkQualificationGet, + GnoiLinkqualificationList: gnoipacket_link_qualificationLinkQualificationList, + GnoiSystemAllRPC: gnoisystemALL, + GnoiSystemCancelReboot: gnoisystemCancelReboot, + GnoiSystemKillProcess: gnoisystemKillProcess, + GnoiSystemReboot: gnoisystemReboot, + GnoiSystemRebootStatus: gnoisystemRebootStatus, + GnoiSystemSetPackage: gnoisystemSetPackage, + GnoiSystemSwitchControlProcessor: gnoisystemSwitchControlProcessor, + GnoiSystemTime: gnoisystemTime, + GnoiSystemTraceroute: gnoisystemTraceroute, + GnoiSystemPing: gnoisystemPing, + GnoiWavelengthrouterAdjustPSD: gnoiopticalWavelengthRouterAdjustPSD, + GnsiAcctzAllRPC: gnsiacctzv1AcctzALL, + GnsiAcctzRecordSubscribe: gnsiacctzv1AcctzRecordSubscribe, + GnsiAuthzAllRPC: gnsiauthzv1AuthzALL, + GnsiAuthzGet: gnsiauthzv1AuthzGet, + GnsiAuthzProbe: gnsiauthzv1AuthzProbe, + GnsiAuthzRotate: gnsiauthzv1AuthzRotate, + GnsiCertzAddProfile: gnsicertzv1CertzAddProfile, + GnsiCertzAllRPC: gnsicertzv1CertzALL, + GnsiCertzCanGenerateCSR: gnsicertzv1CertzCanGenerateCSR, + GnsiCertzDeleteProfile: gnsicertzv1CertzDeleteProfile, + GnsiCertzGetProfileList: gnsicertzv1CertzGetProfileList, + GnsiCertzRotate: gnsicertzv1CertzRotate, + GnsiCredentialzAllRPC: gnsicredentialzv1CredentialzALL, + GnsiCredentialzCanGenerateKey: gnsicredentialzv1CredentialzCanGenerateKey, + GnsiCredentialzGetPublicKeys: gnsicredentialzv1CredentialzGetPublicKeys, + GnsiCredentialzRotateHostParameters: gnsicredentialzv1CredentialzRotateHostParameters, + GnsiCredentialzRotateAccountCredentials: gnsicredentialzv1CredentialzRotateAccountCredentials, + GnsiPathzAllRPC: gnsipathzv1PathzALL, + GnsiPathzGet: gnsipathzv1PathzGet, + GnsiPathzProbe: gnsipathzv1PathzProbe, + GnsiPathzRotate: gnsipathzv1PathzRotate, + GribiAllRPC: gribiALL, + GribiFlush: gribiFlush, + GribiGet: gribiGet, + GribiModify: gribiModify, + P4P4runtimeAllRPC: p4v1P4RuntimeALL, + P4P4runtimeCapabilities: p4v1P4RuntimeCapabilities, + P4P4runtimeGetForwardingPipelineConfig: p4v1P4RuntimeGetForwardingPipelineConfig, + P4P4runtimeRead: p4v1P4RuntimeRead, + P4P4runtimeSetForwardingPipelineConfig: p4v1P4RuntimeSetForwardingPipelineConfig, + P4P4runtimeStreamChannel: p4v1P4RuntimeStreamChannel, + P4P4runtimeWrite: p4v1P4RuntimeWrite, } // RPCMAP is a helper that maps path to RPCs data that may be needed in tests. RPCMAP = map[string]*RPC{ - "*": ALL, - "/gnmi.gNMI/*": gnmiALL, - "/gnmi.gNMI/Get": gnmiGet, - "/gnmi.gNMI/Set": gnmiSet, - "/gnmi.gNMI/Subscribe": gnmiSubscribe, - "/gnmi.gNMI/Capabilities": gnmiCapabilities, - "/gnoi.bgp.BGP/*": gnoibgpALL, - "/gnoi.bgp.BGP/ClearBGPNeighbor": gnoibgpClearBGPNeighbor, - "/gnoi.certificate.CertificateManagement/*": gnoicertificateCertificateManagementALL, - "/gnoi.certificate.CertificateManagement/CanGenerateCSR": gnoicertificateCertificateManagementCanGenerateCSR, - "/gnoi.certificate.CertificateManagement/GenerateCSR": gnoicertificateCertificateManagementGenerateCSR, - "/gnoi.certificate.CertificateManagement/GetCertificates": gnoicertificateCertificateManagementGetCertificates, - "/gnoi.certificate.CertificateManagement/Install": gnoicertificateCertificateManagementInstall, - "/gnoi.certificate.CertificateManagement/LoadCertificate": gnoicertificateCertificateManagementLoadCertificate, - "/gnoi.certificate.CertificateManagement/LoadCertificateAuthorityBundle": gnoicertificateCertificateManagementLoadCertificateAuthorityBundle, - "/gnoi.certificate.CertificateManagement/RevokeCertificates": gnoicertificateCertificateManagementRevokeCertificates, - "/gnoi.certificate.CertificateManagement/Rotate": gnoicertificateCertificateManagementRotate, + "*": ALL, + "/gnmi.gNMI/*": gnmiALL, + "/gnmi.gNMI/Get": gnmiGet, + "/gnmi.gNMI/Set": gnmiSet, + "/gnmi.gNMI/Subscribe": gnmiSubscribe, + "/gnmi.gNMI/Capabilities": gnmiCapabilities, + "/gnoi.bgp.BGP/*": gnoibgpALL, + "/gnoi.bgp.BGP/ClearBGPNeighbor": gnoibgpClearBGPNeighbor, "/gnoi.diag.Diag/*": gnoidiagALL, "/gnoi.diag.Diag/GetBERTResult": gnoidiagGetBERTResult, "/gnoi.diag.Diag/StopBERT": gnoidiagStopBERT, diff --git a/internal/security/svid/svid.go b/internal/security/svid/svid.go index 1e66ce406e4..ae5652be395 100644 --- a/internal/security/svid/svid.go +++ b/internal/security/svid/svid.go @@ -108,6 +108,11 @@ func LoadKeyPair(keyPath, certPath string) (any, *x509.Certificate, error) { if err != nil { return nil, nil, err } + case "PRIVATE KEY": + caPrivateKey, err = x509.ParsePKCS8PrivateKey(caKeyPem.Bytes) + if err != nil { + return nil, nil, err + } default: return nil, nil, fmt.Errorf("file does not contain an ECDSA/RSA private key") diff --git a/internal/system/system.go b/internal/system/system.go new file mode 100644 index 00000000000..d21116e2750 --- /dev/null +++ b/internal/system/system.go @@ -0,0 +1,39 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package system provides helper functions for gNMI system related operations. +package system + +import ( + "testing" + + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +// FindProcessIDByName uses telemetry to find out the PID of a process. +func FindProcessIDByName(t *testing.T, dut *ondatra.DUTDevice, pName string) uint64 { + t.Helper() + + var pid uint64 + pList := gnmi.GetAll[*oc.System_Process](t, dut, gnmi.OC().System().ProcessAny().State()) + for _, proc := range pList { + if proc.GetName() == pName { + pid = proc.GetPid() + break + } + } + return pid +} diff --git a/internal/tescale/scale.go b/internal/tescale/scale.go new file mode 100644 index 00000000000..eb23f2c9b40 --- /dev/null +++ b/internal/tescale/scale.go @@ -0,0 +1,268 @@ +// 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 tescale provides functions for tescale +package tescale + +import ( + "sync" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/iputil" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" +) + +const ( + // VRFT vrf t + VRFT = "vrf_t" + // VRFR vrf r + VRFR = "vrf_r" + // VRFRD vrf rd + VRFRD = "vrf_rd" + + // V4TunnelIPBlock tunnel IP block + V4TunnelIPBlock = "198.18.0.1/16" + // V4VIPIPBlock vip IP block + V4VIPIPBlock = "198.18.196.1/22" + + tunnelSrcIP = "198.18.204.1" +) + +// IPPool for IPs +type IPPool struct { + ips []string + index int + rw sync.RWMutex +} + +// NewIPPool creates a new IPPool +func NewIPPool(entries []string) *IPPool { + return &IPPool{ + ips: entries, + index: -1, + } +} + +// NextIP returns the next IP +func (p *IPPool) NextIP() string { + p.rw.Lock() + defer p.rw.Unlock() + + p.index++ + return p.ips[p.index] +} + +// AllIPs returns all IPs in the pool +func (p *IPPool) AllIPs() []string { + return append([]string{}, p.ips...) +} + +// IDPool for NH and NHG IDs +type IDPool struct { + nhIndex uint64 + nhgIndex uint64 + rw sync.RWMutex +} + +// NewIDPool creates a new IDPool +func NewIDPool(base uint64) *IDPool { + return &IDPool{ + nhIndex: base, + nhgIndex: base, + } +} + +// NextNHID returns the next NHID +func (p *IDPool) NextNHID() uint64 { + p.rw.Lock() + defer p.rw.Unlock() + + p.nhIndex++ + return p.nhIndex +} + +// NextNHGID returns the next NHGID +func (p *IDPool) NextNHGID() uint64 { + p.rw.Lock() + defer p.rw.Unlock() + + p.nhgIndex++ + return p.nhgIndex +} + +// VRFConfig holds NH, NHG and IPv4 entries for the VRF. +type VRFConfig struct { + Name string + NHs []fluent.GRIBIEntry + NHGs []fluent.GRIBIEntry + V4Entries []fluent.GRIBIEntry +} + +// Param TE holds scale parameters. +type Param struct { + V4TunnelCount int + V4TunnelNHGCount int + V4TunnelNHGSplitCount int + EgressNHGSplitCount int + V4ReEncapNHGCount int +} + +// BuildVRFConfig creates scale new scale VRF configurations. +func BuildVRFConfig(dut *ondatra.DUTDevice, egressIPs []string, param Param) []*VRFConfig { + v4TunnelIPAddrs := NewIPPool(iputil.GenerateIPs(V4TunnelIPBlock, param.V4TunnelCount)) + v4VIPAddrs := NewIPPool(iputil.GenerateIPs(V4VIPIPBlock, (param.V4TunnelNHGCount*param.V4TunnelNHGSplitCount)+2)) + v4EgressIPAddrs := NewIPPool(egressIPs) + + defaultVRF := deviations.DefaultNetworkInstance(dut) + vrfTConf := &VRFConfig{Name: VRFT} + vrfRConf := &VRFConfig{Name: VRFR} + vrfRDConf := &VRFConfig{Name: VRFRD} + vrfDefault := &VRFConfig{Name: defaultVRF} + idPool := NewIDPool(10000) + + // VRF_T: + + nhgID := idPool.NextNHGID() + nhID := idPool.NextNHID() + nhgRedirectToVrfR := nhgID + // build backup NHG and NH. + vrfDefault.NHs = append(vrfDefault.NHs, + fluent.NextHopEntry().WithIndex(nhID).WithNetworkInstance(defaultVRF).WithNextHopNetworkInstance(VRFR), + ) + vrfDefault.NHGs = append(vrfDefault.NHGs, + fluent.NextHopGroupEntry().WithID(nhgRedirectToVrfR).AddNextHop(nhID, 1).WithNetworkInstance(defaultVRF), + ) + + // Build IPv4 entry and related NHGs and NHs. + // * Mapping tunnel IP per the IP -> NHG ratio + // * Each NHG has unique NHs. + // * Each NHG has the same backup to Repair VRF. + tunnelNHGRatio := param.V4TunnelCount / param.V4TunnelNHGCount + for idx, ip := range v4TunnelIPAddrs.AllIPs() { + if idx%tunnelNHGRatio == 0 { + nhgID = idPool.NextNHGID() + nhgEntry := fluent.NextHopGroupEntry().WithID(nhgID).WithNetworkInstance(defaultVRF).WithBackupNHG(nhgRedirectToVrfR) + + // Build NHs and link NHs to NHG. + for i := 0; i < param.V4TunnelNHGSplitCount; i++ { + vip := v4VIPAddrs.NextIP() + nhID = idPool.NextNHID() + vrfDefault.NHs = append(vrfDefault.NHs, + fluent.NextHopEntry().WithIndex(nhID).WithNetworkInstance(defaultVRF).WithIPAddress(vip), + ) + nhgEntry = nhgEntry.AddNextHop(nhID, 1) + } + vrfDefault.NHGs = append(vrfDefault.NHGs, nhgEntry) + } + + // Build IPv4 entry + vrfTConf.V4Entries = append(vrfTConf.V4Entries, + fluent.IPv4Entry().WithPrefix(ip+"/32").WithNextHopGroup(nhgID).WithNetworkInstance(VRFT).WithNextHopGroupNetworkInstance(defaultVRF), + ) + } + + // Default VRF: + + // * each VIP 1:1 map to a NHG + // * each NHG points to unique NHs + for _, ip := range v4VIPAddrs.AllIPs() { + nhgID := idPool.NextNHGID() + nhgEntry := fluent.NextHopGroupEntry().WithID(nhgID).WithNetworkInstance(defaultVRF) + // Build NHs and link NHs to NHG. + for i := 0; i < param.EgressNHGSplitCount; i++ { + vip := v4EgressIPAddrs.AllIPs()[i] + nhID = idPool.NextNHID() + vrfDefault.NHs = append(vrfDefault.NHs, + fluent.NextHopEntry().WithIndex(nhID).WithNetworkInstance(defaultVRF).WithIPAddress(vip), + ) + nhgEntry = nhgEntry.AddNextHop(nhID, 1) + } + + vrfDefault.NHGs = append(vrfDefault.NHGs, nhgEntry) + // Build IPv4 entry + vrfDefault.V4Entries = append(vrfDefault.V4Entries, + fluent.IPv4Entry().WithPrefix(ip+"/32").WithNextHopGroup(nhgID).WithNetworkInstance(defaultVRF).WithNextHopGroupNetworkInstance(defaultVRF), + ) + } + + // VRF_R + + // build backup NHG and NH. + nhID = idPool.NextNHID() + nhgID = idPool.NextNHGID() + nhgDecapToDefault := nhgID + vrfDefault.NHs = append(vrfDefault.NHs, + fluent.NextHopEntry().WithIndex(nhID).WithDecapsulateHeader(fluent.IPinIP).WithNetworkInstance(defaultVRF).WithNextHopNetworkInstance(defaultVRF), + ) + vrfDefault.NHGs = append(vrfDefault.NHGs, + fluent.NextHopGroupEntry().WithID(nhgID).AddNextHop(nhID, 1).WithNetworkInstance(defaultVRF), + ) + + // build IP entries and related NHG and NHs. + // * Each NHG 1:1 mapping to NH + // * Each NH has one entry for decap and encap + // * All NHG has a backup for decap then goto default VRF. + reEncapNHGRatio := param.V4TunnelCount / param.V4ReEncapNHGCount + nhgID = idPool.NextNHGID() + nhgEntry := fluent.NextHopGroupEntry().WithID(nhgID).WithNetworkInstance(defaultVRF).WithBackupNHG(nhgDecapToDefault) + for idx, ip := range v4TunnelIPAddrs.AllIPs() { + nhID = idPool.NextNHID() + vrfDefault.NHs = append(vrfDefault.NHs, + fluent.NextHopEntry().WithIndex(nhID).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithNetworkInstance(defaultVRF).WithIPinIP(tunnelSrcIP, v4TunnelIPAddrs.AllIPs()[(idx+1)%len(v4TunnelIPAddrs.AllIPs())]), + ) + if idx != 0 && idx%reEncapNHGRatio == 0 { + vrfDefault.NHGs = append(vrfDefault.NHGs, nhgEntry) + nhgID = idPool.NextNHGID() + nhgEntry = fluent.NextHopGroupEntry().WithID(nhgID).WithNetworkInstance(defaultVRF).WithBackupNHG(nhgDecapToDefault) + } + nhgEntry = nhgEntry.AddNextHop(nhID, 1) + vrfRConf.V4Entries = append(vrfRConf.V4Entries, + fluent.IPv4Entry().WithPrefix(ip+"/32").WithNextHopGroup(nhgID).WithNetworkInstance(VRFR).WithNextHopGroupNetworkInstance(defaultVRF), + ) + } + vrfDefault.NHGs = append(vrfDefault.NHGs, nhgEntry) + + v4VIPAddrs = NewIPPool(iputil.GenerateIPs(V4VIPIPBlock, (param.V4TunnelNHGCount*param.V4TunnelNHGSplitCount)+2)) + + // VRF_RP + + // * do the same as Transit VRF + // * but with decap to default NHG + for idx, ip := range v4TunnelIPAddrs.AllIPs() { + if idx%tunnelNHGRatio == 0 { + nhgID = idPool.NextNHGID() + nhgEntry := fluent.NextHopGroupEntry().WithID(nhgID).WithNetworkInstance(defaultVRF).WithBackupNHG(nhgRedirectToVrfR) + + // Build NHs and link NHs to NHG. + for i := 0; i < param.V4TunnelNHGSplitCount; i++ { + vip := v4VIPAddrs.NextIP() + nhID = idPool.NextNHID() + vrfDefault.NHs = append(vrfDefault.NHs, + fluent.NextHopEntry().WithIndex(nhID).WithNetworkInstance(defaultVRF).WithIPAddress(vip), + ) + nhgEntry = nhgEntry.AddNextHop(nhID, 1) + } + vrfDefault.NHGs = append(vrfDefault.NHGs, nhgEntry) + } + + // Build IPv4 entry + vrfRDConf.V4Entries = append(vrfRDConf.V4Entries, + fluent.IPv4Entry().WithPrefix(ip+"/32").WithNextHopGroup(nhgID).WithNetworkInstance(VRFRD).WithNextHopGroupNetworkInstance(defaultVRF), + ) + } + + return []*VRFConfig{vrfDefault, vrfTConf, vrfRConf, vrfRDConf} +} diff --git a/internal/vrfpolicy/vrfpolicy.go b/internal/vrfpolicy/vrfpolicy.go new file mode 100644 index 00000000000..1946093987c --- /dev/null +++ b/internal/vrfpolicy/vrfpolicy.go @@ -0,0 +1,400 @@ +// 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 vrfpolicy contains functions to build specific vrf policies +package vrfpolicy + +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" +) + +const ( + // VRFPolicyW is the policy name + VRFPolicyW = "vrf_selection_policy_w" + // VRFPolicyC is the policy name + VRFPolicyC = "vrf_selection_policy_c" + + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niEncapTeVrfB = "ENCAP_TE_VRF_B" + niEncapTeVrfC = "ENCAP_TE_VRF_C" + niEncapTeVrfD = "ENCAP_TE_VRF_D" + niRepairVrf = "REPAIR_VRF" + niDefault = "DEFAULT" + dscpEncapA1 = 10 + dscpEncapA2 = 18 + dscpEncapB1 = 20 + dscpEncapB2 = 28 + dscpEncapNoMatch = 30 + ipv4OuterSrc111WithMask = "198.51.100.111/32" + ipv4OuterSrc222WithMask = "198.51.100.222/32" + niTeVrf111 = "TE_VRF_111" + niTeVrf222 = "TE_VRF_222" + decapFlowSrc = "198.51.100.111" +) + +type ipInfo struct { + protocol oc.UnionUint8 + dscpSet []uint8 + sourceAddr string +} + +type action struct { + decapNI string + postDecapNI string + decapFallbackNI string + networkInstance string +} + +type policyFwRule struct { + seqID uint32 + ipv4 *ipInfo + ipv6 *ipInfo + action *action +} + +// configureNetworkInstance configures vrfs DECAP_TE_VRF, ENCAP_TE_VRF_A, ENCAP_TE_VRF_B, +// ENCAP_TE_VRF_C, ENCAP_TE_VRF_D, TE_VRF_111, TE_VRF_222 +func configNonDefaultNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + c := &oc.Root{} + vrfs := []string{niDecapTeVrf, niEncapTeVrfA, niEncapTeVrfB, niEncapTeVrfC, niEncapTeVrfD, niTeVrf111, niTeVrf222, niRepairVrf} + for _, vrf := range vrfs { + ni := c.GetOrCreateNetworkInstance(vrf) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf).Config(), ni) + } +} + +// BuildVRFSelectionPolicyW vrf selection policy rule +// Reference: https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/vrf_policy_driven_te/README.md?plain=1#L252 +func BuildVRFSelectionPolicyW(t *testing.T, dut *ondatra.DUTDevice, niName string) *oc.NetworkInstance_PolicyForwarding { + configNonDefaultNetworkInstance(t, dut) + + pfRule1 := &policyFwRule{ + seqID: 1, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, + } + pfRule2 := &policyFwRule{ + seqID: 2, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, + } + pfRule3 := &policyFwRule{ + seqID: 3, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, + } + pfRule4 := &policyFwRule{ + seqID: 4, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, + } + + pfRule5 := &policyFwRule{ + seqID: 5, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, + } + pfRule6 := &policyFwRule{ + seqID: 6, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, + } + pfRule7 := &policyFwRule{ + seqID: 7, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, + } + pfRule8 := &policyFwRule{ + seqID: 8, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, + } + + pfRule9 := &policyFwRule{ + seqID: 9, + ipv4: &ipInfo{protocol: 4, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, + } + pfRule10 := &policyFwRule{ + seqID: 10, + ipv4: &ipInfo{protocol: 41, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, + } + pfRule11 := &policyFwRule{ + seqID: 11, + ipv4: &ipInfo{protocol: 4, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, + } + pfRule12 := &policyFwRule{ + seqID: 12, + ipv4: &ipInfo{protocol: 41, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, + } + + pfRuleList := []*policyFwRule{ + pfRule1, pfRule2, pfRule3, pfRule4, pfRule5, pfRule6, + pfRule7, pfRule8, pfRule9, pfRule10, pfRule11, pfRule12, + } + + if deviations.PfRequireSequentialOrderPbrRules(dut) { + pfRule10.seqID = 910 + pfRule11.seqID = 911 + pfRule12.seqID = 912 + } + + niP := buildVRFSelectionPolicy(niName, VRFPolicyW, pfRuleList) + niPf := niP.GetPolicy(VRFPolicyW) + + if deviations.PfRequireMatchDefaultRule(dut) { + pfR13 := niPf.GetOrCreateRule(913) + pfR13.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4) + pfRAction := pfR13.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + pfR14 := niPf.GetOrCreateRule(914) + pfR14.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6) + pfRAction = pfR14.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } else { + pfR := niPf.GetOrCreateRule(13) + pfRAction := pfR.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } + + return niP +} + +// BuildVRFSelectionPolicyC vrf selection policy rule +// Reference: https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/vrf_policy_driven_te/README.md?plain=1#L40 +func BuildVRFSelectionPolicyC(t *testing.T, dut *ondatra.DUTDevice, niName string) *oc.NetworkInstance_PolicyForwarding { + configNonDefaultNetworkInstance(t, dut) + + pfRule1 := &policyFwRule{ + seqID: 1, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, + } + pfRule2 := &policyFwRule{ + seqID: 2, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, + } + pfRule3 := &policyFwRule{ + seqID: 3, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, + } + pfRule4 := &policyFwRule{ + seqID: 4, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, + } + + pfRule5 := &policyFwRule{ + seqID: 5, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, + } + pfRule6 := &policyFwRule{ + seqID: 6, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, + } + pfRule7 := &policyFwRule{ + seqID: 7, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, + } + pfRule8 := &policyFwRule{ + seqID: 8, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, + } + + pfRule9 := &policyFwRule{ + seqID: 9, + ipv4: &ipInfo{protocol: 4, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, + } + pfRule10 := &policyFwRule{ + seqID: 10, + ipv4: &ipInfo{protocol: 41, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, + } + pfRule11 := &policyFwRule{ + seqID: 11, + ipv4: &ipInfo{protocol: 4, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, + } + pfRule12 := &policyFwRule{ + seqID: 12, + ipv4: &ipInfo{protocol: 41, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, + } + + pfRule13 := &policyFwRule{ + seqID: 13, + ipv4: &ipInfo{dscpSet: []uint8{dscpEncapA1, dscpEncapA2}}, + action: &action{networkInstance: niEncapTeVrfA}, + } + pfRule14 := &policyFwRule{ + seqID: 14, + ipv6: &ipInfo{dscpSet: []uint8{dscpEncapA1, dscpEncapA2}}, + action: &action{networkInstance: niEncapTeVrfA}, + } + pfRule15 := &policyFwRule{ + seqID: 15, + ipv4: &ipInfo{dscpSet: []uint8{dscpEncapB1, dscpEncapB2}}, + action: &action{networkInstance: niEncapTeVrfB}, + } + pfRule16 := &policyFwRule{ + seqID: 16, + ipv6: &ipInfo{dscpSet: []uint8{dscpEncapB1, dscpEncapB2}}, + action: &action{networkInstance: niEncapTeVrfB}, + } + + pfRuleList := []*policyFwRule{ + pfRule1, pfRule2, pfRule3, pfRule4, pfRule5, pfRule6, pfRule7, pfRule8, + pfRule9, pfRule10, pfRule11, pfRule12, pfRule13, pfRule14, pfRule15, pfRule16, + } + + if deviations.PfRequireSequentialOrderPbrRules(dut) { + pfRule10.seqID = 910 + pfRule11.seqID = 911 + pfRule12.seqID = 912 + pfRule13.seqID = 913 + pfRule14.seqID = 914 + pfRule15.seqID = 915 + pfRule16.seqID = 916 + } + + niP := buildVRFSelectionPolicy(niName, VRFPolicyC, pfRuleList) + niPf := niP.GetPolicy(VRFPolicyC) + + if deviations.PfRequireMatchDefaultRule(dut) { + pfR17 := niPf.GetOrCreateRule(917) + pfR17.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4) + pfRAction := pfR17.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + pfR18 := niPf.GetOrCreateRule(918) + pfR18.GetOrCreateL2().SetEthertype(oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6) + pfRAction = pfR18.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } else { + pfR := niPf.GetOrCreateRule(17) + pfRAction := pfR.GetOrCreateAction() + pfRAction.NetworkInstance = ygot.String(niDefault) + } + + return niP +} + +// ConfigureVRFSelectionPolicy configures vrf selection policy on default NI and applies to DUT port1 +func ConfigureVRFSelectionPolicy(t *testing.T, dut *ondatra.DUTDevice, policyName string) { + t.Helper() + + port1 := dut.Port(t, "port1") + interfaceID := port1.Name() + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = interfaceID + ".0" + } + + var niForwarding *oc.NetworkInstance_PolicyForwarding + switch policyName { + case VRFPolicyC: + niForwarding = BuildVRFSelectionPolicyC(t, dut, deviations.DefaultNetworkInstance(dut)) + case VRFPolicyW: + niForwarding = BuildVRFSelectionPolicyW(t, dut, deviations.DefaultNetworkInstance(dut)) + default: + t.Fatalf("unsupported policy name: %s", policyName) + } + + dutForwardingPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() + gnmi.Replace(t, dut, dutForwardingPath.Config(), niForwarding) + + interface1 := niForwarding.GetOrCreateInterface(interfaceID) + interface1.ApplyVrfSelectionPolicy = ygot.String(policyName) + interface1.GetOrCreateInterfaceRef().Interface = ygot.String(port1.Name()) + interface1.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + interface1.InterfaceRef = nil + } + gnmi.Replace(t, dut, dutForwardingPath.Interface(interfaceID).Config(), interface1) +} + +func buildVRFSelectionPolicy(niName string, policyName string, pfRules []*policyFwRule) *oc.NetworkInstance_PolicyForwarding { + r := &oc.Root{} + ni := r.GetOrCreateNetworkInstance(niName) + niP := ni.GetOrCreatePolicyForwarding() + niPf := niP.GetOrCreatePolicy(policyName) + niPf.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + + for _, pfRule := range pfRules { + pfR := niPf.GetOrCreateRule(pfRule.seqID) + if pfRule.ipv4 != nil { + pfRProtoIP := pfR.GetOrCreateIpv4() + if pfRule.ipv4.dscpSet != nil { + pfRProtoIP.DscpSet = pfRule.ipv4.dscpSet + } + if pfRule.ipv4.protocol != 0 { + pfRProtoIP.Protocol = pfRule.ipv4.protocol + } + if pfRule.ipv4.sourceAddr != "" { + pfRProtoIP.SourceAddress = ygot.String(pfRule.ipv4.sourceAddr) + } + } else { + pfRProtoIP := pfR.GetOrCreateIpv4() + if pfRule.ipv6.dscpSet != nil { + pfRProtoIP.DscpSet = pfRule.ipv6.dscpSet + } + } + + pfRAction := pfR.GetOrCreateAction() + if pfRule.action.decapNI != "" { + pfRAction.DecapNetworkInstance = ygot.String(pfRule.action.decapNI) + } + if pfRule.action.postDecapNI != "" { + pfRAction.PostDecapNetworkInstance = ygot.String(pfRule.action.postDecapNI) + } + if pfRule.action.decapFallbackNI != "" { + pfRAction.DecapFallbackNetworkInstance = ygot.String(pfRule.action.decapFallbackNI) + } + if pfRule.action.networkInstance != "" { + pfRAction.NetworkInstance = ygot.String(pfRule.action.networkInstance) + } + } + + return niP +} + +// DeletePolicyForwarding deletes policy configured under given interface. +func DeletePolicyForwarding(t *testing.T, dut *ondatra.DUTDevice, portID string) { + t.Helper() + p1 := dut.Port(t, portID) + ingressPort := p1.Name() + interfaceID := ingressPort + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = ingressPort + ".0" + } + pfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Interface(interfaceID) + gnmi.Delete(t, dut, pfPath.Config()) +} diff --git a/proto/metadata.proto b/proto/metadata.proto index c2bad954691..29957c9d35d 100644 --- a/proto/metadata.proto +++ b/proto/metadata.proto @@ -36,6 +36,8 @@ message Metadata { TESTBED_DUT_ATE_4LINKS = 4; TESTBED_DUT_ATE_9LINKS_LAG = 5; TESTBED_DUT_DUT_ATE_2LINKS = 6; + TESTBED_DUT_ATE_8LINKS = 7; + TESTBED_DUT_400ZR = 8; } // Testbed on which the test is intended to run. Testbed testbed = 4; @@ -98,9 +100,6 @@ message Metadata { bool osinstall_for_standby_rp = 17; // Set this flag for LLDP interface config to override the global config. bool lldp_interface_config_override_global = 18; - // Skip BGP TestPassword mismatch subtest if value is true. - // Cisco: partnerissuetracker.corp.google.com/273285907 - bool skip_bgp_test_password_mismatch = 19; // Skip check for // bgp/neighbors/neighbor/state/messages/received/last-notification-error-code // leaf missing case. @@ -145,10 +144,6 @@ message Metadata { // Use this deviation when the device does not support a mix of tagged and // untagged subinterfaces. bool no_mix_of_tagged_and_untagged_subinterfaces = 34; - // Device does not report P4RT node names in the component hierarchy. - bool explicit_p4rt_node_component = 35; - // Configure ACLs using vendor native model specifically for RT-1.4. - bool use_vendor_native_acl_config = 36; // Device does not support reporting software version according to the // requirements in gNMI-1.10. bool sw_version_unsupported = 37; @@ -158,8 +153,6 @@ message Metadata { // Device does not support telemetry path /components/component/storage. // Juniper: partnerissuetracker.corp.google.com/284239001 bool storage_component_unsupported = 39; - // Device requires gribi-protocol to be enabled under network-instance. - bool explicit_gribi_under_network_instance = 40; // Device requires port-speed to be set because its default value may not be // usable. bool explicit_port_speed = 41; @@ -280,7 +273,8 @@ message Metadata { // Devices require configuring subinterface with tagged vlan for p4rt // packet in. bool p4rt_gdp_requires_dot1q_subinterface = 93; - // ATE port link state operations are a no-op in KNE/virtualized environments. + // ATE port link state operations are a no-op in KNE/virtualized + // environments. bool ate_port_link_state_operations_unsupported = 94; // Creates a user and assigns role/rbac to said user via native model. bool set_native_user = 95; @@ -299,7 +293,8 @@ message Metadata { bool controller_card_cpu_utilization_unsupported = 100; // Device does not support counter for fabric block lost packets. bool fabric_drop_counter_unsupported = 101; - // Device does not support memory utilization related leaves for linecard components. + // Device does not support memory utilization related leaves for linecard + // components. bool linecard_memory_utilization_unsupported = 102; // Device does not support telemetry path // /qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/dropped-pkts. @@ -314,14 +309,15 @@ message Metadata { // Arista: partnerissuetracker.corp.google.com/299285115 bool isis_counter_manual_address_drop_from_areas_unsupported = 106; // Devices do not support telemetry for isis counter: part-changes. - // Arista: partnerissuetracker.corp.google.com/299285991 + // Arista: partnerissuetracker.corp.google.com/317086576 bool isis_counter_part_changes_unsupported = 107; - // Devices do not support threshold container under /components/component/transceiver. + // Devices do not support threshold container under + // /components/component/transceiver. bool transceiver_thresholds_unsupported = 108; // Update interface loopback mode using raw gnmi API due to server version. bool interface_loopback_mode_raw_gnmi = 109; - // Devices do not support showing negotiated tcp mss value in bgp tcp mss telemetry. - // Juniper: b/300499125 + // Devices do not support showing negotiated tcp mss value in bgp tcp mss + // telemetry. Juniper: b/300499125 bool skip_tcp_negotiated_mss_check = 110; // Devices don't support ISIS-Lsp metadata paths: checksum, sequence-number, // remaining-lifetime. @@ -332,8 +328,9 @@ message Metadata { bool skip_fib_failed_traffic_forwarding_check = 113; // QOS requires buffer-allocation-profile configuration bool qos_buffer_allocation_config_required = 114; - // Devices do not support configuring ExtendedNextHopEncoding at the BGP global level. - // Arista: https://partnerissuetracker.corp.google.com/issues/203683090 + // Devices do not support configuring ExtendedNextHopEncoding at the BGP + // global level. Arista: + // https://partnerissuetracker.corp.google.com/issues/203683090 bool bgp_global_extended_next_hop_encoding_unsupported = 115; // OC unsupported for BGP LLGR disable. // Juniper: b/303479602 @@ -341,21 +338,24 @@ message Metadata { // Device does not support tunnel interfaces state paths // Juniper: partnerissuetracker.corp.google.com/300111031 bool tunnel_state_path_unsupported = 117; - // Device does not support tunnel interfaces source and destination address config paths - // Juniper: partnerissuetracker.corp.google.com/300111031 + // Device does not support tunnel interfaces source and destination address + // config paths Juniper: partnerissuetracker.corp.google.com/300111031 bool tunnel_config_path_unsupported = 118; - // Cisco: Device does not support same minimun and maximum threshold value in QOS ECN config. + // Cisco: Device does not support same minimun and maximum threshold value + // in QOS ECN config. bool ecn_same_min_max_threshold_unsupported = 119; // Cisco: QOS requires scheduler configuration. bool qos_scheduler_config_required = 120; - // Cisco: Device does not support set weight config under QOS ECN configuration. + // Cisco: Device does not support set weight config under QOS ECN + // configuration. bool qos_set_weight_config_unsupported = 121; // Cisco: Device does not support these get state path. bool qos_get_state_path_unsupported = 122; // Devices requires enabled leaf under isis level // Juniper: partnerissuetracker.corp.google.com/302661486 bool isis_level_enabled = 123; - // Devices which require to use interface-id format of interface name + .subinterface index with Interface-ref container + // Devices which require to use interface-id format of interface name + + // .subinterface index with Interface-ref container bool interface_ref_interface_id_format = 124; // Devices does not support member link loopback // Juniper: b/307763669 @@ -363,12 +363,313 @@ message Metadata { // Device does not support PLQ operational status check on interface // Juniper: b/308990185 bool skip_plq_interface_oper_status_check = 126; - // Device set received prefix limits explicitly under prefix-limit-received rather than - // "prefix-limit" + // Device set received prefix limits explicitly under prefix-limit-received + // rather than "prefix-limit" bool bgp_explicit_prefix_limit_received = 127; + // Device does not configure BGP maximum routes correctly when max-prefixes + // leaf is configured + bool bgp_missing_oc_max_prefixes_configuration = 128; + // Devices which needs to skip checking AFI-SAFI disable. + // Juniper: b/310698466 + bool skip_bgp_session_check_without_afisafi = 129; + // Devices that have separate naming conventions for hardware resource name + // in /system/ tree and /components/ tree. + bool mismatched_hardware_resource_name_in_component = 130; + // Devices that don't support telemetry for hardware resources before + // used-threshold-upper configuration. + bool missing_hardware_resource_telemetry_before_config = 131; + // Device does not support reboot status check on subcomponents. + bool gnoi_subcomponent_reboot_status_unsupported = 132; + // Devices exports routes from all protocols to BGP if the export-policy is + // ACCEPT Juniper: b/308970803 + bool skip_non_bgp_route_export_check = 133; + // Devices do not support path + // /network-instances/network-instance/protocols/protocol/isis/levels/level/state/metric-style + // Arista: https://partnerissuetracker.corp.google.com/issues/317064733 + bool isis_metric_style_telemetry_unsupported = 134; + // Devices do not support configuring Interface-ref under Static-Route + // Next-Hop + bool static_route_next_hop_interface_ref_unsupported = 135; + // Devices which does not support nexthop index state + // Juniper: b/304729237 + bool skip_static_nexthop_check = 136; + // Device doesn't support router advertisement enable and mode config + // Juniper: b/316173974 + bool ipv6_router_advertisement_config_unsupported = 138; + // Devices does not support setting prefix limit exceeded flag. + // Juniper : b/317181227 + bool prefix_limit_exceeded_telemetry_unsupported = 139; + // Skip setting allow-multiple-as while configuring eBGP + // Arista: partnerissuetracker.corp.google.com/issues/317422300 + bool skip_setting_allow_multiple_as = 140; + // Skip tests with decap encap vrf as PBF action + // Nokia: partnerissuetracker.corp.google.com/issues/323251581 + bool skip_pbf_with_decap_encap_vrf = 141; + // Devices which does not support copying TTL. + // Juniper: b/307258544 + bool ttl_copy_unsupported = 142; + // Devices does not support mixed prefix length in gribi. + // Juniper: b/307824407 + bool gribi_decap_mixed_plen_unsupported = 143; + // Skip setting isis-actions set-level while configuring routing-policy + // statement action + bool skip_isis_set_level = 144; + // Skip setting isis-actions set-metric-style-type while configuring + // routing-policy statement action + bool skip_isis_set_metric_style_type = 145; + // Skip setting match-prefix-set match-set-options while configuring + // routing-policy statement condition + bool skip_set_rp_match_set_options = 146; + // Skip setting disable-metric-propagation while configuring + // table-connection + bool skip_setting_disable_metric_propagation = 147; + // Devices do not support BGP conditions match-community-set + bool bgp_conditions_match_community_set_unsupported = 148; + // Device requires match condition for ethertype v4 and v6 for default rule + // with network-instance default-vrf in policy-forwarding. + bool pf_require_match_default_rule = 149; + // Devices missing component tree mapping from hardware port + // to optical channel. + bool missing_port_to_optical_channel_component_mapping = 150; + // Skip gNMI container OP tc. + // Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 + bool skip_container_op = 151; + // Reorder calls for vendor compatibility. + // Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 + bool reorder_calls_for_vendor_compatibilty = 152; + // Add missing base config using cli. + // Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 + bool add_missing_base_config_via_cli = 153; + // skip_macaddress_check returns true if mac address for an interface via + // gNMI needs to be skipped. Cisco: + // https://partnerissuetracker.corp.google.com/issues/322291556 + bool skip_macaddress_check = 154; + // Devices are having native telemetry paths for BGP RIB verification. + // Juniper : b/306144372 + bool bgp_rib_oc_path_unsupported = 155; + // Skip setting prefix-set mode while configuring prefix-set routing-policy + bool skip_prefix_set_mode = 156; + // Devices set metric as preference for static next-hop + bool set_metric_as_preference = 157; + // Devices don't support having an IPv6 static Route with an IPv4 address + // as next hop and requires configuring a static ARP entry. + // Arista: https://partnerissuetracker.corp.google.com/issues/316593298 + bool ipv6_static_route_with_ipv4_next_hop_requires_static_arp = 158; + // Device requires policy-forwarding rules to be in sequential order in the + // gNMI set-request. + bool pf_require_sequential_order_pbr_rules = 159; + // Device telemetry missing next hop metric value. + // Arista: https://partnerissuetracker.corp.google.com/issues/321010782 + bool missing_static_route_next_hop_metric_telemetry = 160; + // Device does not support recursive resolution of static route next hop. + // Arista: https://partnerissuetracker.corp.google.com/issues/314449182 + bool unsupported_static_route_next_hop_recurse = 161; + // Device missing telemetry for static route that has DROP next hop. + // Arista: https://partnerissuetracker.corp.google.com/issues/330619816 + bool missing_static_route_drop_next_hop_telemetry = 162; + // Device missing 400ZR optical-channel tunable parameters telemetry: + // min/max/avg. + // Arista: https://partnerissuetracker.corp.google.com/issues/319314781 + bool missing_zr_optical_channel_tunable_parameters_telemetry = 163; + // Device that does not support packet link qualification reflector packet + // sent/received stats. + bool plq_reflector_stats_unsupported = 164; + // Device that does not support PLQ Generator max_mtu to be atleast >= 8184. + uint32 plq_generator_capabilities_max_mtu = 165; + // Device that does not support PLQ Generator max_pps to be atleast >= + // 100000000. + uint64 plq_generator_capabilities_max_pps = 166; + // Support for bgp extended community index + bool bgp_extended_community_index_unsupported = 167; + // Support for bgp community set refs + bool bgp_community_set_refs_unsupported = 168; + // Arista device needs CLI knob to enable WECMP feature + bool rib_wecmp = 169; + // Device not supporting table-connection need to set this true + bool table_connections_unsupported = 170; + // Configure tag-set using vendor native model + bool use_vendor_native_tag_set_config = 171; + // Skip setting send-community-type in bgp global config + bool skip_bgp_send_community_type = 172; + // Support for bgp actions set-community method + bool bgp_actions_set_community_method_unsupported = 174; + // Ensure no configurations exist under BGP Peer Groups + bool set_no_peer_group = 175; + // Bgp community member is a string + bool bgp_community_member_is_a_string = 176; + // Flag to indicate whether IPv4 static routes with IPv6 next-hops are + // unsupported. + bool ipv4_static_route_with_ipv6_nh_unsupported = 177; + // Flag to indicate whether IPv6 static routes with IPv4 next-hops are + // unsupported. + bool ipv6_static_route_with_ipv4_nh_unsupported = 178; + // Flag to indicate support for static routes that simply drop packets + bool static_route_with_drop_nh = 179; + // Flag to indicate support for static routes that can be configured with an + // explicit metric. + bool static_route_with_explicit_metric = 180; + // Support for bgp default import/export policy + bool bgp_default_policy_unsupported = 181; + // Flag to enable bgp explicity on default vrf + // Arista: b/329094094#comment9 + bool explicit_enable_bgp_on_default_vrf = 182; + // tag-set is not a real separate entity, but is embedded in the policy + // statement. this implies that 1. routing policy tag set name needs to be + // ' ' + // 2. only one policy statement can make use of a tag-set, and 3. tag must + // be refered by a policy + bool routing_policy_tag_set_embedded = 183; + // Devices does not support allow multiple as under AFI/SAFI. + // CISCO: b/340859662 + bool skip_afi_safi_path_for_bgp_multiple_as = 184; + // Device does not support regex with routing-policy community-member. + bool community_member_regex_unsupported = 185; + // Support for same import policy attached to all AFIs for given + // (src-protocol, dst-protocol, network-instance) triple Arista: + // b/339645876#comment4 + bool same_policy_attached_to_all_afis = 186; + // Devices needs to skip setting statement for policy to be applied as + // action pass otherwise it will be configured as action done. + // CISCO: b/338523730 + bool skip_setting_statement_for_policy = 187; + // Devices does not support index specific attribute fetching and hence + // wildcards has to be used. + // CISCO: b/338523730 + bool skip_checking_attribute_index = 188; + // Devices does not suppport policy-chaining, so needs to flatten policies + // with multiple statements. + // CISCO: b/338526243 + bool flatten_policy_with_multiple_statements = 189; + // default_route_policy_unsupported is set to true for devices that do not + // support default route policy. + bool default_route_policy_unsupported = 190; + // CISCO: b/339801843 + bool slaac_prefix_length128 = 191; + // Devices does not support bgp max multipaths + // Juniper: b/319301559 + bool bgp_max_multipath_paths_unsupported = 192; + // Devices does not multipath config at neighbor or afisafi level + // Juniper: b/341130490 + bool multipath_unsupported_neighbor_or_afisafi = 193; + // Devices that do not support /components/component/state/model-name for + // any component types. + // Note that for model name to be supported, the + // /components/component/state/model-name of the chassis component must be + // equal to the canonical hardware model name of its device. + bool model_name_unsupported = 194; + // community_match_with_redistribution_unsupported is set to true for devices that do not support matching community at the redistribution attach point. + bool community_match_with_redistribution_unsupported = 195; + // Devices that do not support components/component/state/install-component + // and components/component/state/install-position. + bool install_position_and_install_component_unsupported = 196; + // Encap tunnel is shut then zero traffic will flow to backup NHG + bool encap_tunnel_shut_backup_nhg_zero_traffic = 197; + // Flag to indicate support for max ecmp paths for isis. + bool max_ecmp_paths = 198; + // wecmp_auto_unsupported is set to true for devices that do not support auto wecmp + bool wecmp_auto_unsupported = 199; + // policy chaining, ie. more than one policy at an attachement point is not supported + bool routing_policy_chaining_unsupported = 200; + // isis loopback config required + bool isis_loopback_required = 201; + // weighted ecmp feature verification using fixed packet + bool weighted_ecmp_fixed_packet_verification = 202; + // Override default NextHop scale while enabling encap/decap scale + // CISCO: + bool override_default_nh_scale = 203; + // Devices that donot support setting bgp extended community set + bool bgp_extended_community_set_unsupported = 204; + // Devices that do not support setting bgp extended community set refs + bool bgp_set_ext_community_set_refs_unsupported = 205; + // Devices that do not support deleting link bandwidth + bool bgp_delete_link_bandwidth_unsupported = 206; + // qos_inqueue_drop_counter_Unsupported is set to true for devices that do not support qos ingress queue drop counters. + // Juniper: b/341130490 + bool qos_inqueue_drop_counter_unsupported = 207; + // Devices that need bgp extended community enable explicitly + bool bgp_explicit_extended_community_enable = 208; + // devices that do not support match tag set condition + bool match_tag_set_condition_unsupported = 209; + // peer_group_def_bgp_vrf_unsupported is set to true for devices that do not support peer group definition under bgp vrf configuration. + bool peer_group_def_ebgp_vrf_unsupported = 210; + // redis_uconnected_under_ebgp_vrf_unsupported is set to true for devices that do not support redistribution of connected routes under ebgp vrf configuration. + bool redis_connected_under_ebgp_vrf_unsupported = 211; + // bgp_afisafi_in_default_ni_before_other_ni is set to true for devices that require certain afi/safis to be enabled + // in default network instance (ni) before enabling afi/safis for neighbors in default or non-default ni. + bool bgp_afi_safi_in_default_ni_before_other_ni = 212; + // Devices which do not support default import export policy. + bool default_import_export_policy_unsupported = 213; + // ipv6_router_advertisement_interval_unsupported is set to true for devices that do not support ipv6 router advertisement interval configuration. + bool ipv6_router_advertisement_interval_unsupported = 214; + // Decap NH with NextHopNetworkInstance is unsupported + bool decap_nh_with_nexthop_ni_unsupported = 215; + // Juniper: b/356898098 + bool community_invert_any_unsupported = 216; + // SFlow source address update is unsupported + // Arista: b/357914789 + bool sflow_source_address_update_unsupported = 217; + // Linklocal mask length is not 64 + // Cisco: b/368271859 + bool link_local_mask_len = 218; + // use parent component for temperature telemetry + bool use_parent_component_for_temperature_telemetry = 219; + // component manufactured date is unsupported + bool component_mfg_date_unsupported = 220; + // trib protocol field under otn channel config unsupported + bool otn_channel_trib_unsupported = 221; + // ingress parameters under eth channel config unsupported + bool eth_channel_ingress_parameters_unsupported = 222; + // Cisco numbering for eth channel assignment starts from 1 instead of 0 + bool eth_channel_assignment_cisco_numbering = 223; + // Devices needs time to update interface counters. + bool interface_counters_update_delayed = 224; + // device does not support a Healthz GET RPC against Chassis level component like "CHASSIS" or "Rack 0" + bool chassis_get_rpc_unsupported = 225; + // Leaf-ref validation for list keys which is enforced for Cisco and hence deviation + // b/373581140 + bool power_disable_enable_leaf_ref_validation = 226; + // Device does not support ssh server counters. + bool ssh_server_counters_unsupported = 227; + // True when the optical-channel operational-mode is unsupported. + // Juniper: b/355456031 + bool operational_mode_unsupported = 228; + // BGP session state idle is supported in passive mode instead of active + // Cisco: b/376021545 + bool bgp_session_state_idle_in_passive_mode = 229; + // EnableMultipathUnderAfiSafi returns true for devices that do not support multipath under /global path and instead support under global/afi/safi path + // 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 + bool cisco_pre_fec_ber_inactive_value = 233; + // Device does not support bgp extended next hop encoding leaf. + // Cisco: b/377433951 + bool bgp_extended_next_hop_encoding_leaf_unsupported = 234; + // Device does not support bgp afi safi wildcard. + // Cisco: b/379863985 + bool bgp_afi_safi_wildcard_not_supported = 235; + // Nokia; b/304493065 comment#7 SRL native admin_enable for table-connections + bool enable_table_connections = 236; + // Device has default zero suppression. + // Juniper : b/378646018 + bool no_zero_suppression = 237; + // Cisco: b/378801305 + bool isis_interface_level_passive_unsupported = 238; + // Cisco: b/378616912 + bool isis_dis_sysid_unsupported = 239; + // Cisco: b/378616912 + bool isis_database_overloads_unsupported = 240; + // Juniper: b/358534837 + // Devices that do not support setting med value using union type in OC. + bool bgp_set_med_v7_unsupported = 241; // Reserved field numbers and identifiers. - reserved 84, 9, 28, 20, 90, 97, 55, 89; + reserved 84, 9, 28, 20, 90, 97, 55, 89, 19, 36, 35, 40, 173; } message PlatformExceptions { @@ -391,4 +692,8 @@ message Metadata { // The `tags` used to identify the area(s) testcase applies to. An empty tag // is the default implying it applies to all areas. repeated Tags tags = 6; + + // Whether this test only checks paths for presence rather than semantic + // checks. + bool path_presence_test = 7; } diff --git a/proto/metadata_go_proto/metadata.pb.go b/proto/metadata_go_proto/metadata.pb.go index 04e7d9e6833..64a87d81f78 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 v4.25.1 +// protoc-gen-go v1.34.2 +// protoc v3.6.1 // source: metadata.proto package metadata_go_proto @@ -47,6 +47,8 @@ const ( Metadata_TESTBED_DUT_ATE_4LINKS Metadata_Testbed = 4 Metadata_TESTBED_DUT_ATE_9LINKS_LAG Metadata_Testbed = 5 Metadata_TESTBED_DUT_DUT_ATE_2LINKS Metadata_Testbed = 6 + Metadata_TESTBED_DUT_ATE_8LINKS Metadata_Testbed = 7 + Metadata_TESTBED_DUT_400ZR Metadata_Testbed = 8 ) // Enum value maps for Metadata_Testbed. @@ -59,6 +61,8 @@ var ( 4: "TESTBED_DUT_ATE_4LINKS", 5: "TESTBED_DUT_ATE_9LINKS_LAG", 6: "TESTBED_DUT_DUT_ATE_2LINKS", + 7: "TESTBED_DUT_ATE_8LINKS", + 8: "TESTBED_DUT_400ZR", } Metadata_Testbed_value = map[string]int32{ "TESTBED_UNSPECIFIED": 0, @@ -68,6 +72,8 @@ var ( "TESTBED_DUT_ATE_4LINKS": 4, "TESTBED_DUT_ATE_9LINKS_LAG": 5, "TESTBED_DUT_DUT_ATE_2LINKS": 6, + "TESTBED_DUT_ATE_8LINKS": 7, + "TESTBED_DUT_400ZR": 8, } ) @@ -173,6 +179,9 @@ type Metadata struct { // The `tags` used to identify the area(s) testcase applies to. An empty tag // is the default implying it applies to all areas. Tags []Metadata_Tags `protobuf:"varint,6,rep,packed,name=tags,proto3,enum=openconfig.testing.Metadata_Tags" json:"tags,omitempty"` + // 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"` } func (x *Metadata) Reset() { @@ -249,6 +258,13 @@ func (x *Metadata) GetTags() []Metadata_Tags { return nil } +func (x *Metadata) GetPathPresenceTest() bool { + if x != nil { + return x.PathPresenceTest + } + return false +} + type Metadata_Platform struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -365,9 +381,6 @@ type Metadata_Deviations struct { OsinstallForStandbyRp bool `protobuf:"varint,17,opt,name=osinstall_for_standby_rp,json=osinstallForStandbyRp,proto3" json:"osinstall_for_standby_rp,omitempty"` // Set this flag for LLDP interface config to override the global config. LldpInterfaceConfigOverrideGlobal bool `protobuf:"varint,18,opt,name=lldp_interface_config_override_global,json=lldpInterfaceConfigOverrideGlobal,proto3" json:"lldp_interface_config_override_global,omitempty"` - // Skip BGP TestPassword mismatch subtest if value is true. - // Cisco: partnerissuetracker.corp.google.com/273285907 - SkipBgpTestPasswordMismatch bool `protobuf:"varint,19,opt,name=skip_bgp_test_password_mismatch,json=skipBgpTestPasswordMismatch,proto3" json:"skip_bgp_test_password_mismatch,omitempty"` // Skip check for // bgp/neighbors/neighbor/state/messages/received/last-notification-error-code // leaf missing case. @@ -412,10 +425,6 @@ type Metadata_Deviations struct { // Use this deviation when the device does not support a mix of tagged and // untagged subinterfaces. NoMixOfTaggedAndUntaggedSubinterfaces bool `protobuf:"varint,34,opt,name=no_mix_of_tagged_and_untagged_subinterfaces,json=noMixOfTaggedAndUntaggedSubinterfaces,proto3" json:"no_mix_of_tagged_and_untagged_subinterfaces,omitempty"` - // Device does not report P4RT node names in the component hierarchy. - ExplicitP4RtNodeComponent bool `protobuf:"varint,35,opt,name=explicit_p4rt_node_component,json=explicitP4rtNodeComponent,proto3" json:"explicit_p4rt_node_component,omitempty"` - // Configure ACLs using vendor native model specifically for RT-1.4. - UseVendorNativeAclConfig bool `protobuf:"varint,36,opt,name=use_vendor_native_acl_config,json=useVendorNativeAclConfig,proto3" json:"use_vendor_native_acl_config,omitempty"` // Device does not support reporting software version according to the // requirements in gNMI-1.10. SwVersionUnsupported bool `protobuf:"varint,37,opt,name=sw_version_unsupported,json=swVersionUnsupported,proto3" json:"sw_version_unsupported,omitempty"` @@ -425,8 +434,6 @@ type Metadata_Deviations struct { // Device does not support telemetry path /components/component/storage. // Juniper: partnerissuetracker.corp.google.com/284239001 StorageComponentUnsupported bool `protobuf:"varint,39,opt,name=storage_component_unsupported,json=storageComponentUnsupported,proto3" json:"storage_component_unsupported,omitempty"` - // Device requires gribi-protocol to be enabled under network-instance. - ExplicitGribiUnderNetworkInstance bool `protobuf:"varint,40,opt,name=explicit_gribi_under_network_instance,json=explicitGribiUnderNetworkInstance,proto3" json:"explicit_gribi_under_network_instance,omitempty"` // Device requires port-speed to be set because its default value may not be // usable. ExplicitPortSpeed bool `protobuf:"varint,41,opt,name=explicit_port_speed,json=explicitPortSpeed,proto3" json:"explicit_port_speed,omitempty"` @@ -547,7 +554,8 @@ type Metadata_Deviations struct { // Devices require configuring subinterface with tagged vlan for p4rt // packet in. P4RtGdpRequiresDot1QSubinterface bool `protobuf:"varint,93,opt,name=p4rt_gdp_requires_dot1q_subinterface,json=p4rtGdpRequiresDot1qSubinterface,proto3" json:"p4rt_gdp_requires_dot1q_subinterface,omitempty"` - // ATE port link state operations are a no-op in KNE/virtualized environments. + // ATE port link state operations are a no-op in KNE/virtualized + // environments. AtePortLinkStateOperationsUnsupported bool `protobuf:"varint,94,opt,name=ate_port_link_state_operations_unsupported,json=atePortLinkStateOperationsUnsupported,proto3" json:"ate_port_link_state_operations_unsupported,omitempty"` // Creates a user and assigns role/rbac to said user via native model. SetNativeUser bool `protobuf:"varint,95,opt,name=set_native_user,json=setNativeUser,proto3" json:"set_native_user,omitempty"` @@ -566,7 +574,8 @@ type Metadata_Deviations struct { ControllerCardCpuUtilizationUnsupported bool `protobuf:"varint,100,opt,name=controller_card_cpu_utilization_unsupported,json=controllerCardCpuUtilizationUnsupported,proto3" json:"controller_card_cpu_utilization_unsupported,omitempty"` // Device does not support counter for fabric block lost packets. FabricDropCounterUnsupported bool `protobuf:"varint,101,opt,name=fabric_drop_counter_unsupported,json=fabricDropCounterUnsupported,proto3" json:"fabric_drop_counter_unsupported,omitempty"` - // Device does not support memory utilization related leaves for linecard components. + // Device does not support memory utilization related leaves for linecard + // components. LinecardMemoryUtilizationUnsupported bool `protobuf:"varint,102,opt,name=linecard_memory_utilization_unsupported,json=linecardMemoryUtilizationUnsupported,proto3" json:"linecard_memory_utilization_unsupported,omitempty"` // Device does not support telemetry path // /qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/dropped-pkts. @@ -581,14 +590,15 @@ type Metadata_Deviations struct { // Arista: partnerissuetracker.corp.google.com/299285115 IsisCounterManualAddressDropFromAreasUnsupported bool `protobuf:"varint,106,opt,name=isis_counter_manual_address_drop_from_areas_unsupported,json=isisCounterManualAddressDropFromAreasUnsupported,proto3" json:"isis_counter_manual_address_drop_from_areas_unsupported,omitempty"` // Devices do not support telemetry for isis counter: part-changes. - // Arista: partnerissuetracker.corp.google.com/299285991 + // Arista: partnerissuetracker.corp.google.com/317086576 IsisCounterPartChangesUnsupported bool `protobuf:"varint,107,opt,name=isis_counter_part_changes_unsupported,json=isisCounterPartChangesUnsupported,proto3" json:"isis_counter_part_changes_unsupported,omitempty"` - // Devices do not support threshold container under /components/component/transceiver. + // Devices do not support threshold container under + // /components/component/transceiver. TransceiverThresholdsUnsupported bool `protobuf:"varint,108,opt,name=transceiver_thresholds_unsupported,json=transceiverThresholdsUnsupported,proto3" json:"transceiver_thresholds_unsupported,omitempty"` // Update interface loopback mode using raw gnmi API due to server version. InterfaceLoopbackModeRawGnmi bool `protobuf:"varint,109,opt,name=interface_loopback_mode_raw_gnmi,json=interfaceLoopbackModeRawGnmi,proto3" json:"interface_loopback_mode_raw_gnmi,omitempty"` - // Devices do not support showing negotiated tcp mss value in bgp tcp mss telemetry. - // Juniper: b/300499125 + // Devices do not support showing negotiated tcp mss value in bgp tcp mss + // telemetry. Juniper: b/300499125 SkipTcpNegotiatedMssCheck bool `protobuf:"varint,110,opt,name=skip_tcp_negotiated_mss_check,json=skipTcpNegotiatedMssCheck,proto3" json:"skip_tcp_negotiated_mss_check,omitempty"` // Devices don't support ISIS-Lsp metadata paths: checksum, sequence-number, // remaining-lifetime. @@ -599,8 +609,9 @@ type Metadata_Deviations struct { SkipFibFailedTrafficForwardingCheck bool `protobuf:"varint,113,opt,name=skip_fib_failed_traffic_forwarding_check,json=skipFibFailedTrafficForwardingCheck,proto3" json:"skip_fib_failed_traffic_forwarding_check,omitempty"` // QOS requires buffer-allocation-profile configuration QosBufferAllocationConfigRequired bool `protobuf:"varint,114,opt,name=qos_buffer_allocation_config_required,json=qosBufferAllocationConfigRequired,proto3" json:"qos_buffer_allocation_config_required,omitempty"` - // Devices do not support configuring ExtendedNextHopEncoding at the BGP global level. - // Arista: https://partnerissuetracker.corp.google.com/issues/203683090 + // Devices do not support configuring ExtendedNextHopEncoding at the BGP + // global level. Arista: + // https://partnerissuetracker.corp.google.com/issues/203683090 BgpGlobalExtendedNextHopEncodingUnsupported bool `protobuf:"varint,115,opt,name=bgp_global_extended_next_hop_encoding_unsupported,json=bgpGlobalExtendedNextHopEncodingUnsupported,proto3" json:"bgp_global_extended_next_hop_encoding_unsupported,omitempty"` // OC unsupported for BGP LLGR disable. // Juniper: b/303479602 @@ -608,21 +619,24 @@ type Metadata_Deviations struct { // Device does not support tunnel interfaces state paths // Juniper: partnerissuetracker.corp.google.com/300111031 TunnelStatePathUnsupported bool `protobuf:"varint,117,opt,name=tunnel_state_path_unsupported,json=tunnelStatePathUnsupported,proto3" json:"tunnel_state_path_unsupported,omitempty"` - // Device does not support tunnel interfaces source and destination address config paths - // Juniper: partnerissuetracker.corp.google.com/300111031 + // Device does not support tunnel interfaces source and destination address + // config paths Juniper: partnerissuetracker.corp.google.com/300111031 TunnelConfigPathUnsupported bool `protobuf:"varint,118,opt,name=tunnel_config_path_unsupported,json=tunnelConfigPathUnsupported,proto3" json:"tunnel_config_path_unsupported,omitempty"` - // Cisco: Device does not support same minimun and maximum threshold value in QOS ECN config. + // Cisco: Device does not support same minimun and maximum threshold value + // in QOS ECN config. EcnSameMinMaxThresholdUnsupported bool `protobuf:"varint,119,opt,name=ecn_same_min_max_threshold_unsupported,json=ecnSameMinMaxThresholdUnsupported,proto3" json:"ecn_same_min_max_threshold_unsupported,omitempty"` // Cisco: QOS requires scheduler configuration. QosSchedulerConfigRequired bool `protobuf:"varint,120,opt,name=qos_scheduler_config_required,json=qosSchedulerConfigRequired,proto3" json:"qos_scheduler_config_required,omitempty"` - // Cisco: Device does not support set weight config under QOS ECN configuration. + // Cisco: Device does not support set weight config under QOS ECN + // configuration. QosSetWeightConfigUnsupported bool `protobuf:"varint,121,opt,name=qos_set_weight_config_unsupported,json=qosSetWeightConfigUnsupported,proto3" json:"qos_set_weight_config_unsupported,omitempty"` // Cisco: Device does not support these get state path. QosGetStatePathUnsupported bool `protobuf:"varint,122,opt,name=qos_get_state_path_unsupported,json=qosGetStatePathUnsupported,proto3" json:"qos_get_state_path_unsupported,omitempty"` // Devices requires enabled leaf under isis level // Juniper: partnerissuetracker.corp.google.com/302661486 IsisLevelEnabled bool `protobuf:"varint,123,opt,name=isis_level_enabled,json=isisLevelEnabled,proto3" json:"isis_level_enabled,omitempty"` - // Devices which require to use interface-id format of interface name + .subinterface index with Interface-ref container + // Devices which require to use interface-id format of interface name + + // .subinterface index with Interface-ref container InterfaceRefInterfaceIdFormat bool `protobuf:"varint,124,opt,name=interface_ref_interface_id_format,json=interfaceRefInterfaceIdFormat,proto3" json:"interface_ref_interface_id_format,omitempty"` // Devices does not support member link loopback // Juniper: b/307763669 @@ -630,9 +644,311 @@ type Metadata_Deviations struct { // Device does not support PLQ operational status check on interface // Juniper: b/308990185 SkipPlqInterfaceOperStatusCheck bool `protobuf:"varint,126,opt,name=skip_plq_interface_oper_status_check,json=skipPlqInterfaceOperStatusCheck,proto3" json:"skip_plq_interface_oper_status_check,omitempty"` - // Device set received prefix limits explicitly under prefix-limit-received rather than - // "prefix-limit" + // Device set received prefix limits explicitly under prefix-limit-received + // rather than "prefix-limit" BgpExplicitPrefixLimitReceived bool `protobuf:"varint,127,opt,name=bgp_explicit_prefix_limit_received,json=bgpExplicitPrefixLimitReceived,proto3" json:"bgp_explicit_prefix_limit_received,omitempty"` + // Device does not configure BGP maximum routes correctly when max-prefixes + // leaf is configured + BgpMissingOcMaxPrefixesConfiguration bool `protobuf:"varint,128,opt,name=bgp_missing_oc_max_prefixes_configuration,json=bgpMissingOcMaxPrefixesConfiguration,proto3" json:"bgp_missing_oc_max_prefixes_configuration,omitempty"` + // Devices which needs to skip checking AFI-SAFI disable. + // Juniper: b/310698466 + SkipBgpSessionCheckWithoutAfisafi bool `protobuf:"varint,129,opt,name=skip_bgp_session_check_without_afisafi,json=skipBgpSessionCheckWithoutAfisafi,proto3" json:"skip_bgp_session_check_without_afisafi,omitempty"` + // Devices that have separate naming conventions for hardware resource name + // in /system/ tree and /components/ tree. + MismatchedHardwareResourceNameInComponent bool `protobuf:"varint,130,opt,name=mismatched_hardware_resource_name_in_component,json=mismatchedHardwareResourceNameInComponent,proto3" json:"mismatched_hardware_resource_name_in_component,omitempty"` + // Devices that don't support telemetry for hardware resources before + // used-threshold-upper configuration. + MissingHardwareResourceTelemetryBeforeConfig bool `protobuf:"varint,131,opt,name=missing_hardware_resource_telemetry_before_config,json=missingHardwareResourceTelemetryBeforeConfig,proto3" json:"missing_hardware_resource_telemetry_before_config,omitempty"` + // Device does not support reboot status check on subcomponents. + GnoiSubcomponentRebootStatusUnsupported bool `protobuf:"varint,132,opt,name=gnoi_subcomponent_reboot_status_unsupported,json=gnoiSubcomponentRebootStatusUnsupported,proto3" json:"gnoi_subcomponent_reboot_status_unsupported,omitempty"` + // Devices exports routes from all protocols to BGP if the export-policy is + // ACCEPT Juniper: b/308970803 + SkipNonBgpRouteExportCheck bool `protobuf:"varint,133,opt,name=skip_non_bgp_route_export_check,json=skipNonBgpRouteExportCheck,proto3" json:"skip_non_bgp_route_export_check,omitempty"` + // Devices do not support path + // /network-instances/network-instance/protocols/protocol/isis/levels/level/state/metric-style + // Arista: https://partnerissuetracker.corp.google.com/issues/317064733 + IsisMetricStyleTelemetryUnsupported bool `protobuf:"varint,134,opt,name=isis_metric_style_telemetry_unsupported,json=isisMetricStyleTelemetryUnsupported,proto3" json:"isis_metric_style_telemetry_unsupported,omitempty"` + // Devices do not support configuring Interface-ref under Static-Route + // Next-Hop + StaticRouteNextHopInterfaceRefUnsupported bool `protobuf:"varint,135,opt,name=static_route_next_hop_interface_ref_unsupported,json=staticRouteNextHopInterfaceRefUnsupported,proto3" json:"static_route_next_hop_interface_ref_unsupported,omitempty"` + // Devices which does not support nexthop index state + // Juniper: b/304729237 + SkipStaticNexthopCheck bool `protobuf:"varint,136,opt,name=skip_static_nexthop_check,json=skipStaticNexthopCheck,proto3" json:"skip_static_nexthop_check,omitempty"` + // Device doesn't support router advertisement enable and mode config + // Juniper: b/316173974 + Ipv6RouterAdvertisementConfigUnsupported bool `protobuf:"varint,138,opt,name=ipv6_router_advertisement_config_unsupported,json=ipv6RouterAdvertisementConfigUnsupported,proto3" json:"ipv6_router_advertisement_config_unsupported,omitempty"` + // Devices does not support setting prefix limit exceeded flag. + // Juniper : b/317181227 + PrefixLimitExceededTelemetryUnsupported bool `protobuf:"varint,139,opt,name=prefix_limit_exceeded_telemetry_unsupported,json=prefixLimitExceededTelemetryUnsupported,proto3" json:"prefix_limit_exceeded_telemetry_unsupported,omitempty"` + // Skip setting allow-multiple-as while configuring eBGP + // Arista: partnerissuetracker.corp.google.com/issues/317422300 + SkipSettingAllowMultipleAs bool `protobuf:"varint,140,opt,name=skip_setting_allow_multiple_as,json=skipSettingAllowMultipleAs,proto3" json:"skip_setting_allow_multiple_as,omitempty"` + // Skip tests with decap encap vrf as PBF action + // + // Nokia: partnerissuetracker.corp.google.com/issues/323251581 + SkipPbfWithDecapEncapVrf bool `protobuf:"varint,141,opt,name=skip_pbf_with_decap_encap_vrf,json=skipPbfWithDecapEncapVrf,proto3" json:"skip_pbf_with_decap_encap_vrf,omitempty"` + // Devices which does not support copying TTL. + // Juniper: b/307258544 + TtlCopyUnsupported bool `protobuf:"varint,142,opt,name=ttl_copy_unsupported,json=ttlCopyUnsupported,proto3" json:"ttl_copy_unsupported,omitempty"` + // Devices does not support mixed prefix length in gribi. + // Juniper: b/307824407 + GribiDecapMixedPlenUnsupported bool `protobuf:"varint,143,opt,name=gribi_decap_mixed_plen_unsupported,json=gribiDecapMixedPlenUnsupported,proto3" json:"gribi_decap_mixed_plen_unsupported,omitempty"` + // Skip setting isis-actions set-level while configuring routing-policy + // statement action + SkipIsisSetLevel bool `protobuf:"varint,144,opt,name=skip_isis_set_level,json=skipIsisSetLevel,proto3" json:"skip_isis_set_level,omitempty"` + // Skip setting isis-actions set-metric-style-type while configuring + // routing-policy statement action + SkipIsisSetMetricStyleType bool `protobuf:"varint,145,opt,name=skip_isis_set_metric_style_type,json=skipIsisSetMetricStyleType,proto3" json:"skip_isis_set_metric_style_type,omitempty"` + // Skip setting match-prefix-set match-set-options while configuring + // routing-policy statement condition + SkipSetRpMatchSetOptions bool `protobuf:"varint,146,opt,name=skip_set_rp_match_set_options,json=skipSetRpMatchSetOptions,proto3" json:"skip_set_rp_match_set_options,omitempty"` + // Skip setting disable-metric-propagation while configuring + // table-connection + SkipSettingDisableMetricPropagation bool `protobuf:"varint,147,opt,name=skip_setting_disable_metric_propagation,json=skipSettingDisableMetricPropagation,proto3" json:"skip_setting_disable_metric_propagation,omitempty"` + // Devices do not support BGP conditions match-community-set + BgpConditionsMatchCommunitySetUnsupported bool `protobuf:"varint,148,opt,name=bgp_conditions_match_community_set_unsupported,json=bgpConditionsMatchCommunitySetUnsupported,proto3" json:"bgp_conditions_match_community_set_unsupported,omitempty"` + // Device requires match condition for ethertype v4 and v6 for default rule + // with network-instance default-vrf in policy-forwarding. + PfRequireMatchDefaultRule bool `protobuf:"varint,149,opt,name=pf_require_match_default_rule,json=pfRequireMatchDefaultRule,proto3" json:"pf_require_match_default_rule,omitempty"` + // Devices missing component tree mapping from hardware port + // to optical channel. + MissingPortToOpticalChannelComponentMapping bool `protobuf:"varint,150,opt,name=missing_port_to_optical_channel_component_mapping,json=missingPortToOpticalChannelComponentMapping,proto3" json:"missing_port_to_optical_channel_component_mapping,omitempty"` + // Skip gNMI container OP tc. + // Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 + SkipContainerOp bool `protobuf:"varint,151,opt,name=skip_container_op,json=skipContainerOp,proto3" json:"skip_container_op,omitempty"` + // Reorder calls for vendor compatibility. + // Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 + ReorderCallsForVendorCompatibilty bool `protobuf:"varint,152,opt,name=reorder_calls_for_vendor_compatibilty,json=reorderCallsForVendorCompatibilty,proto3" json:"reorder_calls_for_vendor_compatibilty,omitempty"` + // Add missing base config using cli. + // Cisco: https://partnerissuetracker.corp.google.com/issues/322291556 + AddMissingBaseConfigViaCli bool `protobuf:"varint,153,opt,name=add_missing_base_config_via_cli,json=addMissingBaseConfigViaCli,proto3" json:"add_missing_base_config_via_cli,omitempty"` + // skip_macaddress_check returns true if mac address for an interface via + // gNMI needs to be skipped. Cisco: + // https://partnerissuetracker.corp.google.com/issues/322291556 + SkipMacaddressCheck bool `protobuf:"varint,154,opt,name=skip_macaddress_check,json=skipMacaddressCheck,proto3" json:"skip_macaddress_check,omitempty"` + // Devices are having native telemetry paths for BGP RIB verification. + // Juniper : b/306144372 + BgpRibOcPathUnsupported bool `protobuf:"varint,155,opt,name=bgp_rib_oc_path_unsupported,json=bgpRibOcPathUnsupported,proto3" json:"bgp_rib_oc_path_unsupported,omitempty"` + // Skip setting prefix-set mode while configuring prefix-set routing-policy + SkipPrefixSetMode bool `protobuf:"varint,156,opt,name=skip_prefix_set_mode,json=skipPrefixSetMode,proto3" json:"skip_prefix_set_mode,omitempty"` + // Devices set metric as preference for static next-hop + SetMetricAsPreference bool `protobuf:"varint,157,opt,name=set_metric_as_preference,json=setMetricAsPreference,proto3" json:"set_metric_as_preference,omitempty"` + // Devices don't support having an IPv6 static Route with an IPv4 address + // as next hop and requires configuring a static ARP entry. + // Arista: https://partnerissuetracker.corp.google.com/issues/316593298 + Ipv6StaticRouteWithIpv4NextHopRequiresStaticArp bool `protobuf:"varint,158,opt,name=ipv6_static_route_with_ipv4_next_hop_requires_static_arp,json=ipv6StaticRouteWithIpv4NextHopRequiresStaticArp,proto3" json:"ipv6_static_route_with_ipv4_next_hop_requires_static_arp,omitempty"` + // Device requires policy-forwarding rules to be in sequential order in the + // gNMI set-request. + PfRequireSequentialOrderPbrRules bool `protobuf:"varint,159,opt,name=pf_require_sequential_order_pbr_rules,json=pfRequireSequentialOrderPbrRules,proto3" json:"pf_require_sequential_order_pbr_rules,omitempty"` + // Device telemetry missing next hop metric value. + // Arista: https://partnerissuetracker.corp.google.com/issues/321010782 + MissingStaticRouteNextHopMetricTelemetry bool `protobuf:"varint,160,opt,name=missing_static_route_next_hop_metric_telemetry,json=missingStaticRouteNextHopMetricTelemetry,proto3" json:"missing_static_route_next_hop_metric_telemetry,omitempty"` + // Device does not support recursive resolution of static route next hop. + // Arista: https://partnerissuetracker.corp.google.com/issues/314449182 + UnsupportedStaticRouteNextHopRecurse bool `protobuf:"varint,161,opt,name=unsupported_static_route_next_hop_recurse,json=unsupportedStaticRouteNextHopRecurse,proto3" json:"unsupported_static_route_next_hop_recurse,omitempty"` + // Device missing telemetry for static route that has DROP next hop. + // Arista: https://partnerissuetracker.corp.google.com/issues/330619816 + MissingStaticRouteDropNextHopTelemetry bool `protobuf:"varint,162,opt,name=missing_static_route_drop_next_hop_telemetry,json=missingStaticRouteDropNextHopTelemetry,proto3" json:"missing_static_route_drop_next_hop_telemetry,omitempty"` + // Device missing 400ZR optical-channel tunable parameters telemetry: + // min/max/avg. + // Arista: https://partnerissuetracker.corp.google.com/issues/319314781 + MissingZrOpticalChannelTunableParametersTelemetry bool `protobuf:"varint,163,opt,name=missing_zr_optical_channel_tunable_parameters_telemetry,json=missingZrOpticalChannelTunableParametersTelemetry,proto3" json:"missing_zr_optical_channel_tunable_parameters_telemetry,omitempty"` + // Device that does not support packet link qualification reflector packet + // sent/received stats. + PlqReflectorStatsUnsupported bool `protobuf:"varint,164,opt,name=plq_reflector_stats_unsupported,json=plqReflectorStatsUnsupported,proto3" json:"plq_reflector_stats_unsupported,omitempty"` + // Device that does not support PLQ Generator max_mtu to be atleast >= 8184. + PlqGeneratorCapabilitiesMaxMtu uint32 `protobuf:"varint,165,opt,name=plq_generator_capabilities_max_mtu,json=plqGeneratorCapabilitiesMaxMtu,proto3" json:"plq_generator_capabilities_max_mtu,omitempty"` + // Device that does not support PLQ Generator max_pps to be atleast >= + // 100000000. + PlqGeneratorCapabilitiesMaxPps uint64 `protobuf:"varint,166,opt,name=plq_generator_capabilities_max_pps,json=plqGeneratorCapabilitiesMaxPps,proto3" json:"plq_generator_capabilities_max_pps,omitempty"` + // Support for bgp extended community index + BgpExtendedCommunityIndexUnsupported bool `protobuf:"varint,167,opt,name=bgp_extended_community_index_unsupported,json=bgpExtendedCommunityIndexUnsupported,proto3" json:"bgp_extended_community_index_unsupported,omitempty"` + // Support for bgp community set refs + BgpCommunitySetRefsUnsupported bool `protobuf:"varint,168,opt,name=bgp_community_set_refs_unsupported,json=bgpCommunitySetRefsUnsupported,proto3" json:"bgp_community_set_refs_unsupported,omitempty"` + // Arista device needs CLI knob to enable WECMP feature + RibWecmp bool `protobuf:"varint,169,opt,name=rib_wecmp,json=ribWecmp,proto3" json:"rib_wecmp,omitempty"` + // Device not supporting table-connection need to set this true + TableConnectionsUnsupported bool `protobuf:"varint,170,opt,name=table_connections_unsupported,json=tableConnectionsUnsupported,proto3" json:"table_connections_unsupported,omitempty"` + // Configure tag-set using vendor native model + UseVendorNativeTagSetConfig bool `protobuf:"varint,171,opt,name=use_vendor_native_tag_set_config,json=useVendorNativeTagSetConfig,proto3" json:"use_vendor_native_tag_set_config,omitempty"` + // Skip setting send-community-type in bgp global config + SkipBgpSendCommunityType bool `protobuf:"varint,172,opt,name=skip_bgp_send_community_type,json=skipBgpSendCommunityType,proto3" json:"skip_bgp_send_community_type,omitempty"` + // Support for bgp actions set-community method + BgpActionsSetCommunityMethodUnsupported bool `protobuf:"varint,174,opt,name=bgp_actions_set_community_method_unsupported,json=bgpActionsSetCommunityMethodUnsupported,proto3" json:"bgp_actions_set_community_method_unsupported,omitempty"` + // Ensure no configurations exist under BGP Peer Groups + SetNoPeerGroup bool `protobuf:"varint,175,opt,name=set_no_peer_group,json=setNoPeerGroup,proto3" json:"set_no_peer_group,omitempty"` + // Bgp community member is a string + BgpCommunityMemberIsAString bool `protobuf:"varint,176,opt,name=bgp_community_member_is_a_string,json=bgpCommunityMemberIsAString,proto3" json:"bgp_community_member_is_a_string,omitempty"` + // Flag to indicate whether IPv4 static routes with IPv6 next-hops are + // unsupported. + Ipv4StaticRouteWithIpv6NhUnsupported bool `protobuf:"varint,177,opt,name=ipv4_static_route_with_ipv6_nh_unsupported,json=ipv4StaticRouteWithIpv6NhUnsupported,proto3" json:"ipv4_static_route_with_ipv6_nh_unsupported,omitempty"` + // Flag to indicate whether IPv6 static routes with IPv4 next-hops are + // unsupported. + Ipv6StaticRouteWithIpv4NhUnsupported bool `protobuf:"varint,178,opt,name=ipv6_static_route_with_ipv4_nh_unsupported,json=ipv6StaticRouteWithIpv4NhUnsupported,proto3" json:"ipv6_static_route_with_ipv4_nh_unsupported,omitempty"` + // Flag to indicate support for static routes that simply drop packets + StaticRouteWithDropNh bool `protobuf:"varint,179,opt,name=static_route_with_drop_nh,json=staticRouteWithDropNh,proto3" json:"static_route_with_drop_nh,omitempty"` + // Flag to indicate support for static routes that can be configured with an + // explicit metric. + StaticRouteWithExplicitMetric bool `protobuf:"varint,180,opt,name=static_route_with_explicit_metric,json=staticRouteWithExplicitMetric,proto3" json:"static_route_with_explicit_metric,omitempty"` + // Support for bgp default import/export policy + BgpDefaultPolicyUnsupported bool `protobuf:"varint,181,opt,name=bgp_default_policy_unsupported,json=bgpDefaultPolicyUnsupported,proto3" json:"bgp_default_policy_unsupported,omitempty"` + // Flag to enable bgp explicity on default vrf + // Arista: b/329094094#comment9 + ExplicitEnableBgpOnDefaultVrf bool `protobuf:"varint,182,opt,name=explicit_enable_bgp_on_default_vrf,json=explicitEnableBgpOnDefaultVrf,proto3" json:"explicit_enable_bgp_on_default_vrf,omitempty"` + // tag-set is not a real separate entity, but is embedded in the policy + // statement. this implies that 1. routing policy tag set name needs to be + // ' ' + // 2. only one policy statement can make use of a tag-set, and 3. tag must + // be refered by a policy + RoutingPolicyTagSetEmbedded bool `protobuf:"varint,183,opt,name=routing_policy_tag_set_embedded,json=routingPolicyTagSetEmbedded,proto3" json:"routing_policy_tag_set_embedded,omitempty"` + // Devices does not support allow multiple as under AFI/SAFI. + // CISCO: b/340859662 + SkipAfiSafiPathForBgpMultipleAs bool `protobuf:"varint,184,opt,name=skip_afi_safi_path_for_bgp_multiple_as,json=skipAfiSafiPathForBgpMultipleAs,proto3" json:"skip_afi_safi_path_for_bgp_multiple_as,omitempty"` + // Device does not support regex with routing-policy community-member. + CommunityMemberRegexUnsupported bool `protobuf:"varint,185,opt,name=community_member_regex_unsupported,json=communityMemberRegexUnsupported,proto3" json:"community_member_regex_unsupported,omitempty"` + // Support for same import policy attached to all AFIs for given + // (src-protocol, dst-protocol, network-instance) triple Arista: + // b/339645876#comment4 + SamePolicyAttachedToAllAfis bool `protobuf:"varint,186,opt,name=same_policy_attached_to_all_afis,json=samePolicyAttachedToAllAfis,proto3" json:"same_policy_attached_to_all_afis,omitempty"` + // Devices needs to skip setting statement for policy to be applied as + // action pass otherwise it will be configured as action done. + // CISCO: b/338523730 + SkipSettingStatementForPolicy bool `protobuf:"varint,187,opt,name=skip_setting_statement_for_policy,json=skipSettingStatementForPolicy,proto3" json:"skip_setting_statement_for_policy,omitempty"` + // Devices does not support index specific attribute fetching and hence + // wildcards has to be used. + // CISCO: b/338523730 + SkipCheckingAttributeIndex bool `protobuf:"varint,188,opt,name=skip_checking_attribute_index,json=skipCheckingAttributeIndex,proto3" json:"skip_checking_attribute_index,omitempty"` + // Devices does not suppport policy-chaining, so needs to flatten policies + // with multiple statements. + // CISCO: b/338526243 + FlattenPolicyWithMultipleStatements bool `protobuf:"varint,189,opt,name=flatten_policy_with_multiple_statements,json=flattenPolicyWithMultipleStatements,proto3" json:"flatten_policy_with_multiple_statements,omitempty"` + // default_route_policy_unsupported is set to true for devices that do not + // support default route policy. + DefaultRoutePolicyUnsupported bool `protobuf:"varint,190,opt,name=default_route_policy_unsupported,json=defaultRoutePolicyUnsupported,proto3" json:"default_route_policy_unsupported,omitempty"` + // CISCO: b/339801843 + SlaacPrefixLength128 bool `protobuf:"varint,191,opt,name=slaac_prefix_length128,json=slaacPrefixLength128,proto3" json:"slaac_prefix_length128,omitempty"` + // Devices does not support bgp max multipaths + // Juniper: b/319301559 + BgpMaxMultipathPathsUnsupported bool `protobuf:"varint,192,opt,name=bgp_max_multipath_paths_unsupported,json=bgpMaxMultipathPathsUnsupported,proto3" json:"bgp_max_multipath_paths_unsupported,omitempty"` + // Devices does not multipath config at neighbor or afisafi level + // Juniper: b/341130490 + MultipathUnsupportedNeighborOrAfisafi bool `protobuf:"varint,193,opt,name=multipath_unsupported_neighbor_or_afisafi,json=multipathUnsupportedNeighborOrAfisafi,proto3" json:"multipath_unsupported_neighbor_or_afisafi,omitempty"` + // Devices that do not support /components/component/state/model-name for + // any component types. + // Note that for model name to be supported, the + // /components/component/state/model-name of the chassis component must be + // equal to the canonical hardware model name of its device. + ModelNameUnsupported bool `protobuf:"varint,194,opt,name=model_name_unsupported,json=modelNameUnsupported,proto3" json:"model_name_unsupported,omitempty"` + // community_match_with_redistribution_unsupported is set to true for devices that do not support matching community at the redistribution attach point. + CommunityMatchWithRedistributionUnsupported bool `protobuf:"varint,195,opt,name=community_match_with_redistribution_unsupported,json=communityMatchWithRedistributionUnsupported,proto3" json:"community_match_with_redistribution_unsupported,omitempty"` + // Devices that do not support components/component/state/install-component + // and components/component/state/install-position. + InstallPositionAndInstallComponentUnsupported bool `protobuf:"varint,196,opt,name=install_position_and_install_component_unsupported,json=installPositionAndInstallComponentUnsupported,proto3" json:"install_position_and_install_component_unsupported,omitempty"` + // Encap tunnel is shut then zero traffic will flow to backup NHG + EncapTunnelShutBackupNhgZeroTraffic bool `protobuf:"varint,197,opt,name=encap_tunnel_shut_backup_nhg_zero_traffic,json=encapTunnelShutBackupNhgZeroTraffic,proto3" json:"encap_tunnel_shut_backup_nhg_zero_traffic,omitempty"` + // Flag to indicate support for max ecmp paths for isis. + MaxEcmpPaths bool `protobuf:"varint,198,opt,name=max_ecmp_paths,json=maxEcmpPaths,proto3" json:"max_ecmp_paths,omitempty"` + // wecmp_auto_unsupported is set to true for devices that do not support auto wecmp + WecmpAutoUnsupported bool `protobuf:"varint,199,opt,name=wecmp_auto_unsupported,json=wecmpAutoUnsupported,proto3" json:"wecmp_auto_unsupported,omitempty"` + // policy chaining, ie. more than one policy at an attachement point is not supported + RoutingPolicyChainingUnsupported bool `protobuf:"varint,200,opt,name=routing_policy_chaining_unsupported,json=routingPolicyChainingUnsupported,proto3" json:"routing_policy_chaining_unsupported,omitempty"` + // isis loopback config required + IsisLoopbackRequired bool `protobuf:"varint,201,opt,name=isis_loopback_required,json=isisLoopbackRequired,proto3" json:"isis_loopback_required,omitempty"` + // weighted ecmp feature verification using fixed packet + WeightedEcmpFixedPacketVerification bool `protobuf:"varint,202,opt,name=weighted_ecmp_fixed_packet_verification,json=weightedEcmpFixedPacketVerification,proto3" json:"weighted_ecmp_fixed_packet_verification,omitempty"` + // Override default NextHop scale while enabling encap/decap scale + // CISCO: + OverrideDefaultNhScale bool `protobuf:"varint,203,opt,name=override_default_nh_scale,json=overrideDefaultNhScale,proto3" json:"override_default_nh_scale,omitempty"` + // Devices that donot support setting bgp extended community set + BgpExtendedCommunitySetUnsupported bool `protobuf:"varint,204,opt,name=bgp_extended_community_set_unsupported,json=bgpExtendedCommunitySetUnsupported,proto3" json:"bgp_extended_community_set_unsupported,omitempty"` + // Devices that do not support setting bgp extended community set refs + BgpSetExtCommunitySetRefsUnsupported bool `protobuf:"varint,205,opt,name=bgp_set_ext_community_set_refs_unsupported,json=bgpSetExtCommunitySetRefsUnsupported,proto3" json:"bgp_set_ext_community_set_refs_unsupported,omitempty"` + // Devices that do not support deleting link bandwidth + BgpDeleteLinkBandwidthUnsupported bool `protobuf:"varint,206,opt,name=bgp_delete_link_bandwidth_unsupported,json=bgpDeleteLinkBandwidthUnsupported,proto3" json:"bgp_delete_link_bandwidth_unsupported,omitempty"` + // qos_inqueue_drop_counter_Unsupported is set to true for devices that do not support qos ingress queue drop counters. + // Juniper: b/341130490 + QosInqueueDropCounterUnsupported bool `protobuf:"varint,207,opt,name=qos_inqueue_drop_counter_unsupported,json=qosInqueueDropCounterUnsupported,proto3" json:"qos_inqueue_drop_counter_unsupported,omitempty"` + // Devices that need bgp extended community enable explicitly + BgpExplicitExtendedCommunityEnable bool `protobuf:"varint,208,opt,name=bgp_explicit_extended_community_enable,json=bgpExplicitExtendedCommunityEnable,proto3" json:"bgp_explicit_extended_community_enable,omitempty"` + // devices that do not support match tag set condition + MatchTagSetConditionUnsupported bool `protobuf:"varint,209,opt,name=match_tag_set_condition_unsupported,json=matchTagSetConditionUnsupported,proto3" json:"match_tag_set_condition_unsupported,omitempty"` + // peer_group_def_bgp_vrf_unsupported is set to true for devices that do not support peer group definition under bgp vrf configuration. + PeerGroupDefEbgpVrfUnsupported bool `protobuf:"varint,210,opt,name=peer_group_def_ebgp_vrf_unsupported,json=peerGroupDefEbgpVrfUnsupported,proto3" json:"peer_group_def_ebgp_vrf_unsupported,omitempty"` + // redis_uconnected_under_ebgp_vrf_unsupported is set to true for devices that do not support redistribution of connected routes under ebgp vrf configuration. + RedisConnectedUnderEbgpVrfUnsupported bool `protobuf:"varint,211,opt,name=redis_connected_under_ebgp_vrf_unsupported,json=redisConnectedUnderEbgpVrfUnsupported,proto3" json:"redis_connected_under_ebgp_vrf_unsupported,omitempty"` + // bgp_afisafi_in_default_ni_before_other_ni is set to true for devices that require certain afi/safis to be enabled + // in default network instance (ni) before enabling afi/safis for neighbors in default or non-default ni. + BgpAfiSafiInDefaultNiBeforeOtherNi bool `protobuf:"varint,212,opt,name=bgp_afi_safi_in_default_ni_before_other_ni,json=bgpAfiSafiInDefaultNiBeforeOtherNi,proto3" json:"bgp_afi_safi_in_default_ni_before_other_ni,omitempty"` + // Devices which do not support default import export policy. + DefaultImportExportPolicyUnsupported bool `protobuf:"varint,213,opt,name=default_import_export_policy_unsupported,json=defaultImportExportPolicyUnsupported,proto3" json:"default_import_export_policy_unsupported,omitempty"` + // ipv6_router_advertisement_interval_unsupported is set to true for devices that do not support ipv6 router advertisement interval configuration. + Ipv6RouterAdvertisementIntervalUnsupported bool `protobuf:"varint,214,opt,name=ipv6_router_advertisement_interval_unsupported,json=ipv6RouterAdvertisementIntervalUnsupported,proto3" json:"ipv6_router_advertisement_interval_unsupported,omitempty"` + // Decap NH with NextHopNetworkInstance is unsupported + DecapNhWithNexthopNiUnsupported bool `protobuf:"varint,215,opt,name=decap_nh_with_nexthop_ni_unsupported,json=decapNhWithNexthopNiUnsupported,proto3" json:"decap_nh_with_nexthop_ni_unsupported,omitempty"` + // Juniper: b/356898098 + CommunityInvertAnyUnsupported bool `protobuf:"varint,216,opt,name=community_invert_any_unsupported,json=communityInvertAnyUnsupported,proto3" json:"community_invert_any_unsupported,omitempty"` + // SFlow source address update is unsupported + // Arista: b/357914789 + SflowSourceAddressUpdateUnsupported bool `protobuf:"varint,217,opt,name=sflow_source_address_update_unsupported,json=sflowSourceAddressUpdateUnsupported,proto3" json:"sflow_source_address_update_unsupported,omitempty"` + // Linklocal mask length is not 64 + // Cisco: b/368271859 + LinkLocalMaskLen bool `protobuf:"varint,218,opt,name=link_local_mask_len,json=linkLocalMaskLen,proto3" json:"link_local_mask_len,omitempty"` + // use parent component for temperature telemetry + UseParentComponentForTemperatureTelemetry bool `protobuf:"varint,219,opt,name=use_parent_component_for_temperature_telemetry,json=useParentComponentForTemperatureTelemetry,proto3" json:"use_parent_component_for_temperature_telemetry,omitempty"` + // component manufactured date is unsupported + ComponentMfgDateUnsupported bool `protobuf:"varint,220,opt,name=component_mfg_date_unsupported,json=componentMfgDateUnsupported,proto3" json:"component_mfg_date_unsupported,omitempty"` + // trib protocol field under otn channel config unsupported + OtnChannelTribUnsupported bool `protobuf:"varint,221,opt,name=otn_channel_trib_unsupported,json=otnChannelTribUnsupported,proto3" json:"otn_channel_trib_unsupported,omitempty"` + // ingress parameters under eth channel config unsupported + EthChannelIngressParametersUnsupported bool `protobuf:"varint,222,opt,name=eth_channel_ingress_parameters_unsupported,json=ethChannelIngressParametersUnsupported,proto3" json:"eth_channel_ingress_parameters_unsupported,omitempty"` + // Cisco numbering for eth channel assignment starts from 1 instead of 0 + EthChannelAssignmentCiscoNumbering bool `protobuf:"varint,223,opt,name=eth_channel_assignment_cisco_numbering,json=ethChannelAssignmentCiscoNumbering,proto3" json:"eth_channel_assignment_cisco_numbering,omitempty"` + // Devices needs time to update interface counters. + InterfaceCountersUpdateDelayed bool `protobuf:"varint,224,opt,name=interface_counters_update_delayed,json=interfaceCountersUpdateDelayed,proto3" json:"interface_counters_update_delayed,omitempty"` + // device does not support a Healthz GET RPC against Chassis level component like "CHASSIS" or "Rack 0" + ChassisGetRpcUnsupported bool `protobuf:"varint,225,opt,name=chassis_get_rpc_unsupported,json=chassisGetRpcUnsupported,proto3" json:"chassis_get_rpc_unsupported,omitempty"` + // Leaf-ref validation for list keys which is enforced for Cisco and hence deviation + // b/373581140 + PowerDisableEnableLeafRefValidation bool `protobuf:"varint,226,opt,name=power_disable_enable_leaf_ref_validation,json=powerDisableEnableLeafRefValidation,proto3" json:"power_disable_enable_leaf_ref_validation,omitempty"` + // Device does not support ssh server counters. + SshServerCountersUnsupported bool `protobuf:"varint,227,opt,name=ssh_server_counters_unsupported,json=sshServerCountersUnsupported,proto3" json:"ssh_server_counters_unsupported,omitempty"` + // True when the optical-channel operational-mode is unsupported. + // Juniper: b/355456031 + OperationalModeUnsupported bool `protobuf:"varint,228,opt,name=operational_mode_unsupported,json=operationalModeUnsupported,proto3" json:"operational_mode_unsupported,omitempty"` + // BGP session state idle is supported in passive mode instead of active + // Cisco: b/376021545 + BgpSessionStateIdleInPassiveMode bool `protobuf:"varint,229,opt,name=bgp_session_state_idle_in_passive_mode,json=bgpSessionStateIdleInPassiveMode,proto3" json:"bgp_session_state_idle_in_passive_mode,omitempty"` + // EnableMultipathUnderAfiSafi returns true for devices that do not support multipath under /global path and instead support under global/afi/safi path + // 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 + CiscoPreFecBerInactiveValue bool `protobuf:"varint,233,opt,name=cisco_pre_fec_ber_inactive_value,json=ciscoPreFecBerInactiveValue,proto3" json:"cisco_pre_fec_ber_inactive_value,omitempty"` + // Device does not support bgp extended next hop encoding leaf. + // Cisco: b/377433951 + BgpExtendedNextHopEncodingLeafUnsupported bool `protobuf:"varint,234,opt,name=bgp_extended_next_hop_encoding_leaf_unsupported,json=bgpExtendedNextHopEncodingLeafUnsupported,proto3" json:"bgp_extended_next_hop_encoding_leaf_unsupported,omitempty"` + // Device does not support bgp afi safi wildcard. + // Cisco: b/379863985 + BgpAfiSafiWildcardNotSupported bool `protobuf:"varint,235,opt,name=bgp_afi_safi_wildcard_not_supported,json=bgpAfiSafiWildcardNotSupported,proto3" json:"bgp_afi_safi_wildcard_not_supported,omitempty"` + // Nokia; b/304493065 comment#7 SRL native admin_enable for table-connections + EnableTableConnections bool `protobuf:"varint,236,opt,name=enable_table_connections,json=enableTableConnections,proto3" json:"enable_table_connections,omitempty"` + // Device has default zero suppression. + // Juniper : b/378646018 + NoZeroSuppression bool `protobuf:"varint,237,opt,name=no_zero_suppression,json=noZeroSuppression,proto3" json:"no_zero_suppression,omitempty"` + // Cisco: b/378801305 + IsisInterfaceLevelPassiveUnsupported bool `protobuf:"varint,238,opt,name=isis_interface_level_passive_unsupported,json=isisInterfaceLevelPassiveUnsupported,proto3" json:"isis_interface_level_passive_unsupported,omitempty"` + // Cisco: b/378616912 + IsisDisSysidUnsupported bool `protobuf:"varint,239,opt,name=isis_dis_sysid_unsupported,json=isisDisSysidUnsupported,proto3" json:"isis_dis_sysid_unsupported,omitempty"` + // Cisco: b/378616912 + IsisDatabaseOverloadsUnsupported bool `protobuf:"varint,240,opt,name=isis_database_overloads_unsupported,json=isisDatabaseOverloadsUnsupported,proto3" json:"isis_database_overloads_unsupported,omitempty"` + // Juniper: b/358534837 + // Devices that do not support setting med value using union type in OC. + BgpSetMedV7Unsupported bool `protobuf:"varint,241,opt,name=bgp_set_med_v7_unsupported,json=bgpSetMedV7Unsupported,proto3" json:"bgp_set_med_v7_unsupported,omitempty"` } func (x *Metadata_Deviations) Reset() { @@ -786,13 +1102,6 @@ func (x *Metadata_Deviations) GetLldpInterfaceConfigOverrideGlobal() bool { return false } -func (x *Metadata_Deviations) GetSkipBgpTestPasswordMismatch() bool { - if x != nil { - return x.SkipBgpTestPasswordMismatch - } - return false -} - func (x *Metadata_Deviations) GetMissingBgpLastNotificationErrorCode() bool { if x != nil { return x.MissingBgpLastNotificationErrorCode @@ -884,20 +1193,6 @@ func (x *Metadata_Deviations) GetNoMixOfTaggedAndUntaggedSubinterfaces() bool { return false } -func (x *Metadata_Deviations) GetExplicitP4RtNodeComponent() bool { - if x != nil { - return x.ExplicitP4RtNodeComponent - } - return false -} - -func (x *Metadata_Deviations) GetUseVendorNativeAclConfig() bool { - if x != nil { - return x.UseVendorNativeAclConfig - } - return false -} - func (x *Metadata_Deviations) GetSwVersionUnsupported() bool { if x != nil { return x.SwVersionUnsupported @@ -919,13 +1214,6 @@ func (x *Metadata_Deviations) GetStorageComponentUnsupported() bool { return false } -func (x *Metadata_Deviations) GetExplicitGribiUnderNetworkInstance() bool { - if x != nil { - return x.ExplicitGribiUnderNetworkInstance - } - return false -} - func (x *Metadata_Deviations) GetExplicitPortSpeed() bool { if x != nil { return x.ExplicitPortSpeed @@ -1451,628 +1739,1944 @@ func (x *Metadata_Deviations) GetBgpExplicitPrefixLimitReceived() bool { return false } -type Metadata_PlatformExceptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields +func (x *Metadata_Deviations) GetBgpMissingOcMaxPrefixesConfiguration() bool { + if x != nil { + return x.BgpMissingOcMaxPrefixesConfiguration + } + return false +} - 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"` +func (x *Metadata_Deviations) GetSkipBgpSessionCheckWithoutAfisafi() bool { + if x != nil { + return x.SkipBgpSessionCheckWithoutAfisafi + } + return false } -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) +func (x *Metadata_Deviations) GetMismatchedHardwareResourceNameInComponent() bool { + if x != nil { + return x.MismatchedHardwareResourceNameInComponent } + return false } -func (x *Metadata_PlatformExceptions) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *Metadata_Deviations) GetMissingHardwareResourceTelemetryBeforeConfig() bool { + if x != nil { + return x.MissingHardwareResourceTelemetryBeforeConfig + } + return false } -func (*Metadata_PlatformExceptions) ProtoMessage() {} +func (x *Metadata_Deviations) GetGnoiSubcomponentRebootStatusUnsupported() bool { + if x != nil { + return x.GnoiSubcomponentRebootStatusUnsupported + } + return false +} -func (x *Metadata_PlatformExceptions) ProtoReflect() protoreflect.Message { - mi := &file_metadata_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms +func (x *Metadata_Deviations) GetSkipNonBgpRouteExportCheck() bool { + if x != nil { + return x.SkipNonBgpRouteExportCheck } - return mi.MessageOf(x) + return false } -// Deprecated: Use Metadata_PlatformExceptions.ProtoReflect.Descriptor instead. -func (*Metadata_PlatformExceptions) Descriptor() ([]byte, []int) { - return file_metadata_proto_rawDescGZIP(), []int{0, 2} +func (x *Metadata_Deviations) GetIsisMetricStyleTelemetryUnsupported() bool { + if x != nil { + return x.IsisMetricStyleTelemetryUnsupported + } + return false } -func (x *Metadata_PlatformExceptions) GetPlatform() *Metadata_Platform { +func (x *Metadata_Deviations) GetStaticRouteNextHopInterfaceRefUnsupported() bool { if x != nil { - return x.Platform + return x.StaticRouteNextHopInterfaceRefUnsupported } - return nil + return false } -func (x *Metadata_PlatformExceptions) GetDeviations() *Metadata_Deviations { +func (x *Metadata_Deviations) GetSkipStaticNexthopCheck() bool { if x != nil { - return x.Deviations + return x.SkipStaticNexthopCheck } - return nil + return false } -var File_metadata_proto protoreflect.FileDescriptor +func (x *Metadata_Deviations) GetIpv6RouterAdvertisementConfigUnsupported() bool { + if x != nil { + return x.Ipv6RouterAdvertisementConfigUnsupported + } + return false +} -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, 0xd9, 0x45, 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, 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, 0x8d, 0x3e, 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, 0x44, 0x0a, 0x1f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x67, 0x70, 0x5f, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x6d, 0x69, 0x73, - 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x73, 0x6b, 0x69, - 0x70, 0x42, 0x67, 0x70, 0x54, 0x65, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x4d, 0x69, 0x73, 0x6d, 0x61, 0x74, 0x63, 0x68, 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, 0x3f, 0x0a, 0x1c, 0x65, 0x78, - 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x70, 0x34, 0x72, 0x74, 0x5f, 0x6e, 0x6f, 0x64, 0x65, - 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x23, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x19, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x50, 0x34, 0x72, 0x74, 0x4e, 0x6f, - 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x1c, 0x75, - 0x73, 0x65, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, - 0x5f, 0x61, 0x63, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x24, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x18, 0x75, 0x73, 0x65, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x4e, 0x61, 0x74, 0x69, - 0x76, 0x65, 0x41, 0x63, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x49, 0x0a, 0x21, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x5f, 0x64, 0x65, 0x66, 0x69, - 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x26, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x65, 0x78, - 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, - 0x65, 0x66, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 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, 0x50, 0x0a, 0x25, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x67, 0x72, 0x69, - 0x62, 0x69, 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x21, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x47, 0x72, 0x69, 0x62, 0x69, 0x55, 0x6e, - 0x64, 0x65, 0x72, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 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, 0x2c, 0x0a, 0x12, - 0x71, 0x6f, 0x73, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6f, 0x63, 0x74, 0x65, - 0x74, 0x73, 0x18, 0x2b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x71, 0x6f, 0x73, 0x44, 0x72, 0x6f, - 0x70, 0x70, 0x65, 0x64, 0x4f, 0x63, 0x74, 0x65, 0x74, 0x73, 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, 0x73, 0x0a, 0x38, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x6c, - 0x73, 0x70, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x6c, 0x73, - 0x70, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x18, 0x60, 0x20, 0x01, 0x28, 0x08, 0x52, 0x31, 0x69, 0x73, 0x69, 0x73, 0x4c, 0x73, - 0x70, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x4c, 0x73, 0x70, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 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, 0x55, 0x0a, 0x28, 0x73, - 0x6b, 0x69, 0x70, 0x5f, 0x66, 0x69, 0x62, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x74, - 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, - 0x67, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x71, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x73, - 0x6b, 0x69, 0x70, 0x46, 0x69, 0x62, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x54, 0x72, 0x61, 0x66, - 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, - 0x63, 0x6b, 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, 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, 0x5a, 0x10, 0x5b, 0x4a, 0x04, 0x08, 0x61, 0x10, 0x62, 0x4a, 0x04, 0x08, 0x37, 0x10, - 0x38, 0x4a, 0x04, 0x08, 0x59, 0x10, 0x5a, 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, 0xc7, 0x01, 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, 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, +func (x *Metadata_Deviations) GetPrefixLimitExceededTelemetryUnsupported() bool { + if x != nil { + return x.PrefixLimitExceededTelemetryUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSkipSettingAllowMultipleAs() bool { + if x != nil { + return x.SkipSettingAllowMultipleAs + } + return false +} + +func (x *Metadata_Deviations) GetSkipPbfWithDecapEncapVrf() bool { + if x != nil { + return x.SkipPbfWithDecapEncapVrf + } + return false +} + +func (x *Metadata_Deviations) GetTtlCopyUnsupported() bool { + if x != nil { + return x.TtlCopyUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetGribiDecapMixedPlenUnsupported() bool { + if x != nil { + return x.GribiDecapMixedPlenUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSkipIsisSetLevel() bool { + if x != nil { + return x.SkipIsisSetLevel + } + return false +} + +func (x *Metadata_Deviations) GetSkipIsisSetMetricStyleType() bool { + if x != nil { + return x.SkipIsisSetMetricStyleType + } + return false +} + +func (x *Metadata_Deviations) GetSkipSetRpMatchSetOptions() bool { + if x != nil { + return x.SkipSetRpMatchSetOptions + } + return false +} + +func (x *Metadata_Deviations) GetSkipSettingDisableMetricPropagation() bool { + if x != nil { + return x.SkipSettingDisableMetricPropagation + } + return false +} + +func (x *Metadata_Deviations) GetBgpConditionsMatchCommunitySetUnsupported() bool { + if x != nil { + return x.BgpConditionsMatchCommunitySetUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPfRequireMatchDefaultRule() bool { + if x != nil { + return x.PfRequireMatchDefaultRule + } + return false +} + +func (x *Metadata_Deviations) GetMissingPortToOpticalChannelComponentMapping() bool { + if x != nil { + return x.MissingPortToOpticalChannelComponentMapping + } + return false +} + +func (x *Metadata_Deviations) GetSkipContainerOp() bool { + if x != nil { + return x.SkipContainerOp + } + return false +} + +func (x *Metadata_Deviations) GetReorderCallsForVendorCompatibilty() bool { + if x != nil { + return x.ReorderCallsForVendorCompatibilty + } + return false +} + +func (x *Metadata_Deviations) GetAddMissingBaseConfigViaCli() bool { + if x != nil { + return x.AddMissingBaseConfigViaCli + } + return false +} + +func (x *Metadata_Deviations) GetSkipMacaddressCheck() bool { + if x != nil { + return x.SkipMacaddressCheck + } + return false +} + +func (x *Metadata_Deviations) GetBgpRibOcPathUnsupported() bool { + if x != nil { + return x.BgpRibOcPathUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSkipPrefixSetMode() bool { + if x != nil { + return x.SkipPrefixSetMode + } + return false +} + +func (x *Metadata_Deviations) GetSetMetricAsPreference() bool { + if x != nil { + return x.SetMetricAsPreference + } + return false +} + +func (x *Metadata_Deviations) GetIpv6StaticRouteWithIpv4NextHopRequiresStaticArp() bool { + if x != nil { + return x.Ipv6StaticRouteWithIpv4NextHopRequiresStaticArp + } + return false +} + +func (x *Metadata_Deviations) GetPfRequireSequentialOrderPbrRules() bool { + if x != nil { + return x.PfRequireSequentialOrderPbrRules + } + return false +} + +func (x *Metadata_Deviations) GetMissingStaticRouteNextHopMetricTelemetry() bool { + if x != nil { + return x.MissingStaticRouteNextHopMetricTelemetry + } + return false +} + +func (x *Metadata_Deviations) GetUnsupportedStaticRouteNextHopRecurse() bool { + if x != nil { + return x.UnsupportedStaticRouteNextHopRecurse + } + return false +} + +func (x *Metadata_Deviations) GetMissingStaticRouteDropNextHopTelemetry() bool { + if x != nil { + return x.MissingStaticRouteDropNextHopTelemetry + } + return false +} + +func (x *Metadata_Deviations) GetMissingZrOpticalChannelTunableParametersTelemetry() bool { + if x != nil { + return x.MissingZrOpticalChannelTunableParametersTelemetry + } + return false +} + +func (x *Metadata_Deviations) GetPlqReflectorStatsUnsupported() bool { + if x != nil { + return x.PlqReflectorStatsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPlqGeneratorCapabilitiesMaxMtu() uint32 { + if x != nil { + return x.PlqGeneratorCapabilitiesMaxMtu + } + return 0 +} + +func (x *Metadata_Deviations) GetPlqGeneratorCapabilitiesMaxPps() uint64 { + if x != nil { + return x.PlqGeneratorCapabilitiesMaxPps + } + return 0 +} + +func (x *Metadata_Deviations) GetBgpExtendedCommunityIndexUnsupported() bool { + if x != nil { + return x.BgpExtendedCommunityIndexUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpCommunitySetRefsUnsupported() bool { + if x != nil { + return x.BgpCommunitySetRefsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetRibWecmp() bool { + if x != nil { + return x.RibWecmp + } + return false +} + +func (x *Metadata_Deviations) GetTableConnectionsUnsupported() bool { + if x != nil { + return x.TableConnectionsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetUseVendorNativeTagSetConfig() bool { + if x != nil { + return x.UseVendorNativeTagSetConfig + } + return false +} + +func (x *Metadata_Deviations) GetSkipBgpSendCommunityType() bool { + if x != nil { + return x.SkipBgpSendCommunityType + } + return false +} + +func (x *Metadata_Deviations) GetBgpActionsSetCommunityMethodUnsupported() bool { + if x != nil { + return x.BgpActionsSetCommunityMethodUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSetNoPeerGroup() bool { + if x != nil { + return x.SetNoPeerGroup + } + return false +} + +func (x *Metadata_Deviations) GetBgpCommunityMemberIsAString() bool { + if x != nil { + return x.BgpCommunityMemberIsAString + } + return false +} + +func (x *Metadata_Deviations) GetIpv4StaticRouteWithIpv6NhUnsupported() bool { + if x != nil { + return x.Ipv4StaticRouteWithIpv6NhUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIpv6StaticRouteWithIpv4NhUnsupported() bool { + if x != nil { + return x.Ipv6StaticRouteWithIpv4NhUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetStaticRouteWithDropNh() bool { + if x != nil { + return x.StaticRouteWithDropNh + } + return false +} + +func (x *Metadata_Deviations) GetStaticRouteWithExplicitMetric() bool { + if x != nil { + return x.StaticRouteWithExplicitMetric + } + return false +} + +func (x *Metadata_Deviations) GetBgpDefaultPolicyUnsupported() bool { + if x != nil { + return x.BgpDefaultPolicyUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetExplicitEnableBgpOnDefaultVrf() bool { + if x != nil { + return x.ExplicitEnableBgpOnDefaultVrf + } + return false +} + +func (x *Metadata_Deviations) GetRoutingPolicyTagSetEmbedded() bool { + if x != nil { + return x.RoutingPolicyTagSetEmbedded + } + return false +} + +func (x *Metadata_Deviations) GetSkipAfiSafiPathForBgpMultipleAs() bool { + if x != nil { + return x.SkipAfiSafiPathForBgpMultipleAs + } + return false +} + +func (x *Metadata_Deviations) GetCommunityMemberRegexUnsupported() bool { + if x != nil { + return x.CommunityMemberRegexUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSamePolicyAttachedToAllAfis() bool { + if x != nil { + return x.SamePolicyAttachedToAllAfis + } + return false +} + +func (x *Metadata_Deviations) GetSkipSettingStatementForPolicy() bool { + if x != nil { + return x.SkipSettingStatementForPolicy + } + return false +} + +func (x *Metadata_Deviations) GetSkipCheckingAttributeIndex() bool { + if x != nil { + return x.SkipCheckingAttributeIndex + } + return false +} + +func (x *Metadata_Deviations) GetFlattenPolicyWithMultipleStatements() bool { + if x != nil { + return x.FlattenPolicyWithMultipleStatements + } + return false +} + +func (x *Metadata_Deviations) GetDefaultRoutePolicyUnsupported() bool { + if x != nil { + return x.DefaultRoutePolicyUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSlaacPrefixLength128() bool { + if x != nil { + return x.SlaacPrefixLength128 + } + return false +} + +func (x *Metadata_Deviations) GetBgpMaxMultipathPathsUnsupported() bool { + if x != nil { + return x.BgpMaxMultipathPathsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetMultipathUnsupportedNeighborOrAfisafi() bool { + if x != nil { + return x.MultipathUnsupportedNeighborOrAfisafi + } + return false +} + +func (x *Metadata_Deviations) GetModelNameUnsupported() bool { + if x != nil { + return x.ModelNameUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetCommunityMatchWithRedistributionUnsupported() bool { + if x != nil { + return x.CommunityMatchWithRedistributionUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetInstallPositionAndInstallComponentUnsupported() bool { + if x != nil { + return x.InstallPositionAndInstallComponentUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetEncapTunnelShutBackupNhgZeroTraffic() bool { + if x != nil { + return x.EncapTunnelShutBackupNhgZeroTraffic + } + return false +} + +func (x *Metadata_Deviations) GetMaxEcmpPaths() bool { + if x != nil { + return x.MaxEcmpPaths + } + return false +} + +func (x *Metadata_Deviations) GetWecmpAutoUnsupported() bool { + if x != nil { + return x.WecmpAutoUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetRoutingPolicyChainingUnsupported() bool { + if x != nil { + return x.RoutingPolicyChainingUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIsisLoopbackRequired() bool { + if x != nil { + return x.IsisLoopbackRequired + } + return false +} + +func (x *Metadata_Deviations) GetWeightedEcmpFixedPacketVerification() bool { + if x != nil { + return x.WeightedEcmpFixedPacketVerification + } + return false +} + +func (x *Metadata_Deviations) GetOverrideDefaultNhScale() bool { + if x != nil { + return x.OverrideDefaultNhScale + } + return false +} + +func (x *Metadata_Deviations) GetBgpExtendedCommunitySetUnsupported() bool { + if x != nil { + return x.BgpExtendedCommunitySetUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpSetExtCommunitySetRefsUnsupported() bool { + if x != nil { + return x.BgpSetExtCommunitySetRefsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpDeleteLinkBandwidthUnsupported() bool { + if x != nil { + return x.BgpDeleteLinkBandwidthUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetQosInqueueDropCounterUnsupported() bool { + if x != nil { + return x.QosInqueueDropCounterUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpExplicitExtendedCommunityEnable() bool { + if x != nil { + return x.BgpExplicitExtendedCommunityEnable + } + return false +} + +func (x *Metadata_Deviations) GetMatchTagSetConditionUnsupported() bool { + if x != nil { + return x.MatchTagSetConditionUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPeerGroupDefEbgpVrfUnsupported() bool { + if x != nil { + return x.PeerGroupDefEbgpVrfUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetRedisConnectedUnderEbgpVrfUnsupported() bool { + if x != nil { + return x.RedisConnectedUnderEbgpVrfUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpAfiSafiInDefaultNiBeforeOtherNi() bool { + if x != nil { + return x.BgpAfiSafiInDefaultNiBeforeOtherNi + } + return false +} + +func (x *Metadata_Deviations) GetDefaultImportExportPolicyUnsupported() bool { + if x != nil { + return x.DefaultImportExportPolicyUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIpv6RouterAdvertisementIntervalUnsupported() bool { + if x != nil { + return x.Ipv6RouterAdvertisementIntervalUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetDecapNhWithNexthopNiUnsupported() bool { + if x != nil { + return x.DecapNhWithNexthopNiUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetCommunityInvertAnyUnsupported() bool { + if x != nil { + return x.CommunityInvertAnyUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSflowSourceAddressUpdateUnsupported() bool { + if x != nil { + return x.SflowSourceAddressUpdateUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetLinkLocalMaskLen() bool { + if x != nil { + return x.LinkLocalMaskLen + } + return false +} + +func (x *Metadata_Deviations) GetUseParentComponentForTemperatureTelemetry() bool { + if x != nil { + return x.UseParentComponentForTemperatureTelemetry + } + return false +} + +func (x *Metadata_Deviations) GetComponentMfgDateUnsupported() bool { + if x != nil { + return x.ComponentMfgDateUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetOtnChannelTribUnsupported() bool { + if x != nil { + return x.OtnChannelTribUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetEthChannelIngressParametersUnsupported() bool { + if x != nil { + return x.EthChannelIngressParametersUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetEthChannelAssignmentCiscoNumbering() bool { + if x != nil { + return x.EthChannelAssignmentCiscoNumbering + } + return false +} + +func (x *Metadata_Deviations) GetInterfaceCountersUpdateDelayed() bool { + if x != nil { + return x.InterfaceCountersUpdateDelayed + } + return false +} + +func (x *Metadata_Deviations) GetChassisGetRpcUnsupported() bool { + if x != nil { + return x.ChassisGetRpcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPowerDisableEnableLeafRefValidation() bool { + if x != nil { + return x.PowerDisableEnableLeafRefValidation + } + return false +} + +func (x *Metadata_Deviations) GetSshServerCountersUnsupported() bool { + if x != nil { + return x.SshServerCountersUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetOperationalModeUnsupported() bool { + if x != nil { + return x.OperationalModeUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpSessionStateIdleInPassiveMode() bool { + if x != nil { + return x.BgpSessionStateIdleInPassiveMode + } + return false +} + +func (x *Metadata_Deviations) GetEnableMultipathUnderAfiSafi() bool { + if x != nil { + return x.EnableMultipathUnderAfiSafi + } + 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 + } + return false +} + +func (x *Metadata_Deviations) GetCiscoPreFecBerInactiveValue() bool { + if x != nil { + return x.CiscoPreFecBerInactiveValue + } + return false +} + +func (x *Metadata_Deviations) GetBgpExtendedNextHopEncodingLeafUnsupported() bool { + if x != nil { + return x.BgpExtendedNextHopEncodingLeafUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpAfiSafiWildcardNotSupported() bool { + if x != nil { + return x.BgpAfiSafiWildcardNotSupported + } + return false +} + +func (x *Metadata_Deviations) GetEnableTableConnections() bool { + if x != nil { + return x.EnableTableConnections + } + return false +} + +func (x *Metadata_Deviations) GetNoZeroSuppression() bool { + if x != nil { + return x.NoZeroSuppression + } + return false +} + +func (x *Metadata_Deviations) GetIsisInterfaceLevelPassiveUnsupported() bool { + if x != nil { + return x.IsisInterfaceLevelPassiveUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIsisDisSysidUnsupported() bool { + if x != nil { + return x.IsisDisSysidUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIsisDatabaseOverloadsUnsupported() bool { + if x != nil { + return x.IsisDatabaseOverloadsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetBgpSetMedV7Unsupported() bool { + if x != nil { + return x.BgpSetMedV7Unsupported + } + return false +} + +type Metadata_PlatformExceptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + 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"` +} + +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) + } +} + +func (x *Metadata_PlatformExceptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Metadata_PlatformExceptions) ProtoMessage() {} + +func (x *Metadata_PlatformExceptions) ProtoReflect() protoreflect.Message { + mi := &file_metadata_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Metadata_PlatformExceptions.ProtoReflect.Descriptor instead. +func (*Metadata_PlatformExceptions) Descriptor() ([]byte, []int) { + return file_metadata_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *Metadata_PlatformExceptions) GetPlatform() *Metadata_Platform { + if x != nil { + return x.Platform + } + return nil +} + +func (x *Metadata_PlatformExceptions) GetDeviations() *Metadata_Deviations { + if x != nil { + return x.Deviations + } + return nil +} + +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, 0x9a, 0x88, 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, 0xed, 0x7f, 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, 0x49, 0x0a, 0x21, 0x65, 0x78, 0x70, + 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, + 0x72, 0x65, 0x66, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x26, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x65, 0x66, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 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, 0x2c, 0x0a, 0x12, 0x71, 0x6f, 0x73, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, + 0x64, 0x5f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x73, 0x18, 0x2b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, + 0x71, 0x6f, 0x73, 0x44, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4f, 0x63, 0x74, 0x65, 0x74, 0x73, + 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, 0x73, 0x0a, 0x38, + 0x69, 0x73, 0x69, 0x73, 0x5f, 0x6c, 0x73, 0x70, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x73, 0x5f, 0x6c, 0x73, 0x70, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x60, 0x20, 0x01, 0x28, 0x08, 0x52, 0x31, + 0x69, 0x73, 0x69, 0x73, 0x4c, 0x73, 0x70, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x4c, + 0x73, 0x70, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 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, 0x55, 0x0a, 0x28, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x66, 0x69, 0x62, 0x5f, 0x66, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x66, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x71, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x23, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x69, 0x62, 0x46, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, 0x63, 0x6b, 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, + 0x68, 0x0a, 0x31, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x72, 0x64, 0x77, + 0x61, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x65, 0x6c, + 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x83, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2c, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x42, 0x65, 0x66, + 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x40, 0x0a, 0x1d, 0x73, + 0x6b, 0x69, 0x70, 0x5f, 0x70, 0x62, 0x66, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x64, 0x65, 0x63, + 0x61, 0x70, 0x5f, 0x65, 0x6e, 0x63, 0x61, 0x70, 0x5f, 0x76, 0x72, 0x66, 0x18, 0x8d, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x18, 0x73, 0x6b, 0x69, 0x70, 0x50, 0x62, 0x66, 0x57, 0x69, 0x74, 0x68, + 0x44, 0x65, 0x63, 0x61, 0x70, 0x45, 0x6e, 0x63, 0x61, 0x70, 0x56, 0x72, 0x66, 0x12, 0x31, 0x0a, + 0x14, 0x74, 0x74, 0x6c, 0x5f, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x8e, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x74, 0x74, + 0x6c, 0x43, 0x6f, 0x70, 0x79, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, + 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, 0x63, 0x0a, 0x2f, 0x62, 0x67, 0x70, 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, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xea, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x29, 0x62, + 0x67, 0x70, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4e, 0x65, 0x78, 0x74, 0x48, 0x6f, + 0x70, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x61, 0x66, 0x55, 0x6e, 0x73, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 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, 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, 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, 0x06, + 0x08, 0xad, 0x01, 0x10, 0xae, 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, 0xfa, 0x01, 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, 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, } var ( @@ -2089,7 +3693,7 @@ func file_metadata_proto_rawDescGZIP() []byte { 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 @@ -2118,7 +3722,7 @@ func file_metadata_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_metadata_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Metadata); i { case 0: return &v.state @@ -2130,7 +3734,7 @@ func file_metadata_proto_init() { return nil } } - file_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_metadata_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Metadata_Platform); i { case 0: return &v.state @@ -2142,7 +3746,7 @@ func file_metadata_proto_init() { return nil } } - file_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_metadata_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*Metadata_Deviations); i { case 0: return &v.state @@ -2154,7 +3758,7 @@ func file_metadata_proto_init() { return nil } } - file_metadata_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_metadata_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*Metadata_PlatformExceptions); i { case 0: return &v.state diff --git a/proto/nosimage.proto b/proto/nosimage.proto index 8410d0c06e0..c55e49d7272 100644 --- a/proto/nosimage.proto +++ b/proto/nosimage.proto @@ -41,6 +41,10 @@ message NOSImageProfile { // /system/state/software-version. string software_version = 3; + // The name of the vendor's networking hardware device that is compatible with + // the NOS software version. + string hardware_name = 7; + // The date the network operating system is released. // The date could be a value in the future indicating a future release. google.protobuf.Timestamp release_date = 4; diff --git a/proto/nosimage_go_proto/nosimage.pb.go b/proto/nosimage_go_proto/nosimage.pb.go index 543cdc8cf92..3eed2fb8051 100644 --- a/proto/nosimage_go_proto/nosimage.pb.go +++ b/proto/nosimage_go_proto/nosimage.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.28.1 // protoc v3.21.12 // source: nosimage.proto @@ -60,6 +60,9 @@ type NOSImageProfile struct { // This should match the output of the OpenConfig Path // /system/state/software-version. SoftwareVersion string `protobuf:"bytes,3,opt,name=software_version,json=softwareVersion,proto3" json:"software_version,omitempty"` + // The name of the vendor's networking hardware device that is compatible with + // the NOS software version. + HardwareName string `protobuf:"bytes,7,opt,name=hardware_name,json=hardwareName,proto3" json:"hardware_name,omitempty"` // The date the network operating system is released. // The date could be a value in the future indicating a future release. ReleaseDate *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=release_date,json=releaseDate,proto3" json:"release_date,omitempty"` @@ -122,6 +125,13 @@ func (x *NOSImageProfile) GetSoftwareVersion() string { return "" } +func (x *NOSImageProfile) GetHardwareName() string { + if x != nil { + return x.HardwareName + } + return "" +} + func (x *NOSImageProfile) GetReleaseDate() *timestamppb.Timestamp { if x != nil { return x.ReleaseDate @@ -161,7 +171,7 @@ var file_nosimage_proto_rawDesc = []byte{ 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x62, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x4e, 0x4f, 0x53, 0x49, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, 0x02, 0x0a, 0x0f, 0x4e, 0x4f, 0x53, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x72, 0x61, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2e, @@ -169,18 +179,21 @@ var file_nosimage_proto_rawDesc = []byte{ 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6e, 0x6f, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, - 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, - 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x07, - 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x6f, 0x63, 0x70, 0x61, 0x74, - 0x68, 0x73, 0x2e, 0x4f, 0x43, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x07, 0x6f, 0x63, 0x70, 0x61, - 0x74, 0x68, 0x73, 0x12, 0x31, 0x0a, 0x06, 0x6f, 0x63, 0x72, 0x70, 0x63, 0x73, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x6f, 0x63, 0x72, 0x70, 0x63, 0x73, 0x2e, 0x4f, 0x43, 0x52, 0x50, 0x43, 0x73, 0x52, 0x06, - 0x6f, 0x63, 0x72, 0x70, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, + 0x0d, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x61, 0x74, + 0x65, 0x12, 0x35, 0x0a, 0x07, 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x73, 0x2e, 0x4f, 0x43, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, + 0x07, 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x73, 0x12, 0x31, 0x0a, 0x06, 0x6f, 0x63, 0x72, 0x70, + 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x6f, 0x63, 0x72, 0x70, 0x63, 0x73, 0x2e, 0x4f, 0x43, 0x52, + 0x50, 0x43, 0x73, 0x52, 0x06, 0x6f, 0x63, 0x72, 0x70, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/proto/ocpaths.proto b/proto/ocpaths.proto index 7df7857c970..ea7d21e6d57 100644 --- a/proto/ocpaths.proto +++ b/proto/ocpaths.proto @@ -24,6 +24,8 @@ syntax = "proto3"; package openconfig.ocpaths; +option go_package = "github.com/openconfig/featureprofiles/proto/ocpaths_go_proto;ocpaths"; + // OCPaths is the complete list of all OpenConfig paths associated with some // entity (e.g. NOS, or path requirements list for a particular device role). message OCPaths { @@ -52,6 +54,14 @@ message OCPath { // This destination featureprofiles folder serves as a grouping of the paths // related to the tests which appear in this folder and subfolders the folder. string featureprofileid = 3; + + // A set of opaque tags that are used for this path. These tags can be used + // to group paths according to use-case specific criteria. + repeated string tags = 4; + + // GNMIRpc describes expected (or supported) behavior for a particular + // Openconfig path. + GNMIRpc gnmi_rpc = 5; } // OCPathConstraint enumerates platform_types that are required to be supported @@ -70,3 +80,39 @@ message OCPathConstraint { string platform_type = 1; } } + +// GNMIRpc describes expected (or supported) behavior for a particular +// Openconfig path. +message GNMIRpc { + bool get = 1; + bool set = 2; + bool subscribe = 3; + + // SubscribeMode, describes how updates are triggered for the request. + enum SubscribeMode { + UNSPECIFIED_SUBSCRIBE_MODE = 0; + NO_READ_SUPPORT = 1; // No requirement / support for path. + STREAM = 2; // Values streamed by the target (Sec. 3.5.1.5.2). + ONCE = 3; // Values sent once-off by the target (Sec. 3.5.1.5.1). + POLL = 4; // Values sent in response to a poll request (Sec. 3.5.1.5.3). + } + repeated SubscribeMode sub_mode = 4; + + // StreamMode is the mode of a streamed subscription, specifying how the + // target must return values for that subscription. + // Reference: gNMI Specification Section 3.5.1.3 + enum StreamMode { + UNSPECIFIED_STREAM_MODE = 0; + NO_STREAMING_SUPPORT = 1; // No requirement / support for streaming path. + TARGET_DEFINED = 2; // The target selects for each element. + ON_CHANGE = 3; // The target sends an update on element value change. + SAMPLE = 4; // The target samples values according to the interval. + } + repeated StreamMode stream_mode = 5; + + // If listed as part of a requirement, sample_interval_nanoseconds is the + // maximum allowable interval between updates. + // If listed as part of the description of level of support, it should be the + // smallest, recommended value. + uint64 sample_interval_nanoseconds = 6; +} diff --git a/proto/ocpaths_go_proto/ocpaths.pb.go b/proto/ocpaths_go_proto/ocpaths.pb.go index a1d36171bd5..cf1fad187b0 100644 --- a/proto/ocpaths_go_proto/ocpaths.pb.go +++ b/proto/ocpaths_go_proto/ocpaths.pb.go @@ -26,7 +26,7 @@ // protoc v3.21.12 // source: ocpaths.proto -package ocpaths_go_proto +package ocpaths import ( reflect "reflect" @@ -122,6 +122,9 @@ type OCPath struct { // This destination featureprofiles folder serves as a grouping of the paths // related to the tests which appear in this folder and subfolders the folder. Featureprofileid string `protobuf:"bytes,3,opt,name=featureprofileid,proto3" json:"featureprofileid,omitempty"` + // A set of opaque tags that are used for this path. These tags can be used + // to group paths according to use-case specific criteria. + Tags []string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` } func (x *OCPath) Reset() { @@ -177,6 +180,13 @@ func (x *OCPath) GetFeatureprofileid() string { return "" } +func (x *OCPath) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + // OCPathConstraint enumerates platform_types that are required to be supported // for all /components/component paths. All OCPath.name which contain a // /components/component path, must also set the oc-platform-type the path @@ -265,7 +275,7 @@ var file_ocpaths_proto_rawDesc = []byte{ 0x1a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x73, 0x2e, 0x4f, 0x43, 0x50, 0x61, 0x74, 0x68, 0x52, 0x07, 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x9b, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x06, 0x4f, 0x43, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x11, 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, @@ -275,12 +285,18 @@ var file_ocpaths_proto_rawDesc = []byte{ 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x69, 0x64, 0x22, 0x47, 0x0a, 0x10, - 0x4f, 0x43, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, - 0x12, 0x25, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x74, 0x66, - 0x6f, 0x72, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x73, 0x74, - 0x72, 0x61, 0x69, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, + 0x22, 0x47, 0x0a, 0x10, 0x4f, 0x43, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, + 0x61, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x63, + 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, 0x73, + 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x6f, 0x63, 0x70, 0x61, 0x74, 0x68, + 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/testregistry_go_proto/testregistry.pb.go b/proto/testregistry_go_proto/testregistry.pb.go new file mode 100644 index 00000000000..98feb5421cd --- /dev/null +++ b/proto/testregistry_go_proto/testregistry.pb.go @@ -0,0 +1,293 @@ +// 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. +// +// +// testregistry.proto -- specifying structure of a list of tests +// in featureprofiles + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: testregistry.proto + +package testregistry_go_proto + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TestRegistry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // name -- the human readable name of this TestSuite + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Test []*Test `protobuf:"bytes,2,rep,name=test,proto3" json:"test,omitempty"` +} + +func (x *TestRegistry) Reset() { + *x = TestRegistry{} + if protoimpl.UnsafeEnabled { + mi := &file_testregistry_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestRegistry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRegistry) ProtoMessage() {} + +func (x *TestRegistry) ProtoReflect() protoreflect.Message { + mi := &file_testregistry_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestRegistry.ProtoReflect.Descriptor instead. +func (*TestRegistry) Descriptor() ([]byte, []int) { + return file_testregistry_proto_rawDescGZIP(), []int{0} +} + +func (x *TestRegistry) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TestRegistry) GetTest() []*Test { + if x != nil { + return x.Test + } + return nil +} + +// Test specifies resources for a single functional test that applies to a +// Feature. It +type Test struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // id -- Test ID, required, must be unique, must match the regex: + // + // [A-Z][A-Z]+\-[0-9]+(\.[0-9]+)? + // Test ID should match the rundata.TestPlanID of the linked exec. + // For example: AA-1.1 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // version -- should be incremented each time any changes are made to the + // + // Test message instance for a given Test ID. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + // description -- should be a human readable common name for the Test + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // readme -- must be a URL, should be a link to the human readable + // + // readme.md or other documentation describing the test + Readme []string `protobuf:"bytes,4,rep,name=readme,proto3" json:"readme,omitempty"` + // exec -- must be a URL, may be a link to google3 code, should be a + // + // link to an ondatra test in the public repo location + Exec string `protobuf:"bytes,5,opt,name=exec,proto3" json:"exec,omitempty"` +} + +func (x *Test) Reset() { + *x = Test{} + if protoimpl.UnsafeEnabled { + mi := &file_testregistry_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Test) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test) ProtoMessage() {} + +func (x *Test) ProtoReflect() protoreflect.Message { + mi := &file_testregistry_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test.ProtoReflect.Descriptor instead. +func (*Test) Descriptor() ([]byte, []int) { + return file_testregistry_proto_rawDescGZIP(), []int{1} +} + +func (x *Test) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Test) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Test) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Test) GetReadme() []string { + if x != nil { + return x.Readme + } + return nil +} + +func (x *Test) GetExec() string { + if x != nil { + return x.Exec + } + return "" +} + +var File_testregistry_proto protoreflect.FileDescriptor + +var file_testregistry_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x74, 0x65, 0x73, 0x74, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x22, 0x65, 0x0a, + 0x0c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x41, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x74, 0x65, 0x73, + 0x74, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x04, + 0x74, 0x65, 0x73, 0x74, 0x22, 0x7e, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 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, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x65, 0x78, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x65, 0x78, 0x65, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_testregistry_proto_rawDescOnce sync.Once + file_testregistry_proto_rawDescData = file_testregistry_proto_rawDesc +) + +func file_testregistry_proto_rawDescGZIP() []byte { + file_testregistry_proto_rawDescOnce.Do(func() { + file_testregistry_proto_rawDescData = protoimpl.X.CompressGZIP(file_testregistry_proto_rawDescData) + }) + return file_testregistry_proto_rawDescData +} + +var file_testregistry_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_testregistry_proto_goTypes = []interface{}{ + (*TestRegistry)(nil), // 0: openconfig.featureprofiles.testregistry.TestRegistry + (*Test)(nil), // 1: openconfig.featureprofiles.testregistry.Test +} +var file_testregistry_proto_depIdxs = []int32{ + 1, // 0: openconfig.featureprofiles.testregistry.TestRegistry.test:type_name -> openconfig.featureprofiles.testregistry.Test + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_testregistry_proto_init() } +func file_testregistry_proto_init() { + if File_testregistry_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_testregistry_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestRegistry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_testregistry_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Test); 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_testregistry_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_testregistry_proto_goTypes, + DependencyIndexes: file_testregistry_proto_depIdxs, + MessageInfos: file_testregistry_proto_msgTypes, + }.Build() + File_testregistry_proto = out.File + file_testregistry_proto_rawDesc = nil + file_testregistry_proto_goTypes = nil + file_testregistry_proto_depIdxs = nil +} diff --git a/testregistry.textproto b/testregistry.textproto index cc4762786bf..a70dfc099b8 100644 --- a/testregistry.textproto +++ b/testregistry.textproto @@ -2,6 +2,65 @@ # proto-message: TestRegistry name: "WBB Test Registry" +test: { + id: "ACCTZ-1.1" + description: "gNSI.acctz.v1 (Accounting) Test Record Subscribe Full" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordSubscribeFull/README.md" +} +test: { + id: "ACCTZ-1.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordSubscribeFull/README.md" +} +test: { + id: "ACCTZ-10.1" + description: "gNSI.acctz.v1 (Accounting) Test Accounting Authentication Error - Multi-transaction" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md" +} +test: { + id: "ACCTZ-2.1" + description: "gNSI.acctz.v1 (Accounting) Test Record Subscribe Partial" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordSubscribePartial/README.md:" +} +test: { + id: "ACCTZ-3.1" + description: "gNSI.acctz.v1 (Accounting) Test Record Subscribe Non-gRPC" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordSubscribeNongrpc/README.md" +} +test: { + id: "ACCTZ-4.1" + description: "gNSI.acctz.v1 (Accounting) Test Record History Truncation" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordHistoryTruncation/README.md" +} +test: { + id: "ACCTZ-4.2" + description: "gNSI.acctz.v1 (Accounting) Test Record Payload Truncation" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordPayloadTruncation/README.md" +} +test: { + id: "ACCTZ-5.1" + description: "gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - client becomes silent" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md" +} +test: { + id: "ACCTZ-6.1" + description: "gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - DoA client" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md" +} +test: { + id: "ACCTZ-7.1" + description: "gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Multi-transaction" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md" +} +test: { + id: "ACCTZ-8.1" + description: "gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Uni-transaction" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md" +} +test: { + id: "ACCTZ-9.1" + description: "gNSI.acctz.v1 (Accounting) Test Accounting Privilege Escalation" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/acctz/AccountingPrivEscalation/README.md" +} test: { id: "ACL-1.1" description: "Layer 3 filtering" @@ -13,6 +72,21 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/acl/otg_tests/acl_update_test/README.md" exec: " " } +test: { + id: "attestz-1" + description: "Validate attestz for initial install" + readme: "https://github.com/openconfig/featureprofiles/tree/main/feature/system/attestz/tests/README.md" +} +test: { + id: "attestz-2" + description: "Validate oIAK and oIDevID rotation" + readme: "https://github.com/openconfig/featureprofiles/tree/main/feature/system/attestz/tests/README.md" +} +test: { + id: "attestz-3" + description: "Validate post-install re-attestation" + readme: "https://github.com/openconfig/featureprofiles/tree/main/feature/system/attestz/tests/README.md" +} test: { id: "Authz-1" description: "test policy behaviors, and probe results matches actual client results" @@ -73,6 +147,52 @@ test: { readme: "" exec: " " } +test: { + id: "Certz-1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/certz/client_certificates/README.md" +} +test: { + id: "Certz-2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/certz/server_certificates/README.md" +} +test: { + id: "Certz-3" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/certz/server_certificate_rotation/README.md" +} +test: { + id: "Certz-4" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/certz/trust_bundle/README.md" +} +test: { + id: "Certz-5" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/ssecurity/gnsi/certz/trust_bundle_rotation/README.md" +} +test: { + id: "CPT-1.1" + description: "Ingress Interface ARP Policer" + readme: "" + exec: " " +} +test: { + id: "Credentialz-1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/credentialz/tests/README.md" +} +test: { + id: "Credentialz-2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/credentialz/tests/README.md" +} +test: { + id: "Credentialz-3" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/credentialz/tests/README.md" +} +test: { + id: "Credentialz-4" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/credentialz/tests/README.md" +} +test: { + id: "Credentialz-5" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/gnsi/credentialz/tests/README.md" +} test: { id: "DP-1.10" description: "Mixed strict priority and WRR traffic test" @@ -103,6 +223,24 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ecn/otg_tests/DSCP-transparency/README.md" exec: " " } +test: { + id: "DP-1.15" + description: "Egress Strict Priority scheduler" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/otg_tests/egress_strict_priority_scheduler_test/README.md" + exec: " " +} +test: { + id: "DP-1.16" + description: "Ingress traffic classification and rewrite" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/README.md" + exec: " " +} +test: { + id: "DP-1.18" + description: "Flow matching using ACL and to Port Mirror/Redirect" + readme: "" + exec: " " +} test: { id: "DP-1.2" description: "QoS policy feature config" @@ -115,6 +253,16 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ate_tests/qos_ecn_config_test/README.md" exec: " " } +test: { + id: "DP-1.4" + description: "QoS Interface Output Queue Counters" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/otg_tests/qos_output_queue_counters_test/README.md" +} +test: { + id: "DP-1.5" + description: "Egress Strict Priority scheduler with bursty traffic" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/README.md" +} test: { id: "DP-1.7" description: "One strict priority queue traffic test" @@ -127,10 +275,35 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ate_tests/two_sp_queue_traffic_test/README.md" exec: " " } +test: { + id: "DP-1.9" + description: "WRR traffic test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ate_tests/wrr_traffic_test/README.md" + exec: " " +} +test: { + id: "DP-2.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/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" +} +test: { + id: "FP-1.1" + description: "Power admin DOWN/UP Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/power_admin_down_up_test/README.md" +} test: { id: "Health-1.1" description: "Generic Health Check" - readme: "https://github.com/openconfig/featureprofiles/blob/d26ac7fac5406af29c9a582b8d8ee73d56953e3b/feature/experimental/system/health/tests/system_generic_health_check/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/health/tests/system_generic_health_check/README.md" + exec: " " +} +test: { + id: "Health-1.1" + description: "Healthz component status paths" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/healthz/tests/status/README.md" exec: " " } test: { @@ -140,9 +313,27 @@ test: { exec: " " } test: { - id: "DP-1.9" - description: "WRR traffic test" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ate_tests/wrr_traffic_test/README.md" + id: "MGT-1" + description: "Management HA test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/management/README.md" + exec: " " +} +test: { + id: "MPLS-1.1" + description: "MPLS label blocks: Static and MPLS-SR" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/mpls/otg_tests/label_block/README.md" + exec: " " +} +test: { + id: "MTU-1.3" + description: "Large IP Packet Transmission" + readme: "https://github.com/openconfig/featureprofiles/tree/main/feature/mtu/largeippacket/otg_tests/large_ip_packet_transmission" + exec: " " +} +test: { + id: "MTU-1.5" + description: "Path MTU handling" + readme: "https://github.com/openconfig/featureprofiles/tree/main/feature/mtu/otg_tests/pmtu_handing" exec: " " } test: { @@ -163,6 +354,135 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/ntp/tests/system_ntp_test/README.md" exec: " " } +test: { + id: "P4RT-1.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/base_p4rt/README.md" +} +test: { + id: "P4RT-1.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/p4rt_daemon_failure_test/README.md" +} +test: { + id: "P4RT-2.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/tests/p4rt_election/README.md" +} +test: { + id: "P4RT-2.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/tests/metadata_validation_test/README.md" +} +test: { + id: "P4RT-3.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md" +} +test: { + id: "P4RT-3.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md" +} +test: { + id: "P4RT-5.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/traceroute_packetin_test/README.md" +} +test: { + id: "P4RT-5.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/traceroute_packetout_test/README.md" +} +test: { + id: "P4RT-6.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/performance_test/README.md" +} +test: { + id: "P4RT-7.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/lldp_packetin_test/README.md" +} +test: { + id: "P4RT-7.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/otg_tests/lldp_packetout_test/README.md" +} +test: { + id: "PF-1.1" + description: "IPv4/IPv6 policy-forwarding to indirect NH matching DSCP/TC" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/README.md" + exec: " " +} +test: { + id: "PF-1.2" + description: "Policy-based traffic GRE Encapsulation to IPv4 GRE tunnel" + 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" + readme: " " + exec: " " +} +test: { + id: "PF-1.5" + description: "Interface based MPLSoGUE Decapsulation to IPv4 tunnel" + readme: " " + exec: " " +} +test: { + id: "PF-1.6" + description: "IPv4 & IPV6 based traffic steering from Non-default VRF to Default VRF using Policy based VRF selection" + readme: "" + exec: " " +} +test: { + id: "PLT-1.1" + description: "Interface breakout Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/breakout_configuration/README.md" + exec: " " +} +test: { + id: "RT-1.2" + description: "BGP Policy & Route Installation" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/route_installation_test/README.md" + exec: " " +} +test: { + id: "RT-1.3" + description: "BGP Route Propagation" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/addpath/ate_tests/route_propagation_test/README.md" + exec: " " +} +test: { + id: "RT-1.4" + description: "BGP Graceful Restart" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/README.md" + exec: " " +} +test: { + id: "RT-1.5" + description: "BGP Prefix Limit" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/README.md" + exec: " " +} +test: { + id: "RT-1.7" + description: "Local BGP Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/tests/local_bgp_test/README.md" + exec: " " +} +test: { + id: "RT-1.8" + description: "BGP Route Reflector test at scale" + readme: "" + exec: " " +} +test: { + id: "RT-1.9" + description: "BGP Transport Parameters test" + readme: "" + exec: " " +} test: { id: "RT-1.10" description: "RT-1.10: BGP Keepalive and Holdtimer configuration" @@ -172,13 +492,13 @@ test: { test: { id: "RT-1.11" description: "RT-1.11: BGP remove private AS" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/bgp/ate_tests/bgp_remove_private_as/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/otg_tests/bgp_remove_private_as/README.md" exec: " " } test: { id: "RT-1.12" description: "RT-1.12: BGP always compare MED" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/bgp/ate_tests/bgp_always_compare_med/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/otg_tests/bgp_always_compare_med/README.md" exec: " " } test: { @@ -207,19 +527,19 @@ test: { } test: { id: "RT-1.17" - description: "RT-1.17: BGP Link Bandwidth Community Forwarding" + description: "RT-1.17: BGP route-refresh capability" readme: "" exec: " " } test: { id: "RT-1.18" - description: "RT-1.18: BGP Link Bandwidth Community - Aggregation" + description: "RT-1.18: Support for MP-BGP w/ Multiple AFI-SAFI Tuples on a single neighborship" readme: "" exec: " " } test: { id: "RT-1.19" - description: "RT-1.19: BGP Link Bandwidth Community - Set Bandwidth" + description: "RT-1.19: BGP 2-Byte and 4-Byte ASN support" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/otg_tests/bgp_2byte_4byte_asn/README.md" exec: " " } @@ -260,42 +580,181 @@ test: { exec: " " } test: { - id: "RT-1.3" - description: "BGP Route Propagation" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/addpath/ate_tests/route_propagation_test/README.md" + 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" exec: " " } test: { - id: "RT-1.4" - description: "BGP Graceful Restart" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/README.md" + id: "RT-1.27" + description: "Static route to BGP redistribution" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md" exec: " " } test: { - id: "RT-1.5" - description: "BGP Prefix Limit" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/README.md" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/README.md" + id: "RT-1.28" + description: "BGP to IS-IS redistribution" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/bgp_isis_redistribution/README.md" exec: " " } test: { - id: "RT-1.7" - description: "Local BGP Test" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/tests/local_bgp_test/README.md" + id: "RT-1.29" + description: "BGP chained import/export policy attachment" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/chained_policies/README.md" exec: " " } test: { - id: "RT-1.8" - description: "BGP Route Reflector test at scale" + id: "RT-1.30" + description: "BGP nested import/export policy attachment" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/nested_policies/README.md" + exec: " " +} +test: { + id: "RT-1.31" + description: "BGP 3 levels of nested import/export policy with match-set-options" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md" + exec: " " +} +test: { + id: "RT-1.32" + description: "BGP policy actions - MED, LocPref, prepend, flow-control" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/actions-MED_LocPref_prepend_flow-control/README.md" + exec: " " +} +test: { + id: "RT-1.33" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/prefix_set_test/README.md" +} +test: { + id: "RT-1.34" + description: "Administrative Distance test for iBGP and eBGP" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/admin_distance/README.md" + exec: " " +} +test: { + id: "RT-1.35" + description: "Extended routes retention" readme: "" exec: " " } test: { - id: "RT-1.9" - description: "BGP Transport Parameters test" + id: "RT-1.51" + description: "BGP multipath ECMP" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/README.md" + exec: " " +} +test: { + id: "RT-1.52" + description: "BGP multipath ECMP support with Link Bandwidth Community" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/README.md" + exec: " " +} +test: { + id: "RT-1.53" + description: "Prefix-set test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/otg_tests/prefix_set_test/README.md" + exec: " " +} +test: { + id: "RT-1.55" + description: "BGP session mode (active/passive)" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/bgp_session_mode_configuration_test/README.md" + exec: " " +} +test: { + id: "RT-1.56" + description: "BGP extended Next-hop support (RFC 5549)" + readme: " " + exec: " " +} +test: { + id: "RT-1.57" + description: "Support [E|I]BGP updates for IPv6 NLRI (including ULA/64 address space) with IPv4 Next-hop." + readme: " " + exec: " " +} +test: { + id: "RT-1.58" + description: "Support for learning IPv6 ULA/64 address space over [E|I]BGP sessions" + readme: " " + exec: " " +} +test: { + id: "RT-1.59" + description: "Support for configuring static IPv6 ULA/64 routes with higher administrative distance" + readme: " " + exec: " " +} +test: { + id: "RT-1.60" + description: "ECMP hashing support for [E|I]BGP learnt ULA/64 address space" + readme: " " + exec: " " +} +test: { + id: "RT-1.61" + description: "ECMP hashing support for statically confifgured ULA/64 address space" + readme: " " + exec: " " +} +test: { + id: "RT-1.62" + description: "ECMP hash testing for [E|I]BGP learnt IPv6 NLRI (including ULA/64 address space) with IPv4 Next-hop." + readme: " " + exec: " " +} +test: { + id: "RT-1.63" + description: "BGP Multihop" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/multihop/README.md" + exec: " " +} +test: { + id: "RT-2.10" + description: "IS-IS change LSP lifetime" + readme: "" + exec: " " +} +test: { + id: "RT-2.11" + description: "IS-IS metric style wide not enabled" readme: "" exec: " " } +test: { + id: "RT-2.12" + description: "Static route to IS-IS redistribution" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/static_route_isis_redistribution/README.md" + exec: " " +} +test: { + id: "RT-2.13" + description: "Weighted-ECMP for IS-IS" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/weighted_ECMP/README.md" +} +test: { + id: "RT-2.14" + description: "ISIS Drain Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/isis_drain_test/README.md" + exec: " " +} +test: { + id: "RT-2.15" + description: "IS-IS Extensions for Segment Routing" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/isis_extensions_segment_routing_test/README.md" + exec: " " +} +test: { + id: "RT-2.15" + 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.2" + description: "IS-IS LSP Updates" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/lsp_updates_test/README.md" +} test: { id: "RT-2.6" description: "IS-IS Hello-Padding enabled at interface level" @@ -309,41 +768,60 @@ test: { exec: " " } test: { - id: "RT-2.8" - description: "IS-IS Passive is enabled at the area level" - readme: "" + id: "RT-2.8" + description: "IS-IS Passive is enabled at the area level" + readme: "" + exec: " " +} +test: { + id: "RT-2.9" + description: "IS-IS metric style wide enabled" + readme: "" + exec: " " +} +test: { + id: "RT-3.1" + description: "Policy based VRF selection base" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/README.md" + exec: " " +} +test: { + id: "RT-3.2" + description: "Policy based VRF selection with multiple protocol and DSCP values" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md" exec: " " } test: { - id: "RT-2.9" - description: "IS-IS metric style wide enabled" + id: "RT-3.3" + description: "Multiple VRFs and GUE DECAP in Default VRF" readme: "" exec: " " } test: { - id: "RT-2.10" - description: "IS-IS change LSP lifetime" + id: "RT-3.31" + description: "DSCP based traffic steering from default VRF to non-Default VRF using Policy based VRF selection plus GUE DECAP" readme: "" exec: " " } test: { - id: "RT-2.11" - description: "IS-IS metric style wide not enabled" + id: "RT-3.32" + description: "DSCP based traffic steering from Non-default VRF to Default VRF using Policy based VRF selection plus GUE DECAP and ENCAP" readme: "" exec: " " } test: { - id: "RT-3.1" - description: "Policy based VRF selection base" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/policy/policy_vrf_selection/ate_tests/base_vrf_selection/README.md" + 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" exec: " " } test: { - id: "RT-3.2" - description: "Policy based VRF selection with multiple protocol and DSCP values" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/policy/policy_vrf_selection/ate_tests/protocol_dscp_rules_for_vrf_selection_test/README.md" + 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" exec: " " } + test: { id: "RT-5.1" description: "Singleton Interface" @@ -356,6 +834,12 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/singleton/ate_tests/singleton_test/README.md" exec: " " } +test: { + id: "RT-5.10" + description: "IPv6 Link Local generated by SLAAC" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/ip/ipv6_slaac_link_local_test/otg_tests/ipv6_slaac_link_local_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/ip/ipv6_slaac_link_local_test/otg_tests/ipv6_slaac_link_local_test/ipv6_slaac_link_local_test.go" +} test: { id: "RT-5.2" description: "Aggregate Interfaces" @@ -394,9 +878,8 @@ test: { } test: { id: "RT-5.6" - description: "Interface IPv6 ND RA disable" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/holdtime/otg_tests/holdtime_test/README.md" - exec: " " + description: "Interface Loopback mode" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md" } test: { id: "RT-5.7" @@ -407,7 +890,13 @@ test: { test: { id: "RT-5.8" description: "IPv6 link local" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/README.md" + exec: " " +} +test: { + id: "RT-5.9" + description: "Interface IPv6 ND RA disable" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md" exec: " " } test: { @@ -422,18 +911,100 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/default_policies_test/README.md" exec: " " } +test: { + id: "RT-7.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/default_policies_test/README.md" +} +test: { + id: "RT-7.11" + description: "RT-7.11: BGP Policy - Import/Export Policy Action Using Multiple Criteria" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/import_export_multi_test/README.md" + exec: " " +} +test: { + id: "RT-7.2" + description: "BGP Policy Community Set" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/community_test/README.md" + exec: " " +} +test: { + id: "RT-7.3" + description: "BGP Policy AS Path Set" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/aspath_test/README.md" + exec: " " +} +test: { + id: "RT-7.4" + description: "BGP Policy AS Path Set and Community Set" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/aspath_and_community_test/README.md" + exec: " " +} +test: { + id: "RT-7.5" + description: "BGP Policy - Set Link Bandwidth Community" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/otg_tests/link_bandwidth_test/README.md" +} +test: { + id: "RT-7.6" + description: "RT-7.6: BGP Link Bandwidth Community - Aggregation" + readme: "" + exec: " " +} +test: { + id: "RT-7.7" + description: "RT-7.7: BGP Link Bandwidth Community - Set Bandwidth" + readme: " " + exec: " " +} +test: { + id: "RT-7.8" + description: "BGP Policy Match Standard Community and Add Community Import/Export Policy" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/comm_match_action_test/README.md" + exec: " " +} +test: { + id: "RT-7.9" + description: "Support for configuring ECMP hashing based on *any* subset of the following (L3 src IP, L3 dst IP, protocol, L4 src port, L4 dst port, L3 IPv6 flow label)" + readme: " " + exec: " " +} test: { id: "RT-8" description: "Singleton with breakouts" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/singleton/tests/singleton_with_breakouts/README.md" exec: " " } +test: { + id: "RT-9" + description: "Interface IPv6 ND RA disable" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/holdtime/otg_tests/holdtime_test/README.md" + exec: " " +} +test: { + id: "RT-9.1" + description: "Equal distribution of traffic" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/weighted_ECMP/README.md" + exec: " " +} +test: { + id: "RT-9.2" + description: "Unequal distribution of traffic" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/weighted_ECMP/README.md" +} +test: { + id: "Replay-1.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/replay/tests/p4rt_replay/README.md" +} test: { id: "SFLOW-1" description: "sFlow Configuration and Traffic Sampling" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/sflow/otg_tests/sflow_base_test/README.md" exec: " " } +test: { + id: "System-1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/tests/system_base_test/README.md" +} test: { id: "TE-1.1" description: "Static ARP" @@ -446,6 +1017,11 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/interface/staticarp/ate_tests/static_arp_test/README.md" exec: " " } +test: { + id: "TE-10" + description: "gRIBI MPLS Forwarding" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_forwarding/README.md" +} test: { id: "TE-11.2" description: "Backup NHG: Multiple NH" @@ -482,6 +1058,34 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/ate_tests/gribigo_compliance_test/README.md" exec: " " } +test: { + id: "TE-16.1" + description: "gRIBI Traffic Engineering - Basic encapsulation tests" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/basic_encap_test/README.md" +} +test: { + id: "TE-16.2" + description: "gRIBI Traffic Engineering encapsulation FRR scenarios" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/encap_frr/README.md" +} +test: { + id: "TE-16.3" + description: "gRIBI Traffic Engineering Encapsulation with Re-encap NI FRR scenarios" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/README.md" +} +test: { + id: "TE-17.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/vrf_policy_driven_te/README.md" +} +test { + id: "TE-18.1" + description: "MPLS in UDP Encapsulation with QoS scheduler" + readme: "" +} +test: { + id: "TE-18.3" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_in_udp_scale/README.md" +} test: { id: "TE-2.1" description: "gRIBI IPv4 Entry" @@ -633,19 +1237,19 @@ test: { exec: " " } test: { - id: "TE-9" - description: "MPLS based forwarding Static LSP" + id: "TE-9.1" + description: "Push MPLS Labels to MPLS payload" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_compliance/README.md" } test: { - id: "TE-9.1" - description: "Base gRIBI MPLS Compliance" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/static_lsp/README.md" + id: "TE-9.3" + description: "gRIBI FIB Failure due to hardware resource exhaust" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md" } test: { - id: "TE-10" - description: "gRIBI MPLS Forwarding" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_forwarding/README.md" + id: "TE-9.2" + description: "MPLS based forwarding Static LSP" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_compliance/README.md" } test: { id: "TR-6.1" @@ -655,10 +1259,70 @@ test: { } test: { id: "TR-6.2" - description: "system logging console vty file" + description: "Local logging destinations" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/logging/console_vty_file/tests/README.md" exec: " " } +test: { + id: "TRANSCEIVER-1" + description: "400ZR Chromatic Dispersion(CD) telemetry values streaming" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_cd_test/README.md" + exec: " " +} +test: { + id: "TRANSCEIVER-10" + description: "400ZR Optics FEC(Forward Error Correction) Uncorrectable Frames Streaming" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_fec_uncorrectable_frames_test/README.md" + exec: " " +} +test: { + id: "TRANSCEIVER-11" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_logical_channels_test/README.md" +} +test: { + id: "TRANSCEIVER-12" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_supply_voltage_test/README.md" +} +test: { + id: "TRANSCEIVER-13" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_low_power_mode_test/README.md" +} +test: { + id: "TRANSCEIVER-3" + description: "400ZR Optics firmware version streaming" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_firmware_version_test/README.md" + exec: " " +} +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" + 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" + exec: " " +} +test: { + id: "TRANSCEIVER-6" + description: "400ZR Optics performance metrics (pm) streaming." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_pm_test/README.md" + exec: " " +} +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" + exec: " " +} +test: { + id: "TRANSCEIVER-9" + description: "400ZR TX laser bias current telemetry values streaming" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_laser_bias_current_test/README.md" + exec: " " +} test: { id: "TUN-1.1" description: "Filter based IPv4 GRE encapsulation" @@ -743,6 +1407,157 @@ test: { readme: "" exec: " " } +test: { + id: "TUN-2.6" + description: "MPLSoUDP Encap" + readme: "" + exec: " " +} +test: { + id: "TUN-2.7" + description: "MPLSoUDP Decap" + readme: "" + exec: " " +} +test: { + id: "TUN-2.8" + description: "ECMP hashing based on outer header of MPLSoUDP packets" + readme: "" + exec: " " +} +test: { + id: "TUN-2.9" + description: "ECMP hashing for GUE flows with IPv4 and IPv6 outer and inner headers" + readme: "" + exec: " " +} +test: { + id: "TUN-2.10" + description: "ECMP hashing based on outer and Inner header for GRE packets" + readme: "" + exec: " " +} +test: { + id: "TUN-2.11" + description: "Filter based GUE decap with IPv4 tunnel endpoints" + readme: "" + exec: " " +} +test: { + id: "TUN-2.12" + description: "Filter based GUE decap with IPv6 tunnel endpoints" + readme: "" + exec: " " +} +test: { + id: "TUN-2.13" + description: "Interface based GUE decap with IPv4 tunnel endpoints" + readme: "" + exec: " " +} +test: { + id: "TUN-2.14" + description: "Interface based GUE decap with IPv6 tunnel endpoints" + readme: "" + exec: " " +} +test: { + id: "TUN-2.15" + description: "Interface based GUE encapsulation with IPv4 outer header" + readme: "" + exec: " " +} +test: { + id: "TUN-2.16" + description: "Interface based GUE encapsulation with IPv6 outer header" + readme: "" + exec: " " +} +test: { + id: "SEC-3.1" + description: "TLS Authentication over gRPC" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/README.md" +} +test: { + id: "bootz-1.1" + description: "Missing configuration - Device fails with status invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-1.2" + description: "Invalid configuration - Device fails with status invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-1.3" + description: "Valid configuration - Device succeded with status ok" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-2.1" + description: "Software version is different - Device is upgraded to the new version" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-2.2" + description: "Invalid software image - Device fails with status invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-3" + description: "bootz-3: Validate Ownership Voucher in bootz configuration" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-3.1" + description: "No ownership voucher - Device boots without OV present" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-3.2" + description: "Invalid OV - Device fails with status invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-3.3" + description: "OV fails - Device fails with status invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-3.4" + description: "OV valid - Device boots with OV installed" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-4" + description: "bootz-4: Validate device properly resets if provided invalid image" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-4.1" + description: "no OS provided - Device boots with existing image" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-4.2" + description: "Invalid OS image provided - Device fails with status invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-4.3" + description: "failed to fetch image from remote URL - Device fails with status invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-4.4" + description: "OS checksum doesn't match - Device fails with invalid parameter" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} +test: { + id: "bootz-5" + description: "Validate gNSI components in bootz configuration" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/bootz/tests/README.md" +} test: { id: "gNMI-1.1" description: "cli Origin" @@ -788,7 +1603,6 @@ test: { test: { id: "gNMI-1.14" description: "OpenConfig metadate consistency during large config push" - ## test intended to cover bug reported in b/271476345 readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/gnmi/metadata/tests/large_set_consistency_test/README.md" exec: " " } @@ -822,6 +1636,22 @@ test: { readme: "https://github.com/openconfig/featureprofiles/tree/main/feature/platform/controllercard/tests/SetRequest_controll_card_switchover/README.md" exec: " " } +test: { + id: "gNMI-1.20" + description: "Telemetry: Optics Thresholds" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/optics_thresholds_test/README.md" + exec: " " +} +test: { + id: "gNMI-1.21" + description: "Integrated Circuit Hardware Resource Utilization Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/integrated_circuit/otg_tests/utilization_test/README.md" +} +test: { + id: "gNMI-1.22" + description: "Controller card port attributes" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/controllercard/tests/port/README.md" +} test: { id: "gNMI-1.4" description: "Telemetry: Inventory" @@ -907,38 +1737,90 @@ test: { exec: " " } test: { - id: "TRANSCEIVER-1" - description: "400ZR Electrical Signal to Noise Ratio(eSNR) and Chromatic Dispersion(CD) telemetry values streaming" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_esnr_and_cd_test/README.md" + id: "gNOI-5.3" + description: "Copying Debug Files" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gnoi/system/tests/copying_debug_files_test/README.md" exec: " " } test: { - id: "TRANSCEIVER-2" - description: "400ZR Optics Pre-FEC(Forward Error Correction) BER(Bit Error Rate)" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/ZR_pre-fec_ber_test/README.md" + id: "gNOI-6.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gnoi/factory_reset/tests/factory_reset_test/README.md" +} +test: { + id: "gNPSI-1" + description: "Sampling and Subscription Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gnpsi/otg_tests/sampling_test/README.md" exec: " " } test: { - id: "TRANSCEIVER-3" - description: "400ZR Optics firmware version streaming" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_firmware_version_test/README.md" + id: "CNTR-1" + description: "Basic container lifecycle via `gnoi.Containerz`." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/containerz/tests/container_lifecycle/README.md" exec: " " } 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" + id: "CNTR-1.1" + description: "Deploy and Start a Container" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/containerz/tests/container_lifecycle/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" + id: "CNTR-1.2" + description: "Retrieve a running container's logs." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/containerz/tests/container_lifecycle/README.md" exec: " " } test: { - id: "PLT-1.1" - description: "Interface breakout Test" - readme: "https://github.com/openconfig/featureprofiles/feature/experimental/platform/tests/breakout_configuration/README.md" + id: "CNTR-1.3" + description: "List the running containers on a DUT" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/containerz/tests/container_lifecycle/README.md" + exec: " " +} +test: { + id: "CNTR-1.4" + description: "Stop a container running on a DUT." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/containerz/tests/container_lifecycle/README.md" + exec: " " +} +test: { + id: "CNTR-2" + description: "Container network connectivity tests" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/networking/tests/container_connectivity/README.md" + exec: " " +} +test: { + id: "CNTR-2.1" + description: "Connect to container from external client." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/networking/tests/container_connectivity/README.md" + exec: " " +} +test: { + id: "CNTR-2.2" + description: "Connect to locally running service." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/networking/tests/container_connectivity/README.md" + exec: " " +} +test: { + id: "CNTR-2.3" + description: "Connect to a remote node." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/networking/tests/container_connectivity/README.md" exec: " " } +test: { + id: "CNTR-2.4" + description: "Connect to another container on a local node" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/container/networking/tests/container_connectivity/README.md" + 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" + 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" + exec: " " +} \ No newline at end of file diff --git a/tools/addrundata/case.go b/tools/addrundata/case.go index 8d2389331dd..03fa2e8622d 100644 --- a/tools/addrundata/case.go +++ b/tools/addrundata/case.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" mpb "github.com/openconfig/featureprofiles/proto/metadata_go_proto" + "github.com/openconfig/featureprofiles/tools/internal/fpciutil" "google.golang.org/protobuf/proto" ) @@ -23,8 +24,8 @@ type testcase struct { // read reads the markdown and existing rundata from the test directory. func (tc *testcase) read(testdir string) error { - if err := readFile(filepath.Join(testdir, "README.md"), tc.readMarkdown); err != nil { - return fmt.Errorf("could not parse README.md: %w", err) + if err := readFile(filepath.Join(testdir, fpciutil.READMEname), tc.readMarkdown); err != nil { + return fmt.Errorf("could not parse %s: %w", fpciutil.READMEname, err) } if err := readFile(filepath.Join(testdir, "metadata.textproto"), tc.readProto); err != nil && !os.IsNotExist(err) { return fmt.Errorf("could not parse metadata.textproto: %w", err) @@ -120,6 +121,7 @@ func (tc *testcase) fix() error { if tc.existing != nil { tc.fixed.Testbed = tc.existing.Testbed tc.fixed.PlatformExceptions = tc.existing.PlatformExceptions + tc.fixed.Tags = tc.existing.Tags u, err := uuid.Parse(tc.existing.Uuid) if err == nil && u.Variant() == uuid.RFC4122 && u.Version() == 4 { // Existing UUID is valid, but make sure it is normalized. diff --git a/tools/addrundata/case_test.go b/tools/addrundata/case_test.go index a5af6514eb3..b1c4a026706 100644 --- a/tools/addrundata/case_test.go +++ b/tools/addrundata/case_test.go @@ -227,6 +227,7 @@ func TestCase_FixWithPlatformExceptions(t *testing.T) { }, }, }, + Tags: []mpb.Metadata_Tags{mpb.Metadata_TAGS_AGGREGATION}, }, } if err := tc.fix(); err != nil { @@ -248,6 +249,7 @@ func TestCase_FixWithPlatformExceptions(t *testing.T) { }, }, }, + Tags: []mpb.Metadata_Tags{mpb.Metadata_TAGS_AGGREGATION}, } if diff := cmp.Diff(want, got, tcopts...); diff != "" { t.Errorf("fixed -want,+got:\n%s", diff) diff --git a/tools/addrundata/main.go b/tools/addrundata/main.go index 4322510e96c..dcc68dc748c 100644 --- a/tools/addrundata/main.go +++ b/tools/addrundata/main.go @@ -14,15 +14,13 @@ package main import ( - "errors" "fmt" "os" - "path/filepath" - "runtime" "flag" "github.com/golang/glog" + "github.com/openconfig/featureprofiles/tools/internal/fpciutil" ) var ( @@ -32,37 +30,13 @@ var ( mergejson = flag.String("mergejson", "", "Merge the JSON listing from this JSON file.") ) -func isDir(path string) bool { - info, err := os.Stat(path) - if err != nil { - return false - } - return info.IsDir() -} - -func featureDir() (string, error) { - _, path, _, ok := runtime.Caller(0) - if !ok { - return "", errors.New("could not detect caller") - } - newpath := filepath.Dir(path) - for newpath != "." && newpath != "/" { - featurepath := filepath.Join(newpath, "feature") - if isDir(featurepath) { - return featurepath, nil - } - newpath = filepath.Dir(newpath) - } - return "", fmt.Errorf("feature root not found from %s", path) -} - func main() { flag.Parse() featuredir := *dir if featuredir == "" { var err error - featuredir, err = featureDir() + featuredir, err = fpciutil.FeatureDir() if err != nil { glog.Exitf("Unable to locate feature root: %v", err) } diff --git a/tools/check_ip_addresses.pl b/tools/check_ip_addresses.pl index f34a2236da8..e2d6602d550 100755 --- a/tools/check_ip_addresses.pl +++ b/tools/check_ip_addresses.pl @@ -43,7 +43,11 @@ END next if $ip =~ /192\.0\.2\./; # TEST-NET-1 (RFC 5737) next if $ip =~ /198\.51\.100\./; # TEST-NET-2 (RFC 5737) next if $ip =~ /203\.0\.113\./; # TEST-NET-3 (RFC 5737) - next if $ip =~ /198\.(18|19)\./; # BMWG (RFC 2544) + next if $ip =~ /100\.(6[4-9])\./; # 64-69, CGN Shared Space (RFC 6598) + next if $ip =~ /100\.([789][0-9])\./; # 70-99, CGN Shared Space (RFC 6598) + next if $ip =~ /100\.(1[01][0-9])\./; # 100 - 119, CGN Shared Space (RFC 6598) + next if $ip =~ /100\.(12[0-7])\./; # 120 - 127, CGN Shared Space (RFC 6598) + next if $ip =~ /198\.(18|19)\./; # Device Benchmark Testing (RFC 2544) next if $ip == "0.0.0.0"; # Wildcard $lineok = 0; } @@ -55,6 +59,7 @@ END my $ip = $parsed->ip(); next if $ip =~ /2001:0?db8:/i; # IPv6 Test Net (RFC 3849) + next if $ip =~ /fe80:/i; # IPv6 Link Local $lineok = 0; } diff --git a/tools/ci-trigger/README.md b/tools/ci-trigger/README.md index 6467ea677ae..4165edd4d91 100644 --- a/tools/ci-trigger/README.md +++ b/tools/ci-trigger/README.md @@ -84,7 +84,7 @@ docker push us-west1-docker.pkg.dev/disco-idea-817/featureprofiles-ci/featurepro To deploy the container into the project: ``` -gcloud run deploy featureprofiles-ci-trigger --cpu 2000m --memory 2Gi --region us-west1 --image us-west1-docker.pkg.dev/disco-idea-817/featureprofiles-ci/featureprofiles-ci-trigger:latest +gcloud run deploy featureprofiles-ci-trigger --cpu 2000m --memory 2Gi --region us-west1 --image us-west1-docker.pkg.dev/disco-idea-817/featureprofiles-ci/featureprofiles-ci-trigger:latest --service-account [SERVICE_ACCOUNT] ``` Allow for background CPU and a minimum instance count for pubsub pull to continue processing. diff --git a/tools/ci-trigger/cloudbuild.go b/tools/ci-trigger/cloudbuild.go index 6b9df6d07e0..01a19b457d9 100644 --- a/tools/ci-trigger/cloudbuild.go +++ b/tools/ci-trigger/cloudbuild.go @@ -72,6 +72,7 @@ func (c *cloudBuild) submitBuild(objPath string) (string, string, error) { Object: objPath, }, } + build.ServiceAccount = "projects/" + gcpProjectID + "/serviceAccounts/" + gcpCloudBuildServiceAccount resp, err := c.buildClient.Projects.Locations.Builds.Create("projects/"+gcpProjectID+"/locations/us-west1", build).Do() if err != nil { diff --git a/tools/ci-trigger/cloudbuild.yaml b/tools/ci-trigger/cloudbuild.yaml index 2fc6e316e4a..41819d1663c 100644 --- a/tools/ci-trigger/cloudbuild.yaml +++ b/tools/ci-trigger/cloudbuild.yaml @@ -11,3 +11,5 @@ steps: args: ['run', 'deploy', 'featureprofiles-ci-trigger', '--image', 'us-west1-docker.pkg.dev/$PROJECT_ID/featureprofiles-ci/featureprofiles-ci-trigger:$COMMIT_SHA', '--region', 'us-west1'] images: - us-west1-docker.pkg.dev/$PROJECT_ID/featureprofiles-ci/featureprofiles-ci-trigger +options: + logging: CLOUD_LOGGING_ONLY diff --git a/tools/ci-trigger/config.go b/tools/ci-trigger/config.go index 17ac4399fc4..3ab5af1e244 100644 --- a/tools/ci-trigger/config.go +++ b/tools/ci-trigger/config.go @@ -51,6 +51,9 @@ const ( // gcpPhysicalTestTopic is the name of the pubsub topic in gcpProjectID for launching physical tests. gcpPhysicalTestTopic = "featureprofiles-physical-tests" + + // gcpCloudBuildServiceAccount is the service account used by all Cloud Build jobs launched for KNE tests. + gcpCloudBuildServiceAccount = "fp-kne-cloudbuild@disco-idea-817.iam.gserviceaccount.com" ) // authorizedTeams is the list of GitHub organization teams authorized to launch Cloud Build jobs. @@ -67,7 +70,6 @@ var triggerKeywords = map[string][]deviceType{ {Vendor: opb.Device_ARISTA, HardwareModel: "cEOS"}, {Vendor: opb.Device_CISCO, HardwareModel: "8000E"}, {Vendor: opb.Device_CISCO, HardwareModel: "XRd"}, - {Vendor: opb.Device_JUNIPER, HardwareModel: "cPTX"}, {Vendor: opb.Device_JUNIPER, HardwareModel: "ncPTX"}, {Vendor: opb.Device_NOKIA, HardwareModel: "SR Linux"}, {Vendor: opb.Device_OPENCONFIG, HardwareModel: "Lemming"}, @@ -82,7 +84,6 @@ var triggerKeywords = map[string][]deviceType{ {Vendor: opb.Device_ARISTA, HardwareModel: "cEOS"}, {Vendor: opb.Device_CISCO, HardwareModel: "8000E"}, {Vendor: opb.Device_CISCO, HardwareModel: "XRd"}, - {Vendor: opb.Device_JUNIPER, HardwareModel: "cPTX"}, {Vendor: opb.Device_JUNIPER, HardwareModel: "ncPTX"}, {Vendor: opb.Device_NOKIA, HardwareModel: "SR Linux"}, {Vendor: opb.Device_OPENCONFIG, HardwareModel: "Lemming"}, @@ -92,7 +93,6 @@ var triggerKeywords = map[string][]deviceType{ "/fptest cisco-8000e": {{Vendor: opb.Device_CISCO, HardwareModel: "8000E"}}, "/fptest cisco-8808": {{Vendor: opb.Device_CISCO, HardwareModel: "8808"}}, "/fptest cisco-xrd": {{Vendor: opb.Device_CISCO, HardwareModel: "XRd"}}, - "/fptest juniper-cptx": {{Vendor: opb.Device_JUNIPER, HardwareModel: "cPTX"}}, "/fptest juniper-ncptx": {{Vendor: opb.Device_JUNIPER, HardwareModel: "ncPTX"}}, "/fptest juniper-ptx10008": {{Vendor: opb.Device_JUNIPER, HardwareModel: "PTX10008"}}, "/fptest nokia-7250": {{Vendor: opb.Device_NOKIA, HardwareModel: "7250 IXR-10e"}}, @@ -103,7 +103,6 @@ var triggerKeywords = map[string][]deviceType{ "/fptest ceos": {{Vendor: opb.Device_ARISTA, HardwareModel: "cEOS"}}, "/fptest 8000e": {{Vendor: opb.Device_CISCO, HardwareModel: "8000E"}}, "/fptest xrd": {{Vendor: opb.Device_CISCO, HardwareModel: "XRd"}}, - "/fptest cptx": {{Vendor: opb.Device_JUNIPER, HardwareModel: "cPTX"}}, "/fptest srl": {{Vendor: opb.Device_NOKIA, HardwareModel: "SR Linux"}}, "/fptest lemming": {{Vendor: opb.Device_OPENCONFIG, HardwareModel: "Lemming"}}, } @@ -113,7 +112,6 @@ var virtualDeviceTypes = []deviceType{ {Vendor: opb.Device_ARISTA, HardwareModel: "cEOS"}, {Vendor: opb.Device_CISCO, HardwareModel: "8000E"}, {Vendor: opb.Device_CISCO, HardwareModel: "XRd"}, - {Vendor: opb.Device_JUNIPER, HardwareModel: "cPTX"}, {Vendor: opb.Device_JUNIPER, HardwareModel: "ncPTX"}, {Vendor: opb.Device_NOKIA, HardwareModel: "SR Linux"}, {Vendor: opb.Device_OPENCONFIG, HardwareModel: "Lemming"}, @@ -124,7 +122,6 @@ var virtualDeviceMachineType = map[deviceType]string{ {Vendor: opb.Device_ARISTA, HardwareModel: "cEOS"}: "e2-standard-16", {Vendor: opb.Device_CISCO, HardwareModel: "8000E"}: "n2-standard-32", {Vendor: opb.Device_CISCO, HardwareModel: "XRd"}: "e2-standard-16", - {Vendor: opb.Device_JUNIPER, HardwareModel: "cPTX"}: "n2-standard-32", {Vendor: opb.Device_JUNIPER, HardwareModel: "ncPTX"}: "e2-standard-16", {Vendor: opb.Device_NOKIA, HardwareModel: "SR Linux"}: "e2-standard-16", {Vendor: opb.Device_OPENCONFIG, HardwareModel: "Lemming"}: "e2-standard-16", diff --git a/tools/ci-trigger/hwgcebind/hwgcebind.go b/tools/ci-trigger/hwgcebind/hwgcebind.go new file mode 100644 index 00000000000..52d3cb876c5 --- /dev/null +++ b/tools/ci-trigger/hwgcebind/hwgcebind.go @@ -0,0 +1,7 @@ +// Package hwgcebind is a placeholder to include additional dependencies into go.mod. +package hwgcebind + +import ( + // Register xDS resolver required for c2p directpath. + _ "google.golang.org/grpc/xds/googledirectpath" +) diff --git a/tools/fpcli/README.md b/tools/fpcli/README.md new file mode 100644 index 00000000000..b864ab0459b --- /dev/null +++ b/tools/fpcli/README.md @@ -0,0 +1,20 @@ +fpcli is an OpenConfig helper CLI for featureprofile-related use cases. + +For example, you can use it to show what RPCs exist for a particular OpenConfig +protocol: + +### Example + +```bash +go install ./tools/fpcli +fpcli show rpcs gnoi -d tmp +``` + +Output: + +``` +gnoi.bgp.BGP.ClearBGPNeighbor +gnoi.bootconfig.BootConfig.GetBootConfig +gnoi.bootconfig.BootConfig.SetBootConfig +... +``` diff --git a/tools/fpcli/cmd/root.go b/tools/fpcli/cmd/root.go new file mode 100644 index 00000000000..41753078e03 --- /dev/null +++ b/tools/fpcli/cmd/root.go @@ -0,0 +1,93 @@ +// 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 cmd implements fpcli +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "fpcli", + Short: "fpcli is an OpenConfig helper CLI for featureprofile-related use cases", + Long: `fpcli is an OpenConfig helper CLI for featureprofile-related use cases. + +For example, you can use it to show what RPCs exist for a particular OpenConfig protocol: + +Example: +$ fpcli show rpcs gnoi -d tmp + +gnoi.bgp.BGP.ClearBGPNeighbor +gnoi.bootconfig.BootConfig.GetBootConfig +gnoi.bootconfig.BootConfig.SetBootConfig +...`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.fpcli.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".fpcli" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".fpcli") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/tools/fpcli/cmd/rpcs.go b/tools/fpcli/cmd/rpcs.go new file mode 100644 index 00000000000..fe2de683f2d --- /dev/null +++ b/tools/fpcli/cmd/rpcs.go @@ -0,0 +1,76 @@ +// 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 cmd + +import ( + "fmt" + "os" + "sort" + "strings" + + "github.com/openconfig/featureprofiles/tools/internal/ocrpcs" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/exp/maps" +) + +// rpcsCmd represents the rpcs command +var rpcsCmd = &cobra.Command{ + Use: "rpcs", + Short: "rpcs is used to show all RPCs belonging to one or more OpenConfig interfaces (e.g. gnmi, gnoi)", + Long: `rpcs is used to show all RPCs belonging to one or more OpenConfig interfaces (e.g. gnmi, gnoi) + +Example: +$ fpcli show rpcs gnoi -d tmp + +gnoi.bgp.BGP.ClearBGPNeighbor +gnoi.bootconfig.BootConfig.GetBootConfig +gnoi.bootconfig.BootConfig.SetBootConfig +...`, + Run: func(cmd *cobra.Command, args []string) { + downloadPath := viper.GetString("download-dir") + if err := os.MkdirAll(downloadPath, 0750); err != nil { + fmt.Fprintf(os.Stderr, "cannot create download path directory: %v", downloadPath) + os.Exit(1) + } + for _, protocol := range args { + ps, err := ocrpcs.Read(downloadPath, protocol) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read OC protocol %q: %v", protocol, err) + os.Exit(1) + } + rpcs := maps.Keys(ps) + sort.Strings(rpcs) + fmt.Println(strings.Join(rpcs, "\n")) + } + }, +} + +func init() { + showCmd.AddCommand(rpcsCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // rpcsCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // rpcsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rpcsCmd.Flags().StringP("download-dir", "d", "", "Directory to download OC repositories. If already downloaded, then won't download again.") + rpcsCmd.MarkFlagRequired("download-dir") + viper.BindPFlag("download-dir", rpcsCmd.Flags().Lookup("download-dir")) +} diff --git a/tools/fpcli/cmd/show.go b/tools/fpcli/cmd/show.go new file mode 100644 index 00000000000..fe35a60d4a4 --- /dev/null +++ b/tools/fpcli/cmd/show.go @@ -0,0 +1,53 @@ +// 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 cmd + +import ( + "github.com/spf13/cobra" +) + +// showCmd represents the show command +var showCmd = &cobra.Command{ + Use: "show", + Short: "show is used to show information related to OpenConfig featureprofiles", + Long: `show is used to show information related to OpenConfig featureprofiles. + +For example, you can use it to show what RPCs exist for a particular OpenConfig protocol: + +Example: +$ fpcli show rpcs gnoi -d tmp + +gnoi.bgp.BGP.ClearBGPNeighbor +gnoi.bootconfig.BootConfig.GetBootConfig +gnoi.bootconfig.BootConfig.SetBootConfig +...`, + // Uncomment the following line if "show" + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +func init() { + rootCmd.AddCommand(showCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // showCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/tools/fpcli/main.go b/tools/fpcli/main.go new file mode 100644 index 00000000000..9498544e1d5 --- /dev/null +++ b/tools/fpcli/main.go @@ -0,0 +1,22 @@ +// 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. + +// fpcli is a helper CLI for FP-related tooling +package main + +import "github.com/openconfig/featureprofiles/tools/fpcli/cmd" + +func main() { + cmd.Execute() +} diff --git a/tools/internal/fpciutil/filepathutil.go b/tools/internal/fpciutil/filepathutil.go new file mode 100644 index 00000000000..2abd4ae2062 --- /dev/null +++ b/tools/internal/fpciutil/filepathutil.go @@ -0,0 +1,54 @@ +// 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. + +// Package fpciutil contains filepath related utilities for featureprofiles CI. +package fpciutil + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" +) + +const ( + // READMEname is the name of all test READMEs according to the contribution guide. + READMEname = "README.md" +) + +func isDir(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + return info.IsDir() +} + +// FeatureDir finds the path to the feature directory from CWD. +func FeatureDir() (string, error) { + _, path, _, ok := runtime.Caller(0) + if !ok { + return "", errors.New("could not detect caller") + } + newpath := filepath.Dir(path) + for newpath != "." && newpath != "/" { + featurepath := filepath.Join(newpath, "feature") + if isDir(featurepath) { + return featurepath, nil + } + newpath = filepath.Dir(newpath) + } + return "", fmt.Errorf("feature root not found from %s", path) +} diff --git a/tools/internal/mdocspec/md.go b/tools/internal/mdocspec/md.go new file mode 100644 index 00000000000..f1fc8601bae --- /dev/null +++ b/tools/internal/mdocspec/md.go @@ -0,0 +1,104 @@ +// 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. + +package mdocspec + +import ( + "io" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/renderer" +) + +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" +) + +type mdOCSpecs struct{} + +// MDOCSpecs is an extension that only renders the first yaml block from a +// functional test README that comes after the pre-established OC Spec heading +// `OCSpecHeading`. +var MDOCSpecs = &mdOCSpecs{} + +func (e *mdOCSpecs) Extend(m goldmark.Markdown) { + extension.GFM.Extend(m) + m.SetRenderer(&yamlRenderer{}) +} + +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) + }) +} + +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 { + if h.Lines().Len() == 0 { + return nil, false + } + headingSegment := h.Lines().At(0) + if string(headingSegment.Value(source)) == OCSpecHeading { + 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" { + return c, true + } + } + return nil, false +} + +func renderYAML(w io.Writer, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + heading, ok := ocSpecHeading(source, n) + if !ok { + return ast.WalkContinue, nil + } + // Check if prior to the next heading of the same level, + // a yaml code block 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 + } + } + return ast.WalkContinue, nil +} diff --git a/tools/internal/mdocspec/md_test.go b/tools/internal/mdocspec/md_test.go new file mode 100644 index 00000000000..155846f2dc9 --- /dev/null +++ b/tools/internal/mdocspec/md_test.go @@ -0,0 +1,634 @@ +// 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. + +package mdocspec + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/yuin/goldmark" +) + +func TestRenderer(t *testing.T) { + tests := []struct { + desc string + inSource []byte + want string + }{{ + desc: "basic", + inSource: []byte(`--- +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-3.7: Base Hierarchical NHG Update](/feature/gribi/otg_tests/base_hierarchical_nhg_update/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) +* [RT-5.1: Singleton Interface](https://github.com/openconfig/featureprofiles/blob/main/feature/interface/singleton/otg_tests/singleton_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 RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +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 +`), + want: `paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +`, + }, { + desc: "second-yaml-block-in-separate-heading", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +` + "```" + ` + +## Required DUT platform + +` + "```" + `yaml +paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +` + "```" + ` + +* 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 +`), + want: `paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +`, + }, { + desc: "two-yaml-blocks-same-heading", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +` + "```" + ` + +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +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 +`), + want: `paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +`, + }, { + desc: "yaml-block-after-next-heading-ignored", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +## Required DUT platform + +` + "```" + `yaml +paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +` + "```" + ` + +* 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 +`), + want: ``, + }, { + desc: "yaml-block-after-next-higher-heading-ignored", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +# Required DUT platform + +Some text + +` + "```" + `yaml +paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +` + "```" + ` + +* 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 +`), + want: ``, + }, { + desc: "yaml-block-after-next-lower-heading-accepted", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +### Required DUT platform + +Some text + +` + "```" + `yaml +paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +` + "```" + ` + +* 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 +`), + want: `paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +`, + }, { + desc: "two-blocks-same-heading-first-language-not-specified-and-ignored", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + ` +paths: + # interface configuration + /a/b/c: + /d/e/f: + +rpcs: + fooi: + fooi.Set: + union_replace: true + fooi.Subscribe: + on_change: true +` + "```" + ` + +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +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 +`), + want: `paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +`, + }, { + desc: "no-yaml-blocks", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +## 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 +`), + want: ``, + }, { + desc: "no-yaml-blocks-last-heading", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## Procedure + +* Test environment setup + * Description of procedure to configure ATE and DUT with pre-requisites making it possible to cover the intended paths and RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +* 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 +`), + want: ``, + }, { + desc: "yaml-block-empty", + inSource: []byte(`--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +` + "```" + ` +`), + want: ``, + }} + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + var buf strings.Builder + md := goldmark.New( + goldmark.WithExtensions(MDOCSpecs), + ) + if err := md.Convert(tt.inSource, &buf); err != nil { + t.Fatalf("MDOCSpecs.Convert: %v", err) + } + if diff := cmp.Diff(tt.want, buf.String()); diff != "" { + t.Errorf("MDOCSpecs.Convert (-want, +got):\n%s", diff) + } + }) + } +} diff --git a/tools/internal/mdocspec/ocspec.go b/tools/internal/mdocspec/ocspec.go new file mode 100644 index 00000000000..5535272d763 --- /dev/null +++ b/tools/internal/mdocspec/ocspec.go @@ -0,0 +1,170 @@ +// 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. + +// Package mdocspec parses yaml OC requirements from functional test READMEs. +package mdocspec + +import ( + "bytes" + "fmt" + "sort" + + "github.com/yuin/goldmark" + "golang.org/x/exp/maps" + "gopkg.in/yaml.v3" + + ppb "github.com/openconfig/featureprofiles/proto/ocpaths_go_proto" + rpb "github.com/openconfig/featureprofiles/proto/ocrpcs_go_proto" +) + +// ErrNotFound indicates the OpenConfig Path and RPC Coverage YAML block was +// not found or was invalid. +var ErrNotFound = fmt.Errorf(`did not detect valid yaml block under a heading titled %q, please see https://github.com/openconfig/featureprofiles/blob/main/doc/test-requirements-template.md#openconfig-path-and-rpc-coverage for example, and https://github.com/openconfig/featureprofiles/tree/main/tools/fpcli/README.md for a tool for viewing the full names of all extant OC RPCs`, OCSpecHeading) + +// Parse extracts sorted OpenConfig Path and RPC Coverage from a +// featureprofiles README. +// +// If such a coverage section is not found in the README, `ErrNotFound` will be +// returned. +// +// Expected markdown format: +// +// ## OpenConfig Path and RPC Coverage +// +// ```yaml +// paths: +// /interfaces/interface/config/description: +// /interfaces/interface/config/enabled: +// /components/component/state/name: +// platform_type: [ +// "CHASSIS" +// "CONTROLLER_CARD", +// "LINECARD", +// "FABRIC", +// ] +// +// rpcs: +// gnmi: +// gNMI.Set: +// union_replace: true +// gNMI.Subscribe: +// on_change: true +// ``` +// +// The first yaml code block after a heading line named exactly as +// "OpenConfig Path and RPC Coverage" will be parsed. Any other code blocks are +// ignored. +func Parse(source []byte) (*ppb.OCPaths, *rpb.OCRPCs, error) { + var buf bytes.Buffer + md := goldmark.New( + goldmark.WithExtensions(MDOCSpecs), + ) + if err := md.Convert(source, &buf); err != nil { + return nil, nil, fmt.Errorf("MDOCSpec.Convert: %v", err) + } + if buf.Len() == 0 { + return nil, nil, ErrNotFound + } + + return parseYAML(buf.Bytes()) +} + +func parseYAML(source []byte) (*ppb.OCPaths, *rpb.OCRPCs, error) { + s := map[string]map[string]map[string]any{} + if err := yaml.Unmarshal(source, &s); err != nil { + return nil, nil, fmt.Errorf("mdocspec: error parsing YAML: %v", err) + } + + protoPaths := &ppb.OCPaths{} + + paths := s["paths"] + pathNames := maps.Keys(paths) + sort.Strings(pathNames) + for _, name := range pathNames { + platformTypes := map[string]struct{}{} + for propertyName, property := range paths[name] { + switch propertyName { + case "platform_type": + ps, ok := property.([]any) + if !ok { + return nil, nil, fmt.Errorf("mdocspec: path %q: got (%T, %v) for `platform_type` attribute, but expected []any", name, property, property) + } + if len(ps) == 0 { + return nil, nil, fmt.Errorf("mdocspec: path %q: `platform_type` attribute must not be empty", name) + } + for i, p := range ps { + sp, ok := p.(string) + if !ok { + return nil, nil, fmt.Errorf("mdocspec: path %q: got (%T, %v), for `platform_type` element index %v, but must be string", name, p, p, i) + } + if _, ok := platformTypes[sp]; ok { + return nil, nil, fmt.Errorf("mdocspec: path %q: got duplicate element %q for `platform_type` element index %v", name, sp, i) + } + platformTypes[sp] = struct{}{} + } + case "value", "values": // Accept value/values as a property names used to specify what property of the path is used in the test. + default: + return nil, nil, fmt.Errorf("mdocspec: path %q: only `platform_type` is expected as a valid attribute for paths, got %q", name, propertyName) + } + } + if len(platformTypes) == 0 { + protoPaths.Ocpaths = append(protoPaths.Ocpaths, &ppb.OCPath{ + Name: name, + }) + continue + } + platformTypesSlice := maps.Keys(platformTypes) + sort.Strings(platformTypesSlice) + for _, platformType := range platformTypesSlice { + protoPaths.Ocpaths = append(protoPaths.Ocpaths, &ppb.OCPath{ + Name: name, + OcpathConstraint: &ppb.OCPathConstraint{ + Constraint: &ppb.OCPathConstraint_PlatformType{ + PlatformType: platformType, + }, + }, + }) + } + } + + protoRPCs := &rpb.OCRPCs{ + OcProtocols: map[string]*rpb.OCProtocol{}, + } + + rpcs, ok := s["rpcs"] + if !ok { + return nil, nil, fmt.Errorf("mdocspec: YAML does not have mandatory top-level \"rpcs\" attribute") + } + rpcNames := maps.Keys(rpcs) + sort.Strings(rpcNames) + var hasMethod bool + for _, name := range rpcNames { + methods := maps.Keys(rpcs[name]) + if len(methods) > 0 { + hasMethod = true + } + sort.Strings(methods) + for i, method := range methods { + methods[i] = name + "." + method + } + protoRPCs.OcProtocols[name] = &rpb.OCProtocol{ + MethodName: methods, + } + } + if !hasMethod { + return nil, nil, fmt.Errorf("mdocspec: YAML does not have least one RPC method specified") + } + + return protoPaths, protoRPCs, nil +} diff --git a/tools/internal/mdocspec/ocspec_test.go b/tools/internal/mdocspec/ocspec_test.go new file mode 100644 index 00000000000..6c15447ea4b --- /dev/null +++ b/tools/internal/mdocspec/ocspec_test.go @@ -0,0 +1,644 @@ +// 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. + +package mdocspec + +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" + ppb "github.com/openconfig/featureprofiles/proto/ocpaths_go_proto" + rpb "github.com/openconfig/featureprofiles/proto/ocrpcs_go_proto" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/testing/protocmp" +) + +func mustOCPaths(t *testing.T, textproto string) *ppb.OCPaths { + ocPaths := &ppb.OCPaths{} + if err := prototext.Unmarshal([]byte(textproto), ocPaths); err != nil { + t.Fatal(err) + } + return ocPaths +} + +func mustOCRPCs(t *testing.T, textproto string) *rpb.OCRPCs { + ocRPCs := &rpb.OCRPCs{} + if err := prototext.Unmarshal([]byte(textproto), ocRPCs); err != nil { + t.Fatal(err) + } + return ocRPCs +} + +func TestParse(t *testing.T) { + tests := []struct { + desc string + inMD string + wantOCPaths *ppb.OCPaths + wantOCRPCs *rpb.OCRPCs + wantNotFoundErr bool + wantErr bool + }{{ + desc: "good", + inMD: `--- +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-3.7: Base Hierarchical NHG Update](/feature/gribi/otg_tests/base_hierarchical_nhg_update/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) +* [RT-5.1: Singleton Interface](https://github.com/openconfig/featureprofiles/blob/main/feature/interface/singleton/otg_tests/singleton_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 RPC's. + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +* TestID-x.y.z - Name of subtest + * Step 1 + * Step 2 + * Validation and pass/fail criteria + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true + gnoi: + healthz.Healthz.Get: + healthz.Healthz.List: + healthz.Healthz.Acknowledge: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + bgp.BGP.ClearBGPNeighbor: +` + "```" + ` + +## 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 +`, + wantOCPaths: mustOCPaths(t, ` +ocpaths: { + name: "/components/component/state/name" + ocpath_constraint: { + platform_type: "CHASSIS" + } +} +ocpaths: { + name: "/interfaces/interface/config/description" +} +ocpaths: { + name: "/interfaces/interface/config/enabled" +} +`), + wantOCRPCs: mustOCRPCs(t, ` +oc_protocols: { + key: "gnmi" + value: { + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" + } +} +oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + } +} +`), + }, { + desc: "empty", + inMD: ``, + wantNotFoundErr: true, + wantErr: true, + }, { + desc: "no-heading", + inMD: ` +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +` + "```" + ` + `, + wantNotFoundErr: true, + wantErr: true, + }, { + desc: "zero-rpcs", + inMD: `--- +name: New featureprofiles test requirement +--- + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] +rpcs: +` + "```" + ` + +## Required DUT platform +`, + wantErr: true, + }, { + desc: "no-rpcs", + inMD: `--- +name: New featureprofiles test requirement +--- + +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + +` + "```" + ` + +## Required DUT platform +`, + wantErr: true, + }, { + desc: "zero-paths-one-rpc", + inMD: ` +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: +rpcs: + gnoi: + healthz.Healthz.Get: +` + "```" + ` + +## Required DUT platform +`, + wantOCPaths: mustOCPaths(t, ``), + wantOCRPCs: mustOCRPCs(t, ` +oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.healthz.Healthz.Get" + } +} +`), + }, { + desc: "no-paths-one-rpc", + inMD: ` +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +rpcs: + gnoi: + healthz.Healthz.Get: +` + "```" + ` + +## Required DUT platform +`, + wantOCPaths: mustOCPaths(t, ``), + wantOCRPCs: mustOCRPCs(t, ` +oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.healthz.Healthz.Get" + } +} +`), + }, { + desc: "zero-paths-one-rpc-zero-methods", + inMD: ` +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: +rpcs: + gnoi: +` + "```" + ` + +## Required DUT platform +`, + wantErr: true, + }, { + desc: "zero-paths-zero-rpcs", + inMD: ` +## OpenConfig Path and RPC Coverage + +This example yaml 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. + +` + "```" + `yaml +paths: +rpcs: +` + "```" + ` + +## Required DUT platform +`, + wantErr: true, + }} + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + gotOCPaths, gotOCRPCs, err := Parse([]byte(tt.inMD)) + if gotNotFoundErr := errors.Is(err, ErrNotFound); gotNotFoundErr != tt.wantNotFoundErr { + t.Fatalf("Parse gotNotFoundErr: %v, wantNotFoundErr: %v", gotNotFoundErr, tt.wantNotFoundErr) + } + if gotErr := err != nil; gotErr != tt.wantErr { + t.Fatalf("Parse gotErr: %v, wantErr: %v", err, tt.wantErr) + } + if diff := cmp.Diff(tt.wantOCPaths, gotOCPaths, protocmp.Transform()); diff != "" { + t.Errorf("Parse OCPaths (-want, +got):\n%s", diff) + } + if diff := cmp.Diff(tt.wantOCRPCs, gotOCRPCs, protocmp.Transform()); diff != "" { + t.Errorf("Parse OCRPCs (-want, +got):\n%s", diff) + } + }) + } +} + +func TestParseYAML(t *testing.T) { + tests := []struct { + desc string + inYAML string + wantOCPaths *ppb.OCPaths + wantOCRPCs *rpb.OCRPCs + wantErr bool + }{{ + desc: "good", + inYAML: ` +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: [ + "CHASSIS", + "CONTROLLER_CARD", + "LINECARD", + "FABRIC", + ] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true + gnoi: + healthz.Healthz.Get: + healthz.Healthz.List: + healthz.Healthz.Acknowledge: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + bgp.BGP.ClearBGPNeighbor: +`, + wantOCPaths: mustOCPaths(t, ` +ocpaths: { + name: "/components/component/state/name" + ocpath_constraint: { + platform_type: "CHASSIS" + } +} +ocpaths: { + name: "/components/component/state/name" + ocpath_constraint: { + platform_type: "CONTROLLER_CARD" + } +} +ocpaths: { + name: "/components/component/state/name" + ocpath_constraint: { + platform_type: "FABRIC" + } +} +ocpaths: { + name: "/components/component/state/name" + ocpath_constraint: { + platform_type: "LINECARD" + } +} +ocpaths: { + name: "/interfaces/interface/config/description" +} +ocpaths: { + name: "/interfaces/interface/config/enabled" +} +`), + wantOCRPCs: mustOCRPCs(t, ` +oc_protocols: { + key: "gnmi" + value: { + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" + } +} +oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + } +} +`), + }, { + desc: "missing-rpcs", + inYAML: `paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] +`, + wantErr: true, + }, { + desc: "missing-paths", + inYAML: `rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true + gnoi: + healthz.Healthz.Get: + healthz.Healthz.List: + healthz.Healthz.Acknowledge: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + bgp.BGP.ClearBGPNeighbor: +`, + wantOCPaths: mustOCPaths(t, ``), + wantOCRPCs: mustOCRPCs(t, ` +oc_protocols: { + key: "gnmi" + value: { + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" + } +} +oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + } +} +`), + }, { + desc: "empty", + inYAML: ``, + wantErr: true, + }, { + desc: "extra-spaces", + inYAML: ` +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: ["CHASSIS"] + + + +rpcs: + + + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true + gnoi: + healthz.Healthz.Get: + healthz.Healthz.List: + healthz.Healthz.Acknowledge: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + bgp.BGP.ClearBGPNeighbor: + +`, + wantOCPaths: mustOCPaths(t, ` +ocpaths: { + name: "/components/component/state/name" + ocpath_constraint: { + platform_type: "CHASSIS" + } +} +ocpaths: { + name: "/interfaces/interface/config/description" +} +ocpaths: { + name: "/interfaces/interface/config/enabled" +} +`), + wantOCRPCs: mustOCRPCs(t, ` +oc_protocols: { + key: "gnmi" + value: { + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" + } +} +oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + } +} +`), + }, { + desc: "platform_type-wrong-type", + inYAML: ` +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: "CHASSIS", + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true + gnoi: + healthz.Healthz.Get: + healthz.Healthz.List: + healthz.Healthz.Acknowledge: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + bgp.BGP.ClearBGPNeighbor: +`, + wantErr: true, + }, { + desc: "platform_type-wrong-element-type", + inYAML: ` +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: [ + "CHASSIS", + 42, + "LINECARD", + "FABRIC", + ] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true + gnoi: + healthz.Healthz.Get: + healthz.Healthz.List: + healthz.Healthz.Acknowledge: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + bgp.BGP.ClearBGPNeighbor: +`, + wantErr: true, + }, { + desc: "platform_type-duplicate-element", + inYAML: ` +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis component + /components/component/state/name: + platform_type: [ + "CHASSIS", + "LINECARD", + "FABRIC", + "LINECARD", + ] + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true + gnoi: + healthz.Healthz.Get: + healthz.Healthz.List: + healthz.Healthz.Acknowledge: + healthz.Healthz.Artifact: + healthz.Healthz.Check: + bgp.BGP.ClearBGPNeighbor: +`, + wantErr: true, + }} + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + gotOCPaths, gotOCRPCs, err := parseYAML([]byte(tt.inYAML)) + if gotErr := err != nil; gotErr != tt.wantErr { + t.Fatalf("parseYAML gotErr: %v, wantErr: %v", err, tt.wantErr) + } + if diff := cmp.Diff(tt.wantOCPaths, gotOCPaths, protocmp.Transform()); diff != "" { + t.Errorf("parseYAML OCPaths (-want, +got):\n%s", diff) + } + if diff := cmp.Diff(tt.wantOCRPCs, gotOCRPCs, protocmp.Transform()); diff != "" { + t.Errorf("parseYAML OCRPCs (-want, +got):\n%s", diff) + } + }) + } +} diff --git a/tools/internal/ocpaths/clone_public.go b/tools/internal/ocpaths/clone_public.go new file mode 100644 index 00000000000..4821cab0db0 --- /dev/null +++ b/tools/internal/ocpaths/clone_public.go @@ -0,0 +1,47 @@ +package ocpaths + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" +) + +// ClonePublicRepo clones the openconfig/public repo at the given path. +// +// - branch is the branch to be downloaded (passed to git clone -b). If it is empty then the argument will be omitted. +// +// # Note +// +// - If the "public" folder already exists, then no additional downloads will +// be made. +// - A manual deletion of the downloadPath folder is required if no longer used. +func ClonePublicRepo(downloadPath, branch string) (string, error) { + if downloadPath == "" { + return "", fmt.Errorf("must provide download path") + } + publicPath := filepath.Join(downloadPath, "public") + + if _, err := os.Stat(publicPath); err == nil { // If NO error + return publicPath, nil + } + + args := []string{"clone", "--depth", "1", "https://github.com/openconfig/public.git", publicPath} + if branch != "" { + args = append(args, "-b", branch, "--single-branch") + } + cmd := exec.Command("git", args...) + stderr, err := cmd.StderrPipe() + if err != nil { + return "", err + } + if err := cmd.Start(); err != nil { + return "", fmt.Errorf("failed to clone public repo: %v, command failed to start: %q", err, cmd.String()) + } + stderrOutput, _ := io.ReadAll(stderr) + if err := cmd.Wait(); err != nil { + return "", fmt.Errorf("failed to clone public repo: %v, command failed during execution: %q\n%s", err, cmd.String(), stderrOutput) + } + return publicPath, nil +} diff --git a/tools/internal/ocpaths/ocpaths.go b/tools/internal/ocpaths/ocpaths.go index 36d7ebcc78d..26e329578af 100644 --- a/tools/internal/ocpaths/ocpaths.go +++ b/tools/internal/ocpaths/ocpaths.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" "regexp" + "sort" "strings" "github.com/openconfig/gnmi/errlist" @@ -51,12 +52,17 @@ var ( } return names }() + validComponentNamesSorted = func() []string { + compNames := maps.Keys(validComponentNames) + sort.Strings(compNames) + return compNames + }() ) // OCPathKey contains the fields that uniquely identify an OC path. type OCPathKey struct { - Path string - Component string + Path string + PlatformType string } // OCPath is the parsed version of the spreadsheet's paths. @@ -91,22 +97,14 @@ func getSchemaFakeroot(publicPath string) (*yang.Entry, error) { return root, nil } -func validatePath(ocpathProto *ppb.OCPath, root *yang.Entry) (*OCPath, error) { - ocpath := &OCPath{ - Key: OCPathKey{ - Path: ocpathProto.GetName(), - Component: ocpathProto.GetOcpathConstraint().GetPlatformType(), - }, - FeatureprofileID: ocpathProto.GetFeatureprofileid(), - } - +func validatePath(ocpath *OCPath, root *yang.Entry) error { // Validate path path := ocpath.Key.Path if !strings.HasPrefix(path, "/") { - return nil, fmt.Errorf("path does not begin with slash: %q", path) + return fmt.Errorf("path does not begin with slash: %q", path) } if strings.HasSuffix(path, "/") { - return nil, fmt.Errorf("path must not end with slash: %q", path) + return fmt.Errorf("path must not end with slash: %q", path) } if entry := root.Find(path); entry == nil { deepestEntry := root @@ -124,23 +122,23 @@ func validatePath(ocpathProto *ppb.OCPath, root *yang.Entry) (*OCPath, error) { } deepestEntry = next } - return nil, fmt.Errorf("path not found: %q, remaining path: %q", path, entryNotFound) + return fmt.Errorf("path not found: %q, remaining path: %q", path, entryNotFound) } else if !entry.IsLeaf() && !entry.IsLeafList() { - return nil, fmt.Errorf("path %q is not a leaf: got kind %s", path, entry.Kind) + return fmt.Errorf("path %q is not a leaf: got kind %s", path, entry.Kind) } // Validate component - component := ocpath.Key.Component + component := ocpath.Key.PlatformType isComponentPath := strings.HasPrefix(path, componentPrefix) switch { case !isComponentPath && component != "": - return nil, fmt.Errorf("non-component path %q has component value %q", path, component) + return fmt.Errorf("non-component path %q has component value %q", path, component) case !isComponentPath: default: if _, ok := validComponentNames[component]; !ok { - return nil, fmt.Errorf("path %q has invalid component %q (must be one of %v)", path, component, maps.Keys(validComponentNames)) + return fmt.Errorf("path %q has invalid component %q (must be one of %v)", path, component, validComponentNamesSorted) } - ocpath.Key.Component = component + ocpath.Key.PlatformType = component } // featureprofileID is optional. Only validate the string format if it exists. @@ -149,11 +147,11 @@ func validatePath(ocpathProto *ppb.OCPath, root *yang.Entry) (*OCPath, error) { case featureprofileIDMatcher.MatchString(featureprofileID): ocpath.FeatureprofileID = featureprofileID default: - return nil, fmt.Errorf("unexpected featureprofileID string %q for path %v (must match regex %q)", featureprofileID, path, featureprofileIDRegex) + return fmt.Errorf("unexpected featureprofileID string %q for path %v (must match regex %q)", featureprofileID, path, featureprofileIDRegex) } } - return ocpath, nil + return nil } func insert(dstMap map[OCPathKey]*OCPath, src *OCPath) error { @@ -167,28 +165,50 @@ func insert(dstMap map[OCPathKey]*OCPath, src *OCPath) error { return nil } +func convertOCPath(ocpathProto *ppb.OCPath) *OCPath { + return &OCPath{ + Key: OCPathKey{ + Path: ocpathProto.GetName(), + PlatformType: ocpathProto.GetOcpathConstraint().GetPlatformType(), + }, + FeatureprofileID: ocpathProto.GetFeatureprofileid(), + } +} + // ValidatePaths parses and validates ocpaths, and puts them into a more // user-friendly Go structure. -func ValidatePaths(ocpathsProto []*ppb.OCPath, publicPath string) (map[OCPathKey]*OCPath, error) { +// +// The first set of paths contain only valid path, while the second contain only invalid paths. +func ValidatePaths(ocpathsProto []*ppb.OCPath, publicPath string) (map[OCPathKey]*OCPath, map[OCPathKey]*OCPath, error) { root, err := getSchemaFakeroot(publicPath) if err != nil { - return nil, err + return nil, nil, err } ocpaths := map[OCPathKey]*OCPath{} + invalidOCPaths := map[OCPathKey]*OCPath{} errs := errlist.List{ Separator: "\n", } for _, ocpathProto := range ocpathsProto { - ocpath, err := validatePath(ocpathProto, root) - if err != nil { - errs.Add(err) - } else if ocpath == nil { + ocpath := convertOCPath(ocpathProto) + if ocpath == nil { errs.Add(fmt.Errorf("failed to parse proto: %v", ocpathProto)) + } else if err := validatePath(ocpath, root); err != nil { + errs.Add(err) + if ocpath != nil { + invalidOCPaths[ocpath.Key] = ocpath + } } else if err := insert(ocpaths, ocpath); err != nil { errs.Add(err) } } - return ocpaths, errs.Err() + if len(ocpaths) == 0 { + ocpaths = nil + } + if len(invalidOCPaths) == 0 { + invalidOCPaths = nil + } + return ocpaths, invalidOCPaths, errs.Err() } diff --git a/tools/internal/ocpaths/ocpaths_test.go b/tools/internal/ocpaths/ocpaths_test.go index f3efa687c63..237e8947b59 100644 --- a/tools/internal/ocpaths/ocpaths_test.go +++ b/tools/internal/ocpaths/ocpaths_test.go @@ -15,9 +15,11 @@ package ocpaths import ( + "fmt" "testing" "github.com/google/go-cmp/cmp" + "github.com/openconfig/gnmi/errdiff" "github.com/openconfig/goyang/pkg/yang" ppb "github.com/openconfig/featureprofiles/proto/ocpaths_go_proto" @@ -37,7 +39,7 @@ func TestValidatePath(t *testing.T) { desc string inOcPathProto *ppb.OCPath wantOCPath *OCPath - wantErr bool + wantErrSubstr string }{{ desc: "no-component", inOcPathProto: &ppb.OCPath{ @@ -46,8 +48,8 @@ func TestValidatePath(t *testing.T) { }, wantOCPath: &OCPath{ Key: OCPathKey{ - Path: "/interfaces/interface/config/name", - Component: "", + Path: "/interfaces/interface/config/name", + PlatformType: "", }, FeatureprofileID: "interface_base", }, @@ -57,7 +59,7 @@ func TestValidatePath(t *testing.T) { Name: "/interfaces/interface", Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `"/interfaces/interface" is not a leaf: got kind Directory`, }, { desc: "with-component", inOcPathProto: &ppb.OCPath{ @@ -67,8 +69,8 @@ func TestValidatePath(t *testing.T) { }, wantOCPath: &OCPath{ Key: OCPathKey{ - Path: "/components/component/state/name", - Component: "CPU", + Path: "/components/component/state/name", + PlatformType: "CPU", }, FeatureprofileID: "interface_base", }, @@ -78,7 +80,7 @@ func TestValidatePath(t *testing.T) { Name: "/components/component/state/name", Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path "/components/component/state/name" has invalid component "" (must be one of ` + fmt.Sprint(validComponentNamesSorted), }, { desc: "non-component-path-has-component", inOcPathProto: &ppb.OCPath{ @@ -86,7 +88,7 @@ func TestValidatePath(t *testing.T) { OcpathConstraint: &ppb.OCPathConstraint{Constraint: &ppb.OCPathConstraint_PlatformType{PlatformType: "CPU"}}, Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `non-component path "/interfaces/interface/config/name" has component value "CPU"`, }, { desc: "invalid-component", inOcPathProto: &ppb.OCPath{ @@ -94,7 +96,7 @@ func TestValidatePath(t *testing.T) { OcpathConstraint: &ppb.OCPathConstraint{Constraint: &ppb.OCPathConstraint_PlatformType{PlatformType: "cpu"}}, Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path "/components/component/state/name" has invalid component "cpu" (must be one of ` + fmt.Sprint(validComponentNamesSorted), }, { desc: "with-bad-component", inOcPathProto: &ppb.OCPath{ @@ -102,68 +104,74 @@ func TestValidatePath(t *testing.T) { OcpathConstraint: &ppb.OCPathConstraint{Constraint: &ppb.OCPathConstraint_PlatformType{PlatformType: "NOT-A-COMPONENT"}}, Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path "/components/component/state/name" has invalid component "NOT-A-COMPONENT" (must be one of ` + fmt.Sprint(validComponentNamesSorted), }, { desc: "spaces-after-path", inOcPathProto: &ppb.OCPath{ Name: "/interfaces/interface/config/name ", Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path not found: "/interfaces/interface/config/name "`, }, { desc: "extra-slash", inOcPathProto: &ppb.OCPath{ Name: "/interfaces/interface/config//name", Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path not found: "/interfaces/interface/config//name"`, }, { desc: "no-starting-slash", inOcPathProto: &ppb.OCPath{ Name: "interfaces/interface/config/name", Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path does not begin with slash: "interfaces/interface/config/name"`, }, { desc: "ending-slash", inOcPathProto: &ppb.OCPath{ Name: "/interfaces/interface/config/name/", Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path must not end with slash: "/interfaces/interface/config/name/"`, }, { desc: "path-not-found", inOcPathProto: &ppb.OCPath{ Name: "/interfaces/interface/intraface/config/name", Featureprofileid: "interface_base", }, - wantErr: true, + wantErrSubstr: `path not found: "/interfaces/interface/intraface/config/name"`, }, { desc: "invalid-featureprofileid", inOcPathProto: &ppb.OCPath{ Name: "/interfaces/interface/config/name", Featureprofileid: "Interface_Base", }, - wantErr: true, + wantErrSubstr: `unexpected featureprofileID string "Interface_Base" for path /interfaces/interface/config/name (must match regex "^([a-z0-9]+_)*[a-z0-9]+$")`, }, { desc: "invalid-featureprofileid-2", inOcPathProto: &ppb.OCPath{ Name: "/interfaces/interface/config/name", Featureprofileid: "interface-base", }, - wantErr: true, + wantErrSubstr: `unexpected featureprofileID string "interface-base" for path /interfaces/interface/config/name (must match regex "^([a-z0-9]+_)*[a-z0-9]+$")`, }} root := getFakeroot(t) for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - got, err := validatePath(tt.inOcPathProto, root) - if (err != nil) != tt.wantErr { - t.Fatalf("gotErr: %v, wantErr: %v", err, tt.wantErr) + ocpath := convertOCPath(tt.inOcPathProto) + + err := validatePath(ocpath, root) + if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { + t.Error(diff) } - if diff := cmp.Diff(tt.wantOCPath, got); diff != "" { + if err != nil { + return + } + + if diff := cmp.Diff(tt.wantOCPath, ocpath); diff != "" { t.Errorf("(-want, +got):\n%s", diff) } }) @@ -175,6 +183,7 @@ func TestValidatePaths(t *testing.T) { desc string inOcPathsProto []*ppb.OCPath wantOCPaths map[OCPathKey]*OCPath + wantInvalids map[OCPathKey]*OCPath wantErr bool }{{ desc: "valid", @@ -192,8 +201,8 @@ func TestValidatePaths(t *testing.T) { }}, wantOCPaths: map[OCPathKey]*OCPath{ { - Path: "/interfaces/interface/config/name", - Component: "", + Path: "/interfaces/interface/config/name", + PlatformType: "", }: { Key: OCPathKey{ Path: "/interfaces/interface/config/name", @@ -201,22 +210,22 @@ func TestValidatePaths(t *testing.T) { FeatureprofileID: "interface_base", }, { - Path: "/components/component/config/description", - Component: "CPU", + Path: "/components/component/config/description", + PlatformType: "CPU", }: { Key: OCPathKey{ - Path: "/components/component/config/description", - Component: "CPU", + Path: "/components/component/config/description", + PlatformType: "CPU", }, FeatureprofileID: "interface_base", }, { - Path: "/components/component/config/description", - Component: "PORT", + Path: "/components/component/config/description", + PlatformType: "PORT", }: { Key: OCPathKey{ - Path: "/components/component/config/description", - Component: "PORT", + Path: "/components/component/config/description", + PlatformType: "PORT", }, FeatureprofileID: "interface_basic", }, @@ -227,9 +236,20 @@ func TestValidatePaths(t *testing.T) { Name: "/interfaces/interface/config", Featureprofileid: "interface_base", }}, + wantInvalids: map[OCPathKey]*OCPath{ + { + Path: "/interfaces/interface/config", + PlatformType: "", + }: { + Key: OCPathKey{ + Path: "/interfaces/interface/config", + }, + FeatureprofileID: "interface_base", + }, + }, wantErr: true, }, { - desc: "duplicate", + desc: "duplicate-and-invalid", inOcPathsProto: []*ppb.OCPath{{ Name: "/interfaces/interface/config/name", Featureprofileid: "interface_base", @@ -241,23 +261,59 @@ func TestValidatePaths(t *testing.T) { Name: "/components/component/config/description", OcpathConstraint: &ppb.OCPathConstraint{Constraint: &ppb.OCPathConstraint_PlatformType{PlatformType: "CPU"}}, Featureprofileid: "interface_basic", + }, { + Name: "/interfaces/interface/config", + Featureprofileid: "interface_base", }}, + wantOCPaths: map[OCPathKey]*OCPath{ + { + Path: "/interfaces/interface/config/name", + PlatformType: "", + }: { + Key: OCPathKey{ + Path: "/interfaces/interface/config/name", + }, + FeatureprofileID: "interface_base", + }, + { + Path: "/components/component/config/description", + PlatformType: "CPU", + }: { + Key: OCPathKey{ + Path: "/components/component/config/description", + PlatformType: "CPU", + }, + FeatureprofileID: "interface_base", + }, + }, + wantInvalids: map[OCPathKey]*OCPath{ + { + Path: "/interfaces/interface/config", + PlatformType: "", + }: { + Key: OCPathKey{ + Path: "/interfaces/interface/config", + }, + FeatureprofileID: "interface_base", + }, + }, wantErr: true, }} for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - got, err := ValidatePaths(tt.inOcPathsProto, "testdata/models") + got, gotInvalids, err := ValidatePaths(tt.inOcPathsProto, "testdata/models") if (err != nil) != tt.wantErr { - t.Fatalf("gotErr: %v, wantErr: %v", err, tt.wantErr) - } - if err != nil { - return + t.Errorf("gotErr: %v, wantErr: %v", err, tt.wantErr) } if diff := cmp.Diff(tt.wantOCPaths, got); diff != "" { t.Errorf("(-want, +got):\n%s", diff) } + + if diff := cmp.Diff(tt.wantInvalids, gotInvalids); diff != "" { + t.Errorf("(-want, +got):\n%s", diff) + } }) } } diff --git a/tools/internal/ocrpcs/ocrpcs.go b/tools/internal/ocrpcs/ocrpcs.go new file mode 100644 index 00000000000..5e535b90928 --- /dev/null +++ b/tools/internal/ocrpcs/ocrpcs.go @@ -0,0 +1,132 @@ +// 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 ocrpcs contains utilities related to ocrpcs.proto. +package ocrpcs + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/yoheimuta/go-protoparser/v4" + + rpb "github.com/openconfig/featureprofiles/proto/ocrpcs_go_proto" + "github.com/openconfig/gnmi/errlist" +) + +// cloneAPIRepo clones the openconfig/ repo at the given path. +// +// # Note +// +// * If the folder already exists, then no additional downloads will be made. +// * A manual deletion of the folder is required if no longer used. +func cloneAPIRepo(downloadPath, api string) (string, error) { + if downloadPath == "" { + return "", fmt.Errorf("must provide download path") + } + repoPath := filepath.Join(downloadPath, api) + + if _, err := os.Stat(repoPath); err == nil { // If NO error + return repoPath, nil + } + + cmd := exec.Command("git", "clone", "--single-branch", "--depth", "1", fmt.Sprintf("https://github.com/openconfig/%s.git", api), repoPath) + stderr, err := cmd.StderrPipe() + if err != nil { + return "", err + } + if err := cmd.Start(); err != nil { + return "", fmt.Errorf("failed to clone %s repo: %v, command failed to start: %q", api, err, cmd.String()) + } + stderrOutput, _ := io.ReadAll(stderr) + if err := cmd.Wait(); err != nil { + return "", fmt.Errorf("failed to clone %s repo: %v, command failed during execution: %q\n%s", api, err, cmd.String(), stderrOutput) + } + return repoPath, nil +} + +// Read returns all RPCs for the given OpenConfig API. +// +// - downloadPath specifies the folder to download the associated OpenConfig +// repo in order to allow for proto file parsing. +func Read(downloadPath, api string) (map[string]struct{}, error) { + repoPath, err := cloneAPIRepo(downloadPath, api) + if err != nil { + return nil, err + } + + rpcs := map[string]struct{}{} + + if err := filepath.Walk(repoPath, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(info.Name(), ".proto") { + reader, err := os.Open(path) + if err != nil { + return err + } + got, err := protoparser.Parse(reader) + if err != nil { + return fmt.Errorf("failed to parse, err %v", err) + } + visitor := &rpcServiceAccumulator{} + got.Accept(visitor) + for _, rpc := range visitor.rpcs { + rpcs[rpc] = struct{}{} + } + } + return nil + }); err != nil { + return nil, err + } + + return rpcs, nil +} + +// ValidateRPCs verifies whether the RPCs listed in protocols are valid. +// +// It returns the number of valid RPCs found, and an error if any RPC is +// invalid, or there was an issue downloading or parsing OpenConfig protobuf +// files. +// +// - downloadPath is a path to the folder which will contain OpenConfig +// repositories that will be downloaded in order to validate the existence of +// provided RPCs. +func ValidateRPCs(downloadPath string, protocols map[string]*rpb.OCProtocol) (uint, error) { + var validCount uint + + var errs errlist.List + errs.Separator = "\n" + for api, protocol := range protocols { + rpcs, err := Read(downloadPath, api) + if err != nil { + return 0, err + } + for _, name := range protocol.GetMethodName() { + if _, ok := rpcs[name]; !ok { + errs.Add(fmt.Errorf("RPC not found in openconfig repo %v: %v", api, name)) + continue + } + validCount++ + } + } + + return validCount, errs.Err() +} diff --git a/tools/internal/ocrpcs/visitor.go b/tools/internal/ocrpcs/visitor.go new file mode 100644 index 00000000000..6748224ce86 --- /dev/null +++ b/tools/internal/ocrpcs/visitor.go @@ -0,0 +1,94 @@ +package ocrpcs + +import ( + "fmt" + + "github.com/yoheimuta/go-protoparser/v4/parser" +) + +type rpcServiceAccumulator struct { + packageName string + currentService string + + rpcs []string +} + +var _ parser.Visitor = &rpcServiceAccumulator{} + +func (v *rpcServiceAccumulator) VisitComment(*parser.Comment) { +} + +func (v *rpcServiceAccumulator) VisitEmptyStatement(*parser.EmptyStatement) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitEnum(*parser.Enum) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitEnumField(*parser.EnumField) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitExtend(*parser.Extend) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitExtensions(*parser.Extensions) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitField(*parser.Field) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitGroupField(*parser.GroupField) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitImport(*parser.Import) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitMapField(*parser.MapField) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitMessage(*parser.Message) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitOneof(*parser.Oneof) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitOneofField(*parser.OneofField) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitOption(*parser.Option) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitPackage(p *parser.Package) (next bool) { + v.packageName = p.Name + return +} + +func (v *rpcServiceAccumulator) VisitReserved(*parser.Reserved) (next bool) { + return +} + +func (v *rpcServiceAccumulator) VisitRPC(r *parser.RPC) (next bool) { + v.rpcs = append(v.rpcs, fmt.Sprintf("%s.%s.%s", v.packageName, v.currentService, r.RPCName)) + return +} + +func (v *rpcServiceAccumulator) VisitService(s *parser.Service) (next bool) { + v.currentService = s.ServiceName + return true +} + +func (v *rpcServiceAccumulator) VisitSyntax(*parser.Syntax) (next bool) { + return +} diff --git a/tools/nosimage/README.md b/tools/nosimage/README.md index 1de7c505eaf..5f827a1c87d 100644 --- a/tools/nosimage/README.md +++ b/tools/nosimage/README.md @@ -4,12 +4,12 @@ ``` cd $GOPATH/src/github.com/openconfig/featureprofiles/tools/nosimage -go run validate/validate.go -file example/example_nosimageprofile.textproto; rm -rf public +go run validate/validate.go -file example/example_nosimageprofile.textproto; rm -rf tmp ``` ``` cd $GOPATH/src/github.com/openconfig/featureprofiles/tools/nosimage -go run validate/validate.go -file example/example_nosimageprofile_invalid.textproto; rm -rf public +go run validate/validate.go -file example/example_nosimageprofile_invalid.textproto; rm -rf tmp ``` ### Re-generating Example Files diff --git a/tools/nosimage/example/generate_example.go b/tools/nosimage/example/generate_example.go index 6555d1d2f9f..f26e24796d4 100644 --- a/tools/nosimage/example/generate_example.go +++ b/tools/nosimage/example/generate_example.go @@ -21,8 +21,10 @@ import ( "flag" "fmt" "os" + "path/filepath" "time" + log "github.com/golang/glog" "github.com/protocolbuffers/txtpbfmt/parser" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" @@ -34,10 +36,13 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) +//go:generate go run generate_example.go -folder-path . +//go:generate go run generate_example.go -folder-path . -invalid + // Config is the set of flags for this binary. type Config struct { - FilePath string - Invalid bool + FolderPath string + Invalid bool } // New registers a flagset with the configuration needed by this binary. @@ -47,8 +52,8 @@ func New(fs *flag.FlagSet) *Config { if fs == nil { fs = flag.CommandLine } - fs.StringVar(&c.FilePath, "file-path", "example_nosimage.textproto", "generate an example file at the given path rather than validating a file.") - fs.BoolVar(&c.Invalid, "invalid", false, "generate an invalid example") + fs.StringVar(&c.FolderPath, "folder-path", "", "generate an example file in the given folder rather than validating a file.") + fs.BoolVar(&c.Invalid, "invalid", false, "generate invalid examples") return c } @@ -61,19 +66,30 @@ func init() { config = New(nil) } -func generateExample(filepath string, invalid bool) error { +func generateExample(invalidPaths, invalidProtocols, invalidSoftwareName, invalidHardwareName bool) ([]byte, error) { componentPrefix := "/components/component" softwareComponent := "OPERATING_SYSTEM" interfaceLeafName := "name" - if invalid { + if invalidPaths { componentPrefix = "/componentsssssssssss/component" softwareComponent = "JOVIAN_ATMOSPHERE" interfaceLeafName = "does-not-exist" } - bs, err := formatTxtpb(&npb.NOSImageProfile{ + var ( + softwareVersion string + hardwareName string + ) + if !invalidSoftwareName { + softwareVersion = "7a1cb734c83f0d9ba5b273f920bc002ad0056178" + } + if !invalidHardwareName { + hardwareName = "lemming" + } + return formatTxtpb(&npb.NOSImageProfile{ VendorId: opb.Device_OPENCONFIG, Nos: "lemming", - SoftwareVersion: "7a1cb734c83f0d9ba5b273f920bc002ad0056178", + SoftwareVersion: softwareVersion, + HardwareName: hardwareName, ReleaseDate: timestamppb.New(time.Date(2023, time.November, 16, 16, 20, 0, 0, time.FixedZone("UTC-8", -8*60*60))), Ocpaths: &ppb.OCPaths{ Version: "2.5.0", @@ -95,37 +111,54 @@ func generateExample(filepath string, invalid bool) error { }}, }, Ocrpcs: &rpb.OCRPCs{ - OcProtocols: map[string]*rpb.OCProtocol{ - "gnmi.gNMI": { - Version: "0.10.0", - MethodName: []string{ - "Set", - "Subscribe", - }, - }, - "gnoi.healthz.Healthz": { - Version: "1.3.0", - MethodName: []string{ - "Get", - "List", - "Acknowledge", - "Artifact", - "Check", + OcProtocols: func() map[string]*rpb.OCProtocol { + if !invalidProtocols { + return map[string]*rpb.OCProtocol{ + "gnmi": { + Version: "0.10.0", + MethodName: []string{ + "gnmi.gNMI.Set", + "gnmi.gNMI.Subscribe", + }, + }, + "gnoi": { + Version: "0.3.0", + MethodName: []string{ + "gnoi.healthz.Healthz.Get", + "gnoi.healthz.Healthz.List", + "gnoi.healthz.Healthz.Acknowledge", + "gnoi.healthz.Healthz.Artifact", + "gnoi.healthz.Healthz.Check", + "gnoi.bgp.BGP.ClearBGPNeighbor", + }, + }, + } + } + return map[string]*rpb.OCProtocol{ + "gnmi": { + Version: "0.10.0", + MethodName: []string{ + "gnmi.gNMI.Set", + "gnmi.gNMI.Subscribe", + "gnmi.gNMI.Whatsup", + }, }, - }, - "gnoi.bgp.BGP": { - Version: "0.1.0", - MethodName: []string{ - "ClearBGPNeighbor", + "gnoi": { + Version: "0.3.0", + MethodName: []string{ + "gnoi.healthz.Healthz.Get", + "gnoi.healthz.Healthz.List", + "gnoi.healthz.Healthz.Acknowledge", + "gnoi.healthz.Healthz.Artifact", + "gnoi.healthz.Healthz.Check", + "gnoi.bgp.BGP.ClearBGPNeighbor", + "gnmi.gNMI.Get", + }, }, - }, - }, + } + }(), }, }) - if err != nil { - return err - } - return os.WriteFile(filepath, bs, 0664) } func formatTxtpb(msg proto.Message) ([]byte, error) { @@ -150,13 +183,50 @@ func formatTxtpb(msg proto.Message) ([]byte, error) { func main() { flag.Parse() - if config.FilePath == "" { - fmt.Println("must provide example file path to write") - os.Exit(1) + if config.FolderPath == "" { + log.Exitln("must provide example folder path to write to") } - if err := generateExample(config.FilePath, config.Invalid); err != nil { - fmt.Println(err) - os.Exit(1) + fileSpecs := []struct { + name string + isInvalid bool + invalidPaths bool + invalidProtocols bool + invalidSoftwareName bool + invalidHardwareName bool + }{{ + name: "valid", + }, { + name: "invalid-path", + isInvalid: true, + invalidPaths: true, + }, { + name: "invalid-protocols", + isInvalid: true, + invalidProtocols: true, + }, { + name: "invalid-software-name", + isInvalid: true, + invalidSoftwareName: true, + }, { + name: "invalid-hw-name", + isInvalid: true, + invalidHardwareName: true, + }} + + for _, spec := range fileSpecs { + if config.Invalid != spec.isInvalid { + continue + } + + bs, err := generateExample(spec.invalidPaths, spec.invalidProtocols, spec.invalidSoftwareName, spec.invalidHardwareName) + if err != nil { + log.Exitln(err) + } + path := filepath.Join(config.FolderPath, spec.name+"_example_nosimageprofile.textproto") + fmt.Printf("writing to %q\n", path) + if err := os.WriteFile(path, bs, 0664); err != nil { + log.Exitln(err) + } } } diff --git a/tools/nosimage/example/example_nosimageprofile.textproto b/tools/nosimage/example/invalid-hw-name_example_nosimageprofile.textproto similarity index 71% rename from tools/nosimage/example/example_nosimageprofile.textproto rename to tools/nosimage/example/invalid-hw-name_example_nosimageprofile.textproto index d66cd55c01a..0aeb76b90d5 100644 --- a/tools/nosimage/example/example_nosimageprofile.textproto +++ b/tools/nosimage/example/invalid-hw-name_example_nosimageprofile.textproto @@ -37,29 +37,23 @@ ocpaths: { } ocrpcs: { oc_protocols: { - key: "gnmi.gNMI" + key: "gnmi" value: { - method_name: "Set" - method_name: "Subscribe" + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" version: "0.10.0" } } oc_protocols: { - key: "gnoi.bgp.BGP" + key: "gnoi" value: { - method_name: "ClearBGPNeighbor" - version: "0.1.0" - } - } - oc_protocols: { - key: "gnoi.healthz.Healthz" - value: { - method_name: "Acknowledge" - method_name: "Artifact" - method_name: "Check" - method_name: "Get" - method_name: "List" - version: "1.3.0" + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + version: "0.3.0" } } } diff --git a/tools/nosimage/example/example_nosimageprofile_invalid.textproto b/tools/nosimage/example/invalid-path_example_nosimageprofile.textproto similarity index 70% rename from tools/nosimage/example/example_nosimageprofile_invalid.textproto rename to tools/nosimage/example/invalid-path_example_nosimageprofile.textproto index ed20a9a1650..bdff079de5d 100644 --- a/tools/nosimage/example/example_nosimageprofile_invalid.textproto +++ b/tools/nosimage/example/invalid-path_example_nosimageprofile.textproto @@ -5,6 +5,7 @@ vendor_id: OPENCONFIG nos: "lemming" software_version: "7a1cb734c83f0d9ba5b273f920bc002ad0056178" +hardware_name: "lemming" release_date: { seconds: 1700180400 } @@ -37,29 +38,23 @@ ocpaths: { } ocrpcs: { oc_protocols: { - key: "gnmi.gNMI" + key: "gnmi" value: { - method_name: "Set" - method_name: "Subscribe" + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" version: "0.10.0" } } oc_protocols: { - key: "gnoi.bgp.BGP" + key: "gnoi" value: { - method_name: "ClearBGPNeighbor" - version: "0.1.0" - } - } - oc_protocols: { - key: "gnoi.healthz.Healthz" - value: { - method_name: "Acknowledge" - method_name: "Artifact" - method_name: "Check" - method_name: "Get" - method_name: "List" - version: "1.3.0" + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + version: "0.3.0" } } } diff --git a/tools/nosimage/example/invalid-protocols_example_nosimageprofile.textproto b/tools/nosimage/example/invalid-protocols_example_nosimageprofile.textproto new file mode 100644 index 00000000000..eaeffbf6780 --- /dev/null +++ b/tools/nosimage/example/invalid-protocols_example_nosimageprofile.textproto @@ -0,0 +1,62 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/nosimage.proto +# proto-message: NOSImageProfile +# txtpbfmt: expand_all_children +# txtpbfmt: sort_repeated_fields_by_content +vendor_id: OPENCONFIG +nos: "lemming" +software_version: "7a1cb734c83f0d9ba5b273f920bc002ad0056178" +hardware_name: "lemming" +release_date: { + seconds: 1700180400 +} +ocpaths: { + ocpaths: { + name: "/interfaces/interface/config/name" + } + ocpaths: { + name: "/components/component/state/location" + ocpath_constraint: { + platform_type: "PORT" + } + } + ocpaths: { + name: "/components/component/state/serial-no" + ocpath_constraint: { + platform_type: "STORAGE" + } + } + ocpaths: { + name: "/components/component/state/software-version" + ocpath_constraint: { + platform_type: "OPERATING_SYSTEM" + } + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-as" + } + version: "2.5.0" +} +ocrpcs: { + oc_protocols: { + key: "gnmi" + value: { + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" + method_name: "gnmi.gNMI.Whatsup" + version: "0.10.0" + } + } + oc_protocols: { + key: "gnoi" + value: { + method_name: "gnmi.gNMI.Get" + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + version: "0.3.0" + } + } +} diff --git a/tools/nosimage/example/invalid-software-name_example_nosimageprofile.textproto b/tools/nosimage/example/invalid-software-name_example_nosimageprofile.textproto new file mode 100644 index 00000000000..e6ec07a2843 --- /dev/null +++ b/tools/nosimage/example/invalid-software-name_example_nosimageprofile.textproto @@ -0,0 +1,59 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/nosimage.proto +# proto-message: NOSImageProfile +# txtpbfmt: expand_all_children +# txtpbfmt: sort_repeated_fields_by_content +vendor_id: OPENCONFIG +nos: "lemming" +hardware_name: "lemming" +release_date: { + seconds: 1700180400 +} +ocpaths: { + ocpaths: { + name: "/interfaces/interface/config/name" + } + ocpaths: { + name: "/components/component/state/location" + ocpath_constraint: { + platform_type: "PORT" + } + } + ocpaths: { + name: "/components/component/state/serial-no" + ocpath_constraint: { + platform_type: "STORAGE" + } + } + ocpaths: { + name: "/components/component/state/software-version" + ocpath_constraint: { + platform_type: "OPERATING_SYSTEM" + } + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-as" + } + version: "2.5.0" +} +ocrpcs: { + oc_protocols: { + key: "gnmi" + value: { + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" + version: "0.10.0" + } + } + oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + version: "0.3.0" + } + } +} diff --git a/tools/nosimage/example/valid_example_nosimageprofile.textproto b/tools/nosimage/example/valid_example_nosimageprofile.textproto new file mode 100644 index 00000000000..42f544e5054 --- /dev/null +++ b/tools/nosimage/example/valid_example_nosimageprofile.textproto @@ -0,0 +1,60 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/nosimage.proto +# proto-message: NOSImageProfile +# txtpbfmt: expand_all_children +# txtpbfmt: sort_repeated_fields_by_content +vendor_id: OPENCONFIG +nos: "lemming" +software_version: "7a1cb734c83f0d9ba5b273f920bc002ad0056178" +hardware_name: "lemming" +release_date: { + seconds: 1700180400 +} +ocpaths: { + ocpaths: { + name: "/interfaces/interface/config/name" + } + ocpaths: { + name: "/components/component/state/location" + ocpath_constraint: { + platform_type: "PORT" + } + } + ocpaths: { + name: "/components/component/state/serial-no" + ocpath_constraint: { + platform_type: "STORAGE" + } + } + ocpaths: { + name: "/components/component/state/software-version" + ocpath_constraint: { + platform_type: "OPERATING_SYSTEM" + } + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-as" + } + version: "2.5.0" +} +ocrpcs: { + oc_protocols: { + key: "gnmi" + value: { + method_name: "gnmi.gNMI.Set" + method_name: "gnmi.gNMI.Subscribe" + version: "0.10.0" + } + } + oc_protocols: { + key: "gnoi" + value: { + method_name: "gnoi.bgp.BGP.ClearBGPNeighbor" + method_name: "gnoi.healthz.Healthz.Acknowledge" + method_name: "gnoi.healthz.Healthz.Artifact" + method_name: "gnoi.healthz.Healthz.Check" + method_name: "gnoi.healthz.Healthz.Get" + method_name: "gnoi.healthz.Healthz.List" + version: "0.3.0" + } + } +} diff --git a/tools/nosimage/validate/validate.go b/tools/nosimage/validate/validate.go index aed6814e129..76115528626 100644 --- a/tools/nosimage/validate/validate.go +++ b/tools/nosimage/validate/validate.go @@ -18,12 +18,11 @@ package main import ( "flag" "fmt" - "io" "os" - "os/exec" - "path/filepath" + log "github.com/golang/glog" "github.com/openconfig/featureprofiles/tools/internal/ocpaths" + "github.com/openconfig/featureprofiles/tools/internal/ocrpcs" "google.golang.org/protobuf/encoding/prototext" npb "github.com/openconfig/featureprofiles/proto/nosimage_go_proto" @@ -44,7 +43,7 @@ func New(fs *flag.FlagSet) *Config { } fs.StringVar(&c.FilePath, "file", "", "txtpb file containing an instance of nosimage.proto data") // TODO(wenovus): Consider allowing using a manual location to avoid git clone. - fs.StringVar(&c.DownloadPath, "download-path", "./", "path into which to download OpenConfig GitHub repos for validation") + fs.StringVar(&c.DownloadPath, "download-path", "./tmp", "path into which to download OpenConfig GitHub repos for validation") return c } @@ -57,27 +56,6 @@ func init() { config = New(nil) } -func clonePublicRepo(downloadPath, branch string) (string, error) { - if downloadPath == "" { - return "", fmt.Errorf("must provide download path") - } - publicPath := filepath.Join(config.DownloadPath, "public") - - cmd := exec.Command("git", "clone", "-b", branch, "--single-branch", "--depth", "1", "git@github.com:openconfig/public.git", publicPath) - stderr, err := cmd.StderrPipe() - if err != nil { - return "", err - } - if err := cmd.Start(); err != nil { - return "", fmt.Errorf("failed to clone public repo: %v, command failed to start: %q", err, cmd.String()) - } - stderrOutput, _ := io.ReadAll(stderr) - if err := cmd.Wait(); err != nil { - return "", fmt.Errorf("failed to clone public repo: %v, command failed during execution: %q\n%s", err, cmd.String(), stderrOutput) - } - return publicPath, nil -} - func unmarshalFile(filePath string) (*npb.NOSImageProfile, error) { if filePath == "" { return nil, fmt.Errorf("must provide non-empty file path to read from") @@ -91,7 +69,6 @@ func unmarshalFile(filePath string) (*npb.NOSImageProfile, error) { return nil, err } return profile, nil - } func main() { @@ -103,16 +80,47 @@ func main() { os.Exit(1) } - publicPath, err := clonePublicRepo(config.DownloadPath, "v"+profile.Ocpaths.GetVersion()) + if profile.GetSoftwareVersion() == "" { + log.Exitln("Software version must be specified") + } + + if profile.GetHardwareName() == "" { + log.Exitln("HW name must be specified") + } + + if err := os.MkdirAll(config.DownloadPath, 0750); err != nil { + fmt.Println(fmt.Errorf("cannot create download path directory: %v", config.DownloadPath)) + } + + ocReleaseTag := "" + if profile.Ocpaths.GetVersion() != "" { + ocReleaseTag = "v" + profile.Ocpaths.GetVersion() + } + publicPath, err := ocpaths.ClonePublicRepo(config.DownloadPath, ocReleaseTag) if err != nil { fmt.Println(err) os.Exit(1) } - paths, err := ocpaths.ValidatePaths(profile.GetOcpaths().GetOcpaths(), publicPath) + var hasErr bool + paths, invalidPaths, err := ocpaths.ValidatePaths(profile.GetOcpaths().GetOcpaths(), publicPath) if err != nil { + fmt.Printf("profile contains %d invalid OCPaths:\n%v", len(invalidPaths), err) fmt.Println(err) + hasErr = true + } else { + fmt.Printf("profile contains %d valid OCPaths\n", len(paths)) + } + + rpcValidCount, err := ocrpcs.ValidateRPCs(config.DownloadPath, profile.GetOcrpcs().GetOcProtocols()) + if err != nil { + fmt.Println(err) + hasErr = true + } else { + fmt.Printf("profile contains %d valid OCRPCs\n", rpcValidCount) + } + + if hasErr { os.Exit(1) } - fmt.Printf("profile contains %d valid OCPaths\n", len(paths)) } diff --git a/tools/sort_testregistry/sort_testregistry.go b/tools/sort_testregistry/sort_testregistry.go new file mode 100644 index 00000000000..fa271a1daa3 --- /dev/null +++ b/tools/sort_testregistry/sort_testregistry.go @@ -0,0 +1,93 @@ +// Binary sort_registry sorts the test registry lexically such that it is easier +// for humans to add to the file and find the next available ID. It can be run +// by running: +// +// go run tools/sort_testregistry/sort_testregistry.go +// +// prior to submitting. +package main + +import ( + "bytes" + "flag" + "os" + "sort" + + log "github.com/golang/glog" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" + + tpb "github.com/openconfig/featureprofiles/proto/testregistry_go_proto" +) + +var ( + file = flag.String("file", "testregistry.textproto", "input file to read registry from") +) + +func main() { + flag.Parse() + r := &tpb.TestRegistry{} + + f, err := os.ReadFile(*file) + if err != nil { + log.Exitf("cannot read input, err: %v", err) + } + if err := prototext.Unmarshal(f, r); err != nil { + log.Exitf("invalid registry input, err: %v", err) + } + + ids := []string{} + tests := map[string][]*tpb.Test{} + for _, t := range r.GetTest() { + if _, ok := tests[t.GetId()]; !ok { + tests[t.GetId()] = []*tpb.Test{} + ids = append(ids, t.GetId()) + } + + // Deduplicate identical entries. + var skip bool + for _, existing := range tests[t.GetId()] { + if proto.Equal(existing, t) { + log.Infof("skipping duplicate for %s", t.GetId()) + skip = true + } + } + if skip { + continue + } + + tests[t.GetId()] = append(tests[t.GetId()], t) + + } + + n := &tpb.TestRegistry{ + Name: r.GetName(), + Test: []*tpb.Test{}, + } + + sort.Strings(ids) + for _, i := range ids { + for _, tc := range tests[i] { + log.Infof("appending %s to %s", tc.GetId(), i) + n.Test = append(n.Test, tc) + } + } + mo := &prototext.MarshalOptions{ + Multiline: true, + Indent: " ", + } + + s, err := mo.Marshal(n) + if err != nil { + log.Exitf("cannot marshal proceessed proto, err: %v", err) + } + + b := &bytes.Buffer{} + b.WriteString("# proto-file: /proto/testregistry.proto\n") + b.WriteString("# proto-message: TestRegistry\n\n") + b.Write(s) + + if err := os.WriteFile(*file, b.Bytes(), 0644); err != nil { + log.Exitf("cannot write out processed file, err: %v", err) + } +} diff --git a/tools/validate_readme_spec/testdata/invalid_all_empty.md b/tools/validate_readme_spec/testdata/invalid_all_empty.md new file mode 100644 index 00000000000..fafa429c9fc --- /dev/null +++ b/tools/validate_readme_spec/testdata/invalid_all_empty.md @@ -0,0 +1,6 @@ +## OpenConfig Path and RPC Coverage + +```yaml +paths: +rpcs: +``` diff --git a/tools/validate_readme_spec/testdata/invalid_empty_rpcs.md b/tools/validate_readme_spec/testdata/invalid_empty_rpcs.md new file mode 100644 index 00000000000..e52afb6b707 --- /dev/null +++ b/tools/validate_readme_spec/testdata/invalid_empty_rpcs.md @@ -0,0 +1,7 @@ +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /interfaces/interface/config/name: +rpcs: +``` diff --git a/tools/validate_readme_spec/testdata/invalid_heading.md b/tools/validate_readme_spec/testdata/invalid_heading.md new file mode 100644 index 00000000000..bcaf33c72bb --- /dev/null +++ b/tools/validate_readme_spec/testdata/invalid_heading.md @@ -0,0 +1,6 @@ +## Hello world + +```yaml +paths: +rpcs: +``` diff --git a/tools/validate_readme_spec/testdata/invalid_path.md b/tools/validate_readme_spec/testdata/invalid_path.md new file mode 100644 index 00000000000..f5f7a93ed2f --- /dev/null +++ b/tools/validate_readme_spec/testdata/invalid_path.md @@ -0,0 +1,9 @@ +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /interfaces/interface/config: +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/tools/validate_readme_spec/testdata/valid_empty_paths.md b/tools/validate_readme_spec/testdata/valid_empty_paths.md new file mode 100644 index 00000000000..3dfaf3ef72b --- /dev/null +++ b/tools/validate_readme_spec/testdata/valid_empty_paths.md @@ -0,0 +1,7 @@ +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/tools/validate_readme_spec/validate_readme_spec.go b/tools/validate_readme_spec/validate_readme_spec.go new file mode 100644 index 00000000000..39ad92b4d22 --- /dev/null +++ b/tools/validate_readme_spec/validate_readme_spec.go @@ -0,0 +1,180 @@ +// 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_spec validates Paths and RPCs listed by MarkDown +// (READMEs) against the most recent repository states in +// github.com/openconfig. +// +// Note: For `rpcs`, only the RPC name and methods are validated. Any +// attributes defined below RPC methods (e.g. union_replace) are not validated. +package main + +import ( + goflag "flag" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + log "github.com/golang/glog" + "github.com/openconfig/featureprofiles/tools/internal/fpciutil" + "github.com/openconfig/featureprofiles/tools/internal/mdocspec" + "github.com/openconfig/featureprofiles/tools/internal/ocpaths" + "github.com/openconfig/featureprofiles/tools/internal/ocrpcs" + flag "github.com/spf13/pflag" + "golang.org/x/exp/maps" +) + +// Config is the set of flags for this binary. +type Config struct { + DownloadPath string + 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.DownloadPath, "download-path", "./tmp", "path into which to download OpenConfig GitHub repos for validation") + 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 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.") + } + + if err := os.MkdirAll(config.DownloadPath, 0750); err != nil { + fmt.Println(fmt.Errorf("cannot create download path directory: %v", config.DownloadPath)) + } + publicPath, err := ocpaths.ClonePublicRepo(config.DownloadPath, "") + if err != nil { + log.Exit(err) + } + + 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) + } + ocPaths, ocRPCs, err := mdocspec.Parse(b) + if err != nil { + log.Errorf("file %v: %v", file, err) + erredFiles[file] = struct{}{} + continue + } + + paths, invalidPaths, err := ocpaths.ValidatePaths(ocPaths.GetOcpaths(), publicPath) + if err != nil { + log.Errorf("%q contains %d invalid OCPaths:\n%v", file, len(invalidPaths), err) + erredFiles[file] = struct{}{} + } else { + log.Infof("%q contains %d valid OCPaths\n", file, len(paths)) + } + + rpcValidCount, err := ocrpcs.ValidateRPCs(config.DownloadPath, ocRPCs.GetOcProtocols()) + if err != nil { + log.Errorf("%q contains invalid RPCs: %v", file, err) + erredFiles[file] = struct{}{} + } else { + log.Infof("%q contains %d valid OCRPCs\n", file, rpcValidCount) + } + } + 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_spec/validate_readme_spec_test.sh b/tools/validate_readme_spec/validate_readme_spec_test.sh new file mode 100755 index 00000000000..c13ac7bd1e6 --- /dev/null +++ b/tools/validate_readme_spec/validate_readme_spec_test.sh @@ -0,0 +1,44 @@ +# 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. + +#!/bin/bash + +go install ./ + +filename=invalid_all_empty.md +if validate_readme_spec --alsologtostderr testdata/"${filename}"; then + echo "Validation passed, but failure expected" + exit 1 +fi +filename=invalid_empty_rpcs.md +if validate_readme_spec --alsologtostderr testdata/"${filename}"; then + echo "Validation passed, but failure expected" + exit 1 +fi +filename=invalid_heading.md +if validate_readme_spec --alsologtostderr testdata/"${filename}"; then + echo "Validation passed, but failure expected" + exit 1 +fi +filename=invalid_path.md +if validate_readme_spec --alsologtostderr testdata/"${filename}"; then + echo "Validation passed, but failure expected" + exit 1 +fi +filename=valid_empty_paths.md +if ! validate_readme_spec --alsologtostderr testdata/"${filename}"; then + echo "Validation failed, but pass expected" + exit 1 +fi +echo "PASS" diff --git a/tools/wikidoc/wikidoc.go b/tools/wikidoc/wikidoc.go index 7a50c9f7859..fcb0282e5d5 100644 --- a/tools/wikidoc/wikidoc.go +++ b/tools/wikidoc/wikidoc.go @@ -17,6 +17,7 @@ package main import ( + "fmt" "io/fs" "os" "path/filepath" @@ -143,7 +144,7 @@ func fetchTestDocs(rootPath string) ([]testDoc, error) { } md := new(mpb.Metadata) if err := prototext.Unmarshal(bytes, md); err != nil { - return err + return fmt.Errorf("cannot unmarshal %s: %v", path, err) } docMap[md.GetUuid()] = testDoc{ Name: filepath.Base(filepath.Dir(path)), diff --git a/topologies/atedut_8.binding b/topologies/atedut_8.binding new file mode 100644 index 00000000000..ac0ccba27b2 --- /dev/null +++ b/topologies/atedut_8.binding @@ -0,0 +1,117 @@ +# proto-file: github.com/openconfig/featureprofiles/blob/main/topologies/proto/binding.proto +# proto-message: openconfig.testing.Binding + +# This is an example static binding that demonstrates how to specify +# options to be used in conjunction with the atedut_*.testbed +# testbeds. + +# These options are inherited throughout the entire binding for both the +# DUT and the ATE, unless overridden by a specific device or protocol. +options { + username: "username" + password: "password" +} + +duts { + id: "dut" + name: "dut-hostname" # Change this to the device hostname. + + # Options inherited by all protocols on this device unless + # overridden by individual protocols. Remove if not needed. + options { + insecure: true + } + + # Options specific to gNMI. Remove if not needed. + gnmi { + target: "dut-proxy-hostname:6030" + } + + # Options specific to gNOI + gnoi { + max_recv_msg_size: 40000000 + } + + # Before this binding can be used with a topology, add ports mapping + # from its topology ID to the actual port name on the device. + ports { + id: "port1" + name: "Ethernet1/1" # Change this to the actual port name. + } + ports { + id: "port2" + name: "Ethernet2/1" # Change this to the actual port name. + } + ports { + id: "port3" + name: "Ethernet3/1" # Change this to the actual port name. + } + ports { + id: "port4" + name: "Ethernet4/1" # Change this to the actual port name. + } + ports { + id: "port5" + name: "Ethernet5/1" # Change this to the actual port name. + } + ports { + id: "port6" + name: "Ethernet6/1" # Change this to the actual port name. + } + ports { + id: "port7" + name: "Ethernet7/1" # Change this to the actual port name. + } + ports { + id: "port8" + name: "Ethernet8/1" # Change this to the actual port name. + } +} + +ates { + id: "ate" + name: "ate-hostname" # Change this to the Ixia chassis name. + + # Options specific to the IxNetwork API. Remove if not needed. + ixnetwork { + # Change this to the Web UI hostname, if it differs from the Ixia + # chassis name. + target: "ixia-hostname" + skip_verify: true + } + + # Before this binding can be used with a topology, add ports mapping + # from its topology ID to the actual port name on the device. + ports { + id: "port1" + name: "1/1" # Change this to the actual port name. + } + ports { + id: "port2" + name: "1/2" # Change this to the actual port name. + } + ports { + id: "port3" + name: "1/3" # Change this to the actual port name. + } + ports { + id: "port4" + name: "1/4" # Change this to the actual port name. + } + ports { + id: "port5" + name: "1/5" # Change this to the actual port name. + } + ports { + id: "port6" + name: "1/6" # Change this to the actual port name. + } + ports { + id: "port7" + name: "1/7" # Change this to the actual port name. + } + ports { + id: "port8" + name: "1/8" # Change this to the actual port name. + } +} diff --git a/topologies/atedut_8.testbed b/topologies/atedut_8.testbed new file mode 100644 index 00000000000..f7ba2158671 --- /dev/null +++ b/topologies/atedut_8.testbed @@ -0,0 +1,100 @@ +# proto-file: github.com/openconfig/ondatra/blob/main/proto/testbed.proto +# proto-message: ondatra.Testbed + +# This testbed provides a DUT and ATE with 8 links between them. + +duts { + id: "dut" + ports { + id: "port1" + } + ports { + id: "port2" + } + ports { + id: "port3" + } + ports { + id: "port4" + } + ports { + id: "port5" + } + ports { + id: "port6" + } + ports { + id: "port7" + } + ports { + id: "port8" + } +} + +ates { + id: "ate" + ports { + id: "port1" + } + ports { + id: "port2" + } + ports { + id: "port3" + } + ports { + id: "port4" + } + ports { + id: "port5" + } + ports { + id: "port6" + } + ports { + id: "port7" + } + ports { + id: "port8" + } +} + +links { + a: "dut:port1" + b: "ate:port1" +} + +links { + a: "dut:port2" + b: "ate:port2" +} + +links { + a: "dut:port3" + b: "ate:port3" +} + +links { + a: "dut:port4" + b: "ate:port4" +} + +links { + a: "dut:port5" + b: "ate:port5" +} + +links { + a: "dut:port6" + b: "ate:port6" +} + +links { + a: "dut:port7" + b: "ate:port7" +} + +links { + a: "dut:port8" + b: "ate:port8" +} diff --git a/topologies/binding/binding.go b/topologies/binding/binding.go index e419b75d45a..d47f7ce18eb 100644 --- a/topologies/binding/binding.go +++ b/topologies/binding/binding.go @@ -16,22 +16,38 @@ package binding import ( "context" + "crypto/tls" + "crypto/x509" "errors" "fmt" - "strings" + "net" + "net/http" + "os" "time" + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" "github.com/open-traffic-generator/snappi/gosnappi" - "github.com/openconfig/gnoigo" - "github.com/openconfig/ondatra/binding" - "github.com/openconfig/ondatra/binding/ixweb" - "google.golang.org/grpc" - bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/gnoigo" grpb "github.com/openconfig/gribi/v1/proto/service" + "github.com/openconfig/ondatra/binding" + "github.com/openconfig/ondatra/binding/grpcutil" + "github.com/openconfig/ondatra/binding/introspect" + "github.com/openconfig/ondatra/binding/ixweb" opb "github.com/openconfig/ondatra/proto" p4pb "github.com/p4lang/p4runtime/go/p4/v1" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/knownhosts" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +var ( + // To be stubbed out by unit tests. + grpcDialContextFn = grpc.NewClient + gosnappiNewAPIFn = gosnappi.NewApi ) // staticBind implements the binding.Binding interface by creating a @@ -44,12 +60,16 @@ type staticBind struct { pushConfig bool } +var _ binding.Binding = (*staticBind)(nil) + type staticDUT struct { *binding.AbstractDUT r resolver dev *bindpb.Device } +var _ introspect.Introspector = (*staticDUT)(nil) + type staticATE struct { *binding.AbstractATE r resolver @@ -58,7 +78,7 @@ type staticATE struct { ixsess *ixweb.Session } -var _ = binding.Binding(&staticBind{}) +var _ introspect.Introspector = (*staticATE)(nil) const resvID = "STATIC" @@ -69,7 +89,7 @@ func (b *staticBind) Reserve(ctx context.Context, tb *opb.Testbed, runTime, wait if b.resv != nil { return nil, fmt.Errorf("only one reservation is allowed") } - resv, err := reservation(tb, b.r) + resv, err := reservation(ctx, tb, b.r) if err != nil { return nil, err } @@ -114,24 +134,29 @@ func (b *staticBind) reset(ctx context.Context) error { return nil } +func (d *staticDUT) Dialer(svc introspect.Service) (*introspect.Dialer, error) { + params, ok := dutSvcParams[svc] + if !ok { + return nil, fmt.Errorf("no known DUT service %v", svc) + } + bopts := d.r.grpc(d.dev, params) + return makeDialer(params, bopts) +} + func (d *staticDUT) reset(ctx context.Context) error { // Each of the individual reset functions should be no-op if the reset action is not // requested. - if err := resetCLI(ctx, d.dev, d.r); err != nil { + if err := resetCLI(ctx, d); err != nil { return err } - if err := resetGNMI(ctx, d.dev, d.r); err != nil { + if err := resetGNMI(ctx, d); err != nil { return err } - return resetGRIBI(ctx, d.dev, d.r) + return resetGRIBI(ctx, d) } func (d *staticDUT) DialGNMI(ctx context.Context, opts ...grpc.DialOption) (gpb.GNMIClient, error) { - dialer, err := d.r.gnmi(d.Name()) - if err != nil { - return nil, err - } - conn, err := dialer.dialGRPC(ctx, opts...) + conn, err := dialConn(ctx, d, introspect.GNMI, opts) if err != nil { return nil, err } @@ -139,11 +164,7 @@ func (d *staticDUT) DialGNMI(ctx context.Context, opts ...grpc.DialOption) (gpb. } func (d *staticDUT) DialGNOI(ctx context.Context, opts ...grpc.DialOption) (gnoigo.Clients, error) { - dialer, err := d.r.gnoi(d.Name()) - if err != nil { - return nil, err - } - conn, err := dialer.dialGRPC(ctx, opts...) + conn, err := dialConn(ctx, d, introspect.GNOI, opts) if err != nil { return nil, err } @@ -151,11 +172,7 @@ func (d *staticDUT) DialGNOI(ctx context.Context, opts ...grpc.DialOption) (gnoi } func (d *staticDUT) DialGNSI(ctx context.Context, opts ...grpc.DialOption) (binding.GNSIClients, error) { - dialer, err := d.r.gnsi(d.Name()) - if err != nil { - return nil, err - } - conn, err := dialer.dialGRPC(ctx, opts...) + conn, err := dialConn(ctx, d, introspect.GNSI, opts) if err != nil { return nil, err } @@ -163,11 +180,7 @@ func (d *staticDUT) DialGNSI(ctx context.Context, opts ...grpc.DialOption) (bind } func (d *staticDUT) DialGRIBI(ctx context.Context, opts ...grpc.DialOption) (grpb.GRIBIClient, error) { - dialer, err := d.r.gribi(d.Name()) - if err != nil { - return nil, err - } - conn, err := dialer.dialGRPC(ctx, opts...) + conn, err := dialConn(ctx, d, introspect.GRIBI, opts) if err != nil { return nil, err } @@ -175,254 +188,214 @@ func (d *staticDUT) DialGRIBI(ctx context.Context, opts ...grpc.DialOption) (grp } func (d *staticDUT) DialP4RT(ctx context.Context, opts ...grpc.DialOption) (p4pb.P4RuntimeClient, error) { - dialer, err := d.r.p4rt(d.Name()) - if err != nil { - return nil, err - } - conn, err := dialer.dialGRPC(ctx, opts...) + conn, err := dialConn(ctx, d, introspect.P4RT, opts) if err != nil { return nil, err } return p4pb.NewP4RuntimeClient(conn), nil } -func (d *staticDUT) DialCLI(_ context.Context) (binding.CLIClient, error) { - dialer, err := d.r.ssh(d.Name()) - if err != nil { - return nil, err +func (d *staticDUT) DialCLI(context.Context) (binding.CLIClient, error) { + sshOpts := d.r.ssh(d.dev) + c := &ssh.ClientConfig{ + User: sshOpts.Username, + Auth: []ssh.AuthMethod{ + ssh.Password(sshOpts.Password), + ssh.KeyboardInteractive(sshInteractive(sshOpts.Password)), + }, + } + if sshOpts.SkipVerify { + c.HostKeyCallback = ssh.InsecureIgnoreHostKey() + } else { + cb, err := knownHostsCallback() + if err != nil { + return nil, err + } + c.HostKeyCallback = cb } - sc, err := dialer.dialSSH() + sc, err := ssh.Dial("tcp", sshOpts.Target, c) if err != nil { return nil, err } return newCLI(sc) } -func (a *staticATE) DialGNMI(ctx context.Context, opts ...grpc.DialOption) (gpb.GNMIClient, error) { - dialer, err := a.r.ateGNMI(a.Name()) - if err != nil { - return nil, err +// For every question asked in an interactive login ssh session, set the answer to user password. +func sshInteractive(password string) ssh.KeyboardInteractiveChallenge { + return func(_, _ string, questions []string, _ []bool) ([]string, error) { + answers := make([]string, len(questions)) + for n := range questions { + answers[n] = password + } + return answers, nil } - conn, err := dialer.dialGRPC(ctx, opts...) +} + +func (a *staticATE) Dialer(svc introspect.Service) (*introspect.Dialer, error) { + params, ok := ateSvcParams[svc] + if !ok { + return nil, fmt.Errorf("no known ATE service %v", svc) + } + bopts := a.r.grpc(a.dev, params) + return makeDialer(params, bopts) +} + +func (a *staticATE) DialGNMI(ctx context.Context, opts ...grpc.DialOption) (gpb.GNMIClient, error) { + conn, err := dialConn(ctx, a, introspect.GNMI, opts) if err != nil { return nil, err } return gpb.NewGNMIClient(conn), nil } -func (a *staticATE) DialOTG(ctx context.Context, opts ...grpc.DialOption) (gosnappi.GosnappiApi, error) { +func (a *staticATE) DialOTG(ctx context.Context, opts ...grpc.DialOption) (gosnappi.Api, error) { if a.dev.Otg == nil { return nil, fmt.Errorf("otg must be configured in ATE binding to run OTG test") } - dialer, err := a.r.ateOtg(a.Name()) - if err != nil { - return nil, err - } - conn, err := dialer.dialGRPC(ctx, opts...) + conn, err := dialConn(ctx, a, introspect.OTG, opts) if err != nil { return nil, err } - api := gosnappi.NewApi() - grpcTransport := api.NewGrpcTransport().SetClientConnection(conn) - if dialer.Timeout != 0 { - grpcTransport.SetRequestTimeout(time.Duration(dialer.Timeout) * time.Second) + api := gosnappiNewAPIFn() + transport := api.NewGrpcTransport().SetClientConnection(conn) + if timeout := a.r.grpc(a.dev, ateSvcParams[introspect.OTG]).Timeout; timeout != 0 { + transport.SetRequestTimeout(time.Duration(timeout) * time.Second) } return api, nil } func (a *staticATE) DialIxNetwork(ctx context.Context) (*binding.IxNetwork, error) { - dialer, err := a.r.ixnetwork(a.Name()) - if err != nil { - return nil, err - } - ixs, err := a.ixSession(ctx, dialer) + bopts := a.r.ixnetwork(a.dev) + ixs, err := a.ixSession(ctx, bopts) if err != nil { return nil, err } return &binding.IxNetwork{Session: ixs}, nil } -// allerrors implements the error interface and will accumulate and -// report all errors. -type allerrors []error +func reservation(ctx context.Context, tb *opb.Testbed, r resolver) (*binding.Reservation, error) { + if r.Dynamic { + return dynamicReservation(ctx, tb, r) + } + resv, errs := staticReservation(tb, r) + return resv, errors.Join(errs...) +} -var _ = error(allerrors{}) +func staticReservation(tb *opb.Testbed, r resolver) (*binding.Reservation, []error) { + var errs []error -func (errs allerrors) Error() string { - // Shortcut for no error or a single error. - switch len(errs) { - case 0: - return "" - case 1: - return errs[0].Error() + bduts := make(map[string]*bindpb.Device) + for _, bdut := range r.Duts { + bduts[bdut.Id] = bdut } - var b strings.Builder - fmt.Fprintf(&b, "%d errors occurred:", len(errs)) - for _, err := range errs { - // Replace indentation for proper nesting. - fmt.Fprintf(&b, "\n * %s", strings.ReplaceAll(err.Error(), "\n", "\n ")) + bates := make(map[string]*bindpb.Device) + for _, bate := range r.Ates { + bates[bate.Id] = bate } - return b.String() -} - -func reservation(tb *opb.Testbed, r resolver) (*binding.Reservation, error) { - var errs allerrors duts := make(map[string]binding.DUT) for _, tdut := range tb.Duts { - bdut := r.dutByID(tdut.Id) - if bdut == nil { + bdut, ok := bduts[tdut.Id] + if !ok { errs = append(errs, fmt.Errorf("missing binding for DUT %q", tdut.Id)) continue } - d, err := dims(tdut, bdut) - if err != nil { - errs = append(errs, fmt.Errorf("error binding DUT %q: %w", tdut.Id, err)) - duts[tdut.Id] = nil // mark it "found" - continue - } + dims, dimErrs := staticDims(tdut, bdut) + errs = append(errs, dimErrs...) duts[tdut.Id] = &staticDUT{ - AbstractDUT: &binding.AbstractDUT{Dims: d}, + AbstractDUT: &binding.AbstractDUT{Dims: dims}, r: r, dev: bdut, } } - for _, bdut := range r.Duts { - if _, ok := duts[bdut.Id]; !ok { - errs = append(errs, fmt.Errorf("binding DUT %q not found in testbed", bdut.Id)) - } - } ates := make(map[string]binding.ATE) for _, tate := range tb.Ates { - bate := r.ateByID(tate.Id) - if bate == nil { + bate, ok := bates[tate.Id] + if !ok { errs = append(errs, fmt.Errorf("missing binding for ATE %q", tate.Id)) continue } - d, err := dims(tate, bate) - if err != nil { - errs = append(errs, fmt.Errorf("error binding ATE %q: %w", tate.Id, err)) - ates[tate.Id] = nil // mark it "found" - continue - } + dims, dimErrs := staticDims(tate, bate) + errs = append(errs, dimErrs...) ates[tate.Id] = &staticATE{ - AbstractATE: &binding.AbstractATE{Dims: d}, + AbstractATE: &binding.AbstractATE{Dims: dims}, r: r, dev: bate, } } - for _, bate := range r.Ates { - if _, ok := ates[bate.Id]; !ok { - errs = append(errs, fmt.Errorf("binding ATE %q not found in testbed", bate.Id)) - } - } - if errs != nil { - return nil, errs - } - - resv := &binding.Reservation{ + return &binding.Reservation{ DUTs: duts, ATEs: ates, - } - return resv, nil + }, errs } -func dims(td *opb.Device, bd *bindpb.Device) (*binding.Dims, error) { - portmap, err := ports(td.Ports, bd.Ports) - if err != nil { - return nil, err +func staticDims(td *opb.Device, bd *bindpb.Device) (*binding.Dims, []error) { + var errs []error + + // Check that the bound device matches the testbed device. + if tdVendor := td.GetVendor(); tdVendor != opb.Device_VENDOR_UNSPECIFIED && bd.Vendor != tdVendor { + errs = append(errs, fmt.Errorf("binding vendor %v and testbed vendor %v do not match", bd.Vendor, tdVendor)) } - dims := &binding.Dims{ + if tdHardwareModel := td.GetHardwareModel(); tdHardwareModel != "" && bd.HardwareModel != tdHardwareModel { + errs = append(errs, fmt.Errorf("binding hardware model %v and testbed hardware model %v do not match", bd.HardwareModel, tdHardwareModel)) + } + if tdSoftwareVersion := td.GetSoftwareVersion(); tdSoftwareVersion != "" && bd.SoftwareVersion != tdSoftwareVersion { + errs = append(errs, fmt.Errorf("binding software version %v and testbed software version %v do not match", bd.SoftwareVersion, tdSoftwareVersion)) + } + + portmap, portErrs := staticPorts(td.Ports, bd) + errs = append(errs, portErrs...) + + return &binding.Dims{ Name: bd.Name, Vendor: bd.GetVendor(), HardwareModel: bd.GetHardwareModel(), SoftwareVersion: bd.GetSoftwareVersion(), Ports: portmap, - } - // Populate empty binding dimensions with testbed dimensions. - // TODO(prinikasn): Remove testbed override once all vendors are using binding dimensions exclusively. - if tdVendor := td.GetVendor(); tdVendor != opb.Device_VENDOR_UNSPECIFIED { - if dims.Vendor != opb.Device_VENDOR_UNSPECIFIED && dims.Vendor != tdVendor { - return nil, fmt.Errorf("binding vendor %v and testbed vendor %v do not match", dims.Vendor, tdVendor) - } - dims.Vendor = tdVendor - } - if tdHardwareModel := td.GetHardwareModel(); tdHardwareModel != "" { - if dims.HardwareModel != "" && dims.HardwareModel != tdHardwareModel { - return nil, fmt.Errorf("binding hardware model %v and testbed hardware model %v do not match", dims.HardwareModel, tdHardwareModel) - } - dims.HardwareModel = tdHardwareModel - } - if tdSoftwareVersion := td.GetSoftwareVersion(); tdSoftwareVersion != "" { - if dims.SoftwareVersion != "" && dims.SoftwareVersion != tdSoftwareVersion { - return nil, fmt.Errorf("binding software version %v and testbed software version %v do not match", dims.SoftwareVersion, tdSoftwareVersion) - } - dims.SoftwareVersion = tdSoftwareVersion - } - - return dims, nil + }, errs } -func ports(tports []*opb.Port, bports []*bindpb.Port) (map[string]*binding.Port, error) { - var errs allerrors +func staticPorts(tports []*opb.Port, bd *bindpb.Device) (map[string]*binding.Port, []error) { + var errs []error + + bports := make(map[string]*bindpb.Port) + for _, bport := range bd.Ports { + bports[bport.Id] = bport + } portmap := make(map[string]*binding.Port) for _, tport := range tports { - portmap[tport.Id] = &binding.Port{ - Speed: tport.Speed, + bport, ok := bports[tport.Id] + if !ok { + errs = append(errs, fmt.Errorf("missing binding for port %q on %q", tport.Id, bd.Id)) + continue } - } - for _, bport := range bports { - if p, ok := portmap[bport.Id]; ok { - p.Name = bport.Name - // If port speed is empty populate from testbed ports. - if bport.Speed != opb.Port_SPEED_UNSPECIFIED { - if p.Speed != opb.Port_SPEED_UNSPECIFIED && p.Speed != bport.Speed { - return nil, fmt.Errorf("binding port speed %v and testbed port speed %v do not match", bport.Speed, p.Speed) - } - p.Speed = bport.Speed - } - // Populate the PMD type if configured. - if bport.Pmd != opb.Port_PMD_UNSPECIFIED { - if p.PMD != opb.Port_PMD_UNSPECIFIED && p.PMD != bport.Pmd { - return nil, fmt.Errorf("binding port PMD type %v and testbed port PMD type %v do not match", bport.Pmd, p.PMD) - } - p.PMD = bport.Pmd - } + if tport.Speed != opb.Port_SPEED_UNSPECIFIED && tport.Speed != bport.Speed { + errs = append(errs, fmt.Errorf("binding port speed %v and testbed port speed %v do not match", bport.Speed, tport.Speed)) } - } - for id, p := range portmap { - if p.Name == "" { - errs = append(errs, fmt.Errorf("testbed port %q is missing in binding", id)) + if tport.GetPmd() != opb.Port_PMD_UNSPECIFIED && tport.GetPmd() != bport.Pmd { + errs = append(errs, fmt.Errorf("binding port PMD %v and testbed port PMD %v do not match", bport.Pmd, tport.GetPmd())) + } + portmap[tport.Id] = &binding.Port{ + Name: bport.Name, + PMD: bport.Pmd, + Speed: bport.Speed, } } - - if errs != nil { - return nil, errs - } - return portmap, nil + return portmap, errs } func (b *staticBind) reserveIxSessions(ctx context.Context) error { ates := b.resv.ATEs for _, ate := range ates { - - bate := b.r.ateByName(ate.Name()) - if bate == nil { - return fmt.Errorf("missing binding for ATE %q", bate.Id) - } - if bate.Ixnetwork == nil { + a := ate.(*staticATE) + if a.dev.Ixnetwork == nil { continue } - - dialer, err := b.r.ixnetwork(ate.Name()) - if err != nil { - return err - } - if _, err := ate.(*staticATE).ixSession(ctx, dialer); err != nil { + if _, err := a.DialIxNetwork(ctx); err != nil { return err } } @@ -431,11 +404,8 @@ func (b *staticBind) reserveIxSessions(ctx context.Context) error { func (b *staticBind) releaseIxSessions(ctx context.Context) error { for _, ate := range b.resv.ATEs { - dialer, err := b.r.ixnetwork(ate.Name()) - if err != nil { - return err - } sate := ate.(*staticATE) + dialer := b.r.ixnetwork(sate.dev) if sate.ixsess != nil && dialer.SessionId == 0 { if err := sate.ixweb.IxNetwork().DeleteSession(ctx, sate.ixsess.ID()); err != nil { return err @@ -445,9 +415,9 @@ func (b *staticBind) releaseIxSessions(ctx context.Context) error { return nil } -func (a *staticATE) ixWeb(ctx context.Context, d dialer) (*ixweb.IxWeb, error) { +func (a *staticATE) ixWeb(ctx context.Context, opts *bindpb.Options) (*ixweb.IxWeb, error) { if a.ixweb == nil { - ixw, err := d.newIxWebClient(ctx) + ixw, err := newIxWebClient(ctx, opts) if err != nil { return nil, err } @@ -456,14 +426,34 @@ func (a *staticATE) ixWeb(ctx context.Context, d dialer) (*ixweb.IxWeb, error) { return a.ixweb, nil } -func (a *staticATE) ixSession(ctx context.Context, d dialer) (*ixweb.Session, error) { +func newIxWebClient(ctx context.Context, opts *bindpb.Options) (*ixweb.IxWeb, error) { + tr := &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + } + if opts.SkipVerify { + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } + hc := &http.Client{Transport: tr} + username := opts.GetUsername() + password := opts.GetPassword() + if username == "" && password == "" { + username = "admin" + password = "admin" + } + return ixweb.Connect(ctx, opts.Target, ixweb.WithHTTPClient(hc), ixweb.WithLogin(username, password)) +} + +func (a *staticATE) ixSession(ctx context.Context, opts *bindpb.Options) (*ixweb.Session, error) { if a.ixsess == nil { - ixw, err := a.ixWeb(ctx, d) + ixw, err := a.ixWeb(ctx, opts) if err != nil { return nil, err } - if d.SessionId > 0 { - a.ixsess, err = ixw.IxNetwork().FetchSession(ctx, int(d.SessionId)) + if opts.SessionId > 0 { + a.ixsess, err = ixw.IxNetwork().FetchSession(ctx, int(opts.SessionId)) } else { a.ixsess, err = ixw.IxNetwork().NewSession(ctx, a.Name()) } @@ -473,3 +463,129 @@ func (a *staticATE) ixSession(ctx context.Context, d dialer) (*ixweb.Session, er } return a.ixsess, nil } + +func dialConn(ctx context.Context, dev introspect.Introspector, svc introspect.Service, opts []grpc.DialOption) (*grpc.ClientConn, error) { + dialer, err := dev.Dialer(svc) + if err != nil { + return nil, err + } + return dialer.Dial(ctx, opts...) +} + +func dialOpts(bopts *bindpb.Options) ([]grpc.DialOption, error) { + opts := []grpc.DialOption{grpc.WithDisableRetry()} + switch { + case bopts.Insecure: + tc := insecure.NewCredentials() + opts = append(opts, grpc.WithTransportCredentials(tc)) + case bopts.SkipVerify: + tc := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) + opts = append(opts, grpc.WithTransportCredentials(tc)) + case bopts.MutualTls: + trusBundle, keyPair, err := loadCertificates(bopts) + if err != nil { + return nil, err + } + tls := &tls.Config{ + Certificates: []tls.Certificate{keyPair}, + RootCAs: trusBundle, + } + tlsConfig := credentials.NewTLS(tls) + opts = append(opts, grpc.WithTransportCredentials(tlsConfig)) + } + if bopts.Username != "" { + c := &creds{bopts.Username, bopts.Password, !bopts.Insecure} + opts = append(opts, grpc.WithPerRPCCredentials(c)) + } + if bopts.MaxRecvMsgSize != 0 { + opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(bopts.MaxRecvMsgSize)))) + } + if bopts.Timeout != 0 { + timeout := time.Duration(bopts.Timeout) * time.Second + retryOpt := grpc_retry.WithPerRetryTimeout(timeout) + opts = append(opts, + grpc.WithChainUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpt)), + grpc.WithChainStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpt)), + grpcutil.WithUnaryDefaultTimeout(timeout), + grpcutil.WithStreamDefaultTimeout(timeout), + ) + } + return opts, nil +} + +func makeDialer(params *svcParams, bopts *bindpb.Options) (*introspect.Dialer, error) { + opts, err := dialOpts(bopts) + if err != nil { + return nil, err + } + return &introspect.Dialer{ + DevicePort: params.port, + DialFunc: func(ctx context.Context, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { + if bopts.Timeout != 0 { + var cancelFunc context.CancelFunc + _, cancelFunc = context.WithTimeout(ctx, time.Duration(bopts.Timeout)*time.Second) + defer cancelFunc() + } + return grpcDialContextFn(target, opts...) + }, + DialTarget: bopts.Target, + DialOpts: opts, + }, nil +} + +// load trust bundle and client key and certificate +func loadCertificates(bopts *bindpb.Options) (*x509.CertPool, tls.Certificate, error) { + if bopts.CertFile == "" || bopts.KeyFile == "" || bopts.TrustBundleFile == "" { + return nil, tls.Certificate{}, fmt.Errorf("cert_file, key_file, and trust_bundle_file need to be set when mutual tls is set") + } + caCertBytes, err := os.ReadFile(bopts.TrustBundleFile) + if err != nil { + return nil, tls.Certificate{}, err + } + trusBundle := x509.NewCertPool() + if !trusBundle.AppendCertsFromPEM(caCertBytes) { + return nil, tls.Certificate{}, fmt.Errorf("error in loading ca trust bundle") + } + keyPair, err := tls.LoadX509KeyPair(bopts.CertFile, bopts.KeyFile) + if err != nil { + return nil, tls.Certificate{}, err + } + return trusBundle, keyPair, nil +} + +// creds implements the grpc.PerRPCCredentials interface, to be used +// as a grpc.DialOption in dialGRPC. +type creds struct { + username, password string + secure bool +} + +func (c *creds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + return map[string]string{ + "username": c.username, + "password": c.password, + }, nil +} + +func (c *creds) RequireTransportSecurity() bool { + return c.secure +} + +var _ = grpc.PerRPCCredentials(&creds{}) + +var knownHostsFiles = []string{ + "$HOME/.ssh/known_hosts", + "/etc/ssh/ssh_known_hosts", +} + +// knownHostsCallback checks the user and system SSH known_hosts. +func knownHostsCallback() (ssh.HostKeyCallback, error) { + var files []string + for _, file := range knownHostsFiles { + file = os.ExpandEnv(file) + if _, err := os.Stat(file); err == nil { + files = append(files, file) + } + } + return knownhosts.New(files...) +} diff --git a/topologies/binding/binding_test.go b/topologies/binding/binding_test.go index 88294349fc4..5a13b066457 100644 --- a/topologies/binding/binding_test.go +++ b/topologies/binding/binding_test.go @@ -16,13 +16,18 @@ package binding import ( "context" + "fmt" "strings" "testing" + "time" "github.com/google/go-cmp/cmp" + "github.com/open-traffic-generator/snappi/gosnappi" bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" "github.com/openconfig/ondatra/binding" + "github.com/openconfig/ondatra/binding/introspect" opb "github.com/openconfig/ondatra/proto" + "google.golang.org/grpc" "google.golang.org/protobuf/testing/protocmp" ) @@ -48,7 +53,7 @@ func TestReserveRelease(t *testing.T) { } } -func TestReservation(t *testing.T) { +func TestStaticReservation(t *testing.T) { tb := &opb.Testbed{ Duts: []*opb.Device{{ Id: "dut", @@ -93,7 +98,7 @@ func TestReservation(t *testing.T) { }}, } - got, err := reservation(tb, resolver{b}) + got, err := staticReservation(tb, resolver{b}) if err != nil { t.Fatalf("Error building reservation: %v", err) } @@ -144,16 +149,21 @@ func TestReservation(t *testing.T) { } } -func TestReservation_Error(t *testing.T) { +func TestStaticReservation_Error(t *testing.T) { tb := &opb.Testbed{ Duts: []*opb.Device{{ Id: "dut.tb", // only in testbed. }, { - Id: "dut.both", + Id: "dut.both", + Vendor: opb.Device_CIENA, // only in testbed + HardwareModelValue: &opb.Device_HardwareModel{HardwareModel: "modelA"}, // differs from binding + SoftwareVersionValue: &opb.Device_SoftwareVersion{SoftwareVersion: "versionB"}, // matches binding Ports: []*opb.Port{{ Id: "port1", }, { - Id: "port2", + Id: "port2", + Speed: opb.Port_S_100GB, // only in testbed + PmdValue: &opb.Port_Pmd_{Pmd: opb.Port_PMD_100GBASE_CLR4}, // differs in binding }}, }}, Ates: []*opb.Device{{ @@ -161,7 +171,8 @@ func TestReservation_Error(t *testing.T) { }, { Id: "ate.both", Ports: []*opb.Port{{ - Id: "port1", + Id: "port1", + Speed: opb.Port_S_10GB, // matches binding }, { Id: "port2", }}, @@ -173,22 +184,28 @@ func TestReservation_Error(t *testing.T) { Id: "dut.b", // only in binding. Name: "dut.b.name", }, { - Id: "dut.both", - Name: "dut.both.name", + Id: "dut.both", + Name: "dut.both.name", + HardwareModel: "modelB", // differs in testbed + SoftwareVersion: "versionB", // differs in binding Ports: []*bindpb.Port{{ // port1 missing, port3 extra Id: "port2", Name: "Ethernet2", + Pmd: opb.Port_PMD_400GBASE_DR4, // differs in testbed }, { Id: "port3", Name: "Ethernet3", }}, }}, Ates: []*bindpb.Device{{ - Id: "ate.both", - Name: "ate.name", - Ports: []*bindpb.Port{{ // port1 missing, port3 extra - Id: "port2", - Name: "1/2", + Id: "ate.both", + Name: "ate.name", + Vendor: opb.Device_IXIA, // only in binding + Ports: []*bindpb.Port{{ // port2 missing, port3 extra + Id: "port1", + Name: "1/1", + Speed: opb.Port_S_10GB, // matches testbed + Pmd: opb.Port_PMD_40GBASE_SR4, // only in binding }, { Id: "port3", Name: "1/3", @@ -196,25 +213,92 @@ func TestReservation_Error(t *testing.T) { }}, } - _, err := reservation(tb, resolver{b}) - if err == nil { - t.Fatalf("Error building reservation: %v", err) + r, errs := staticReservation(tb, resolver{b}) + if len(errs) == 0 { + t.Fatalf("staticReservation() unexpectedly succeeded: %v", r) } - t.Logf("Got reservation errors: %v", err) wants := []string{ `missing binding for DUT "dut.tb"`, - `error binding DUT "dut.both"`, - `binding DUT "dut.b" not found in testbed`, + `binding vendor`, + `binding hardware model`, + `missing binding for port "port1" on "dut.both"`, + `binding port speed`, + `binding port PMD`, `missing binding for ATE "ate.tb"`, - `error binding ATE "ate.both"`, - `testbed port "port1" is missing in binding`, + `missing binding for port "port2" on "ate.both"`, } - errText := err.Error() - - for _, want := range wants { - if !strings.Contains(errText, want) { - t.Errorf("Want error not found: %s", want) + if got, want := len(errs), len(wants); got != want { + t.Errorf("staticReservation() got %d errors, want %d: %v", got, want, errs) + } + for i, err := range errs { + if got, want := err.Error(), wants[i]; !strings.Contains(got, want) { + t.Errorf("staticReservation() got error %q, want: %q", got, want) } } } + +func TestDialOTGTimeout(t *testing.T) { + const timeoutSecs = 42 + a := &staticATE{ + r: resolver{&bindpb.Binding{}}, + dev: &bindpb.Device{Otg: &bindpb.Options{Timeout: timeoutSecs}}, + } + grpcDialContextFn = func(string, ...grpc.DialOption) (*grpc.ClientConn, error) { + return nil, nil + } + gosnappiNewAPIFn = func() gosnappi.Api { + return &captureAPI{Api: gosnappi.NewApi()} + } + api, err := a.DialOTG(context.Background()) + if err != nil { + t.Errorf("DialOTG() got error %v", err) + } + gotTransport := api.(*captureAPI).gotTransport + if gotTimeout, wantTimeout := gotTransport.RequestTimeout(), timeoutSecs*time.Second; gotTimeout != wantTimeout { + t.Errorf("DialOTG() got timeout %v, want %v", gotTimeout, wantTimeout) + } +} + +type captureAPI struct { + gosnappi.Api + gotTransport gosnappi.GrpcTransport +} + +func (a *captureAPI) NewGrpcTransport() gosnappi.GrpcTransport { + a.gotTransport = a.Api.NewGrpcTransport() + return a.gotTransport +} + +func TestDialer(t *testing.T) { + const ( + wantDevName = "mydev" + wantDevPort = 1234 + ) + fakeSvc := introspect.Service("fake") + dutSvcParams[fakeSvc] = &svcParams{ + port: wantDevPort, + optsFn: func(d *bindpb.Device) *bindpb.Options { return nil }, + } + d := &staticDUT{ + r: resolver{&bindpb.Binding{}}, + dev: &bindpb.Device{Name: wantDevName}, + } + + dialer, err := d.Dialer(fakeSvc) + if err != nil { + t.Fatalf("Dialer() got err: %v", err) + } + if dialer.DevicePort != wantDevPort { + t.Errorf("Dialer() got DevicePort %v, want %v", dialer.DevicePort, wantDevPort) + } + if dialer.DialFunc == nil { + t.Errorf("Dialer() got nil DialFunc, want non-nil DialFunc") + } + if len(dialer.DialOpts) == 0 { + t.Errorf("Dialer() got empty DialOpts, want non-empty DialOpts") + } + if wantTarget := fmt.Sprintf("%v:%v", wantDevName, wantDevPort); dialer.DialTarget != wantTarget { + t.Errorf("Dialer() got Target %v, want %v", dialer.DialTarget, wantTarget) + } +} diff --git a/topologies/binding/dynamic.go b/topologies/binding/dynamic.go new file mode 100644 index 00000000000..aa3f453428d --- /dev/null +++ b/topologies/binding/dynamic.go @@ -0,0 +1,179 @@ +// 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 binding + +import ( + "context" + "errors" + "fmt" + + bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" + "github.com/openconfig/ondatra/binding" + "github.com/openconfig/ondatra/binding/portgraph" + opb "github.com/openconfig/ondatra/proto" + "github.com/pborman/uuid" +) + +func dynamicReservation(ctx context.Context, tb *opb.Testbed, r resolver) (*binding.Reservation, error) { + abstractGraph, absNode2Dev, absPort2BindPort, err := portgraph.TestbedToAbstractGraph(tb, nil) + if err != nil { + return nil, fmt.Errorf("could not parse specified testbed: %w", err) + } + superGraph, conNode2Dev, conPort2BindPort, err := protoToConcreteGraph(r.Binding) + if err != nil { + return nil, fmt.Errorf("could not solve for specified testbed: %w", err) + } + assign, err := portgraph.Solve(ctx, abstractGraph, superGraph) + if err != nil { + return nil, fmt.Errorf("could not solve for specified testbed: %w", err) + } + res, err := assignmentToReservation(assign, r, tb, absNode2Dev, conNode2Dev, absPort2BindPort, conPort2BindPort) + if err != nil { + return nil, fmt.Errorf("could not solve for specified testbed: %w", err) + } + return res, nil +} + +func protoToConcreteGraph(bpb *bindpb.Binding) (*portgraph.ConcreteGraph, map[*portgraph.ConcreteNode]*bindpb.Device, map[*portgraph.ConcretePort]*bindpb.Port, error) { + cg := &portgraph.ConcreteGraph{Desc: "FeatureProfiles binding.proto"} + qualName2Port := make(map[string]*portgraph.ConcretePort) + conNode2Dev := make(map[*portgraph.ConcreteNode]*bindpb.Device) + conPort2BindPort := make(map[*portgraph.ConcretePort]*bindpb.Port) + + addDevice := func(dev *bindpb.Device, devRole string) { + var ports []*portgraph.ConcretePort + for _, ap := range dev.GetPorts() { + port := &portgraph.ConcretePort{ + Desc: ap.Name, + Attrs: make(map[string]string), + } + if name := ap.GetName(); name != "" { + port.Attrs[portgraph.NameAttr] = name + } + if speed := ap.GetSpeed(); speed != opb.Port_SPEED_UNSPECIFIED { + port.Attrs[portgraph.SpeedAttr] = speed.String() + } + if pmd := ap.GetPmd(); pmd != opb.Port_PMD_UNSPECIFIED { + port.Attrs[portgraph.PMDAttr] = pmd.String() + } + ports = append(ports, port) + qualName2Port[dev.Name+":"+ap.Name] = port + conPort2BindPort[port] = ap + } + + node := &portgraph.ConcreteNode{ + Desc: dev.Name, + Ports: ports, + Attrs: map[string]string{portgraph.RoleAttr: devRole}, + } + if name := dev.GetName(); name != "" { + node.Attrs[portgraph.NameAttr] = name + } + if hw := dev.GetHardwareModel(); hw != "" { + node.Attrs[portgraph.HWAttr] = hw + } + if sw := dev.GetSoftwareVersion(); sw != "" { + node.Attrs[portgraph.SWAttr] = sw + } + cg.Nodes = append(cg.Nodes, node) + conNode2Dev[node] = dev + } + for _, dut := range bpb.GetDuts() { + addDevice(dut, portgraph.RoleDUT) + } + for _, ate := range bpb.GetAtes() { + addDevice(ate, portgraph.RoleATE) + } + + for _, link := range bpb.GetLinks() { + pa, ok := qualName2Port[link.GetA()] + if !ok { + return nil, nil, nil, fmt.Errorf("no known port %q in link %v", link.GetA(), link) + } + pb, ok := qualName2Port[link.GetB()] + if !ok { + return nil, nil, nil, fmt.Errorf("no known port %q in link %v", link.GetB(), link) + } + cg.Edges = append(cg.Edges, &portgraph.ConcreteEdge{Src: pa, Dst: pb}) + } + + return cg, conNode2Dev, conPort2BindPort, nil +} + +func assignmentToReservation( + assign *portgraph.Assignment, + r resolver, + tb *opb.Testbed, + absNode2Dev map[*portgraph.AbstractNode]*opb.Device, + conNode2Dev map[*portgraph.ConcreteNode]*bindpb.Device, + absPort2BindPort map[*portgraph.AbstractPort]*opb.Port, + conPort2BindPort map[*portgraph.ConcretePort]*bindpb.Port, +) (*binding.Reservation, error) { + res := &binding.Reservation{ + ID: uuid.New(), + DUTs: make(map[string]binding.DUT), + ATEs: make(map[string]binding.ATE), + } + + tbDev2BindDev := make(map[*opb.Device]*bindpb.Device) + for absNode, conNode := range assign.Node2Node { + tbDev2BindDev[absNode2Dev[absNode]] = conNode2Dev[conNode] + } + + tbPort2BindPort := make(map[*opb.Port]*bindpb.Port) + for absPort, conPort := range assign.Port2Port { + tbPort2BindPort[absPort2BindPort[absPort]] = conPort2BindPort[conPort] + } + + var errs []error + for _, tdut := range tb.GetDuts() { + bdut := tbDev2BindDev[tdut] + d := dynDims(tdut, bdut, tbPort2BindPort) + res.DUTs[tdut.Id] = &staticDUT{ + AbstractDUT: &binding.AbstractDUT{Dims: d}, + r: r, + dev: bdut, + } + } + for _, tate := range tb.GetAtes() { + bate := tbDev2BindDev[tate] + d := dynDims(tate, bate, tbPort2BindPort) + res.ATEs[tate.Id] = &staticATE{ + AbstractATE: &binding.AbstractATE{Dims: d}, + r: r, + dev: bate, + } + } + return res, errors.Join(errs...) +} + +func dynDims(td *opb.Device, bd *bindpb.Device, tbPort2BindPort map[*opb.Port]*bindpb.Port) *binding.Dims { + dims := &binding.Dims{ + Name: bd.Name, + Vendor: bd.GetVendor(), + HardwareModel: bd.GetHardwareModel(), + SoftwareVersion: bd.GetSoftwareVersion(), + Ports: make(map[string]*binding.Port), + } + for _, tport := range td.GetPorts() { + bport := tbPort2BindPort[tport] + dims.Ports[tport.Id] = &binding.Port{ + Name: bport.Name, + PMD: bport.Pmd, + Speed: bport.Speed, + } + } + return dims +} diff --git a/topologies/binding/dynamic_test.go b/topologies/binding/dynamic_test.go new file mode 100644 index 00000000000..085acb379c2 --- /dev/null +++ b/topologies/binding/dynamic_test.go @@ -0,0 +1,216 @@ +// 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 binding + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" + "github.com/openconfig/ondatra/binding" + opb "github.com/openconfig/ondatra/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestDynamicReservation(t *testing.T) { + tb := &opb.Testbed{ + Duts: []*opb.Device{{ + Id: "dut", + Ports: []*opb.Port{{ + Id: "port1", + Speed: opb.Port_S_100GB, + }, { + Id: "port2", + }}, + }}, + Ates: []*opb.Device{{ + Id: "ate", + Ports: []*opb.Port{{ + Id: "port1", + PmdValue: &opb.Port_Pmd_{Pmd: opb.Port_PMD_100GBASE_CR4}, + }, { + Id: "port2", + }}, + }}, + Links: []*opb.Link{{ + A: "dut:port1", + B: "ate:port2", + }, { + A: "dut:port2", + B: "ate:port1", + }}, + } + + b := &bindpb.Binding{ + Dynamic: true, + Duts: []*bindpb.Device{{ + Name: "dut.name", + Ports: []*bindpb.Port{{ + Name: "Ethernet1", + }, { + Name: "Ethernet2", + Speed: opb.Port_S_100GB, + }}, + }}, + Ates: []*bindpb.Device{{ + Name: "ate.name", + Ports: []*bindpb.Port{{ + Name: "1/1", + Pmd: opb.Port_PMD_100GBASE_CR4, + }, { + Name: "1/2", + }}, + }}, + Links: []*bindpb.Link{{ + A: "dut.name:Ethernet2", + B: "ate.name:1/2", + }, { + A: "ate.name:1/1", + B: "dut.name:Ethernet1", + }}, + } + + got, err := dynamicReservation(context.Background(), tb, resolver{b}) + if err != nil { + t.Fatalf("dynamicReservation9) got unexpected error: %v", err) + } + want := &binding.Reservation{ + DUTs: map[string]binding.DUT{ + "dut": &staticDUT{ + AbstractDUT: &binding.AbstractDUT{Dims: &binding.Dims{ + Name: "dut.name", + Ports: map[string]*binding.Port{ + "port1": { + Name: "Ethernet2", + Speed: opb.Port_S_100GB, + }, + "port2": { + Name: "Ethernet1", + }, + }, + }}, + r: resolver{b}, + dev: &bindpb.Device{ + Name: "dut.name", + Ports: []*bindpb.Port{ + { + Name: "Ethernet1", + }, { + Name: "Ethernet2", + Speed: opb.Port_S_100GB, + }, + }, + }, + }, + }, + ATEs: map[string]binding.ATE{ + "ate": &staticATE{ + AbstractATE: &binding.AbstractATE{Dims: &binding.Dims{ + Name: "ate.name", + Ports: map[string]*binding.Port{ + "port1": { + Name: "1/1", + PMD: opb.Port_PMD_100GBASE_CR4, + }, + "port2": { + Name: "1/2", + }, + }, + }}, + r: resolver{b}, + dev: &bindpb.Device{ + Name: "ate.name", + Ports: []*bindpb.Port{ + { + Name: "1/1", + Pmd: opb.Port_PMD_100GBASE_CR4, + }, { + Name: "1/2", + }, + }, + }, + }, + }, + } + + got.ID = "" + if diff := cmp.Diff(want, got, cmp.AllowUnexported(staticDUT{}, staticATE{}), protocmp.Transform()); diff != "" { + t.Errorf("dynamicReservation() got unexpected diff (-want, +got):\n%s", diff) + } +} + +func TestDynamicReservationError(t *testing.T) { + tb := &opb.Testbed{ + Duts: []*opb.Device{{ + Id: "dut", + Ports: []*opb.Port{{ + Id: "port1", + Speed: opb.Port_S_100GB, + }, { + Id: "port2", + }}, + }}, + Ates: []*opb.Device{{ + Id: "ate", + Ports: []*opb.Port{{ + Id: "port1", + PmdValue: &opb.Port_Pmd_{Pmd: opb.Port_PMD_100GBASE_CR4}, + }, { + Id: "port2", + }}, + }}, + Links: []*opb.Link{{ + A: "dut:port1", + B: "ate:port2", + }, { + A: "dut:port2", + B: "ate:port1", + }}, + } + + b := &bindpb.Binding{ + Dynamic: true, + Duts: []*bindpb.Device{{ + Name: "dut.name", + Ports: []*bindpb.Port{{ + Name: "Ethernet1", + }, { + Name: "Ethernet2", + Speed: opb.Port_S_100GB, + }}, + }}, + Ates: []*bindpb.Device{{ + Name: "ate.name", + Ports: []*bindpb.Port{{ + Name: "1/1", + }, { + Name: "1/2", + }}, + }}, + Links: []*bindpb.Link{{ + A: "dut.name:Ethernet2", + B: "ate.name:1/2", + }, { + A: "ate.name:1/1", + B: "dut.name:Ethernet1", + }}, + } + + got, err := dynamicReservation(context.Background(), tb, resolver{b}) + if err == nil { + t.Fatalf("dynamicReservation() got unexpected success: %v", got) + } +} diff --git a/topologies/binding/gnsi.go b/topologies/binding/gnsi.go index 7469edf20b3..6f55ec99da9 100644 --- a/topologies/binding/gnsi.go +++ b/topologies/binding/gnsi.go @@ -43,5 +43,8 @@ func (g gnsiConn) Credentialz() credpb.CredentialzClient { func (g gnsiConn) Acctz() accpb.AcctzClient { return accpb.NewAcctzClient(g.conn) } +func (g gnsiConn) AcctzStream() accpb.AcctzStreamClient { + return accpb.NewAcctzStreamClient(g.conn) +} var _ = binding.GNSIClients(gnsiConn{}) diff --git a/topologies/binding/options.go b/topologies/binding/options.go index a681200ff0e..ffa7460410f 100644 --- a/topologies/binding/options.go +++ b/topologies/binding/options.go @@ -15,346 +15,90 @@ package binding import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "net" - "net/http" - "os" - "time" - "flag" - - grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" + "fmt" bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" - "github.com/openconfig/ondatra/binding/ixweb" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/knownhosts" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" + "github.com/openconfig/ondatra/binding/introspect" "google.golang.org/protobuf/proto" ) -// IANA assigns 9339 for gNxI, and 9559 for P4RT. There hasn't been a -// port assignment for gRIBI, so using Arista's default which is 6040. +// IANA assigns 9339 for gNxI, 9559 for P4RT and 9340 for gRIBI. var ( gnmiPort = flag.Int("gnmi_port", 9339, "default gNMI port") gnoiPort = flag.Int("gnoi_port", 9339, "default gNOI port") gnsiPort = flag.Int("gnsi_port", 9339, "default gNSI port") - gribiPort = flag.Int("gribi_port", 6040, "default gRIBI port") + gribiPort = flag.Int("gribi_port", 9340, "default gRIBI port") p4rtPort = flag.Int("p4rt_port", 9559, "default P4RT part") - ateGnmiPort = flag.Int("ate_gnmi_port", 50051, "default ATE gNMI port") - ateOtgPort = flag.Int("ate_grpc_port", 40051, "default ATE gRPC port for running OTG test") -) - -// creds implements the grpc.PerRPCCredentials interface, to be used -// as a grpc.DialOption in dialGRPC. -type creds struct { - username, password string - secure bool -} - -func (c *creds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { - return map[string]string{ - "username": c.username, - "password": c.password, - }, nil -} - -func (c *creds) RequireTransportSecurity() bool { - return c.secure -} - -var _ = grpc.PerRPCCredentials(&creds{}) - -// dialer wraps *bindpb.Options and implements dialers for various -// protocols. -type dialer struct { - *bindpb.Options -} - -// load trust bundle and client key and certificate -func (d *dialer) loadCertificates() (*x509.CertPool, tls.Certificate, error) { - if d.CertFile == "" || d.KeyFile == "" || d.TrustBundleFile == "" { - return nil, tls.Certificate{}, fmt.Errorf("cert_file, key_file, and trust_bundle_file need to be set when mutual tls is set") - } - caCertBytes, err := os.ReadFile(d.TrustBundleFile) - if err != nil { - return nil, tls.Certificate{}, err - } - trusBundle := x509.NewCertPool() - if !trusBundle.AppendCertsFromPEM(caCertBytes) { - return nil, tls.Certificate{}, fmt.Errorf("error in loading ca trust bundle") - } - keyPair, err := tls.LoadX509KeyPair(d.CertFile, d.KeyFile) - if err != nil { - return nil, tls.Certificate{}, err - } - return trusBundle, keyPair, nil - -} - -// dialGRPC dials a gRPC connection using the binding options. -// -//lint:ignore U1000 will be used by the binding. -func (d *dialer) dialGRPC(ctx context.Context, overrideOpts ...grpc.DialOption) (*grpc.ClientConn, error) { - opts := []grpc.DialOption{grpc.WithBlock()} - switch { - case d.Insecure: - tc := insecure.NewCredentials() - opts = append(opts, grpc.WithTransportCredentials(tc)) - case d.SkipVerify: - tc := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) - opts = append(opts, grpc.WithTransportCredentials(tc)) - case d.MutualTls: - trusBundle, keyPair, err := d.loadCertificates() - if err != nil { - return nil, err - } - tls := &tls.Config{ - Certificates: []tls.Certificate{keyPair}, - RootCAs: trusBundle, - } - tlsConfig := credentials.NewTLS(tls) - opts = append(opts, grpc.WithTransportCredentials(tlsConfig)) - } - if d.Username != "" { - c := &creds{d.Username, d.Password, !d.Insecure} - opts = append(opts, grpc.WithPerRPCCredentials(c)) - } - if d.MaxRecvMsgSize != 0 { - opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(d.MaxRecvMsgSize)))) - } - if d.Timeout != 0 { - retryOpt := grpc_retry.WithPerRetryTimeout(time.Duration(d.Timeout) * time.Second) - opts = append(opts, - grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpt)), - grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpt)), - ) - var cancelFunc context.CancelFunc - ctx, cancelFunc = context.WithTimeout(ctx, time.Duration(d.Timeout)*time.Second) - defer cancelFunc() - } - opts = append(opts, overrideOpts...) - return grpc.DialContext(ctx, d.Target, opts...) -} - -var knownHostsFiles = []string{ - "$HOME/.ssh/known_hosts", - "/etc/ssh/ssh_known_hosts", -} - -// knownHostsCallback checks the user and system SSH known_hosts. -// -//lint:ignore U1000 will be used by the binding. -func knownHostsCallback() (ssh.HostKeyCallback, error) { - var files []string - for _, file := range knownHostsFiles { - file = os.ExpandEnv(file) - if _, err := os.Stat(file); err == nil { - files = append(files, file) - } - } - return knownhosts.New(files...) -} + ateGNMIPort = flag.Int("ate_gnmi_port", 50051, "default ATE gNMI port") + ateOTGPort = flag.Int("ate_grpc_port", 40051, "default ATE OTG port") -// dialSSH dials an SSH client using the binding options. -// -//lint:ignore U1000 will be used by the binding. -func (d *dialer) dialSSH() (*ssh.Client, error) { - c := &ssh.ClientConfig{ - User: d.Username, - Auth: []ssh.AuthMethod{ - ssh.Password(d.Password), - ssh.KeyboardInteractive(d.sshInteractive), + dutSvcParams = map[introspect.Service]*svcParams{ + introspect.GNMI: { + port: *gnmiPort, + optsFn: (*bindpb.Device).GetGnmi, + }, + introspect.GNOI: { + port: *gnoiPort, + optsFn: (*bindpb.Device).GetGnoi, + }, + introspect.GNSI: { + port: *gnsiPort, + optsFn: (*bindpb.Device).GetGnsi, + }, + introspect.GRIBI: { + port: *gribiPort, + optsFn: (*bindpb.Device).GetGribi, + }, + introspect.P4RT: { + port: *p4rtPort, + optsFn: (*bindpb.Device).GetP4Rt, }, - } - if d.SkipVerify { - c.HostKeyCallback = ssh.InsecureIgnoreHostKey() - } else { - cb, err := knownHostsCallback() - if err != nil { - return nil, err - } - c.HostKeyCallback = cb - } - return ssh.Dial("tcp", d.Target, c) -} - -// For every question asked in an interactive login ssh session, set the answer to user password. -func (d *dialer) sshInteractive(user, instruction string, questions []string, echoes []bool) (answers []string, err error) { - _, _, _ = user, instruction, echoes // unused - answers = make([]string, len(questions)) - for n := range questions { - answers[n] = d.Password } - return answers, nil -} - -// newHTTPClient makes an http.Client using the binding options. -// -//lint:ignore U1000 will be used by the binding. -func (d *dialer) newHTTPClient() *http.Client { - tr := &http.Transport{ - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).DialContext, - } - if d.SkipVerify { - tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + ateSvcParams = map[introspect.Service]*svcParams{ + introspect.GNMI: { + port: *ateGNMIPort, + optsFn: (*bindpb.Device).GetGnmi, + }, + introspect.OTG: { + port: *ateOTGPort, + optsFn: (*bindpb.Device).GetOtg, + }, } - return &http.Client{Transport: tr} -} +) -// newIxWebClient makes an IxWeb session using the binding options. -func (d *dialer) newIxWebClient(ctx context.Context) (*ixweb.IxWeb, error) { - hc := d.newHTTPClient() - username := d.GetUsername() - password := d.GetPassword() - if username == "" && password == "" { - username = "admin" - password = "admin" - } - ixw, err := ixweb.Connect(ctx, d.Target, ixweb.WithHTTPClient(hc), ixweb.WithLogin(username, password)) - if err != nil { - return nil, err - } - return ixw, nil +type svcParams struct { + port int + optsFn func(*bindpb.Device) *bindpb.Options } -// merge creates a dialer by combining one or more options. -func merge(bopts ...*bindpb.Options) dialer { +// merge creates combines one or more options into one set of options. +func merge(bopts ...*bindpb.Options) *bindpb.Options { result := &bindpb.Options{} for _, bopt := range bopts { if bopt != nil { proto.Merge(result, bopt) } } - return dialer{result} + return result } -// resolver returns the dialer for specific devices and protocols. type resolver struct { *bindpb.Binding } -// dutByID looks up the *bindpb.Device with the given dutID. -func (r *resolver) dutByID(dutID string) *bindpb.Device { - for _, dut := range r.Duts { - if dut.Id == dutID { - return dut - } - } - return nil -} - -// ateByID looks up the *bindpb.Device with the given ateID. -func (r *resolver) ateByID(ateID string) *bindpb.Device { - for _, ate := range r.Ates { - if ate.Id == ateID { - return ate - } - } - return nil -} - -// dutByName looks up the *bindpb.Device with the given name. -func (r *resolver) dutByName(dutName string) *bindpb.Device { - for _, dut := range r.Duts { - if dut.Name == dutName { - return dut - } - } - return nil +func (r *resolver) grpc(dev *bindpb.Device, params *svcParams) *bindpb.Options { + targetOpts := &bindpb.Options{Target: fmt.Sprintf("%s:%d", dev.Name, params.port)} + return merge(targetOpts, r.Options, dev.Options, params.optsFn(dev)) } -// ateByName looks up the *bindpb.Device with the given name. -func (r *resolver) ateByName(ateName string) *bindpb.Device { - for _, ate := range r.Ates { - if ate.Name == ateName { - return ate - } - } - return nil +func (r *resolver) ssh(dev *bindpb.Device) *bindpb.Options { + targetOpts := &bindpb.Options{Target: dev.Name} + return merge(targetOpts, r.Options, dev.Options, dev.Ssh) } -// dutDialer reconstructs the dialer for a given dut and protocol. -func (r *resolver) dutDialer(dutName string, port int, optionsFn func(*bindpb.Device) *bindpb.Options) (dialer, error) { - dut := r.dutByName(dutName) - if dut == nil { - return dialer{nil}, fmt.Errorf("dut name %q is missing from the binding", dutName) - } - targetOptions := &bindpb.Options{ - Target: fmt.Sprintf("%s:%d", dut.Name, port), - } - return merge(targetOptions, r.Options, dut.Options, optionsFn(dut)), nil -} - -func (r *resolver) ateDialer(ateName string, port int, optionsFn func(*bindpb.Device) *bindpb.Options) (dialer, error) { - ate := r.ateByName(ateName) - if ate == nil { - return dialer{nil}, fmt.Errorf("ATE name %q is missing from the binding", ateName) - } - targetOptions := &bindpb.Options{ - Target: fmt.Sprintf("%s:%d", ate.Name, port), - } - return merge(targetOptions, r.Options, ate.Options, optionsFn(ate)), nil -} - -func (r *resolver) gnmi(dutName string) (dialer, error) { - return r.dutDialer(dutName, *gnmiPort, - func(dut *bindpb.Device) *bindpb.Options { return dut.Gnmi }) -} - -func (r *resolver) gnoi(dutName string) (dialer, error) { - return r.dutDialer(dutName, *gnoiPort, - func(dut *bindpb.Device) *bindpb.Options { return dut.Gnoi }) -} - -func (r *resolver) gnsi(dutName string) (dialer, error) { - return r.dutDialer(dutName, *gnsiPort, - func(dut *bindpb.Device) *bindpb.Options { return dut.Gnsi }) -} - -func (r *resolver) gribi(dutName string) (dialer, error) { - return r.dutDialer(dutName, *gribiPort, - func(dut *bindpb.Device) *bindpb.Options { return dut.Gribi }) -} - -func (r *resolver) p4rt(dutName string) (dialer, error) { - return r.dutDialer(dutName, *p4rtPort, - func(dut *bindpb.Device) *bindpb.Options { return dut.P4Rt }) -} - -func (r *resolver) ssh(dutName string) (dialer, error) { - dut := r.dutByName(dutName) - if dut == nil { - return dialer{nil}, fmt.Errorf("dut name %q is missing from the binding", dutName) - } - targetOptions := &bindpb.Options{Target: dut.Name} - return merge(targetOptions, r.Options, dut.Options, dut.Ssh), nil -} - -func (r *resolver) ateGNMI(ateName string) (dialer, error) { - return r.ateDialer(ateName, *ateGnmiPort, - func(ate *bindpb.Device) *bindpb.Options { return ate.Gnmi }) -} - -func (r *resolver) ateOtg(ateName string) (dialer, error) { - return r.ateDialer(ateName, *ateOtgPort, - func(ate *bindpb.Device) *bindpb.Options { return ate.Otg }) -} - -func (r *resolver) ixnetwork(ateName string) (dialer, error) { - ate := r.ateByName(ateName) - if ate == nil { - return dialer{nil}, fmt.Errorf("ate name %q is missing from the binding", ateName) - } - targetOptions := &bindpb.Options{Target: ate.Name} - return merge(targetOptions, r.Options, ate.Options, ate.Ixnetwork), nil +func (r *resolver) ixnetwork(dev *bindpb.Device) *bindpb.Options { + targetOpts := &bindpb.Options{Target: dev.Name} + return merge(targetOpts, r.Options, dev.Options, dev.Ixnetwork) } diff --git a/topologies/binding/options_test.go b/topologies/binding/options_test.go index d9709bb3c7e..e4ff9b13c7a 100644 --- a/topologies/binding/options_test.go +++ b/topologies/binding/options_test.go @@ -22,6 +22,7 @@ import ( "google.golang.org/protobuf/testing/protocmp" bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" + "github.com/openconfig/ondatra/binding/introspect" ) func TestMerge(t *testing.T) { @@ -133,6 +134,12 @@ var resolverBinding = resolver{&bindpb.Binding{ Options: &bindpb.Options{ Username: "ate.username", }, + Gnmi: &bindpb.Options{ + Password: "gnmi.password", + }, + Otg: &bindpb.Options{ + Password: "otg.password", + }, Ixnetwork: &bindpb.Options{ Password: "ixnetwork.password", }, @@ -145,230 +152,123 @@ var resolverBinding = resolver{&bindpb.Binding{ }}, }} -func TestResolver_ByID(t *testing.T) { - r := resolverBinding - cases := []struct { - test string - id string - fn func(name string) *bindpb.Device - want bool - }{{ - test: "dutByID(dut)", - id: "dut", - fn: r.dutByID, - want: true, - }, { - test: "dutByID(anotherdut)", - id: "anotherdut", - fn: r.dutByID, - want: true, - }, { - test: "ateByID(ate)", - id: "ate", - fn: r.ateByID, - want: true, - }, { - test: "ateByID(anotherate)", - id: "anotherate", - fn: r.ateByID, - want: true, - }, { - test: "dutByID(no.such.dut)", - id: "no.such.dut", - fn: r.dutByID, - want: false, - }, { - test: "ateByID(no.such.ate)", - id: "no.such.ate", - fn: r.ateByID, - want: false, - }, { - test: "ateByID(dut)", - id: "dut", - fn: r.ateByID, - want: false, - }, { - test: "dutByID(ate)", - id: "ate", - fn: r.dutByID, - want: false, - }} - - for _, c := range cases { - t.Run(c.test, func(t *testing.T) { - d := c.fn(c.id) - got := d != nil - if got != c.want { - t.Errorf("Lookup by ID got %v, want %v", got, c.want) - } - }) - } -} - -func TestResolver_ByName_Protocols(t *testing.T) { +func TestResolver_Options(t *testing.T) { r := resolverBinding cases := []struct { test string - fn func(name string) (dialer, error) - name string - want dialer + fn func(*bindpb.Device) *bindpb.Options + dev *bindpb.Device + want *bindpb.Options }{{ test: "ssh", fn: r.ssh, - name: "dut.name", - want: dialer{&bindpb.Options{ + dev: r.Duts[0], + want: &bindpb.Options{ Target: "dut.name", Username: "global.username", Password: "ssh.password", - }}, + }, }, { test: "gnmi", - fn: r.gnmi, - name: "dut.name", - want: dialer{&bindpb.Options{ + fn: func(d *bindpb.Device) *bindpb.Options { + return r.grpc(d, dutSvcParams[introspect.GNMI]) + }, + dev: r.Duts[0], + want: &bindpb.Options{ Target: "dut.name:" + strconv.Itoa(*gnmiPort), Username: "global.username", Password: "gnmi.password", - }}, + }, }, { test: "gnoi", - fn: r.gnoi, - name: "dut.name", - want: dialer{&bindpb.Options{ + fn: func(d *bindpb.Device) *bindpb.Options { + return r.grpc(d, dutSvcParams[introspect.GNOI]) + }, + dev: r.Duts[0], + want: &bindpb.Options{ Target: "dut.name:" + strconv.Itoa(*gnoiPort), Username: "global.username", Password: "gnoi.password", - }}, + }, }, { test: "gnsi", - fn: r.gnsi, - name: "dut.name", - want: dialer{&bindpb.Options{ + fn: func(d *bindpb.Device) *bindpb.Options { + return r.grpc(d, dutSvcParams[introspect.GNSI]) + }, + dev: r.Duts[0], + want: &bindpb.Options{ Target: "dut.name:" + strconv.Itoa(*gnsiPort), Username: "global.username", Password: "gnsi.password", - }}, + }, }, { test: "gribi", - fn: r.gribi, - name: "dut.name", - want: dialer{&bindpb.Options{ + fn: func(d *bindpb.Device) *bindpb.Options { + return r.grpc(d, dutSvcParams[introspect.GRIBI]) + }, + dev: r.Duts[0], + want: &bindpb.Options{ Target: "dut.name:" + strconv.Itoa(*gribiPort), Username: "global.username", Password: "gribi.password", - }}, + }, }, { test: "p4rt", - fn: r.p4rt, - name: "dut.name", - want: dialer{&bindpb.Options{ + fn: func(d *bindpb.Device) *bindpb.Options { + return r.grpc(d, dutSvcParams[introspect.P4RT]) + }, + dev: r.Duts[0], + want: &bindpb.Options{ Target: "dut.name:" + strconv.Itoa(*p4rtPort), Username: "global.username", Password: "p4rt.password", - }}, + }, + }, { + test: "otg", + fn: func(d *bindpb.Device) *bindpb.Options { + return r.grpc(d, ateSvcParams[introspect.OTG]) + }, + dev: r.Ates[0], + want: &bindpb.Options{ + Target: "ate.name:" + strconv.Itoa(*ateOTGPort), + Username: "ate.username", + Password: "otg.password", + }, }, { test: "ixnetwork", fn: r.ixnetwork, - name: "ate.name", - want: dialer{&bindpb.Options{ + dev: r.Ates[0], + want: &bindpb.Options{ Target: "ate.name", Username: "ate.username", Password: "ixnetwork.password", - }}, + }, }, { test: "anotherdut", fn: r.ssh, - name: "anotherdut.name", - want: dialer{&bindpb.Options{ + dev: r.Duts[1], + want: &bindpb.Options{ Target: "anotherdut.name", Username: "global.username", Password: "anotherdut.password", - }}, + }, }, { test: "anotherate", fn: r.ixnetwork, - name: "anotherate.name", - want: dialer{&bindpb.Options{ + dev: r.Ates[1], + want: &bindpb.Options{ Target: "anotherate.name", Username: "global.username", Password: "anotherate.password", - }}, + }, }} for _, c := range cases { t.Run(c.test, func(t *testing.T) { - got, err := c.fn(c.name) - if err != nil { - t.Fatalf("Could not get options: %v", err) - } + got := c.fn(c.dev) if diff := cmp.Diff(c.want, got, protocmp.Transform()); diff != "" { t.Errorf("Resolve diff (-want +got):\n%s", diff) } }) } } - -func TestResolver_ByName_Protocols_Error(t *testing.T) { - r := resolverBinding - cases := []struct { - test string - fn func(name string) (dialer, error) - name string - reason string - }{{ - test: "no.such.dut", - fn: r.ssh, - name: "no.such.dut.name", - reason: "name not found", - }, { - test: "no.such.ate", - fn: r.ixnetwork, - name: "no.such.ate.name", - reason: "name not found", - }, { - test: "ate.ssh", - fn: r.ssh, - name: "ate.name", - reason: "ssh never looks up ate", - }, { - test: "ate.gnmi", - fn: r.gnmi, - name: "ate.name", - reason: "gnmi never looks up ate", - }, { - test: "ate.gnoi", - fn: r.gnoi, - name: "ate.name", - reason: "gnoi never looks up ate", - }, { - test: "ate.gnsi", - fn: r.gnsi, - name: "ate.name", - reason: "gnsi never looks up ate", - }, { - test: "ate.gribi", - fn: r.gribi, - name: "ate.name", - reason: "gribi never looks up ate", - }, { - test: "ate.p4rt", - fn: r.p4rt, - name: "ate.name", - reason: "p4rt never looks up ate", - }, { - test: "dut.ixnetwork", - fn: r.ixnetwork, - name: "dut.name", - reason: "ixnetwork never looks up dut", - }} - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - _, err := c.fn(c.name) - t.Logf("Resolve got error: %v", err) - if err == nil { - t.Errorf("Resolve error got nil, want error because %s", c.reason) - } - }) - } -} diff --git a/topologies/binding/reset.go b/topologies/binding/reset.go index 94e68464957..bff2cf81da4 100644 --- a/topologies/binding/reset.go +++ b/topologies/binding/reset.go @@ -19,7 +19,6 @@ import ( "os" "strings" - bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" gpb "github.com/openconfig/gnmi/proto/gnmi" spb "github.com/openconfig/gribi/v1/proto/service" "google.golang.org/protobuf/encoding/prototext" @@ -33,12 +32,12 @@ func readCLI(path string) (string, error) { return string(data), nil } -func resetCLI(ctx context.Context, bdut *bindpb.Device, r resolver) error { +func resetCLI(ctx context.Context, dut *staticDUT) error { vendorConfig := []string{} - for _, conf := range bdut.GetConfig().GetCli() { + for _, conf := range dut.dev.GetConfig().GetCli() { vendorConfig = append(vendorConfig, string(conf)) } - for _, file := range bdut.GetConfig().GetCliFile() { + for _, file := range dut.dev.GetConfig().GetCliFile() { conf, err := readCLI(file) if err != nil { return err @@ -51,16 +50,7 @@ func resetCLI(ctx context.Context, bdut *bindpb.Device, r resolver) error { return nil } - dialer, err := r.ssh(bdut.GetName()) - if err != nil { - return err - } - sc, err := dialer.dialSSH() - if err != nil { - return err - } - defer sc.Close() - cli, err := newCLI(sc) + cli, err := dut.DialCLI(ctx) if err != nil { return err } @@ -84,9 +74,9 @@ func readGNMI(path string) (*gpb.SetRequest, error) { return req, nil } -func resetGNMI(ctx context.Context, bdut *bindpb.Device, r resolver) error { +func resetGNMI(ctx context.Context, dut *staticDUT) error { setReq := []*gpb.SetRequest{} - for _, file := range bdut.GetConfig().GetGnmiSetFile() { + for _, file := range dut.dev.GetConfig().GetGnmiSetFile() { conf, err := readGNMI(file) if err != nil { return err @@ -97,17 +87,10 @@ func resetGNMI(ctx context.Context, bdut *bindpb.Device, r resolver) error { return nil } - dialer, err := r.gnmi(bdut.GetName()) - if err != nil { - return err - } - conn, err := dialer.dialGRPC(ctx) + gnmi, err := dut.DialGNMI(ctx) if err != nil { return err } - defer conn.Close() - - gnmi := gpb.NewGNMIClient(conn) for _, req := range setReq { if _, err := gnmi.Set(ctx, req); err != nil { @@ -117,22 +100,15 @@ func resetGNMI(ctx context.Context, bdut *bindpb.Device, r resolver) error { return nil } -func resetGRIBI(ctx context.Context, bdut *bindpb.Device, r resolver) error { - if !bdut.GetConfig().GetGribiFlush() { +func resetGRIBI(ctx context.Context, dut *staticDUT) error { + if !dut.dev.GetConfig().GetGribiFlush() { return nil } - dialer, err := r.gribi(bdut.GetName()) + gribi, err := dut.DialGRIBI(ctx) if err != nil { return err } - conn, err := dialer.dialGRPC(ctx) - if err != nil { - return err - } - defer conn.Close() - - gribi := spb.NewGRIBIClient(conn) req := &spb.FlushRequest{ NetworkInstance: &spb.FlushRequest_All{ All: &spb.Empty{}, diff --git a/topologies/dut_400zr.testbed b/topologies/dut_400zr.testbed new file mode 100644 index 00000000000..ce774638ff5 --- /dev/null +++ b/topologies/dut_400zr.testbed @@ -0,0 +1,23 @@ +# proto-file: github.com/openconfig/ondatra/blob/main/proto/testbed.proto +# proto-message: ondatra.Testbed + +# 1 DUT, 2 port, 400ZR optics + +duts { + id: "dut" + ports { + id: "port1" + speed: S_400GB + pmd: PMD_400GBASE_ZR + } + ports { + id: "port2" + speed: S_400GB + pmd: PMD_400GBASE_ZR + } +} + +links { + a: "dut:port1" + b: "dut:port2" +} diff --git a/topologies/kne/arista/ceos/dut.textproto b/topologies/kne/arista/ceos/dut.textproto index 89719208076..c493398cb19 100644 --- a/topologies/kne/arista/ceos/dut.textproto +++ b/topologies/kne/arista/ceos/dut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "arista-ceos-dut" nodes: { name: "dut" diff --git a/topologies/kne/arista/ceos/dutate.textproto b/topologies/kne/arista/ceos/dutate.textproto index 4c1ea45c1bd..024f2a0f542 100644 --- a/topologies/kne/arista/ceos/dutate.textproto +++ b/topologies/kne/arista/ceos/dutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "arista-ceos-dutate" nodes: { name: "dut" diff --git a/topologies/kne/arista/ceos/dutate_lag.textproto b/topologies/kne/arista/ceos/dutate_lag.textproto index 1cb7bf05a11..46e0c31907e 100644 --- a/topologies/kne/arista/ceos/dutate_lag.textproto +++ b/topologies/kne/arista/ceos/dutate_lag.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "arista-ceos-dutate-lag" nodes: { name: "dut" diff --git a/topologies/kne/arista/ceos/dutdut.textproto b/topologies/kne/arista/ceos/dutdut.textproto index 234b1002f5a..b09d232f2ab 100644 --- a/topologies/kne/arista/ceos/dutdut.textproto +++ b/topologies/kne/arista/ceos/dutdut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "arista-ceos-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/arista/ceos/dutdutate.textproto b/topologies/kne/arista/ceos/dutdutate.textproto index a39cde9d718..b864e64655f 100644 --- a/topologies/kne/arista/ceos/dutdutate.textproto +++ b/topologies/kne/arista/ceos/dutdutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "arista-ceos-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/arista/ceos/topology.textproto b/topologies/kne/arista/ceos/topology.textproto index dd99b31428a..455c4647557 100644 --- a/topologies/kne/arista/ceos/topology.textproto +++ b/topologies/kne/arista/ceos/topology.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "arista-ceos" nodes: { name: "dut1" diff --git a/topologies/kne/cisco/8000e/dut.textproto b/topologies/kne/cisco/8000e/dut.textproto index 387ae75508d..bbf34e15a6b 100644 --- a/topologies/kne/cisco/8000e/dut.textproto +++ b/topologies/kne/cisco/8000e/dut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-8000e-dut" nodes: { name: "dut" diff --git a/topologies/kne/cisco/8000e/dutate.textproto b/topologies/kne/cisco/8000e/dutate.textproto index 1fcf0ace9c3..acddea86948 100644 --- a/topologies/kne/cisco/8000e/dutate.textproto +++ b/topologies/kne/cisco/8000e/dutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-8000e-dutate" nodes: { name: "dut" diff --git a/topologies/kne/cisco/8000e/dutate_lag.textproto b/topologies/kne/cisco/8000e/dutate_lag.textproto index b614136de25..2c15f33bd75 100644 --- a/topologies/kne/cisco/8000e/dutate_lag.textproto +++ b/topologies/kne/cisco/8000e/dutate_lag.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-8000e-dutate-lag" nodes: { name: "dut" diff --git a/topologies/kne/cisco/8000e/dutdut.textproto b/topologies/kne/cisco/8000e/dutdut.textproto index aefa13ecad5..919da1e25ea 100644 --- a/topologies/kne/cisco/8000e/dutdut.textproto +++ b/topologies/kne/cisco/8000e/dutdut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-8000e-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/cisco/8000e/dutdutate.textproto b/topologies/kne/cisco/8000e/dutdutate.textproto index 35b040c836f..407d0cf2a9f 100644 --- a/topologies/kne/cisco/8000e/dutdutate.textproto +++ b/topologies/kne/cisco/8000e/dutdutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-8000e-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/cisco/8000e/topology.textproto b/topologies/kne/cisco/8000e/topology.textproto index e9564924dc8..37d3ef20aa1 100644 --- a/topologies/kne/cisco/8000e/topology.textproto +++ b/topologies/kne/cisco/8000e/topology.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-8000e" nodes: { name: "dut1" diff --git a/topologies/kne/cisco/xrd/dut.textproto b/topologies/kne/cisco/xrd/dut.textproto index 549ed5e792a..839b73c0b40 100644 --- a/topologies/kne/cisco/xrd/dut.textproto +++ b/topologies/kne/cisco/xrd/dut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-xrd-dut" nodes: { name: "dut" diff --git a/topologies/kne/cisco/xrd/dutate.textproto b/topologies/kne/cisco/xrd/dutate.textproto index 3b85637a06f..dfa65bc7d14 100644 --- a/topologies/kne/cisco/xrd/dutate.textproto +++ b/topologies/kne/cisco/xrd/dutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-xrd-dutate" nodes: { name: "dut" diff --git a/topologies/kne/cisco/xrd/dutate_lag.textproto b/topologies/kne/cisco/xrd/dutate_lag.textproto index c7028277fe7..869650071c4 100644 --- a/topologies/kne/cisco/xrd/dutate_lag.textproto +++ b/topologies/kne/cisco/xrd/dutate_lag.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-xrd-dutate-lag" nodes: { name: "dut" diff --git a/topologies/kne/cisco/xrd/dutdut.textproto b/topologies/kne/cisco/xrd/dutdut.textproto index bdcbaf6d757..4f9e85c87bf 100644 --- a/topologies/kne/cisco/xrd/dutdut.textproto +++ b/topologies/kne/cisco/xrd/dutdut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-xrd-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/cisco/xrd/dutdutate.textproto b/topologies/kne/cisco/xrd/dutdutate.textproto index b1a3593bfc8..9ec13b06ae0 100644 --- a/topologies/kne/cisco/xrd/dutdutate.textproto +++ b/topologies/kne/cisco/xrd/dutdutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-xrd-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/cisco/xrd/topology.textproto b/topologies/kne/cisco/xrd/topology.textproto index eeb90ec60b3..5995c6c31ec 100644 --- a/topologies/kne/cisco/xrd/topology.textproto +++ b/topologies/kne/cisco/xrd/topology.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "cisco-xrd" nodes: { name: "dut1" diff --git a/topologies/kne/juniper/cptx/config.cfg b/topologies/kne/juniper/cptx/config.cfg deleted file mode 100644 index 41e608213df..00000000000 --- a/topologies/kne/juniper/cptx/config.cfg +++ /dev/null @@ -1,256 +0,0 @@ -system { - host-name cptx; - root-authentication { - encrypted-password "$6$7uA5z8vs$cmHIvL0aLU4ioWAHPR0PLeU/mJj.JO/5pQVQoqRlInK3fJNTLYLhwiDi.Q6gHhltSB3S1P/.raEsuDSH7akcJ/"; ## SECRET-DATA - } - configuration { - input { - format { - json { - reorder-list-keys; - } - } - } - } - services { - ssh { - root-login allow; - } - } - schema { - openconfig { - unhide; - } - } - syslog { - file interactive-commands { - interactive-commands any; - } - file messages { - any notice; - authorization info; - } - } - fib-streaming; -} -chassis { - maximum-ecmp 128; - aggregated-devices { - ethernet { - device-count 100; - } - maximum-links 32; - } -} -interfaces { - et-0/0/0 { - number-of-sub-ports 8; - speed 25g; - } - et-0/0/1 { - number-of-sub-ports 8; - speed 25g; - } - et-0/0/2 { - number-of-sub-ports 8; - speed 25g; - } - et-0/0/3 { - number-of-sub-ports 8; - speed 25g; - } - et-0/0/4 { - number-of-sub-ports 4; - speed 25g; - } - et-0/0/5 { - unused; - } - et-0/0/6 { - number-of-sub-ports 4; - speed 25g; - } - et-0/0/7 { - unused; - } - et-0/0/8 { - number-of-sub-ports 8; - speed 25g; - } - et-0/0/9 { - number-of-sub-ports 8; - speed 25g; - } - et-0/0/10 { - number-of-sub-ports 8; - speed 25g; - } - et-0/0/11 { - number-of-sub-ports 8; - speed 25g; - } - lo0 { - unit 0 { - family inet { - address 127.0.0.1/32; - address 10.255.0.103/32 { - primary; - } - } - family inet6 { - address abcd::10:255:0:103/128 { - primary; - } - } - } - } - re0:mgmt-0 { - unit 0 { - family inet { - address FXP0ADDR; - } - } - } -} -policy-options { - policy-statement balance { - then { - load-balance per-packet; - } - } - policy-statement mpath { - then multipath-resolve; - } -} -routing-instances { - 10 { - routing-options { - resolution { - rib 10.inet.0 { - inet-resolution-ribs inet.0; - } - rib 10.inet6.0 { - inet6-resolution-ribs inet6.0; - } - } - } - } - 20 { - routing-options { - resolution { - rib 20.inet.0 { - inet-resolution-ribs inet.0; - } - rib 20.inet6.0 { - inet6-resolution-ribs inet6.0; - } - } - } - } - VRF-1 { - routing-options { - resolution { - rib VRF-1.inet.0 { - resolution-ribs inet.0; - inet6-resolution-ribs :gribi.inet6.0; - inet6-import mpath; - } - rib VRF-1.inet6.0 { - resolution-ribs [ :gribi.inet6.0 inet6.0 ]; - inet6-import mpath; - } - } - } - } - non-default-vrf { - routing-options { - resolution { - rib non-default-vrf.inet.0 { - inet6-resolution-ribs :gribi.inet6.0; - inet-import mpath; - inet6-import mpath; - } - rib non-default-vrf.inet6.0 { - inet6-resolution-ribs :gribi.inet6.0; - inet-import mpath; - inet6-import mpath; - } - } - } - } - vrf1 { - routing-options { - resolution { - rib vrf1.inet.0 { - resolution-ribs inet.0; - inet6-resolution-ribs :gribi.inet6.0; - inet6-import mpath; - } - rib vrf1.inet6.0 { - resolution-ribs [ :gribi.inet6.0 inet6.0 ]; - inet6-import mpath; - } - } - } - } - vrf2 { - routing-options { - resolution { - rib vrf2.inet.0 { - resolution-ribs inet.0; - inet6-resolution-ribs :gribi.inet6.0; - inet6-import mpath; - } - rib vrf2.inet6.0 { - resolution-ribs [ :gribi.inet6.0 inet6.0 ]; - inet6-import mpath; - } - } - } - } - vrf3 { - routing-options { - resolution { - rib vrf3.inet.0 { - resolution-ribs inet.0; - inet6-resolution-ribs :gribi.inet6.0; - inet6-import mpath; - } - rib vrf3.inet6.0 { - resolution-ribs [ :gribi.inet6.0 inet6.0 ]; - inet6-import mpath; - } - } - } - } -} -routing-options { - resolution { - preserve-nexthop-hierarchy; - rib inet.0 { - inet6-resolution-ribs :gribi.inet6.0; - inet-import mpath; - inet6-import mpath; - } - rib inet6.0 { - inet6-resolution-ribs [ inet6.0 :gribi.inet6.0 ]; - inet-import mpath; - inet6-import mpath; - } - rib :gribi.inet6.0 { - inet-resolution-ribs inet.0; - import mpath; - inet6-import mpath; - } - } - graceful-restart; - forwarding-table { - oc-tlv-support; - export balance; - } -} -protocols { - lldp { - port-id-subtype interface-name; - } -} diff --git a/topologies/kne/juniper/cptx/dut.textproto b/topologies/kne/juniper/cptx/dut.textproto deleted file mode 100644 index 56e10e82f78..00000000000 --- a/topologies/kne/juniper/cptx/dut.textproto +++ /dev/null @@ -1,20 +0,0 @@ -name: "juniper-cptx-dut" -nodes: { - name: "dut" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } -} diff --git a/topologies/kne/juniper/cptx/dutate.textproto b/topologies/kne/juniper/cptx/dutate.textproto deleted file mode 100644 index e7447a21237..00000000000 --- a/topologies/kne/juniper/cptx/dutate.textproto +++ /dev/null @@ -1,133 +0,0 @@ -name: "juniper-cptx-dutate" -nodes: { - name: "dut" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth4" - value: { - name: "et-0/0/0:0" - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } - interfaces: { - key: "eth20" - value: { - name: "et-0/0/2:0" - } - } - interfaces: { - key: "eth28" - value: { - name: "et-0/0/3:0" - } - } - interfaces: { - key: "eth36" - value: { - name: "et-0/0/4:0" - } - } - interfaces: { - key: "eth40" - value: { - name: "et-0/0/6:0" - } - } - interfaces: { - key: "eth44" - value: { - name: "et-0/0/8:0" - } - } - interfaces: { - key: "eth52" - value: { - name: "et-0/0/9:0" - } - } - interfaces: { - key: "eth60" - value: { - name: "et-0/0/10:0" - } - } -} -nodes: { - name: "otg" - vendor: KEYSIGHT - version: "0.0.1-9999" # Please update this with the local version from ixiatg-configmap.yaml -} -links: { - a_node: "otg" - a_int: "eth1" - z_node: "dut" - z_int: "eth4" -} -links: { - a_node: "dut" - a_int: "eth12" - z_node: "otg" - z_int: "eth2" -} -links: { - a_node: "dut" - a_int: "eth20" - z_node: "otg" - z_int: "eth3" -} -links: { - a_node: "dut" - a_int: "eth28" - z_node: "otg" - z_int: "eth4" -} -links: { - a_node: "dut" - a_int: "eth36" - z_node: "otg" - z_int: "eth5" -} -links: { - a_node: "dut" - a_int: "eth40" - z_node: "otg" - z_int: "eth6" -} -links: { - a_node: "dut" - a_int: "eth44" - z_node: "otg" - z_int: "eth7" -} -links: { - a_node: "dut" - a_int: "eth52" - z_node: "otg" - z_int: "eth8" -} -links: { - a_node: "dut" - a_int: "eth60" - z_node: "otg" - z_int: "eth9" -} diff --git a/topologies/kne/juniper/cptx/dutate_lag.textproto b/topologies/kne/juniper/cptx/dutate_lag.textproto deleted file mode 100644 index 283cc9ae09c..00000000000 --- a/topologies/kne/juniper/cptx/dutate_lag.textproto +++ /dev/null @@ -1,184 +0,0 @@ -name: "juniper-cptx-dutate-lag" -nodes: { - name: "dut" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth4" - value: { - name: "et-0/0/0:0" - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } - interfaces: { - key: "eth20" - value: { - name: "et-0/0/2:0" - } - } - interfaces: { - key: "eth28" - value: { - name: "et-0/0/3:0" - } - } - interfaces: { - key: "eth36" - value: { - name: "et-0/0/4:0" - } - } - interfaces: { - key: "eth40" - value: { - name: "et-0/0/6:0" - } - } - interfaces: { - key: "eth44" - value: { - name: "et-0/0/8:0" - } - } - interfaces: { - key: "eth52" - value: { - name: "et-0/0/9:0" - } - } - interfaces: { - key: "eth60" - value: { - name: "et-0/0/10:0" - } - } -} -nodes: { - name: "otg" - vendor: KEYSIGHT - version: "0.0.1-9999" # Please update this with the local version from ixiatg-configmap.yaml - interfaces: { - key: "eth1" - } - interfaces: { - key: "eth2" - value: { - group: "lag" - } - } - interfaces: { - key: "eth3" - value: { - group: "lag" - } - } - interfaces: { - key: "eth4" - value: { - group: "lag" - } - } - interfaces: { - key: "eth5" - value: { - group: "lag" - } - } - interfaces: { - key: "eth6" - value: { - group: "lag" - } - } - interfaces: { - key: "eth7" - value: { - group: "lag" - } - } - interfaces: { - key: "eth8" - value: { - group: "lag" - } - } - interfaces: { - key: "eth9" - value: { - group: "lag" - } - } -} -links: { - a_node: "otg" - a_int: "eth1" - z_node: "dut" - z_int: "eth4" -} -links: { - a_node: "dut" - a_int: "eth12" - z_node: "otg" - z_int: "eth2" -} -links: { - a_node: "dut" - a_int: "eth20" - z_node: "otg" - z_int: "eth3" -} -links: { - a_node: "dut" - a_int: "eth28" - z_node: "otg" - z_int: "eth4" -} -links: { - a_node: "dut" - a_int: "eth36" - z_node: "otg" - z_int: "eth5" -} -links: { - a_node: "dut" - a_int: "eth40" - z_node: "otg" - z_int: "eth6" -} -links: { - a_node: "dut" - a_int: "eth44" - z_node: "otg" - z_int: "eth7" -} -links: { - a_node: "dut" - a_int: "eth52" - z_node: "otg" - z_int: "eth8" -} -links: { - a_node: "dut" - a_int: "eth60" - z_node: "otg" - z_int: "eth9" -} diff --git a/topologies/kne/juniper/cptx/dutdut.textproto b/topologies/kne/juniper/cptx/dutdut.textproto deleted file mode 100644 index 60cdb0386b6..00000000000 --- a/topologies/kne/juniper/cptx/dutdut.textproto +++ /dev/null @@ -1,111 +0,0 @@ -name: "juniper-cptx-dutdut" -nodes: { - name: "dut1" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth4" - value: { - name: "et-0/0/0:0" - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } - interfaces: { - key: "eth20" - value: { - name: "et-0/0/2:0" - } - } - interfaces: { - key: "eth28" - value: { - name: "et-0/0/3:0" - } - } -} -nodes: { - name: "dut2" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth4" - value: { - name: "et-0/0/0:0" - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } - interfaces: { - key: "eth20" - value: { - name: "et-0/0/2:0" - } - } - interfaces: { - key: "eth28" - value: { - name: "et-0/0/3:0" - } - } -} -links: { - a_node: "dut1" - a_int: "eth4" - z_node: "dut2" - z_int: "eth4" -} -links: { - a_node: "dut1" - a_int: "eth12" - z_node: "dut2" - z_int: "eth12" -} -links: { - a_node: "dut1" - a_int: "eth20" - z_node: "dut2" - z_int: "eth20" -} -links: { - a_node: "dut1" - a_int: "eth28" - z_node: "dut2" - z_int: "eth28" -} diff --git a/topologies/kne/juniper/cptx/dutdutate.textproto b/topologies/kne/juniper/cptx/dutdutate.textproto deleted file mode 100644 index a1a97e39204..00000000000 --- a/topologies/kne/juniper/cptx/dutdutate.textproto +++ /dev/null @@ -1,75 +0,0 @@ -name: "juniper-cptx-dutdutate" -nodes: { - name: "dut1" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth4" - value: { - name: "et-0/0/0:0" - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } -} -nodes: { - name: "dut2" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } -} -nodes: { - name: "otg" - vendor: KEYSIGHT - version: "0.0.1-9999" # Please update this with the local version from ixiatg-configmap.yaml -} -links: { - a_node: "otg" - a_int: "eth1" - z_node: "dut1" - z_int: "eth4" -} -links: { - a_node: "dut1" - a_int: "eth12" - z_node: "dut2" - z_int: "eth12" -} - diff --git a/topologies/kne/juniper/cptx/topology.textproto b/topologies/kne/juniper/cptx/topology.textproto deleted file mode 100644 index 6f3ba017c28..00000000000 --- a/topologies/kne/juniper/cptx/topology.textproto +++ /dev/null @@ -1,383 +0,0 @@ -name: "juniper-cptx" -nodes: { - name: "dut1" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth4" - value: { - name: "et-0/0/0:0" - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } - interfaces: { - key: "eth20" - value: { - name: "et-0/0/2:0" - } - } - interfaces: { - key: "eth28" - value: { - name: "et-0/0/3:0" - } - } - interfaces: { - key: "eth36" - value: { - name: "et-0/0/4:0" - } - } - interfaces: { - key: "eth40" - value: { - name: "et-0/0/6:0" - } - } - interfaces: { - key: "eth44" - value: { - name: "et-0/0/8:0" - } - } - interfaces: { - key: "eth52" - value: { - name: "et-0/0/9:0" - } - } - interfaces: { - key: "eth60" - value: { - name: "et-0/0/10:0" - } - } - interfaces: { - key: "eth68" - value: { - name: "et-0/0/11:0" - } - } - interfaces: { - key: "eth69" - value: { - name: "et-0/0/11:1" - } - } - interfaces: { - key: "eth70" - value: { - name: "et-0/0/11:2" - } - } - interfaces: { - key: "eth71" - value: { - name: "et-0/0/11:3" - } - } -} -nodes: { - name: "dut2" - vendor: JUNIPER - model: "cptx" - os: "evo" - config: { - image: "cptx:latest" - config_path: "/home/evo/configdisk" - config_file: "juniper.conf" - file: "config.cfg" - cert: { - self_signed: { - cert_name: "grpc-server-cert" - key_name: "N/A" - key_size: 4096 - } - } - } - interfaces: { - key: "eth4" - value: { - name: "et-0/0/0:0" - } - } - interfaces: { - key: "eth12" - value: { - name: "et-0/0/1:0" - } - } - interfaces: { - key: "eth20" - value: { - name: "et-0/0/2:0" - } - } - interfaces: { - key: "eth28" - value: { - name: "et-0/0/3:0" - } - } - interfaces: { - key: "eth36" - value: { - name: "et-0/0/4:0" - } - } - interfaces: { - key: "eth40" - value: { - name: "et-0/0/6:0" - } - } - interfaces: { - key: "eth44" - value: { - name: "et-0/0/8:0" - } - } - interfaces: { - key: "eth52" - value: { - name: "et-0/0/9:0" - } - } - interfaces: { - key: "eth60" - value: { - name: "et-0/0/10:0" - } - } - interfaces: { - key: "eth68" - value: { - name: "et-0/0/11:0" - } - } - interfaces: { - key: "eth69" - value: { - name: "et-0/0/11:1" - } - } - interfaces: { - key: "eth70" - value: { - name: "et-0/0/11:2" - } - } - interfaces: { - key: "eth71" - value: { - name: "et-0/0/11:3" - } - } -} -nodes: { - name: "otg" - vendor: KEYSIGHT - version: "0.0.1-9999" # Please update this with the local version from ixiatg-configmap.yaml - interfaces: { - key: "eth1" - } - interfaces: { - key: "eth2" - value: { - group: "lag" - } - } - interfaces: { - key: "eth3" - value: { - group: "lag" - } - } - interfaces: { - key: "eth4" - value: { - group: "lag" - } - } - interfaces: { - key: "eth5" - value: { - group: "lag" - } - } - interfaces: { - key: "eth6" - value: { - group: "lag" - } - } - interfaces: { - key: "eth7" - value: { - group: "lag" - } - } - interfaces: { - key: "eth8" - value: { - group: "lag" - } - } - interfaces: { - key: "eth9" - value: { - group: "lag" - } - } -} -links: { - a_node: "otg" - a_int: "eth1" - z_node: "dut1" - z_int: "eth4" -} -links: { - a_node: "dut1" - a_int: "eth12" - z_node: "otg" - z_int: "eth2" -} -links: { - a_node: "dut1" - a_int: "eth20" - z_node: "otg" - z_int: "eth3" -} -links: { - a_node: "dut1" - a_int: "eth28" - z_node: "otg" - z_int: "eth4" -} -links: { - a_node: "dut1" - a_int: "eth36" - z_node: "otg" - z_int: "eth5" -} -links: { - a_node: "dut1" - a_int: "eth40" - z_node: "otg" - z_int: "eth6" -} -links: { - a_node: "dut1" - a_int: "eth44" - z_node: "otg" - z_int: "eth7" -} -links: { - a_node: "dut1" - a_int: "eth52" - z_node: "otg" - z_int: "eth8" -} -links: { - a_node: "dut1" - a_int: "eth60" - z_node: "otg" - z_int: "eth9" -} -links: { - a_node: "otg" - a_int: "eth10" - z_node: "dut2" - z_int: "eth4" -} -links: { - a_node: "dut2" - a_int: "eth12" - z_node: "otg" - z_int: "eth11" -} -links: { - a_node: "dut2" - a_int: "eth20" - z_node: "otg" - z_int: "eth12" -} -links: { - a_node: "dut2" - a_int: "eth28" - z_node: "otg" - z_int: "eth13" -} -links: { - a_node: "dut2" - a_int: "eth36" - z_node: "otg" - z_int: "eth14" -} -links: { - a_node: "dut2" - a_int: "eth40" - z_node: "otg" - z_int: "eth15" -} -links: { - a_node: "dut2" - a_int: "eth44" - z_node: "otg" - z_int: "eth16" -} -links: { - a_node: "dut2" - a_int: "eth52" - z_node: "otg" - z_int: "eth17" -} -links: { - a_node: "dut2" - a_int: "eth60" - z_node: "otg" - z_int: "eth18" -} -links: { - a_node: "dut1" - a_int: "eth68" - z_node: "dut2" - z_int: "eth68" -} -links: { - a_node: "dut1" - a_int: "eth69" - z_node: "dut2" - z_int: "eth69" -} -links: { - a_node: "dut1" - a_int: "eth70" - z_node: "dut2" - z_int: "eth70" -} -links: { - a_node: "dut1" - a_int: "eth71" - z_node: "dut2" - z_int: "eth71" -} diff --git a/topologies/kne/juniper/ncptx/dut.textproto b/topologies/kne/juniper/ncptx/dut.textproto index 022ef7a0ac9..91270ded372 100644 --- a/topologies/kne/juniper/ncptx/dut.textproto +++ b/topologies/kne/juniper/ncptx/dut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "juniper-ncptx-dut" nodes: { name: "dut" diff --git a/topologies/kne/juniper/ncptx/dutate.textproto b/topologies/kne/juniper/ncptx/dutate.textproto index 88b35a765df..44e80de3a7d 100644 --- a/topologies/kne/juniper/ncptx/dutate.textproto +++ b/topologies/kne/juniper/ncptx/dutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "juniper-ncptx-dutate" nodes: { name: "dut" diff --git a/topologies/kne/juniper/ncptx/dutate_lag.textproto b/topologies/kne/juniper/ncptx/dutate_lag.textproto index 75cffbe01c1..501445d4d68 100644 --- a/topologies/kne/juniper/ncptx/dutate_lag.textproto +++ b/topologies/kne/juniper/ncptx/dutate_lag.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "juniper-ncptx-dutate-lag" nodes: { name: "dut" diff --git a/topologies/kne/juniper/ncptx/dutdut.textproto b/topologies/kne/juniper/ncptx/dutdut.textproto index 18d95178b49..383dab806b1 100644 --- a/topologies/kne/juniper/ncptx/dutdut.textproto +++ b/topologies/kne/juniper/ncptx/dutdut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "juniper-ncptx-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/juniper/ncptx/dutdutate.textproto b/topologies/kne/juniper/ncptx/dutdutate.textproto index 395b14120e5..902ded8e9a3 100644 --- a/topologies/kne/juniper/ncptx/dutdutate.textproto +++ b/topologies/kne/juniper/ncptx/dutdutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "juniper-ncptx-dutdutate" nodes: { name: "dut1" diff --git a/topologies/kne/juniper/ncptx/topology.textproto b/topologies/kne/juniper/ncptx/topology.textproto index 16f7f9c227c..1ce0868d482 100644 --- a/topologies/kne/juniper/ncptx/topology.textproto +++ b/topologies/kne/juniper/ncptx/topology.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "juniper-ncptx" nodes: { name: "dut1" diff --git a/topologies/kne/nokia/srlinux/config.cfg b/topologies/kne/nokia/srlinux/config.cfg index 0f34e90c6e5..28e1788311d 100644 --- a/topologies/kne/nokia/srlinux/config.cfg +++ b/topologies/kne/nokia/srlinux/config.cfg @@ -1,64 +1,74 @@ acl { - cpm-filter { - ipv4-filter { - entry 261 { - description "Accept incoming gNMI packets with IANA port 9339" - action { - accept { - } - } - match { + acl-filter cpm type ipv4 { + entry 441 { + match { + ipv4 { protocol tcp + } + transport { destination-port { operator eq value 9339 } } } - entry 351 { - description "Accept incoming gRIBI packets with IANA port 9340" - action { - accept { - } + action { + accept { } - match { + } + } + entry 442 { + match { + ipv4 { protocol tcp + } + transport { destination-port { operator eq value 9340 } } } - } - ipv6-filter { - entry 301 { - description "Accept incoming gNMI packets with IANA port 9339" - action { - accept { - } + action { + accept { } - match { + } + } + } + acl-filter cpm type ipv6 { + entry 481 { + match { + ipv6 { next-header tcp + } + transport { destination-port { operator eq value 9339 } } } - entry 361 { - description "Accept incoming gRIBI packets with IANA port 9340" - action { - accept { - } + action { + accept { } - match { + } + } + entry 482 { + match { + ipv6 { next-header tcp + } + transport { destination-port { operator eq value 9340 } } } + action { + accept { + } + } } } } @@ -78,22 +88,52 @@ system { lldp { admin-state enable } - gnmi-server { + grpc-server mgmt { + rate-limit 65535 + session-limit 65535 + yang-models openconfig + admin-state enable + default-tls-profile true + network-instance mgmt + port 9339 + services [ + gnmi + gnsi + gnoi + ] + } + grpc-server mgmt-gribi { + admin-state enable + rate-limit 65535 + session-limit 65535 + default-tls-profile true + network-instance mgmt + port 9340 + services [ + gribi + ] + } + grpc-server mgmt-p4rt { admin-state enable - rate-limit 65000 - trace-options [ - request - response - common + rate-limit 65535 + session-limit 65535 + default-tls-profile true + network-instance mgmt + port 9559 + services [ + p4rt ] + } + json-rpc-server { + admin-state enable network-instance mgmt { - admin-state enable - port 9339 - yang-models openconfig - tls-profile kne-profile - } - unix-socket { - admin-state enable + http { + admin-state enable + } + https { + admin-state enable + tls-profile kne-profile + } } } tls { @@ -124,26 +164,6 @@ ufJifhmpItpy3mkUCLEJ33ex2AA6 " } } - gribi-server { - admin-state enable - network-instance mgmt { - admin-state enable - port 9340 - tls-profile kne-profile - } - } - json-rpc-server { - admin-state enable - network-instance mgmt { - http { - admin-state enable - } - https { - admin-state enable - tls-profile kne-profile - } - } - } banner { login-banner "................................................................ : Welcome to Nokia SR Linux! : @@ -162,20 +182,6 @@ ufJifhmpItpy3mkUCLEJ33ex2AA6 ................................................................ " } - p4rt-server { - admin-state enable - network-instance mgmt { - admin-state enable - tls-profile kne-profile - } - } } network-instance DEFAULT { } -network-instance mgmt { - protocols { - gribi { - admin-state enable - } - } -} diff --git a/topologies/kne/nokia/srlinux/dut.textproto b/topologies/kne/nokia/srlinux/dut.textproto index e899c143eb6..63b1358d791 100644 --- a/topologies/kne/nokia/srlinux/dut.textproto +++ b/topologies/kne/nokia/srlinux/dut.textproto @@ -1,8 +1,10 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "nokia-srlinux-dut" nodes: { name: "dut" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" diff --git a/topologies/kne/nokia/srlinux/dutate.textproto b/topologies/kne/nokia/srlinux/dutate.textproto index 67a6019ddda..e658396485a 100644 --- a/topologies/kne/nokia/srlinux/dutate.textproto +++ b/topologies/kne/nokia/srlinux/dutate.textproto @@ -1,8 +1,10 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "nokia-srlinux-dutate" nodes: { name: "dut" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" diff --git a/topologies/kne/nokia/srlinux/dutate_lag.textproto b/topologies/kne/nokia/srlinux/dutate_lag.textproto index d2a249aa2b5..9809cc3f623 100644 --- a/topologies/kne/nokia/srlinux/dutate_lag.textproto +++ b/topologies/kne/nokia/srlinux/dutate_lag.textproto @@ -1,8 +1,10 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "nokia-srlinux-dutate-lag" nodes: { name: "dut" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" diff --git a/topologies/kne/nokia/srlinux/dutdut.textproto b/topologies/kne/nokia/srlinux/dutdut.textproto index a7efd8f1b14..ec6b3957c93 100644 --- a/topologies/kne/nokia/srlinux/dutdut.textproto +++ b/topologies/kne/nokia/srlinux/dutdut.textproto @@ -1,8 +1,10 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "nokia-srlinux-dutdut" nodes: { name: "dut1" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" @@ -70,7 +72,7 @@ nodes: { nodes: { name: "dut2" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" diff --git a/topologies/kne/nokia/srlinux/dutdutate.textproto b/topologies/kne/nokia/srlinux/dutdutate.textproto index 01abc5af46f..f3f72c1e8b5 100644 --- a/topologies/kne/nokia/srlinux/dutdutate.textproto +++ b/topologies/kne/nokia/srlinux/dutdutate.textproto @@ -1,8 +1,10 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "nokia-srlinux-dutdut" nodes: { name: "dut1" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" @@ -58,7 +60,7 @@ nodes: { nodes: { name: "dut2" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" diff --git a/topologies/kne/nokia/srlinux/topology.textproto b/topologies/kne/nokia/srlinux/topology.textproto index 809a0f49445..b3d7fed2c37 100644 --- a/topologies/kne/nokia/srlinux/topology.textproto +++ b/topologies/kne/nokia/srlinux/topology.textproto @@ -1,8 +1,10 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "nokia-srlinux" nodes: { name: "dut1" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" @@ -100,7 +102,7 @@ nodes: { nodes: { name: "dut2" vendor: NOKIA - model: "ixr10" + model: "ixr10e" config: { image: "srlinux:latest" file: "config.cfg" diff --git a/topologies/kne/openconfig/lemming/dut.textproto b/topologies/kne/openconfig/lemming/dut.textproto index dbbbfb029ac..fac5db0ef7c 100644 --- a/topologies/kne/openconfig/lemming/dut.textproto +++ b/topologies/kne/openconfig/lemming/dut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "openconfig-lemming-dut" nodes: { name: "dut" diff --git a/topologies/kne/openconfig/lemming/dutate.textproto b/topologies/kne/openconfig/lemming/dutate.textproto index ab946444816..7c8e669516f 100644 --- a/topologies/kne/openconfig/lemming/dutate.textproto +++ b/topologies/kne/openconfig/lemming/dutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "openconfig-lemming-dutate" nodes: { name: "dut" diff --git a/topologies/kne/openconfig/lemming/dutate_lag.textproto b/topologies/kne/openconfig/lemming/dutate_lag.textproto index 72f6f440b53..829e68ddf27 100644 --- a/topologies/kne/openconfig/lemming/dutate_lag.textproto +++ b/topologies/kne/openconfig/lemming/dutate_lag.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "openconfig-lemming-dutate-lag" nodes: { name: "dut" diff --git a/topologies/kne/openconfig/lemming/dutdut.textproto b/topologies/kne/openconfig/lemming/dutdut.textproto index ce6470682da..3c9598202b2 100644 --- a/topologies/kne/openconfig/lemming/dutdut.textproto +++ b/topologies/kne/openconfig/lemming/dutdut.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "openconfig-lemming-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/openconfig/lemming/dutdutate.textproto b/topologies/kne/openconfig/lemming/dutdutate.textproto index c0b8c9423c5..6d3c7e204cd 100644 --- a/topologies/kne/openconfig/lemming/dutdutate.textproto +++ b/topologies/kne/openconfig/lemming/dutdutate.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "openconfig-lemming-dutdut" nodes: { name: "dut1" diff --git a/topologies/kne/openconfig/lemming/topology.textproto b/topologies/kne/openconfig/lemming/topology.textproto index fa137660acc..2a23223f525 100644 --- a/topologies/kne/openconfig/lemming/topology.textproto +++ b/topologies/kne/openconfig/lemming/topology.textproto @@ -1,3 +1,5 @@ +# proto-file: github.com/openconfig/kne/proto/topo.proto +# proto-message: Topology name: "openconfig-lemming" nodes: { name: "dut1" diff --git a/topologies/proto/binding.proto b/topologies/proto/binding.proto index c5703b84624..2391eb9ab51 100644 --- a/topologies/proto/binding.proto +++ b/topologies/proto/binding.proto @@ -27,6 +27,11 @@ message Binding { // Dial options across all devices, unless overridden by the device. Options options = 3; + + // Enable dynamic solving of this binding. + bool dynamic = 4; + // Links only need if dynamic solving is enabled. + repeated Link links = 5; } // Config for resetting the device before the test run. @@ -48,6 +53,7 @@ message Configs { // A device binding. message Device { // Device ID as it appears in the testbed. + // Set this value if and only dynamic solving is disabled. string id = 1; // The actual device hostname to be used for the binding. @@ -142,6 +148,7 @@ message Options { // Port binding. message Port { // Port ID as it appears in the testbed. + // Set this value if and only dynamic solving is disabled. string id = 1; // The actual port name to be used for the binding. @@ -153,3 +160,10 @@ message Port { // PMD type of the port. ondatra.Port.Pmd pmd = 4; } + +// Link between two ports. +// Links are only relevant if dynamic solving is enabled. +message Link { + string a = 1; // First port in the format ":". + string b = 2; // Second port in the format ":". +} diff --git a/topologies/proto/binding/binding.pb.go b/topologies/proto/binding/binding.pb.go index 459dde2f7cb..dcbc653959c 100644 --- a/topologies/proto/binding/binding.pb.go +++ b/topologies/proto/binding/binding.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 +// protoc-gen-go v1.32.0 // protoc v3.21.12 // source: binding.proto @@ -24,10 +24,9 @@ import ( reflect "reflect" sync "sync" + proto "github.com/openconfig/ondatra/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - - proto "github.com/openconfig/ondatra/proto" ) const ( @@ -47,6 +46,10 @@ type Binding struct { Ates []*Device `protobuf:"bytes,2,rep,name=ates,proto3" json:"ates,omitempty"` // Dial options across all devices, unless overridden by the device. Options *Options `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` + // Enable dynamic solving of this binding. + Dynamic bool `protobuf:"varint,4,opt,name=dynamic,proto3" json:"dynamic,omitempty"` + // Links only need if dynamic solving is enabled. + Links []*Link `protobuf:"bytes,5,rep,name=links,proto3" json:"links,omitempty"` } func (x *Binding) Reset() { @@ -102,6 +105,20 @@ func (x *Binding) GetOptions() *Options { return nil } +func (x *Binding) GetDynamic() bool { + if x != nil { + return x.Dynamic + } + return false +} + +func (x *Binding) GetLinks() []*Link { + if x != nil { + return x.Links + } + return nil +} + // Config for resetting the device before the test run. type Configs struct { state protoimpl.MessageState @@ -186,6 +203,7 @@ type Device struct { unknownFields protoimpl.UnknownFields // Device ID as it appears in the testbed. + // Set this value if and only dynamic solving is disabled. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The actual device hostname to be used for the binding. Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` @@ -347,7 +365,7 @@ func (x *Device) GetVendor() proto.Device_Vendor { if x != nil { return x.Vendor } - return proto.Device_VENDOR_UNSPECIFIED + return proto.Device_Vendor(0) } func (x *Device) GetHardwareModel() string { @@ -521,6 +539,7 @@ type Port struct { unknownFields protoimpl.UnknownFields // Port ID as it appears in the testbed. + // Set this value if and only dynamic solving is disabled. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The actual port name to be used for the binding. Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` @@ -580,14 +599,71 @@ func (x *Port) GetSpeed() proto.Port_Speed { if x != nil { return x.Speed } - return proto.Port_SPEED_UNSPECIFIED + return proto.Port_Speed(0) } func (x *Port) GetPmd() proto.Port_Pmd { if x != nil { return x.Pmd } - return proto.Port_PMD_UNSPECIFIED + return proto.Port_Pmd(0) +} + +// Link between two ports. +// Links are only relevant if dynamic solving is enabled. +type Link struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + A string `protobuf:"bytes,1,opt,name=a,proto3" json:"a,omitempty"` // First port in the format ":". + B string `protobuf:"bytes,2,opt,name=b,proto3" json:"b,omitempty"` // Second port in the format ":". +} + +func (x *Link) Reset() { + *x = Link{} + if protoimpl.UnsafeEnabled { + mi := &file_binding_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Link) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Link) ProtoMessage() {} + +func (x *Link) ProtoReflect() protoreflect.Message { + mi := &file_binding_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Link.ProtoReflect.Descriptor instead. +func (*Link) Descriptor() ([]byte, []int) { + return file_binding_proto_rawDescGZIP(), []int{5} +} + +func (x *Link) GetA() string { + if x != nil { + return x.A + } + return "" +} + +func (x *Link) GetB() string { + if x != nil { + return x.B + } + return "" } var File_binding_proto protoreflect.FileDescriptor @@ -598,7 +674,7 @@ var file_binding_proto_rawDesc = []byte{ 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, 0xa0, 0x01, 0x0a, 0x07, 0x42, 0x69, 0x6e, 0x64, 0x69, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xea, 0x01, 0x0a, 0x07, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x64, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x04, 0x64, 0x75, @@ -608,97 +684,104 @@ var file_binding_proto_rawDesc = []byte{ 0x65, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x7b, 0x0a, 0x07, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x6c, 0x69, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0c, 0x52, 0x03, 0x63, 0x6c, 0x69, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x69, 0x5f, 0x66, 0x69, - 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x46, 0x69, 0x6c, - 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x6e, 0x6d, 0x69, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x66, 0x69, - 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x6e, 0x6d, 0x69, 0x53, 0x65, - 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x69, 0x62, 0x69, 0x5f, 0x66, - 0x6c, 0x75, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x67, 0x72, 0x69, 0x62, - 0x69, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x22, 0xda, 0x05, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x05, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x70, - 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, - 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x33, 0x0a, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, + 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x79, 0x6e, + 0x61, 0x6d, 0x69, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x79, 0x6e, 0x61, + 0x6d, 0x69, 0x63, 0x12, 0x2e, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, + 0x6e, 0x6b, 0x73, 0x22, 0x7b, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x10, + 0x0a, 0x03, 0x63, 0x6c, 0x69, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x6c, 0x69, + 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x69, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, + 0x6e, 0x6d, 0x69, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0b, 0x67, 0x6e, 0x6d, 0x69, 0x53, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, + 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x69, 0x62, 0x69, 0x5f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x67, 0x72, 0x69, 0x62, 0x69, 0x46, 0x6c, 0x75, 0x73, 0x68, + 0x22, 0xda, 0x05, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x35, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, + 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x33, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a, 0x03, 0x73, + 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, 0x12, 0x2f, 0x0a, 0x04, 0x67, 0x6e, + 0x6d, 0x69, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x67, 0x6e, 0x6d, 0x69, 0x12, 0x2f, 0x0a, 0x04, 0x67, + 0x6e, 0x6f, 0x69, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x67, 0x6e, 0x6f, 0x69, 0x12, 0x2f, 0x0a, 0x04, + 0x67, 0x6e, 0x73, 0x69, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x67, 0x6e, 0x73, 0x69, 0x12, 0x31, 0x0a, + 0x05, 0x67, 0x72, 0x69, 0x62, 0x69, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x2d, 0x0a, 0x03, 0x73, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x03, 0x73, 0x73, 0x68, - 0x12, 0x2f, 0x0a, 0x04, 0x67, 0x6e, 0x6d, 0x69, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x05, 0x67, 0x72, 0x69, 0x62, 0x69, + 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x34, 0x72, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x67, 0x6e, 0x6d, - 0x69, 0x12, 0x2f, 0x0a, 0x04, 0x67, 0x6e, 0x6f, 0x69, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x67, 0x6e, - 0x6f, 0x69, 0x12, 0x2f, 0x0a, 0x04, 0x67, 0x6e, 0x73, 0x69, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x67, - 0x6e, 0x73, 0x69, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x72, 0x69, 0x62, 0x69, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, - 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x05, 0x67, 0x72, 0x69, 0x62, 0x69, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x34, 0x72, 0x74, 0x18, 0x10, + 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x70, 0x34, 0x72, + 0x74, 0x12, 0x39, 0x0a, 0x09, 0x69, 0x78, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x04, 0x70, 0x34, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x09, 0x69, 0x78, 0x6e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, - 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, - 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x69, 0x78, 0x6e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x12, 0x2d, 0x0a, 0x03, 0x6f, 0x74, 0x67, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x03, 0x6f, 0x74, - 0x67, 0x12, 0x2e, 0x0a, 0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x18, 0x13, 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, 0x25, 0x0a, 0x0e, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x6f, - 0x64, 0x65, 0x6c, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x68, 0x61, 0x72, 0x64, 0x77, - 0x61, 0x72, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x66, 0x74, - 0x77, 0x61, 0x72, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x15, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x22, 0xfd, 0x02, 0x0a, 0x07, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, - 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, - 0x75, 0x72, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, - 0x66, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x6b, 0x69, 0x70, 0x56, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x74, 0x69, - 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x29, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x65, 0x63, - 0x76, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x63, 0x76, 0x4d, 0x73, 0x67, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x54, 0x6c, 0x73, 0x12, - 0x2a, 0x0a, 0x11, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5f, - 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, - 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x63, 0x65, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, - 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x46, - 0x69, 0x6c, 0x65, 0x22, 0x7a, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x29, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, - 0x2e, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x72, 0x61, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x2e, 0x53, 0x70, - 0x65, 0x65, 0x64, 0x52, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x03, 0x70, 0x6d, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x72, - 0x61, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x2e, 0x50, 0x6d, 0x64, 0x52, 0x03, 0x70, 0x6d, 0x64, 0x42, - 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, - 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, - 0x69, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x52, 0x09, 0x69, 0x78, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x2d, 0x0a, 0x03, + 0x6f, 0x74, 0x67, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x03, 0x6f, 0x74, 0x67, 0x12, 0x2e, 0x0a, 0x06, 0x76, + 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x18, 0x13, 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, 0x25, 0x0a, 0x0e, 0x68, + 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x4d, 0x6f, 0x64, + 0x65, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, + 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xfd, 0x02, + 0x0a, 0x07, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x73, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x1a, + 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, + 0x29, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x5f, 0x6d, 0x73, 0x67, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x52, + 0x65, 0x63, 0x76, 0x4d, 0x73, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x75, + 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x54, 0x6c, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x5f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, + 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x46, 0x69, + 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x22, 0x7a, 0x0a, + 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x70, 0x65, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6f, 0x6e, 0x64, 0x61, 0x74, + 0x72, 0x61, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x05, 0x73, + 0x70, 0x65, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x03, 0x70, 0x6d, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x11, 0x2e, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x72, 0x61, 0x2e, 0x50, 0x6f, 0x72, 0x74, + 0x2e, 0x50, 0x6d, 0x64, 0x52, 0x03, 0x70, 0x6d, 0x64, 0x22, 0x22, 0x0a, 0x04, 0x4c, 0x69, 0x6e, + 0x6b, 0x12, 0x0c, 0x0a, 0x01, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x61, 0x12, + 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x62, 0x42, 0x40, 0x5a, + 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, + 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -713,40 +796,42 @@ func file_binding_proto_rawDescGZIP() []byte { return file_binding_proto_rawDescData } -var file_binding_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_binding_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_binding_proto_goTypes = []interface{}{ (*Binding)(nil), // 0: openconfig.testing.Binding (*Configs)(nil), // 1: openconfig.testing.Configs (*Device)(nil), // 2: openconfig.testing.Device (*Options)(nil), // 3: openconfig.testing.Options (*Port)(nil), // 4: openconfig.testing.Port - (proto.Device_Vendor)(0), // 5: ondatra.Device.Vendor - (proto.Port_Speed)(0), // 6: ondatra.Port.Speed - (proto.Port_Pmd)(0), // 7: ondatra.Port.Pmd + (*Link)(nil), // 5: openconfig.testing.Link + (proto.Device_Vendor)(0), // 6: ondatra.Device.Vendor + (proto.Port_Speed)(0), // 7: ondatra.Port.Speed + (proto.Port_Pmd)(0), // 8: ondatra.Port.Pmd } var file_binding_proto_depIdxs = []int32{ 2, // 0: openconfig.testing.Binding.duts:type_name -> openconfig.testing.Device 2, // 1: openconfig.testing.Binding.ates:type_name -> openconfig.testing.Device 3, // 2: openconfig.testing.Binding.options:type_name -> openconfig.testing.Options - 3, // 3: openconfig.testing.Device.options:type_name -> openconfig.testing.Options - 4, // 4: openconfig.testing.Device.ports:type_name -> openconfig.testing.Port - 1, // 5: openconfig.testing.Device.config:type_name -> openconfig.testing.Configs - 3, // 6: openconfig.testing.Device.ssh:type_name -> openconfig.testing.Options - 3, // 7: openconfig.testing.Device.gnmi:type_name -> openconfig.testing.Options - 3, // 8: openconfig.testing.Device.gnoi:type_name -> openconfig.testing.Options - 3, // 9: openconfig.testing.Device.gnsi:type_name -> openconfig.testing.Options - 3, // 10: openconfig.testing.Device.gribi:type_name -> openconfig.testing.Options - 3, // 11: openconfig.testing.Device.p4rt:type_name -> openconfig.testing.Options - 3, // 12: openconfig.testing.Device.ixnetwork:type_name -> openconfig.testing.Options - 3, // 13: openconfig.testing.Device.otg:type_name -> openconfig.testing.Options - 5, // 14: openconfig.testing.Device.vendor:type_name -> ondatra.Device.Vendor - 6, // 15: openconfig.testing.Port.speed:type_name -> ondatra.Port.Speed - 7, // 16: openconfig.testing.Port.pmd:type_name -> ondatra.Port.Pmd - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 5, // 3: openconfig.testing.Binding.links:type_name -> openconfig.testing.Link + 3, // 4: openconfig.testing.Device.options:type_name -> openconfig.testing.Options + 4, // 5: openconfig.testing.Device.ports:type_name -> openconfig.testing.Port + 1, // 6: openconfig.testing.Device.config:type_name -> openconfig.testing.Configs + 3, // 7: openconfig.testing.Device.ssh:type_name -> openconfig.testing.Options + 3, // 8: openconfig.testing.Device.gnmi:type_name -> openconfig.testing.Options + 3, // 9: openconfig.testing.Device.gnoi:type_name -> openconfig.testing.Options + 3, // 10: openconfig.testing.Device.gnsi:type_name -> openconfig.testing.Options + 3, // 11: openconfig.testing.Device.gribi:type_name -> openconfig.testing.Options + 3, // 12: openconfig.testing.Device.p4rt:type_name -> openconfig.testing.Options + 3, // 13: openconfig.testing.Device.ixnetwork:type_name -> openconfig.testing.Options + 3, // 14: openconfig.testing.Device.otg:type_name -> openconfig.testing.Options + 6, // 15: openconfig.testing.Device.vendor:type_name -> ondatra.Device.Vendor + 7, // 16: openconfig.testing.Port.speed:type_name -> ondatra.Port.Speed + 8, // 17: openconfig.testing.Port.pmd:type_name -> ondatra.Port.Pmd + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_binding_proto_init() } @@ -815,6 +900,18 @@ func file_binding_proto_init() { return nil } } + file_binding_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Link); 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{ @@ -822,7 +919,7 @@ func file_binding_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_binding_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/topologies/proto/generate.sh b/topologies/proto/generate.sh deleted file mode 100755 index 5d99e9e0bec..00000000000 --- a/topologies/proto/generate.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# -# 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. - -# This script is used to generate the Feature Profiles topology -# binding proto APIs. - -set -e - -# Set directory to hold symlink -mkdir -p protobuf-import -# Remove any existing symlinks & empty directories -find protobuf-import -type l -delete -find protobuf-import -type d -empty -delete -# Download the required dependencies -go mod download -# Get ondatra modules we use and create required directory structure -go list -f 'protobuf-import/{{ .Path }}' -m github.com/openconfig/ondatra | xargs -L1 dirname | sort | uniq | xargs mkdir -p -go list -f '{{ .Dir }} protobuf-import/{{ .Path }}' -m github.com/openconfig/ondatra | xargs -L1 -- ln -s - -cd "$( dirname "${BASH_SOURCE[0]}" )" - -protoc -I='../../protobuf-import' --proto_path=. --go_out=. --go_opt=module=github.com/openconfig/featureprofiles/topologies/proto *.proto