diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93d40f8b043..70a8a814170 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,16 +1,30 @@ # 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 # review when someone opens a pull request. -* @openconfig/featureprofiles-maintainers @openconfig/featureprofiles-quattro-tl +* @openconfig/featureprofiles-maintainers -# 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/bgp/ @dplore +/feature/ethernet/ @ram-mac +/feature/interface/ @ram-mac +/feature/isis/ @rohit-rp +/feature/mpls/ @swetha-haridasula +/feature/mtu/ @swetha-haridasula +/feature/networkinstance/ @swetha-haridasula +/feature/policy_forwarding/ @swetha-haridasula +/feature/qos @sezhang2 +/feature/routing_policy/ @swetha-haridasula +/feature/security @mihirpitale-googler +/feature/staticroute/ @swetha-haridasula +/feature/system @self-maurya +/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 1e017cf7a91..6ab972f2897 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -23,7 +23,6 @@ jobs: ~/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: @@ -41,7 +40,6 @@ jobs: ~/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 @@ -65,6 +63,8 @@ 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 - name: Cache @@ -75,7 +75,6 @@ jobs: ~/.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,9 +98,11 @@ 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@v1.3.4 - name: Run revive diff --git a/.github/workflows/nosimage.yml b/.github/workflows/nosimage.yml index 73038133b49..0be4a982ccd 100644 --- a/.github/workflows/nosimage.yml +++ b/.github/workflows/nosimage.yml @@ -21,21 +21,22 @@ jobs: - name: Generate Examples and Check No Diff run: | cd tools/nosimage - go run example/generate_example.go -file-path example/example_nosimageprofile.textproto - go run example/generate_example.go -file-path example/example_nosimageprofile_invalid.textproto -invalid + 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/example_nosimageprofile.textproto; rm -rf tmp + go run validate/validate.go -file example/valid_example_nosimageprofile.textproto; rm -rf tmp - name: Validate Bad Example run: | cd tools/nosimage - if go run validate/validate.go -file example/example_nosimageprofile_invalid.textproto; then - echo "Validation passed, but failure expected" - exit 1 - fi + 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..e645507fc8e 100644 --- a/.github/workflows/protobufs.yml +++ b/.github/workflows/protobufs.yml @@ -25,7 +25,6 @@ jobs: ~/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 with: @@ -34,56 +33,16 @@ jobs: - 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@latest + 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 @@ -104,7 +63,6 @@ jobs: ~/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 run: make openconfig_public - name: Validate Paths diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 141a200e01d..5365ed1ee1c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,10 +1,11 @@ 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@v3 - name: Setup Perl uses: perl-actions/install-with-cpanm@v1 with: @@ -14,9 +15,24 @@ jobs: uses: actions/checkout@v3 - 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@v3 - 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 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..b757f475a66 --- /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@v3 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + 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/experimental/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/wiki.yml b/.github/workflows/wiki.yml index c12394a9d6e..2c6e0644c11 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -9,11 +9,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: featureprofiles - name: Checkout wiki - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: "openconfig/featureprofiles.wiki" path: featureprofiles.wiki @@ -28,7 +28,6 @@ jobs: ~/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/CONTRIBUTING.md b/CONTRIBUTING.md index 75baf7ac223..5875bebd432 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,15 +62,17 @@ 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. +* `feature/experimental` contains tests which have automation which is + not confirmed to pass on any hardware platform or software release. + When the test automation is passing against at least one DUT, + 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 +96,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 +107,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 +126,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 +178,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 +190,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 +220,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,44 +341,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. - -* 20.0.0.1/15: data plane source network addresses used for scale testing. - -* 30.0.0.1/15: data plane source network addresses used for scale testing. - -* 100.0.0.0/12: data plane source network addresses used for scale testing. - -* 138.0.11.0/24: data plane source network addresses used for traffic testing; split as needed. - -* 192.51.100.1/24: data plane source network addresses used for traffic testing; split as needed. - -* 192.51.129.0/22: data plane source network addresses used for traffic testing; split as needed. - -* 192.55.200.3/32: data plane source network addresses used for traffic testing; split as needed. - -* 198.100.200.123/24: data plane source network addresses used for traffic testing; split as needed. +* 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. @@ -384,6 +372,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 @@ -442,8 +433,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. @@ -490,7 +479,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 b77dbdd4c2b..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. @@ -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 fe742cbd8c8..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 ;; @@ -114,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..4a4abfb4830 100644 --- a/doc/test-requirements-template.md +++ b/doc/test-requirements-template.md @@ -4,7 +4,6 @@ about: Use this template to document the requirements for a new test to be imple title: '' labels: enhancement assignees: '' - --- # Instructions for this template @@ -28,40 +27,39 @@ Write a few sentences or paragraphs describing the purpose and scope of the 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 + * 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 + * 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 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 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/aft/aft_summary/otg_tests/route_summary_counters_test/README.md b/feature/aft/aft_summary/otg_tests/route_summary_counters_test/README.md index 96bf3776060..5b675caf6ce 100644 --- a/feature/aft/aft_summary/otg_tests/route_summary_counters_test/README.md +++ b/feature/aft/aft_summary/otg_tests/route_summary_counters_test/README.md @@ -14,15 +14,24 @@ Establish eBGP sessions between ATE:port1 and DUT:port1 and another between ATE: * 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 -## Config 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. -## Telemetry Parameter Coverage +```yaml +paths: + ## Config 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 + ## 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: -## Protocol/RPC Parameter Coverage +rpcs: + gnmi: + gNMI.Subscribe: +``` + +## Control Protocol Coverage BGP IS-IS @@ -30,4 +39,3 @@ IS-IS ## Minimum DUT Platform Requirement vRX - diff --git a/feature/aft/aft_summary/otg_tests/route_summary_counters_test/metadata.textproto b/feature/aft/aft_summary/otg_tests/route_summary_counters_test/metadata.textproto index 7decf64f6e7..617ae227d00 100644 --- a/feature/aft/aft_summary/otg_tests/route_summary_counters_test/metadata.textproto +++ b/feature/aft/aft_summary/otg_tests/route_summary_counters_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: "ef34466c-37da-4133-8d03-40ebe2a5168a" @@ -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 diff --git a/feature/aft/aft_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go b/feature/aft/aft_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go index ee6193d047e..e0a77d6d5e1 100644 --- a/feature/aft/aft_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go +++ b/feature/aft/aft_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go @@ -131,14 +131,12 @@ func TestRouteSummaryWithISIS(t *testing.T) { }).Await(t) dni := deviations.DefaultNetworkInstance(ts.DUT) - ipv4Entry := gnmi.Get(t, ts.DUT, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv4Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS).Counters().AftEntries().State()) - if ipv4Entry == 0 { - t.Errorf("ipv4 BGP entries, got: %d, want: %d", ipv4Entry, prefixesCount) + 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) } - ipv6Entry := gnmi.Get(t, ts.DUT, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv6Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS).Counters().AftEntries().State()) - if ipv6Entry == 0 { - t.Errorf("ipv6 BGP entries, got: %d, want: %d", ipv6Entry, 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) } } @@ -218,14 +216,12 @@ func TestRouteSummaryWithBGP(t *testing.T) { dni := deviations.DefaultNetworkInstance(dut) if tc.dut.ipv4 { - ipv4Entry := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv4Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP).Counters().AftEntries().State()) - if ipv4Entry == 0 { - t.Errorf("ipv4 BGP entries, got: %d, want: %d", ipv4Entry, prefixesCount) + 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 { - ipv6Entry := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(dni).Afts().AftSummaries().Ipv6Unicast().Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP).Counters().AftEntries().State()) - if ipv6Entry == 0 { - t.Errorf("ipv4 BGP entries, got: %d, want: %d", ipv6Entry, prefixesCount) + 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) } } }) @@ -339,7 +335,7 @@ func (d *dutData) Configure(t *testing.T, dut *ondatra.DUTDevice) { if !d.ipv4 { aftType = oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST } - bgpOC := cfgplugins.BuildBGPOCConfig(t, dut, d.routerID, aftType, d.neighborConfig) + 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() 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/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..10bfe4f8f01 --- /dev/null +++ b/feature/bgp/admin_distance/otg_tests/admin_distance_test/admin_distance_test.go @@ -0,0 +1,332 @@ +// 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/ondatra/otg" + "github.com/openconfig/ygot/ygot" +) + +const ( + prefixV4Len = uint32(24) + prefixV6Len = uint32(64) + v4Network = "192.168.10.0" + v6Network = "2024:db8:64:64::" + pathID = 1 + prefixesCount = 1 + bgpName = "BGP" + dutAS = uint32(64656) + ateAS = uint32(64657) + peerGrpNamev4 = "BGP-PEER-GROUP-V4" + peerGrpNamev6 = "BGP-PEER-GROUP-V6" + ateSysID = "640000000001" + ateAreaAddress = "49.0002" + 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 = ipAddr{address: v4Network, prefix: prefixV4Len} + advertisedIPv6 ipAddr = 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(t, 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, ts.ATE.OTG(), false) + createFlow(t, ts.ATETop, ts.ATE.OTG(), 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") + + 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, otg *otg.OTG, 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(t *testing.T, 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/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/README.md b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/README.md similarity index 85% rename from feature/bgp/bgp_isis_redistribution/README.md rename to feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/README.md index 8ea4b6f5a39..5a9fe642af5 100644 --- a/feature/bgp/bgp_isis_redistribution/README.md +++ b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/README.md @@ -16,13 +16,13 @@ * 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-1 -* Create an IPv6 networks i.e. ```ipv6-network = 2024:db8:128:128::/64``` attached to ATE port-1 -* Configure IPv4 and IPv6 eBGP between DUT Port-1 and ATE Port-1 +* 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-2 and DUT 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 @@ -119,8 +119,8 @@ ##### 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-2 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` -* Validate that the traffic is received on ATE port-1 +* 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 @@ -132,7 +132,7 @@ * /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/actions/bgp-actions/set-community/reference/config/community-set-ref + * /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 @@ -155,8 +155,8 @@ * 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-2 to the DUT and destined to ```ipv4-network``` i.e. ```192.168.10.0/24``` -* Validate that the traffic is received on ATE port-1 +* 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 @@ -248,8 +248,8 @@ ##### 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-2 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` -* Validate that the traffic is received on ATE port-1 +* 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 @@ -261,7 +261,7 @@ * /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/actions/bgp-actions/set-community/reference/config/community-set-ref + * /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 @@ -284,8 +284,8 @@ * 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-2 to the DUT and destined to ```ipv6-network``` i.e. ```2024:db8:128:128::/64``` -* Validate that the traffic is received on ATE port-1 +* 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 @@ -355,3 +355,57 @@ ## 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..00ff8bae5f0 --- /dev/null +++ b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/bgp_isis_redistribution_test.go @@ -0,0 +1,906 @@ +// 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) + 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) + + // enable bgp isis redistribution + bgpISISRedistribution(t, dut) +} + +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) + } + 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") + } + if deviations.BgpCommunityMemberIsAString(dut) { + cm := nonMatchingCommunityVal + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(cm))) { + t.Errorf("Community set member: %v, want: %s", commSetMember, cm) + } + } else { + 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.UnionUint32(cm))) { + t.Errorf("Community set member: %v, want: %d", commSetMember, 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") + } + if deviations.BgpCommunityMemberIsAString(dut) { + cm := matchingCommunityVal + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(cm))) { + t.Errorf("Community set member: %v, want: %v", commSetMember, cm) + } + } else { + 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.UnionUint32(cm))) { + t.Errorf("Community set member: %v, want: %v", commSetMember, 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) + + // enable bgp isis redistribution + bgpISISRedistributionV6(t, dut) +} + +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) + } + 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") + } + if deviations.BgpCommunityMemberIsAString(dut) { + cm := nonMatchingCommunityVal + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(cm))) { + t.Errorf("Community set member: %v, want: %v", commSetMember, cm) + } + } else { + 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.UnionUint32(cm))) { + t.Errorf("Community set member: %v, want: %d", commSetMember, 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") + } + if deviations.BgpCommunityMemberIsAString(dut) { + cm := matchingCommunityVal + if commSetMember := commSet.GetCommunityMember(); len(commSetMember) == 0 || !containsValue(commSetMember, oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union(oc.UnionString(cm))) { + t.Errorf("Community set member: %v, want: %v", commSetMember, cm) + } + } else { + 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.UnionUint32(cm))) { + t.Errorf("Community set member: %v, want: %v", commSetMember, 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) { + 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 !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) +} + +func bgpISISRedistributionV6(t *testing.T, dut *ondatra.DUTDevice) { + 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 !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) +} +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..f4552e8cdf0 --- /dev/null +++ b/feature/bgp/bgp_isis_redistribution/otg_tests/bgp_isis_redistribution_test/metadata.textproto @@ -0,0 +1,47 @@ +# 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 + bgp_conditions_match_community_set_unsupported: 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_configuration_test/README.md b/feature/bgp/bgp_session_mode_configuration_test/README.md new file mode 100644 index 00000000000..bba3cecb4d8 --- /dev/null +++ b/feature/bgp/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/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 a990301af8a..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 @@ -102,7 +102,6 @@ The origial RFC4724 had no coverage for Graceful restart process post send/recei * Start traffic from ATE Port1 towards ATE Port2. Confirm there is zero packet loss. Stop traffic. * Revert ATE configurtion blocking TCP connection to/from DUT over TCP-Port:179 so the EBGP peering between ATE:Port1 <> DUT:port1 is reestablished. Restart traffic and confirm that there is zero packet loss. * Restart the above procedure for the IBGP peering between DUT port-2 and ATE port-2 - ## Config Parameter Coverage For prefixes: @@ -140,3 +139,15 @@ BGP conifguration: * /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/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 088fa111aa5..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" 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 index 2f18a6b9469..56c9dd115bb 100644 --- a/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/README.md +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/README.md @@ -41,7 +41,7 @@ Validate BGP in multipath scenario * 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/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ebgp/config/maximum-paths + * /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 @@ -58,8 +58,8 @@ Validate BGP in multipath scenario * 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/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ebgp/config/allow-multiple-as - * /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ebgp/config/maximum-paths + * /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 @@ -72,8 +72,8 @@ Validate BGP in multipath scenario ## 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/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ebgp/config/allow-multiple-as -* /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ebgp/config/maximum-paths +* /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 @@ -82,11 +82,14 @@ Validate BGP in multipath scenario * /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 -## Protocol/RPC Parameter Coverage +## OpenConfig Path and RPC Coverage -* gNMI - * Set - * Subscribe +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: +``` ## Required DUT platform 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 index 95c38d00260..2354ccc83ac 100644 --- 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 @@ -20,6 +20,8 @@ import ( "testing" "time" + "math/rand" + "github.com/open-traffic-generator/snappi/gosnappi" "github.com/openconfig/featureprofiles/internal/cfgplugins" "github.com/openconfig/featureprofiles/internal/deviations" @@ -37,8 +39,8 @@ const ( prefixesCount = 4 pathID = 1 maxPaths = 2 - trafficPps = 1000 - totalPackets = 120000 + trafficPps = 100000 + totalPackets = 12000000 lossTolerancePct = 0 lbToleranceFms = 20 ) @@ -69,10 +71,24 @@ func configureOTG(t *testing.T, bs *cfgplugins.BGPSession) { bgp4PeerRoute.AddPath().SetPathId(pathID) } - configureFlow(bs) + 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(bs *cfgplugins.BGPSession) { +func configureFlow(t *testing.T, bs *cfgplugins.BGPSession) { bs.ATETop.Flows().Clear() var rxNames []string @@ -91,11 +107,17 @@ func configureFlow(bs *cfgplugins.BGPSession) { 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) @@ -113,12 +135,15 @@ func verifyECMPLoadBalance(t *testing.T, ate *ondatra.ATEDevice, pc int, expecte t.Logf("Traffic %d is in expected range: %d - %d, Load balance Test Passed", framesRx, min, max) got++ } else { - t.Errorf("Traffic is expected in range %d - %d but got %d. Load balance Test Failed", min, max, framesRx) + if !deviations.BgpMaxMultipathPathsUnsupported(dut) { + t.Errorf("Traffic is expected in range %d - %d but got %d. Load balance Test Failed", min, max, framesRx) + } } } - - if got != expectedLinks { - t.Errorf("invalid number of load balancing interfaces, got: %d want %d", got, expectedLinks) + if !deviations.BgpMaxMultipathPathsUnsupported(dut) { + if got != expectedLinks { + t.Errorf("invalid number of load balancing interfaces, got: %d want %d", got, expectedLinks) + } } } @@ -167,35 +192,47 @@ func TestBGPSetup(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount4) - bs.WithEBGP(t, oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, true, !tc.enableMultiAS) + bs := cfgplugins.NewBGPSession(t, cfgplugins.PortCount4, nil) + bs.WithEBGP(t, []oc.E_BgpTypes_AFI_SAFI_TYPE{oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST}, []string{"port2", "port3", "port4"}, true, !tc.enableMultiAS) dni := deviations.DefaultNetworkInstance(bs.DUT) - niProtocol := bs.DUTConf.GetOrCreateNetworkInstance(dni).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - pgUseMulitplePaths := niProtocol.Bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateUseMultiplePaths() + 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() + pgUseMulitplePaths := bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths() if tc.enableMultipath { + t.Logf("Enable Multipath") pgUseMulitplePaths.Enabled = ygot.Bool(true) - pgUseMulitplePaths.GetOrCreateEbgp().MaximumPaths = ygot.Uint32(maxPaths) + t.Logf("Enable Maximum Paths") + gEBGP.MaximumPaths = ygot.Uint32(maxPaths) } - if tc.enableMultiAS { - pgUseMulitplePaths.GetOrCreateEbgp().AllowMultipleAs = ygot.Bool(true) + 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") - bs.VerifyDUTBGPEstablished(t) + cfgplugins.VerifyDUTBGPEstablished(t, bs.DUT) t.Logf("Verify OTG BGP sessions up") - bs.VerifyOTGBGPEstablished(t) + cfgplugins.VerifyOTGBGPEstablished(t, bs.ATE) aftsPath := gnmi.OC().NetworkInstance(dni).Afts() prefix := prefixesStart + "/" + strconv.Itoa(prefixP4Len) 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), tc.expectedPaths; got != want { - t.Errorf("prefix: %s, found %d hops, want %d", ipv4Entry.GetPrefix(), got, want) + if deviations.BgpMaxMultipathPathsUnsupported(bs.DUT) { + tc.expectedPaths = 3 + } else { + if got, want := len(hopGroup.NextHop), tc.expectedPaths; got != want { + t.Errorf("prefix: %s, found %d hops, want %d", ipv4Entry.GetPrefix(), got, want) + } } sleepTime := time.Duration(totalPackets/trafficPps) + 5 diff --git a/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto index 615c5d709e9..7c594707925 100644 --- a/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto @@ -11,6 +11,15 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + skip_afi_safi_path_for_bgp_multiple_as: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_max_multipath_paths_unsupported: true } } platform_exceptions: { @@ -33,6 +42,8 @@ platform_exceptions: { interface_enabled: true default_network_instance: "default" missing_value_for_defaults: true + skip_setting_allow_multiple_as: false } } 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 index 1bdf9d55f49..fb8c4ec0341 100644 --- 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 @@ -57,12 +57,14 @@ Validate BGP in multipath UCMP support with link bandwidth community * /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 -## Protocol/RPC Parameter Coverage - -* gNMI - * Set - * Subscribe - +## 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_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..3ab2c1cc4fd --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/bgp_multipath_wecmp_test.go @@ -0,0 +1,208 @@ +// 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 = 5 +) + +var linkBw = []int{10, 5} + +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) + bgpExtCom := bgp4PeerRoute.ExtendedCommunities().Add() + bgpExtCom.NonTransitive2OctetAsType().LinkBandwidthSubtype().SetBandwidth(float32(linkBw[i-2] * 1000)) + } + + configureFlow(bs) +} + +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()) + expectedPerLinkFmsP3 := int(float32(linkBw[0]) / (float32(linkBw[0] + linkBw[1])) * float32(framesTx)) + expectedPerLinkFmsP4 := int(float32(linkBw[1]) / (float32(linkBw[0] + linkBw[1])) * 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() + 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 { + t.Logf("AfiSafi Path For BgpMultipleAs is supported") + gEBGP := bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + if !deviations.SkipSettingAllowMultipleAS(bs.DUT) { + gEBGP.AllowMultipleAs = ygot.Bool(true) + } + } + bgp.GetOrCreatePeerGroup(cfgplugins.BGPPeerGroup1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().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) + + aftsPath := gnmi.OC().NetworkInstance(dni).Afts() + prefix := prefixesStart + "/" + strconv.Itoa(prefixP4Len) + 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 { + t.Logf("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), 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..cba5b2008ac --- /dev/null +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/metadata.textproto @@ -0,0 +1,50 @@ +# 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 + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + multipath_unsupported_neighbor_or_afisafi: 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" + missing_value_for_defaults: true + skip_setting_allow_multiple_as: false + } +} +tags: TAGS_DATACENTER_EDGE + 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..d083febdadf --- /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,845 @@ +// 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 ( + "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/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.0" + advertisedRoutesv6Net1 = "2024:db8:128:128::" + advertisedRoutesv4Net2 = "192.168.20.0" + advertisedRoutesv6Net2 = "2024:db8:64:64::" + advertisedRoutesv4PrefixLen = 24 + advertisedRoutesv6PrefixLen = 64 + 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") +} + +// 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, "+") { + actions.GetOrCreateBgpActions().SetMed = oc.UnionString(policyValue) + } 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() + if deviations.DefaultRoutePolicyUnsupported(dut) { + // deleteBGPImportExportPolicy on port2 needed when default policy is not supported + 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(), + 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, 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: + 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), 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), 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(), + 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 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: + 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), 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), 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/prefixlimit/ate_tests/bgp_prefix_limit_test/metadata.textproto b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto similarity index 62% rename from feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/metadata.textproto rename to feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto index 8e53ae5c154..24be17ffe8b 100644 --- a/feature/bgp/prefixlimit/ate_tests/bgp_prefix_limit_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto @@ -1,16 +1,21 @@ # 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" -testbed: TESTBED_DUT_ATE_2LINKS +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: { @@ -21,7 +26,6 @@ platform_exceptions: { explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true - bgp_explicit_prefix_limit_received: true } } platform_exceptions: { @@ -31,8 +35,8 @@ platform_exceptions: { deviations: { route_policy_under_afi_unsupported: true omit_l2_mtu: true - bgp_tolerance_value: 2 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 index b2e352d5b15..8f434b0bcb4 100644 --- a/feature/bgp/policybase/otg_tests/aspath_and_community_test/README.md +++ b/feature/bgp/policybase/otg_tests/aspath_and_community_test/README.md @@ -1,4 +1,4 @@ -# RT-2.4: BGP Policy AS Path Set and Community Set +# RT-7.4: BGP Policy AS Path Set and Community Set ## Summary @@ -6,7 +6,7 @@ BGP policy configuration for AS Paths and Community Sets ## Procedure -* RT-2.4.1 - Test setup +* 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. @@ -22,7 +22,7 @@ BGP policy configuration for AS Paths and Community Sets * Generate traffic from ATE port-2 to all prefixes * Validate that traffic is received on ATE port-1 for all prefixes -* RT-2.4.1 - Validate single routing-policy containing as-path-set and ext-community-set +* 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)" ] }` @@ -126,3 +126,53 @@ import-policy * /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 index 4abaad5f826..fc8bcf61ac6 100644 --- a/feature/bgp/policybase/otg_tests/aspath_and_community_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/aspath_and_community_test/metadata.textproto @@ -1,7 +1,38 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -plan_id: "RT-2.4" + +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_AGGREGATION, TAGS_TRANSIT, TAGS_DATACENTER_EDGE \ No newline at end of file +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 + bgp_conditions_match_community_set_unsupported: 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 index 95bb2d6c24f..24771c17653 100644 --- a/feature/bgp/policybase/otg_tests/aspath_test/README.md +++ b/feature/bgp/policybase/otg_tests/aspath_test/README.md @@ -1,4 +1,4 @@ -# RT-2.2: BGP Policy AS Path Set +# RT-7.3: BGP Policy AS Path Set ## Summary @@ -6,7 +6,7 @@ BGP policy configuration for AS Paths and Community Sets ## Procedure -* RT-2.2.1 - Test setup +* 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 @@ -23,7 +23,7 @@ BGP policy configuration for AS Paths and Community Sets * Generate traffic from ATE port-2 to all prefixes * Validate that traffic is received on ATE port-1 for all installed prefixes -* RT-2.2.2 - Configure as-path-sets +* 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 @@ -57,7 +57,7 @@ BGP policy configuration for AS Paths and Community Sets * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY * actions/config/policy-result = ACCEPT_ROUTE -* RT-2.2.7 - Replace /routing-policy DUT config +* 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 @@ -117,3 +117,23 @@ import-policy * /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 index 61d04df2354..722a02f9a02 100644 --- a/feature/bgp/policybase/otg_tests/aspath_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/aspath_test/metadata.textproto @@ -1,7 +1,32 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -plan_id: "RT-2.2" +uuid: "f4387453-37e8-4c2b-99c3-24b13cc51fd1" +plan_id: "RT-7.3" description: "BGP Policy AS Path Set" testbed: TESTBED_DUT_ATE_2LINKS -tags: TAGS_AGGREGATION, TAGS_TRANSIT, TAGS_DATACENTER_EDGE \ No newline at end of file +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 + bgp_conditions_match_community_set_unsupported: 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..b6d656dc70e --- /dev/null +++ b/feature/bgp/policybase/otg_tests/comm_match_action_test/metadata.textproto @@ -0,0 +1,35 @@ +# 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_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true + bgp_conditions_match_community_set_unsupported: true + bgp_actions_set_community_method_unsupported: true + skip_bgp_send_community_type: true + bgp_community_set_refs_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + bgp_conditions_match_community_set_unsupported: true + } +} +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 index d879931d4d3..f1911b3c359 100644 --- a/feature/bgp/policybase/otg_tests/community_test/README.md +++ b/feature/bgp/policybase/otg_tests/community_test/README.md @@ -1,12 +1,12 @@ -# RT-2.3: BGP Policy Community Set +# RT-7.2: BGP Policy Community Set ## Summary -BGP policy configuration for AS Paths and Community Sets +BGP policy configuration for Community Sets ## Subtests -* RT-2.3.1 - Setup BGP sessions +* 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: @@ -21,7 +21,7 @@ BGP policy configuration for AS Paths and Community Sets * Validate that traffic can be received on ATE port-1 for all installed routes. -* RT-2.3.2 - Validate community-set +* 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" ] }` @@ -38,22 +38,43 @@ BGP policy configuration for AS Paths and Community Sets * 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-as-path-set/config/as-path-set = 'all_3_comms' - * conditions/bgp-conditions/match-as-path-set/config/match-set-options = ALL + * 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-as-path-set/config/as-path-set = 'no_3_comms' - * conditions/bgp-conditions/match-as-path-set/config/match-set-options = INVERT + * 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-as-path-set/config/as-path-set = 'all_3_comms' - * conditions/bgp-conditions/match-as-path-set/config/match-set-options = ANY + * 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 | @@ -63,8 +84,6 @@ BGP policy configuration for AS Paths and Community Sets | prefix-set-3 | reject | reject | accept | accept | | prefix-set-4 | reject | reject | accept | reject | -* TODO: add coverage for link-bandwidth community in separate test. - ## Config Parameter Coverage ### Policy definition @@ -77,7 +96,9 @@ BGP policy configuration for AS Paths and Community Sets * /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 +* /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 @@ -106,3 +127,53 @@ import-policy * /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 index 36bfdc44fab..4c6b8f1496e 100644 --- a/feature/bgp/policybase/otg_tests/community_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/community_test/metadata.textproto @@ -1,7 +1,51 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -plan_id: "RT-2.3" +uuid: "4330effe-a20c-42fb-8372-80fb0901a325" +plan_id: "RT-7.2" description: "BGP Policy Community Set" testbed: TESTBED_DUT_ATE_2LINKS -tags: TAGS_AGGREGATION, TAGS_TRANSIT, TAGS_DATACENTER_EDGE \ No newline at end of file +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 + bgp_conditions_match_community_set_unsupported: 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 index f654c3f7d2c..60178b91633 100644 --- a/feature/bgp/policybase/otg_tests/default_policies_test/bgp_default_policies_test.go +++ b/feature/bgp/policybase/otg_tests/default_policies_test/bgp_default_policies_test.go @@ -39,53 +39,54 @@ func TestMain(m *testing.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" + 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 ( @@ -162,6 +163,17 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { 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) { @@ -180,6 +192,13 @@ func configurePrefixMatchPolicy(t *testing.T, dut *ondatra.DUTDevice, prefixSet, 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) @@ -352,10 +371,13 @@ func configureOTG(t *testing.T, otg *otg.OTG) { 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") @@ -364,10 +386,13 @@ func configureOTG(t *testing.T, otg *otg.OTG) { 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") @@ -376,10 +401,13 @@ func configureOTG(t *testing.T, otg *otg.OTG) { 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") @@ -388,16 +416,19 @@ func configureOTG(t *testing.T, otg *otg.OTG) { 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(30 * time.Second) + time.Sleep(40 * time.Second) otg.StartProtocols(t) - time.Sleep(30 * time.Second) + time.Sleep(40 * time.Second) } func verifyBGPCapabilities(t *testing.T, dut *ondatra.DUTDevice) { @@ -425,7 +456,6 @@ func verifyBGPCapabilities(t *testing.T, dut *ondatra.DUTDevice) { 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) @@ -435,15 +465,25 @@ func verifyPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr string, w } else { prefixPath = statePath.Neighbor(nbr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Prefixes() } - if gotInstalled := gnmi.Get(t, dut, prefixPath.Installed().State()); gotInstalled != wantInstalled { + 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 := gnmi.Get(t, dut, prefixPath.ReceivedPrePolicy().State()); gotRx != wantRx { + 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 := gnmi.Get(t, dut, prefixPath.Sent().State()); gotSent != wantSent { + 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) } } @@ -468,6 +508,7 @@ func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName []string, dutA 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) } @@ -486,6 +527,16 @@ func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName []string, dutA 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) } @@ -540,70 +591,154 @@ func deleteBGPPolicy(t *testing.T, dut *ondatra.DUTDevice, nbrList []*bgpNbrList } } +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) { - configureBGPDefaultPolicy(t, dut, rejectRoute) + 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: 3, wantRxPrePoloicy: 3, wantSent: 1, isV4: true}) + 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: 3, wantRxPrePoloicy: 3, wantSent: 1, isV4: false}) + 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: 3, wantRxPrePoloicy: 3, wantSent: 1, isV4: true}) + 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: 3, wantRxPrePoloicy: 3, wantSent: 1, isV4: false}) + 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) { - configureBGPDefaultPolicy(t, dut, acceptRoute) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: true}) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: false}) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: true}) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: false}) + 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) { - configureBGPDefaultPolicy(t, dut, acceptRoute) - }) - - t.Run("Delete prefix set policies", func(t *testing.T) { - deleteBGPPolicy(t, dut, []*bgpNbrList{ebgpNbrV4, ebgpNbrV6, ibgpNbrV4, ibgpNbrV6}) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: true}) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: false}) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: true}) + 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, wantRxPrePoloicy: 3, wantSent: 3, isV4: false}) + wantInstalled: 3, wantRx: 3, wantRxPrePolicy: 3, wantSent: 3, isV4: false}) } func testNoPolicyConfiguredIBGPPeer(t *testing.T, dut *ondatra.DUTDevice) { @@ -622,21 +757,25 @@ 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) { - configureBGPDefaultPolicy(t, dut, rejectRoute) + 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: 3, wantRxPrePoloicy: 3, wantSent: 0, isV4: true}) + 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: 3, wantRxPrePoloicy: 3, wantSent: 0, isV4: false}) + 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: 3, wantRxPrePoloicy: 3, wantSent: 0, isV4: true}) + 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: 3, wantRxPrePoloicy: 3, wantSent: 0, isV4: false}) + wantInstalled: 0, wantRx: 0, wantRxPrePolicy: 3, wantSent: 0, isV4: false}) } func configureRoutePolicies(t *testing.T, dut *ondatra.DUTDevice) { @@ -668,11 +807,11 @@ func configureRoutePolicies(t *testing.T, dut *ondatra.DUTDevice) { } type peerDetails struct { - ipAddr string - defExportPol, defImportPol oc.E_RoutingPolicy_DefaultPolicyType - exportPol, importPol []string - wantInstalled, wantRx, wantRxPrePoloicy, wantSent uint32 - isV4 bool + 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) { @@ -690,12 +829,14 @@ func verifyPostPolicyPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr * peerTel := gnmi.Get(t, dut, afiSafiPath.State()) - if gotDefExPolicy := peerTel.GetApplyPolicy().GetDefaultExportPolicy(); gotDefExPolicy != nbr.defExportPol { - t.Errorf("Default export policy type mismatch: got %v, want %v", gotDefExPolicy, nbr.defExportPol) - } + 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 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) != "" { @@ -708,19 +849,33 @@ func verifyPostPolicyPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr * } } - if gotInstalled := peerTel.GetPrefixes().GetInstalled(); gotInstalled != nbr.wantInstalled { + 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 := peerTel.GetPrefixes().GetReceivedPrePolicy(); gotRxPrePol != nbr.wantRxPrePoloicy { - t.Errorf("Received pre policy prefixes mismatch: got %v, want %v", gotRxPrePol, nbr.wantRxPrePoloicy) + 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 := peerTel.GetPrefixes().GetReceived(); gotRx != nbr.wantRx { - t.Errorf("Received pre policy prefixes mismatch: got %v, want %v", gotRx, nbr.wantRx) + 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 := peerTel.GetPrefixes().GetSent(); gotSent != nbr.wantSent { + 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) } } @@ -770,6 +925,9 @@ func TestBGPDefaultPolicies(t *testing.T) { 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) }) @@ -798,15 +956,23 @@ func TestBGPDefaultPolicies(t *testing.T) { 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) { - 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) + 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) { @@ -822,25 +988,25 @@ func TestBGPDefaultPolicies(t *testing.T) { funcName func() skipMsg string }{{ - desc: "RT-7.1: Policy definition in policy chain is not satisfied and Default Policy has REJECT_ROUTE action", + 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.2 : Policy definition in policy chain is not satisfied and Default Policy has ACCEPT_ROUTE action", + 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.3 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has ACCEPT_ROUTE action", + 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.4 : No policy attached either at the Peer-group or at the neighbor level and Default Policy has REJECT_ROUTE action", + 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.5 : No policy including the default-policy is attached either at the Peer-group or at the neighbor level for only IBGP peer", + 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.5 should be automated only after the expected behavior is confirmed in issue num 981", + skipMsg: "TODO: RT-7.1.5 should be automated only after the expected behavior is confirmed in issue num 981", }, { - desc: "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", + 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.5 should be automated only after the expected behavior is confirmed in issue num 981", + 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) { diff --git a/feature/bgp/policybase/otg_tests/default_policies_test/metadata.textproto b/feature/bgp/policybase/otg_tests/default_policies_test/metadata.textproto index 2b8ce1a60fa..21a048a2b80 100644 --- a/feature/bgp/policybase/otg_tests/default_policies_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/default_policies_test/metadata.textproto @@ -2,7 +2,7 @@ # proto-message: Metadata uuid: "1d21e3cf-25c0-4e44-8988-582188e37452" -plan_id: "RT-7" +plan_id: "RT-7.1" description: "BGP default policies" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { @@ -21,6 +21,11 @@ platform_exceptions: { 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: { @@ -28,6 +33,23 @@ platform_exceptions: { 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..ec09b76414c --- /dev/null +++ b/feature/bgp/policybase/otg_tests/import_export_multi_test/metadata.textproto @@ -0,0 +1,39 @@ +# 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_conditions_match_community_set_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/metadata.textproto b/feature/bgp/policybase/otg_tests/link_bandwidth_test/metadata.textproto new file mode 100644 index 00000000000..13552ef78af --- /dev/null +++ b/feature/bgp/policybase/otg_tests/link_bandwidth_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +plan_id: "RT-7.5" +description: "BGP Policy - Set Link Bandwidth Community" +testbed: TESTBED_DUT_ATE_2LINKS +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 c4a588e2625..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 } @@ -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/metadata.textproto b/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/metadata.textproto index 20381e342a6..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,7 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + 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/README.md b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md similarity index 95% rename from feature/bgp/static-route_bgp_redistribution/README.md rename to feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md index 6767c6cf0c5..e663f722f25 100644 --- a/feature/bgp/static-route_bgp_redistribution/README.md +++ b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md @@ -77,97 +77,9 @@ * 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 metric propogation diabled ---- -##### Configure redistribution -* Redistribute ```ipv4-route``` to BGP -* 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 ```BGP``` - * /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 -##### Verification -* 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 ```BGP``` - * /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 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.2 [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.3 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] -#### Redistribute IPv6 static routes to BGP with metric propogation diabled ---- -##### Configure redistribution -* 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 ```BGP``` - * /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 -##### Verification -* 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 ```BGP``` - * /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 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.4 [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.5 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] -#### Redistribute IPv4 and IPv6 static routes to BGP with default-import-policy set to reject +### 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``` @@ -176,11 +88,10 @@ * 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``` +* 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 - * /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix -### RT-1.27.6 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -234,7 +145,35 @@ * 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.7 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -252,7 +191,7 @@ * /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.8 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -266,7 +205,7 @@ * /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.9 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -280,7 +219,7 @@ * /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.10 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -302,7 +241,7 @@ * /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.12 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -328,7 +267,7 @@ * 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.13 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -343,7 +282,7 @@ * 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.14 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] +### 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 @@ -365,7 +304,23 @@ * 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.15 [TODO: https://github.com/openconfig/featureprofiles/issues/2568] + + +### 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 @@ -419,6 +374,35 @@ * 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 --- @@ -642,11 +626,13 @@ * /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 -## Protocol/RPC Parameter Coverage - -* gNMI - * Get - * Set +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: +``` ## Required DUT platform 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..2aa5bece36a --- /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,1895 @@ +// 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) { + time.Sleep(5 * time.Second) + + 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 043531d4b08..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 @@ -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/experimental/backup_nhg/otg_tests/backup_nhg_test/backup_nhg_test.go b/feature/experimental/backup_nhg/otg_tests/backup_nhg_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/experimental/backup_nhg/otg_tests/backup_nhg_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/experimental/backup_nhg/otg_tests/backup_nhg_test/metadata.textproto index 5fdde1062f3..b650a1c2ebc 100644 --- a/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/metadata.textproto +++ b/feature/experimental/backup_nhg/otg_tests/backup_nhg_test/metadata.textproto @@ -34,3 +34,4 @@ platform_exceptions: { interface_config_vrf_before_address: true } } +tags: TAGS_TRANSIT 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 index ed5c0b71946..14e9565fdd7 100644 --- 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 @@ -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: "experimental_basic_entries_installed_in_gribi_gribi_ip4_entry" diff --git a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/README.md b/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/README.md deleted file mode 100644 index dc3416dbd47..00000000000 --- a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# RT-1.1: Base BGP Session Parameters - -## Summary - -BGP session establishment between DUT - ATE and verifiying different session parameters. - -## Topology - - DUT port-1 -------- ATE port-1 - -## Procedure - -Test the abnormal termination of session using notification message: - -* Establish BGP session between DUT (AS 65540) and ATE (AS 65550). The DUT/ATE - peers should be configured with MD5 authentication using the same password. - - * Ensure session state should be `ESTABLISHED`. - * Verify BGP capabilities: route refresh, ASN32 and MPBGP. - -* Verify BGP session disconnect by sending notification message from ATE. - - * Send `CEASE` notification from ATE. - * Ensure that DUT telemetry correctly reports the error code. - -Test md5 password authentication on session establishment: - -* Configure matching passwords on DUT and ATE. Verify that BGP adjacency succeeds. - -* Configure mismatched passwords on DUT and ATE. Verify that BGP adjacency fails. - - -Test the normal session establishment and termination: - -* Establish BGP session for the following cases: - - * eBGP using DUT AS 65540 and ATE AS 65550. - * Specifies global AS 65540 on the DUT. - * Specifies global AS 65536 and neighbor AS 65540 on the DUT. - Verify that ATE sees peer AS 65540. - * iBGP using DUT AS 65536 and ATE AS 65536. - * Specifies global AS 65536 on the DUT. - * Specifies both global and neighbor AS 65536 on the DUT. - - And include the following session parameters for all cases: - - * Explicitly specified Router ID. - * 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 diff --git a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go b/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go deleted file mode 100644 index 0974b12bc77..00000000000 --- a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/base_bgp_session_parameters_test.go +++ /dev/null @@ -1,488 +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_bgp_session_parameters_test - -import ( - "testing" - "time" - - "github.com/openconfig/featureprofiles/internal/attrs" - "github.com/openconfig/featureprofiles/internal/confirm" - "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" -) - -// 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", - IPv4: "192.0.2.2", - IPv4Len: 30, - } -) - -// Constants. -const ( - dutAS = 65540 - ateAS = 65550 - dutAS2 = 65536 - ateAS2 = 65536 - peerGrpName = "BGP-PEER-GROUP" - authPassword = "AUTHPASSWORD" - dutHoldTime = 90 - connRetryTime = 100 - ateHoldTime = 135 - dutKeepaliveTime = 30 -) - -type connType string - -const ( - connInternal connType = "INTERNAL" - connExternal connType = "EXTERNAL" -) - -// 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 -} - -// 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) -} - -// 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(bgpParams.peerAS) - pg.PeerGroupName = ygot.String(peerGrpName) - - nv4 := bgp.GetOrCreateNeighbor(ateAttrs.IPv4) - nv4.PeerGroup = ygot.String(peerGrpName) - nv4.PeerAs = ygot.Uint32(bgpParams.peerAS) - nv4.Enabled = ygot.Bool(true) - - if bgpParams.nbrLocalAS != 0 { - nv4.LocalAs = ygot.Uint32(bgpParams.nbrLocalAS) - } - - nv4t := nv4.GetOrCreateTimers() - nv4t.HoldTime = ygot.Uint16(dutHoldTime) - nv4t.KeepaliveInterval = ygot.Uint16(dutKeepaliveTime) - if !deviations.ConnectRetry(dut) { - nv4t.ConnectRetry = ygot.Uint16(connRetryTime) - } - - nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) - return niProto -} - -// Verify BGP capabilities like route refresh as32 and mpbgp. -func verifyBGPCapabilities(t *testing.T, dut *ondatra.DUTDevice) { - t.Log("Verifying BGP capabilities") - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - nbrPath := statePath.Neighbor(ateAttrs.IPv4) - - capabilities := map[oc.E_BgpTypes_BGP_CAPABILITY]bool{ - oc.BgpTypes_BGP_CAPABILITY_ROUTE_REFRESH: false, - oc.BgpTypes_BGP_CAPABILITY_ASN32: false, - oc.BgpTypes_BGP_CAPABILITY_MPBGP: false, - } - for _, cap := range gnmi.Get(t, dut, nbrPath.SupportedCapabilities().State()) { - capabilities[cap] = true - } - for cap, present := range capabilities { - if !present { - t.Errorf("Capability not reported: %v", cap) - } - } - -} - -// verifyBgpTelemetry checks that the dut has an established BGP session with reasonable settings. -func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { - ifName := dut.Port(t, "port1").Name() - lastFlapTime := gnmi.Get(t, dut, gnmi.OC().Interface(ifName).LastChange().State()) - t.Log("Verifying BGP state") - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - nbrPath := statePath.Neighbor(ateAttrs.IPv4) - - // Get BGP adjacency state - t.Log("Waiting for BGP neighbor to establish...") - _, 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 == 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") - } - status := gnmi.Get(t, dut, nbrPath.SessionState().State()) - t.Logf("BGP adjacency for %s: %s", ateAttrs.IPv4, status) - if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; status != want { - t.Errorf("BGP peer %s status got %d, want %d", ateAttrs.IPv4, status, want) - } - - // Check last established timestamp - lestTime := gnmi.Get(t, dut, nbrPath.State()).GetLastEstablished() - t.Logf("BGP last est time :%v, flapTime :%v", lestTime, lastFlapTime) - if lestTime < lastFlapTime { - t.Errorf("Bad last-established timestamp: got %v, want >= %v", lestTime, lastFlapTime) - } - - // Check BGP Transitions - nbr := gnmi.Get(t, dut, statePath.State()).GetNeighbor(ateAttrs.IPv4) - estTrans := nbr.GetEstablishedTransitions() - t.Logf("Got established transitions: %d", estTrans) - if estTrans != 1 { - t.Errorf("Wrong established-transitions: got %v, want 1", estTrans) - } - - // Check BGP neighbor address from telemetry - addrv4 := gnmi.Get(t, dut, nbrPath.State()).GetNeighborAddress() - t.Logf("Got ipv4 neighbor address: %s", addrv4) - if addrv4 != ateAttrs.IPv4 { - t.Errorf("BGP v4 neighbor address: got %v, want %v", addrv4, ateAttrs.IPv4) - } - // Check BGP neighbor address from telemetry - peerAS := gnmi.Get(t, dut, nbrPath.State()).GetPeerAs() - if peerAS != ateAS { - t.Errorf("BGP peerAs: got %v, want %v", peerAS, ateAS) - } - - // Check BGP neighbor is enabled - if !gnmi.Get(t, dut, nbrPath.State()).GetEnabled() { - t.Errorf("Expected neighbor %v to be enabled", ateAttrs.IPv4) - } -} - -// Function to configure ATE configs based on args and returns ate topology handle. -func configureATE(t *testing.T, ateParams *bgpTestParams, connectionType connType) *ondatra.ATETopology { - 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() - - if connectionType == connInternal { - bgpDut1.AddPeer().WithPeerAddress(ateParams.peerIP).WithLocalASN(ateParams.localAS).WithTypeInternal(). - WithHoldTime(ateHoldTime) - } else { - bgpDut1.AddPeer().WithPeerAddress(ateParams.peerIP).WithLocalASN(ateParams.localAS).WithTypeExternal(). - WithHoldTime(ateHoldTime) - } - return topo - -} - -// TestEstablishAndDisconnect Establishes BGP session between DUT and ATE and Verifies -// abnormal termination of session using notification message: -func TestEstablishAndDisconnect(t *testing.T) { - // DUT configurations. - t.Log("Start DUT config load:") - dut := ondatra.DUT(t, "dut") - - // 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) - - t.Log("Configure BGP") - dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - nbrPath := statePath.Neighbor(ateAttrs.IPv4) - - bgpClearConfig(t, dut) - dutConf := bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS}, dut) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - // Configure Md5 auth password. - 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())) - - // 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) - - t.Log("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) - - // Verify Port Status - t.Log("Verifying port status") - verifyPortsUp(t, dut.Device) - - // Verify BGP status - t.Log("Check BGP parameters") - verifyBgpTelemetry(t, dut) - - // Verify BGP capabilities - t.Log("Check BGP Capabilities") - 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("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() - return present && currBgpState != oc.Bgp_Neighbor_SessionState_ESTABLISHED - }).Await(t) - if !ok { - t.Errorf("BGP neighborship is UP when cease Notification message is recieved") - } - - // Verify if Cease notification is received on DUT. - t.Log("Verify Error code received on DUT: BgpTypes_BGP_ERROR_CODE_CEASE") - _, codeok := gnmi.Watch(t, dut, nbrPath.Messages().Received().LastNotificationErrorCode().State(), 60*time.Second, func(val *ygnmi.Value[oc.E_BgpTypes_BGP_ERROR_CODE]) bool { - code, present := val.Val() - t.Logf("On disconnect, received code status %v", present) - return present && code == oc.BgpTypes_BGP_ERROR_CODE_CEASE - }).Await(t) - if !codeok { - t.Errorf("On disconnect: expected error code %v", oc.BgpTypes_BGP_ERROR_CODE_CEASE) - } - - // Clear config on DUT and ATE - topo.StopProtocols(t) - bgpClearConfig(t, dut) -} - -// TestPassword is to verify md5 authentication password on DUT. -// Verification is done through BGP adjacency implicitly. -func TestPassword(t *testing.T) { - // DUT configurations. - t.Log("Start DUT config load:") - dut := ondatra.DUT(t, "dut") - - // 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") - dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) - gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) - - t.Log("Configure BGP") - dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - nbrPath := statePath.Neighbor(ateAttrs.IPv4) - - bgpClearConfig(t, dut) - dutConf := bgpCreateNbr(&bgpTestParams{localAS: dutAS, peerAS: ateAS}, dut) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - 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())) - - // 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) - - t.Log("Pushing config to ATE and starting protocols...") - topo.Push(t) - topo.StartProtocols(t) - - // Verify BGP status - t.Log("Check BGP parameters") - verifyBgpTelemetry(t, 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(), (dutHoldTime+10)*time.Second, oc.Bgp_Neighbor_SessionState_ESTABLISHED) - // Clear config on DUT and ATE - topo.StopProtocols(t) - bgpClearConfig(t, dut) -} - -// TestParameters is to verify normal session establishment and termination -// in both eBGP and iBGP scenarios using session parameters like explicit -// router id , timers. -func TestParameters(t *testing.T) { - ateIP := ateAttrs.IPv4 - dutIP := dutAttrs.IPv4 - dut := ondatra.DUT(t, "dut") - - // Configure Network instance type on DUT - t.Log("Configure Network Instance") - 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") - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - nbrPath := statePath.Neighbor(ateIP) - - cases := []struct { - name string - dutConf *oc.NetworkInstance_Protocol - ateConf *ondatra.ATETopology - }{ - { - 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), - }, - { - 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), - }, - { - name: "Test the iBGP session establishment: Gloabl AS", - dutConf: bgpCreateNbr(&bgpTestParams{localAS: dutAS2, peerAS: ateAS2}, dut), - ateConf: configureATE(t, &bgpTestParams{localAS: ateAS2, peerIP: dutIP}, connInternal), - }, - { - 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), - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - t.Log("Clear BGP Configs on DUT") - 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") - tc.ateConf.Push(t) - tc.ateConf.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()) - wantState := tc.dutConf.Bgp - if deviations.MissingValueForDefaults(dut) { - wantState.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).AfiSafiName = 0 - wantState.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = nil - wantState.GetOrCreateNeighbor(ateAttrs.IPv4).Enabled = nil - } - confirm.State(t, wantState, stateDut) - t.Log("Clear BGP Configs on ATE") - tc.ateConf.StopProtocols(t) - }) - } -} 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_long_lived_graceful_restart/README.md b/feature/experimental/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/experimental/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/experimental/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/experimental/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/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto index f76012ba389..004e3e5a3e6 100644 --- a/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/metadata.textproto +++ b/feature/experimental/bgp/ate_tests/bgp_long_lived_graceful_restart/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/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_2byte_4byte_asn/README.md b/feature/experimental/bgp/otg_tests/bgp_2byte_4byte_asn/README.md new file mode 100644 index 00000000000..b372590de53 --- /dev/null +++ b/feature/experimental/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/experimental/bgp/otg_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go similarity index 81% rename from feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go rename to feature/experimental/bgp/otg_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go index af656255725..dcc80fbed56 100644 --- a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/bgp_2byte_4byte_asn_test.go +++ b/feature/experimental/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,6 +63,11 @@ 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() @@ -81,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) @@ -136,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) }) } } @@ -196,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 } diff --git a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/metadata.textproto b/feature/experimental/bgp/otg_tests/bgp_2byte_4byte_asn/metadata.textproto similarity index 100% rename from feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/metadata.textproto rename to feature/experimental/bgp/otg_tests/bgp_2byte_4byte_asn/metadata.textproto diff --git a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/README.md b/feature/experimental/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/experimental/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/experimental/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/experimental/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/experimental/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go index 7ea95500983..a90d728ebc8 100644 --- a/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go +++ b/feature/experimental/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) }) } } @@ -377,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 { @@ -497,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/experimental/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/experimental/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/experimental/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/experimental/bgp/otg_tests/bgp_afi_safi_defaults/README.md b/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/README.md index a1fa3a84b8b..68fe918d23c 100644 --- a/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/README.md +++ b/feature/experimental/bgp/otg_tests/bgp_afi_safi_defaults/README.md @@ -75,47 +75,43 @@ BGP AFI SAFI OC DEFAULTS TEST * For IPv6 neighbor ensure that the IPv6 neighborship is not ESTABLISHED and IPv6-unicast capabilities are set to FALSE. -## 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 + +## 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: + + ## 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: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + gNMI.Subscribe: +``` +## Minimum DUT Required + +vRX - Virtual Router Device \ No newline at end of file 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 index d5405862c93..568d9060182 100644 --- 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 @@ -286,7 +286,7 @@ func configureOTG(t *testing.T, otg *otg.OTG, otgPeerList []string) gosnappi.Con 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) + 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) @@ -296,7 +296,7 @@ func configureOTG(t *testing.T, otg *otg.OTG, otgPeerList []string) gosnappi.Con 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) + iDut2Bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true).SetExtendedNextHopEncoding(true) iDut2Bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) } } diff --git a/feature/experimental/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md b/feature/experimental/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md new file mode 100644 index 00000000000..b74ba3fad48 --- /dev/null +++ b/feature/experimental/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md @@ -0,0 +1,64 @@ +# RT-1.54: BGP Override AS-path split-horizon + +## Summary + +BGP Override AS-path split-horizon + +## Topology + + ATE Port1 (AS 65502) --- DUT Port1 (AS 65501) DUT Port2 ---eBGP --- ATE Port2 (AS 65503) + +## Procedure + +* Establish BGP Session: Configure and establish an eBGP session between the DUT (Port1) and the ATE (Port1). +* 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. +* 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. +* 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. +* 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/experimental/bgp/otg_tests/link_bandwidth_test/README.md b/feature/experimental/bgp/otg_tests/link_bandwidth_test/README.md new file mode 100644 index 00000000000..4fa01af7fec --- /dev/null +++ b/feature/experimental/bgp/otg_tests/link_bandwidth_test/README.md @@ -0,0 +1,186 @@ +# 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" ]`. + * prefix-set-3 with 2 ipv4 and 2 ipv6 routes with extended communities `[ "link-bandwidth:23456:0" ]`. + * Configure Send community knob to IBGP neigbour to advertise the communities to IBGP peer + * use `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/send-community`. +* 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.8.3.x-) 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* | [none] | + | prefix-set-2 | *DEPRECATED* | [ "100:100" ] | + | prefix-set-3 | *DEPRECATED* | [ "link-bandwidth:23456:0" ] | + + | | 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:0" ] | [ 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: + ## 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/experimental/bgp/otg_tests/link_bandwidth_test/link_bandwidth_test.go b/feature/experimental/bgp/otg_tests/link_bandwidth_test/link_bandwidth_test.go new file mode 100644 index 00000000000..e240cd817d6 --- /dev/null +++ b/feature/experimental/bgp/otg_tests/link_bandwidth_test/link_bandwidth_test.go @@ -0,0 +1,993 @@ +// 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) + if deviations.BgpExplicitExtendedCommunityEnable(dut) { + enableExtCommunityCLIConfig(t, dut) + } + testCases := []testCase{ + { + name: "Policy set not_match_100_set_linkbw_1M", + policyName: "not_match_100_set_linkbw_1M", + applyPolicy: applyPolicyDut, + validate: validatPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "none", prefixSet2Comm: "100:100", prefixSet3Comm: "link-bandwidth:23456:0"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + { + name: "Policy set match_100_set_linkbw_2G", + policyName: "match_100_set_linkbw_2G", + applyPolicy: applyPolicyDut, + validate: validatPolicyDut, + routeCommunity: extCommunity{prefixSet1Comm: "none", prefixSet2Comm: "link-bandwidth:23456:2000000000", prefixSet3Comm: "link-bandwidth:23456:0"}, + localPerf: false, + validateRouteCommunityV4: validateRouteCommunityV4, + validateRouteCommunityV6: validateRouteCommunityV6, + }, + { + name: "Policy set del_linkbw", + policyName: "del_linkbw", + applyPolicy: applyPolicyDut, + validate: validatPolicyDut, + 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) { + 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 applyPolicyDut(t *testing.T, dut *ondatra.DUTDevice, policyName string) { + // Apply ipv4 policy to bgp neighbour. + root := &oc.Root{} + 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{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 validatPolicyDut(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() + 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 got= %v, want %v", importPolicies, []string{policyName}) + } +} + +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 == "0" { + if ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 0 { + t.Errorf("ERROR lb Bandwidth want 0, got:=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } + } else { + if 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 == "0" { + if ygot.BinaryToFloat32(lbSubType.GetBandwidth()) != 0 { + t.Errorf("ERROR lb Bandwidth want 0, got:=%v", ygot.BinaryToFloat32(lbSubType.GetBandwidth())) + } + } else { + if 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 + switch dut.Vendor() { + case ondatra.CISCO: + extCommunitySet = extCommunitySetCisco + default: + t.Logf("extCommunitySet = %v", extCommunitySet) + } + if !deviations.BgpExtendedCommunitySetUnsupported(dut) { + 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) + } + } else { + switch dut.Vendor() { + case ondatra.CISCO: + 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()) + } + } + + if !(deviations.CommunityMemberRegexUnsupported(dut)) { + 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) + } + } else { + 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()) + } + } + + // 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("1-megabit-match") + if err != nil { + t.Fatalf("AppendNewStatement 1-megabit-match failed: %v", err) + } + + if !deviations.BgpSetExtCommunitySetRefsUnsupported(dut) { + ref := pdef2Stmt1.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: + 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("accept_all_routes") + if err != nil { + t.Fatalf("AppendNewStatement accept_all_routes failed: %v", err) + } + pdef2Stmt2.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + if !deviations.BgpSetExtCommunitySetRefsUnsupported(dut) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpNotMatch) + } else { + 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()) + } + } + + // 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().GetMatchCommunitySet() + 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) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpMatch) + } else { + 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()) + } + } + + // 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) { + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), rpDelLinkbw) + } else { + 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) + } +} + +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_BOTH}) + } + 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_BOTH}) + } + 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(0) + 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(0) + + // 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/experimental/bgp/otg_tests/link_bandwidth_test/metadata.textproto b/feature/experimental/bgp/otg_tests/link_bandwidth_test/metadata.textproto new file mode 100644 index 00000000000..5d2b671575b --- /dev/null +++ b/feature/experimental/bgp/otg_tests/link_bandwidth_test/metadata.textproto @@ -0,0 +1,41 @@ +# 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_conditions_match_community_set_unsupported: 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/experimental/gnmi_service/benchmarking_drained_configuration_convergence_time/feature.textproto b/feature/experimental/gnmi_service/benchmarking_drained_configuration_convergence_time/feature.textproto index 9893a032bf4..fc6bb224159 100644 --- a/feature/experimental/gnmi_service/benchmarking_drained_configuration_convergence_time/feature.textproto +++ b/feature/experimental/gnmi_service/benchmarking_drained_configuration_convergence_time/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: "experimental_gnmi_service_benchmarking_drained_configuration_convergence_time" diff --git a/feature/experimental/gnmi_service/telemetry_port_speed/feature.textproto b/feature/experimental/gnmi_service/telemetry_port_speed/feature.textproto index 29b1af29b18..f7a6d7df109 100644 --- a/feature/experimental/gnmi_service/telemetry_port_speed/feature.textproto +++ b/feature/experimental/gnmi_service/telemetry_port_speed/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: "experimental_gnmi_service_telemetry_port_speed" diff --git a/feature/experimental/gribi/otg_tests/backup_nhg_action/README.md b/feature/experimental/gribi/otg_tests/backup_nhg_action/README.md index 23785c6922f..c500f4c352d 100644 --- a/feature/experimental/gribi/otg_tests/backup_nhg_action/README.md +++ b/feature/experimental/gribi/otg_tests/backup_nhg_action/README.md @@ -94,12 +94,6 @@ Different test scenarios requires different setups. traffic with decapsulated traffic with destination IP as `InnerDstIP_1` at ATE port-4. -[TODO]: 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`. -* Replace the existing VRF selection policy with `vrf_selection_policy_w` as in - ## Config Parameter coverage No new configuration covered. @@ -113,3 +107,4 @@ No new telemetry covered. ## 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/experimental/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go index 9b0fee7783b..2a41403d9f7 100644 --- a/feature/experimental/gribi/otg_tests/backup_nhg_action/backup_nhg_action_test.go +++ b/feature/experimental/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 ( @@ -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 @@ -500,6 +511,7 @@ func updateFlows(t *testing.T, ate *ondatra.ATEDevice, flows []gosnappi.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 diff --git a/feature/experimental/gribi/otg_tests/backup_nhg_action/metadata.textproto b/feature/experimental/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/experimental/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/experimental/gribi/otg_tests/backup_nhg_action_pbf/README.md b/feature/experimental/gribi/otg_tests/backup_nhg_action_pbf/README.md new file mode 100644 index 00000000000..b816261f0c6 --- /dev/null +++ b/feature/experimental/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/experimental/gribi/otg_tests/backup_nhg_action_pbf/backup_nhg_action_pbf_test.go b/feature/experimental/gribi/otg_tests/backup_nhg_action_pbf/backup_nhg_action_pbf_test.go new file mode 100644 index 00000000000..acb2cd0e964 --- /dev/null +++ b/feature/experimental/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/experimental/gribi/otg_tests/backup_nhg_action_pbf/metadata.textproto b/feature/experimental/gribi/otg_tests/backup_nhg_action_pbf/metadata.textproto new file mode 100644 index 00000000000..f7a83ce9600 --- /dev/null +++ b/feature/experimental/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/experimental/gribi/otg_tests/dut_daemon_failure/README.md b/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md index a27b42da6d7..6cd0a79e2a2 100644 --- a/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md +++ b/feature/experimental/gribi/otg_tests/dut_daemon_failure/README.md @@ -29,11 +29,14 @@ Ensure that gRIBI entries are persisted over daemon failure. * Issuing a gRIBI Get RPC results in 203.0.113.0/24 being returned. -## Protocol/RPC Parameter Coverage - -* gRIBI - * ModifyRequest - * GetRequest +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Telemetry Parameter Coverage diff --git a/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go b/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go index 1207b3b0346..1558e4252f7 100644 --- a/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go +++ b/feature/experimental/gribi/otg_tests/dut_daemon_failure/dut_daemon_failure_test.go @@ -23,16 +23,14 @@ import ( "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" "github.com/openconfig/featureprofiles/internal/gribi" "github.com/openconfig/featureprofiles/internal/otgutils" - gnps "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnoigo/system" - grps "github.com/openconfig/gribi/v1/proto/service" + grpb "github.com/openconfig/gribi/v1/proto/service" "github.com/openconfig/gribigo/fluent" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ondatra/gnoi" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" ) @@ -88,13 +86,6 @@ var ( IPv4: "192.0.2.6", IPv4Len: ipv4PrefixLen, } - - gRIBIDaemons = map[ondatra.Vendor]string{ - ondatra.ARISTA: "Gribi", - ondatra.CISCO: "emsd", - ondatra.JUNIPER: "rpd", - ondatra.NOKIA: "sr_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/fib_failed_due_to_hw_res_exhaust_test/README.md b/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md index 94fb48612b5..1cf7be3987d 100644 --- a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md +++ b/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/README.md @@ -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/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/fib_failed_due_to_hw_res_exhaust_test.go index 1919def9c00..99761f18b35 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/experimental/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,13 +65,17 @@ 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, } dutPort1 = attrs.Attributes{ Desc: "dutPort1", @@ -101,8 +107,9 @@ var ( IPv4Len: plenIPv4, IPv6Len: plenIPv6, } - fibPassedDstRoute string - fibFailedDstRoute string + fibPassedDstRoute string + fibFailedDstRoute string + fibFailedDstRouteInHex string ) func configureBGP(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { @@ -142,7 +149,7 @@ func configureBGP(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { 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") @@ -167,6 +174,44 @@ func configureOTG(t *testing.T, otg *otg.OTG) (gosnappi.BgpV6Peer, gosnappi.Devi iDut1Bgp6Peer.SetPeerAddress(iDut1Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) 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) @@ -205,6 +250,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) @@ -242,7 +289,7 @@ 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) verifyBgpTelemetry(t, dut) @@ -286,7 +333,7 @@ func TestFibFailDueToHwResExhaust(t *testing.T) { otg: otg, } start := time.Now() - injectEntry(ctx, t, args) + 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.") @@ -296,37 +343,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().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().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) @@ -334,11 +351,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) { @@ -349,16 +363,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 { @@ -367,6 +396,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) { @@ -464,15 +494,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 @@ -501,6 +547,7 @@ routeAddLoop: if v.ProgrammingResult == aftspb.AFTResult_FIB_FAILED { t.Logf("FIB FAILED received %v", v.Details) fibFailedDstRoute = dstIPList[j] + fibFailedDstRouteInHex = IPv4LastTenBitsToHex(fibFailedDstRoute) break routeAddLoop } } @@ -510,7 +557,7 @@ routeAddLoop: // 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/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto b/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto index 0dd4c0377f3..50fde18c196 100644 --- a/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto +++ b/feature/experimental/gribi/otg_tests/fib_failed_due_to_hw_res_exhaust_test/metadata.textproto @@ -26,7 +26,6 @@ platform_exceptions: { vendor: NOKIA } deviations: { - explicit_gribi_under_network_instance: true explicit_port_speed: true explicit_interface_in_default_vrf: true interface_enabled: true diff --git a/feature/experimental/gribi/otg_tests/route_addition_during_failover_test/README.md b/feature/experimental/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/experimental/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/experimental/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/experimental/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/experimental/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go index 8cd803f96a7..fb92e0f4856 100644 --- a/feature/experimental/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go +++ b/feature/experimental/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go @@ -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/experimental/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/experimental/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/experimental/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/experimental/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/experimental/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go index 62933c1c4fd..db63e96f39d 100644 --- a/feature/experimental/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go +++ b/feature/experimental/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go @@ -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/experimental/gribi/otg_tests/vrf_policy_driven_te/README.md b/feature/experimental/gribi/otg_tests/vrf_policy_driven_te/README.md new file mode 100644 index 00000000000..7fa0f46bec3 --- /dev/null +++ b/feature/experimental/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/experimental/gribi/otg_tests/vrf_policy_driven_te/metadata.textproto b/feature/experimental/gribi/otg_tests/vrf_policy_driven_te/metadata.textproto new file mode 100644 index 00000000000..4c73a0c21cd --- /dev/null +++ b/feature/experimental/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/experimental/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go b/feature/experimental/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go new file mode 100644 index 00000000000..136cf377d60 --- /dev/null +++ b/feature/experimental/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go @@ -0,0 +1,2288 @@ +// 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) + if p.PMD() == ondatra.PMD100GBASEFR && dut.Vendor() != ondatra.CISCO { + 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 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/experimental/hierarchical_gribi_entries/base_hierarchical_route_installation/feature.textproto b/feature/experimental/hierarchical_gribi_entries/base_hierarchical_route_installation/feature.textproto index 36675d5be9c..f4d10b21f31 100644 --- a/feature/experimental/hierarchical_gribi_entries/base_hierarchical_route_installation/feature.textproto +++ b/feature/experimental/hierarchical_gribi_entries/base_hierarchical_route_installation/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: "experimental_hierarchical_gribi_entries_base_hierarchical_route_installation" diff --git a/feature/experimental/hierarchical_gribi_entries/traffic_balancing_according_to_weights/feature.textproto b/feature/experimental/hierarchical_gribi_entries/traffic_balancing_according_to_weights/feature.textproto index a18ae203662..2f8a1e24a06 100644 --- a/feature/experimental/hierarchical_gribi_entries/traffic_balancing_according_to_weights/feature.textproto +++ b/feature/experimental/hierarchical_gribi_entries/traffic_balancing_according_to_weights/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: "experimental_hierarchical_gribi_entries_traffic_balancing_according_to_weights" 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 index c3996d72bcf..aea9ddcf069 100644 --- 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 @@ -16,23 +16,24 @@ Ensure Interface mode can be set to loopback mode and can be added as part of st * 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 +## 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/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go index 7b5eb91c7ea..8e31b1550d0 100644 --- a/feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go +++ b/feature/experimental/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" @@ -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/my_station_mac/otg_tests/my_station_mac_test/README.md b/feature/experimental/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/experimental/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/my_station_mac_test.go b/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go index a03ac6965a3..6b9152f6d10 100644 --- a/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go +++ b/feature/experimental/interface/my_station_mac/otg_tests/my_station_mac_test/my_station_mac_test.go @@ -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/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/base_adjacencies_test/metadata.textproto b/feature/experimental/isis/ate_tests/base_adjacencies_test/metadata.textproto deleted file mode 100644 index 22ddf4a534b..00000000000 --- a/feature/experimental/isis/ate_tests/base_adjacencies_test/metadata.textproto +++ /dev/null @@ -1,45 +0,0 @@ -# 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" -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: 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/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/lsp_updates_test/README.md b/feature/experimental/isis/ate_tests/lsp_updates_test/README.md deleted file mode 100644 index d207d7c482a..00000000000 --- a/feature/experimental/isis/ate_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/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 index 3448bd66fb8..1fc8c021715 100644 --- a/feature/experimental/isis/otg_tests/base_adjacencies_test/README.md +++ b/feature/experimental/isis/otg_tests/base_adjacencies_test/README.md @@ -4,164 +4,204 @@ 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 -* 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) +### 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 -vRX +* 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/isis_drain_test/README.md b/feature/experimental/isis/otg_tests/isis_drain_test/README.md index 8537bc0f7f8..8c2bf143e5f 100644 --- a/feature/experimental/isis/otg_tests/isis_drain_test/README.md +++ b/feature/experimental/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 diff --git a/feature/experimental/isis/otg_tests/isis_drain_test/isis_drain_test.go b/feature/experimental/isis/otg_tests/isis_drain_test/isis_drain_test.go index 62e5004aae9..13ba419a8da 100644 --- a/feature/experimental/isis/otg_tests/isis_drain_test/isis_drain_test.go +++ b/feature/experimental/isis/otg_tests/isis_drain_test/isis_drain_test.go @@ -213,12 +213,6 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { } // 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) { @@ -227,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}) } @@ -252,7 +252,9 @@ func configureISISDUT(t *testing.T, dut *ondatra.DUTDevice, intfs []string) { 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) @@ -476,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/experimental/isis/otg_tests/isis_drain_test/metadata.textproto index ccd99b086c3..e704ecde9cb 100644 --- a/feature/experimental/isis/otg_tests/isis_drain_test/metadata.textproto +++ b/feature/experimental/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: { @@ -39,3 +39,11 @@ platform_exceptions: { isis_require_same_l1_metric_with_l2_metric: true } } +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + } +} diff --git a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go b/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go index 3e5ed1a4a69..8823ec9e638 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go +++ b/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/isis_interface_hello_padding_enable_test.go @@ -110,9 +110,11 @@ func configureISIS(t *testing.T, ts *isissession.TestSession) { isisIntfLevelTimers.HelloInterval = ygot.Uint32(5) isisIntfLevelTimers.HelloMultiplier = ygot.Uint8(3) - isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if !deviations.ISISInterfaceAfiUnsupported(ts.DUT) { + isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + } 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) } @@ -347,28 +349,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/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto index d80b6324c7e..b534b4ef211 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto +++ b/feature/experimental/isis/otg_tests/isis_interface_hello_padding_enable_test/metadata.textproto @@ -41,6 +41,7 @@ platform_exceptions: { isis_timers_csnp_interval_unsupported: true isis_counter_manual_address_drop_from_areas_unsupported: true isis_counter_part_changes_unsupported: true + isis_interface_afi_unsupported: true } } platform_exceptions: { diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md b/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md index ce29ac4d93c..cd87a3ba503 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md +++ b/feature/experimental/isis/otg_tests/isis_interface_passive_test/README.md @@ -14,83 +14,78 @@ * Ensure that IS-IS adjacency is not coming up on the passive interface. * TODO-Verify the output of ST path displaying the interface as passive in ISIS database/adj table -## Config Parameter coverage +# OpenConfig Path and RPC Coverage +```yaml +paths: +# config +/network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check: +/network-instances/network-instance/protocols/protocol/isis/global/config/net: +/network-instances/network-instance/protocols/protocol/isis/global/config/level-capability: +/network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding: +/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number: +/network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password: +/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled: -* For prefix: +# isis telemetry +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/state/passive: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-fails: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/auth-type-fails: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-area : +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes : +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips: +/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs: - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * global/config/authentication-check - * global/config/net - * global/config/level-capability - * global/config/hello-padding - * global/afi-safi/af/config/enabled - * levels/level/config/level-number - * levels/level/config/enabled - * levels/level/authentication/config/enabled - * levels/level/authentication/config/auth-mode - * levels/level/authentication/config/auth-password - * levels/level/authentication/config/auth-type - * interfaces/interface/config/interface-id - * interfaces/interface/config/enabled - * interfaces/interface/config/circuit-type - * interfaces/interface/config/passive - * interfaces/interface/timers/config/csnp-interval - * interfaces/interface/timers/config/lsp-pacing-interval - * interfaces/interface/levels/level/config/level-number - * interfaces/interface/levels/level/config/passive - * interfaces/interface/levels/level/timers/config/hello-interval - * interfaces/interface/levels/level/timers/config/hello-multiplier - * interfaces/interface/levels/level/hello-authentication/config/auth-mode - * interfaces/interface/levels/level/hello-authentication/config/auth-password - * interfaces/interface/levels/level/hello-authentication/config/auth-type - * interfaces/interface/levels/level/hello-authentication/config/enabled - * interfaces/interface/afi-safi/af/config/afi-name - * interfaces/interface/afi-safi/af/config/safi-name - * interfaces/interface/afi-safi/af/config/enabled - -## Telemetry Parameter coverage - -* For prefix: - - * /network-instances/network-instance/protocols/protocol/isis/ - -* Parameters: - - * interfaces/interface/state/passive - * interfaces/interface/levels/level/state/passive - * interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/area-address - * interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/local-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/multi-topology - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-extended-circuit-id - * interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa - * interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid - * interfaces/interface/levels/level/adjacencies/adjacency/state/priority - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support - * interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress - * interfaces/interface/levels/level/afi-safi/af/state/afi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * interfaces/interface/levels/level/afi-safi/af/state/safi-name - * interfaces/interface/levels/level/afi-safi/af/state/metric - * levels/level/system-level-counters/state/auth-fails - * levels/level/system-level-counters/state/auth-type-fails - * levels/level/system-level-counters/state/corrupted-lsps - * levels/level/system-level-counters/state/database-overloads - * levels/level/system-level-counters/state/exceed-max-seq-nums - * levels/level/system-level-counters/state/id-len-mismatch - * levels/level/system-level-counters/state/lsp-errors - * levels/level/system-level-counters/state/manual-address-drop-from-area - * levels/level/system-level-counters/state/max-area-address-mismatches - * levels/level/system-level-counters/state/own-lsp-purges - * levels/level/system-level-counters/state/part-changes - * levels/level/system-level-counters/state/seq-num-skips - * levels/level/system-level-counters/state/spf-runs +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go b/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go index 48200bfbf3b..725c6fab80a 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go +++ b/feature/experimental/isis/otg_tests/isis_interface_passive_test/isis_interface_passive_test.go @@ -58,6 +58,9 @@ func configureISIS(t *testing.T, ts *isissession.TestSession) { globalIsis.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) globalIsis.LevelCapability = oc.Isis_LevelType_LEVEL_2 globalIsis.AuthenticationCheck = ygot.Bool(true) + if deviations.ISISGlobalAuthenticationNotRequired(ts.DUT) { + globalIsis.AuthenticationCheck = nil + } globalIsis.HelloPadding = oc.Isis_HelloPaddingType_ADAPTIVE // Level configs. @@ -69,6 +72,11 @@ func configureISIS(t *testing.T, ts *isissession.TestSession) { auth.AuthMode = oc.IsisTypes_AUTH_MODE_MD5 auth.AuthType = oc.KeychainTypes_AUTH_TYPE_SIMPLE_KEY auth.AuthPassword = ygot.String(password) + if deviations.ISISExplicitLevelAuthenticationConfig(ts.DUT) { + auth.DisableCsnp = ygot.Bool(false) + auth.DisableLsp = ygot.Bool(false) + auth.DisablePsnp = ygot.Bool(false) + } // Interface configs. intfName := ts.DUTPort1.Name() @@ -188,16 +196,20 @@ func TestIsisInterfacePassive(t *testing.T) { t.Errorf("FAIL- Expected area address not found, got %s, want %s", got, want) } // Checking dis system id. - if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { - t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.DisSystemId().State()); got != "0000.0000.0000" { + t.Errorf("FAIL- Expected dis system id not found, got %s, want %s", got, "0000.0000.0000") + } } // Checking isis local extended circuit id. if got := gnmi.Get(t, ts.DUT, adjPath.LocalExtendedCircuitId().State()); got == 0 { t.Errorf("FAIL- Expected local extended circuit id not found,expected non-zero value, got %d", got) } // Checking multitopology. - if got := gnmi.Get(t, ts.DUT, adjPath.MultiTopology().State()); got != false { - t.Errorf("FAIL- Expected value for multi topology not found, got %t, want %t", got, false) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, adjPath.MultiTopology().State()); got != false { + t.Errorf("FAIL- Expected value for multi topology not found, got %t, want %t", got, false) + } } // Checking neighbor circuit type. if got := gnmi.Get(t, ts.DUT, adjPath.NeighborCircuitType().State()); got != oc.Isis_LevelType_LEVEL_2 { @@ -240,59 +252,81 @@ func TestIsisInterfacePassive(t *testing.T) { t.Errorf("FAIL- Restart support not present") } // Checking isis restart suppress. - if _, ok := gnmi.Lookup(t, ts.DUT, adjPath.RestartStatus().State()).Val(); !ok { - t.Errorf("FAIL- Restart suppress not present") + if !deviations.MissingValueForDefaults(ts.DUT) { + if _, ok := gnmi.Lookup(t, ts.DUT, adjPath.RestartStatus().State()).Val(); !ok { + t.Errorf("FAIL- Restart suppress not present") + } } }) t.Run("System level counter checks", func(t *testing.T) { // Checking authFail counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthFails().State()); got != 0 { - t.Errorf("FAIL- Not expecting any authentication key failure, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthFails().State()); got != 0 { + t.Errorf("FAIL- Not expecting any authentication key failure, got %d, want %d", got, 0) + } } // Checking authTypeFail counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthTypeFails().State()); got != 0 { - t.Errorf("FAIL- Not expecting any authentication type mismatches, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().AuthTypeFails().State()); got != 0 { + t.Errorf("FAIL- Not expecting any authentication type mismatches, got %d, want %d", got, 0) + } } // Checking corrupted lsps counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { - t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().CorruptedLsps().State()); got != 0 { + t.Errorf("FAIL- Not expecting any corrupted lsps, got %d, want %d", got, 0) + } } // Checking database_overloads counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().DatabaseOverloads().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero database_overloads, got %d, want %d", got, 0) + } } // Checking execeeded maximum seq number counters"). - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ExceedMaxSeqNums().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero max_seqnum counter, got %d, want %d", got, 0) + } } // Checking IdLenMismatch counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().IdLenMismatch().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero IdLen_Mismatch counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().IdLenMismatch().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero IdLen_Mismatch counter, got %d, want %d", got, 0) + } } // Checking LspErrors counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().LspErrors().State()); got != 0 { - t.Errorf("FAIL- Not expecting any lsp errors, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().LspErrors().State()); got != 0 { + t.Errorf("FAIL- Not expecting any lsp errors, got %d, want %d", got, 0) + } } // Checking MaxAreaAddressMismatches counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().MaxAreaAddressMismatches().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero MaxAreaAddressMismatches counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().MaxAreaAddressMismatches().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero MaxAreaAddressMismatches counter, got %d, want %d", got, 0) + } } // Checking OwnLspPurges counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().OwnLspPurges().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero OwnLspPurges counter, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().OwnLspPurges().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero OwnLspPurges counter, got %d, want %d", got, 0) + } } // Checking SeqNumSkips counters. - if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { - t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) + if !deviations.MissingValueForDefaults(ts.DUT) { + if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().SeqNumSkips().State()); got != 0 { + t.Errorf("FAIL- Not expecting non zero SeqNumber skips, got %d, want %d", got, 0) + } } // Checking ManualAddressDropFromAreas counters. - if !deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) { + if !(deviations.ISISCounterManualAddressDropFromAreasUnsupported(ts.DUT) || deviations.MissingValueForDefaults(ts.DUT)) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().ManualAddressDropFromAreas().State()); got != 0 { t.Errorf("FAIL- Not expecting non zero ManualAddressDropFromAreas counter, got %d, want %d", got, 0) } } // Checking PartChanges counters. - if !deviations.ISISCounterPartChangesUnsupported(ts.DUT) { + if !(deviations.ISISCounterPartChangesUnsupported(ts.DUT) || deviations.MissingValueForDefaults(ts.DUT)) { if got := gnmi.Get(t, ts.DUT, statePath.Level(2).SystemLevelCounters().PartChanges().State()); got != 0 { t.Errorf("FAIL- Not expecting partition changes, got %d, want %d", got, 0) } diff --git a/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto b/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto index f14063d5dc4..75bc2932d0e 100644 --- a/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto +++ b/feature/experimental/isis/otg_tests/isis_interface_passive_test/metadata.textproto @@ -10,6 +10,8 @@ platform_exceptions: { vendor: NOKIA } deviations: { + isis_global_authentication_not_required: true + isis_explicit_level_authentication_config: true isis_interface_level1_disable_required: true missing_isis_interface_afi_safi_enable: true explicit_port_speed: true diff --git a/feature/experimental/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 index 7d563c2c2f9..1a08deafff6 100644 --- 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 @@ -16,83 +16,78 @@ * 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 +## OpenConfig Path and RPC 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 +```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/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go index 637993169b5..2ed2ac22d73 100644 --- a/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go +++ b/feature/experimental/isis/otg_tests/isis_metric_style_wide_enabled_test/isis_metric_style_wide_enabled_test.go @@ -27,6 +27,7 @@ import ( "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" ) @@ -331,36 +332,44 @@ func TestISISWideMetricEnabled(t *testing.T) { 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/experimental/isis/otg_tests/lsp_updates_test/metadata.textproto b/feature/experimental/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/experimental/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/experimental/lacp_and_base_interface/aggregate_interfaces/feature.textproto b/feature/experimental/lacp_and_base_interface/aggregate_interfaces/feature.textproto index a96add04cd9..b9f58a8ae68 100644 --- a/feature/experimental/lacp_and_base_interface/aggregate_interfaces/feature.textproto +++ b/feature/experimental/lacp_and_base_interface/aggregate_interfaces/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: "experimental_lacp_and_base_interface_aggregate_interfaces" diff --git a/feature/experimental/p4rt/README.md b/feature/experimental/p4rt/README.md index f673b15bac5..a2dc5701736 100644 --- a/feature/experimental/p4rt/README.md +++ b/feature/experimental/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 + +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 + +``` diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/README.md b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/README.md new file mode 100644 index 00000000000..d96635af3e4 --- /dev/null +++ b/feature/experimental/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/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/google_discovery_protocol_packetout_lag_test.go b/feature/experimental/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/experimental/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/interface/staticarp/ate_tests/static_arp_test/metadata.textproto b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/metadata.textproto similarity index 74% rename from feature/interface/staticarp/ate_tests/static_arp_test/metadata.textproto rename to feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/metadata.textproto index 23329ae109b..4a20b5c1048 100644 --- a/feature/interface/staticarp/ate_tests/static_arp_test/metadata.textproto +++ b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/metadata.textproto @@ -1,9 +1,9 @@ # 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: "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: { @@ -18,7 +18,8 @@ platform_exceptions: { vendor: JUNIPER } deviations: { - enable_flowctrl_flag: true + p4rt_gdp_requires_dot1q_subinterface: true + no_mix_of_tagged_and_untagged_subinterfaces: true } } platform_exceptions: { @@ -40,3 +41,4 @@ platform_exceptions: { default_network_instance: "default" } } + diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/README.md new file mode 100644 index 00000000000..d15347dad27 --- /dev/null +++ b/feature/experimental/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/experimental/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/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto new file mode 100644 index 00000000000..b9ebd5de7e4 --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/metadata.textproto @@ -0,0 +1,24 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "5a5491b5-9900-4a05-b289-43feb1ceb915" +plan_id: "P4RT-5.3" +description: "Traceroute: PacketIn With VRF Selection" +testbed: TESTBED_DUT_ATE_8LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + gnoi_subcomponent_path: true + deprecated_vlan_id: true + interface_enabled: true + static_protocol_name: "STATIC" + default_network_instance: "default" + gribi_mac_override_static_arp_static_route: true + missing_isis_interface_afi_safi_enable: true + isis_interface_afi_unsupported: true + isis_instance_enabled_required: true + ate_port_link_state_operations_unsupported: true + } +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go new file mode 100644 index 00000000000..d8e5c3e9739 --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/packetin_test.go @@ -0,0 +1,275 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package to test P4RT with traceroute traffic of IPV4 and IPV6 with TTL/HopLimit as 0&1. +// go test -v . -testbed /root/ondatra/featureprofiles/topologies/atedut_2.testbed -binding /root/ondatra/featureprofiles/topologies/atedut_2.binding -outputs_dir logs + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ygnmi/ygnmi" + pb "github.com/p4lang/p4runtime/go/p4/v1" +) + +type PacketIO interface { + GetTableEntry(delete bool, isIPv4 bool) []*p4rtutils.ACLWbbIngressTableEntryInfo + GetPacketTemplate() *PacketIOPacket + GetTrafficFlow(ate *ondatra.ATEDevice, dstMac string, isIpv4 bool, + TTL uint8, frameSize uint32, frameRate uint64, dstIP string, flowValues *flowArgs) gosnappi.Flow + GetIngressPort() string +} + +type PacketIOPacket struct { + TTL *uint8 + SrcMAC, DstMAC *string + EthernetType *uint32 + HopLimit *uint8 +} + +// programmTableEntry programs or deletes p4rt table entry based on delete flag. +func programmTableEntry(client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool, isIPv4 bool) error { + err := client.Write(&pb.WriteRequest{ + DeviceId: deviceID, + ElectionId: &pb.Uint128{High: uint64(0), Low: electionID}, + Updates: p4rtutils.ACLWbbIngressTableEntryGet( + packetIO.GetTableEntry(delete, isIPv4), + ), + Atomicity: pb.WriteRequest_CONTINUE_ON_ERROR, + }) + return err +} + +// decodePacket decodes L2 header in the packet and returns source and destination MAC and ethernet type. +func decodePacket(t *testing.T, packetData []byte) (string, string, layers.EthernetType) { + t.Helper() + packet := gopacket.NewPacket(packetData, layers.LayerTypeEthernet, gopacket.Default) + etherHeader := packet.Layer(layers.LayerTypeEthernet) + if etherHeader != nil { + header, decoded := etherHeader.(*layers.Ethernet) + if decoded { + return header.SrcMAC.String(), header.DstMAC.String(), header.EthernetType + } + } + return "", "", layers.EthernetType(0) +} + +// decodePacket decodes L2 header in the packet and returns TTL. packetData[14:0] to remove first 14 bytes of Ethernet header. +func decodePacket4(t *testing.T, packetData []byte) uint8 { + t.Helper() + packet := gopacket.NewPacket(packetData[14:], layers.LayerTypeIPv4, gopacket.Default) + if IPv4 := packet.Layer(layers.LayerTypeIPv4); IPv4 != nil { + ipv4, _ := IPv4.(*layers.IPv4) + IPv4 := ipv4.TTL + return IPv4 + } + return 7 +} + +// decodePacket decodes IPV6 L2 header in the packet and returns HopLimit. packetData[14:] to remove first 14 bytes of Ethernet header. +func decodePacket6(t *testing.T, packetData []byte) uint8 { + t.Helper() + packet := gopacket.NewPacket(packetData[14:], layers.LayerTypeIPv6, gopacket.Default) + if IPv6 := packet.Layer(layers.LayerTypeIPv6); IPv6 != nil { + ipv6, _ := IPv6.(*layers.IPv6) + IPv6 := ipv6.HopLimit + return IPv6 + } + return 7 +} + +// testTraffic sends traffic flow for duration seconds and returns the +// number of packets sent out. +func testTraffic(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, flows []gosnappi.Flow, srcEndPoint gosnappi.Port, duration int, cs gosnappi.ControlState) int { + t.Helper() + top.Flows().Clear() + for _, flow := range flows { + flow.TxRx().Port().SetTxName(srcEndPoint.Name()).SetRxName(srcEndPoint.Name()) + flow.Metrics().SetEnable(true) + top.Flows().Append(flow) + } + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + time.Sleep(30 * time.Second) + ate.OTG().StartTraffic(t) + time.Sleep(time.Duration(duration) * time.Second) + ate.OTG().StopTraffic(t) + + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + ate.OTG().SetControlState(t, cs) + + outPkts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().FlowAny().Counters().OutPkts().State()) + total := 0 + for _, count := range outPkts { + total += int(count) + } + return total +} + +// testPacketIn programs p4rt table entry and sends traffic related to Traceroute, +// then validates packetin message metadata and payload. +func testPacketIn(ctx context.Context, t *testing.T, args *testArgs, isIPv4 bool, cs gosnappi.ControlState, flowValues []*flowArgs, EgressPortMap map[string]bool) []float64 { + leader := args.leader + if isIPv4 { + // Insert p4rtutils acl entry on the DUT + if err := programmTableEntry(leader, args.packetIO, false, isIPv4); err != nil { + t.Fatalf("There is error when programming entry") + } + // Delete p4rtutils acl entry on the device + defer programmTableEntry(leader, args.packetIO, true, isIPv4) + } else { + // Insert p4rtutils acl entry on the DUT + if err := programmTableEntry(leader, args.packetIO, false, false); err != nil { + t.Fatalf("There is error when programming entry") + } + // Delete p4rtutils acl entry on the device + defer programmTableEntry(leader, args.packetIO, true, false) + } + streamChan := args.leader.StreamChannelGet(&streamName) + qSize := 12000 + streamChan.SetArbQSize(qSize) + qSizeRead := streamChan.GetArbQSize() + if qSize != qSizeRead { + t.Errorf("Stream '%s' expecting Arbitration qSize(%d) Got (%d)", + streamName, qSize, qSizeRead) + } + + streamChan.SetPacketQSize(qSize) + qSizeRead = streamChan.GetPacketQSize() + if qSize != qSizeRead { + t.Errorf("Stream '%s' expecting Packet qSize(%d) Got (%d)", + streamName, qSize, qSizeRead) + } + + // Send Traceroute traffic from ATE + srcEndPoint := ateInterface(t, args.top, "port1") + llAddress, found := gnmi.Watch(t, args.ate.OTG(), gnmi.OTG().Interface("atePort1"+".Eth").Ipv4Neighbor(portsIPv4["dut:port1"]).LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { + return val.IsPresent() + }).Await(t) + if !found { + t.Fatalf("Could not get the LinkLayerAddress %s", llAddress) + } + dstMac, _ := llAddress.Val() + var flow []gosnappi.Flow + for _, flowValue := range flowValues { + flow = append(flow, args.packetIO.GetTrafficFlow(args.ate, dstMac, isIPv4, 1, 300, 50, ipv4InnerDst, flowValue)) + } + pktOut := testTraffic(t, args.top, args.ate, flow, srcEndPoint, 60, cs) + var countPkts = map[string]int{"11": 0, "12": 0, "13": 0, "14": 0, "15": 0, "16": 0, "17": 0} + + packetInTests := []struct { + desc string + client *p4rt_client.P4RTClient + wantPkts int + }{{ + desc: "PacketIn to Primary Controller", + client: leader, + wantPkts: pktOut, + }} + + t.Log("TTL/HopLimit 1") + for _, test := range packetInTests { + t.Run(test.desc, func(t *testing.T) { + // Extract packets from PacketIn message sent to p4rt client + _, packets, err := test.client.StreamChannelGetPackets(&streamName, uint64(test.wantPkts), 30*time.Second) + if err != nil { + t.Errorf("Unexpected error on fetchPackets: %v", err) + } + + if test.wantPkts == 0 { + return + } + + gotPkts := 0 + t.Logf("Start to decode packet and compare with expected packets.") + wantPacket := args.packetIO.GetPacketTemplate() + + for _, packet := range packets { + if packet != nil { + srcMAC, _, etherType := decodePacket(t, packet.Pkt.GetPayload()) + if etherType != layers.EthernetTypeIPv4 && etherType != layers.EthernetTypeIPv6 { + continue + } + if !strings.EqualFold(srcMAC, tracerouteSrcMAC) { + continue + } + if wantPacket.TTL != nil { + // TTL/HopLimit comparison for IPV4 & IPV6 + if isIPv4 { + captureTTL := decodePacket4(t, packet.Pkt.GetPayload()) + if captureTTL != TTL1 { + t.Fatalf("Packet in PacketIn message is not matching wanted packet=IPV4 TTL1") + } + + } else { + captureHopLimit := decodePacket6(t, packet.Pkt.GetPayload()) + if captureHopLimit != HopLimit1 { + t.Fatalf("Packet in PacketIn message is not matching wanted packet=IPV6 HopLimit1") + } + } + } + + // Metadata comparision + if metaData := packet.Pkt.GetMetadata(); metaData != nil { + if got := metaData[0].GetMetadataId(); got == MetadataIngressPort { + if gotPortID := string(metaData[0].GetValue()); gotPortID != args.packetIO.GetIngressPort() { + t.Fatalf("Ingress Port Id mismatch: want %s, got %s", args.packetIO.GetIngressPort(), gotPortID) + } + } else { + t.Fatalf("Metadata ingress port mismatch: want %d, got %d", MetadataIngressPort, got) + } + if got := metaData[1].GetMetadataId(); got == MetadataEgressPort { + countPkts[string(metaData[1].GetValue())]++ + if gotPortID := string(metaData[1].GetValue()); !EgressPortMap[gotPortID] { + t.Fatalf("Egress Port Id mismatch: got %s", gotPortID) + } + } else { + t.Fatalf("Metadata egress port mismatch: want %d, got %d", MetadataEgressPort, got) + } + } else { + t.Fatalf("Packet missing metadata information.") + } + gotPkts++ + } + } + if got, want := gotPkts, test.wantPkts; got != want { + t.Errorf("Number of PacketIn, got: %d, want: %d", got, want) + } + }) + } + loadBalancePercent := []float64{float64(countPkts["11"]) / float64(pktOut), float64(countPkts["12"]) / float64(pktOut), + float64(countPkts["13"]) / float64(pktOut), float64(countPkts["14"]) / float64(pktOut), float64(countPkts["15"]) / float64(pktOut), + float64(countPkts["16"]) / float64(pktOut), float64(countPkts["17"]) / float64(pktOut)} + + return loadBalancePercent +} + +func ateInterface(t *testing.T, topo gosnappi.Config, portID string) gosnappi.Port { + for _, p := range topo.Ports().Items() { + if p.Name() == portID { + return p + } + } + return nil +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go new file mode 100644 index 00000000000..d3e831d795a --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_test.go @@ -0,0 +1,264 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package to test P4RT with traceroute traffic of IPV4 and IPV6 with TTL/HopLimit as 0&1. +// go test -v . -testbed /root/ondatra/featureprofiles/topologies/atedut_2.testbed -binding /root/ondatra/featureprofiles/topologies/atedut_2.binding -outputs_dir logs + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/cisco-open/go-p4/utils" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/p4rtutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" + pb "github.com/p4lang/p4runtime/go/p4/v1" +) + +// configureDeviceIDs configures p4rt device-id on the DUT. +func configureDeviceID(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice) { + nodes := p4rtutils.P4RTNodesByPort(t, dut) + p4rtNode, ok := nodes["port1"] + if !ok { + t.Fatal("Couldn't find P4RT Node for port: port1") + } + t.Logf("Configuring P4RT Node: %s", p4rtNode) + c := oc.Component{} + c.Name = ygot.String(p4rtNode) + c.IntegratedCircuit = &oc.Component_IntegratedCircuit{} + c.IntegratedCircuit.NodeId = ygot.Uint64(deviceID) + gnmi.Replace(t, dut, gnmi.OC().Component(p4rtNode).Config(), &c) +} + +// setupP4RTClient sends client arbitration message for both leader and follower clients, +// then sends setforwordingpipelineconfig with leader client. +func setupP4RTClient(ctx context.Context, args *testArgs) error { + // Setup p4rt-client stream parameters + streamParameter := p4rt_client.P4RTStreamParameters{ + Name: streamName, + DeviceId: deviceID, + ElectionIdH: uint64(0), + ElectionIdL: electionID, + } + + // Send ClientArbitration message on both p4rt leader and follower clients. + clients := []*p4rt_client.P4RTClient{args.leader, args.follower} + for index, client := range clients { + if client != nil { + client.StreamChannelCreate(&streamParameter) + if err := client.StreamChannelSendMsg(&streamName, &pb.StreamMessageRequest{ + Update: &pb.StreamMessageRequest_Arbitration{ + Arbitration: &pb.MasterArbitrationUpdate{ + DeviceId: streamParameter.DeviceId, + ElectionId: &pb.Uint128{ + High: streamParameter.ElectionIdH, + Low: streamParameter.ElectionIdL - uint64(index), + }, + }, + }, + }); err != nil { + return fmt.Errorf("errors seen when sending ClientArbitration message: %v", err) + } + if _, _, arbErr := client.StreamChannelGetArbitrationResp(&streamName, 1); arbErr != nil { + if err := p4rtutils.StreamTermErr(client.StreamTermErr); err != nil { + return err + } + return fmt.Errorf("errors seen in ClientArbitration response: %v", arbErr) + } + } + } + + // Load p4info file. + p4Info, err := utils.P4InfoLoad(p4InfoFile) + if err != nil { + return errors.New("errors seen when loading p4info file") + } + + // Send SetForwardingPipelineConfig for p4rt leader client. + if err := args.leader.SetForwardingPipelineConfig(&pb.SetForwardingPipelineConfigRequest{ + DeviceId: deviceID, + ElectionId: &pb.Uint128{High: uint64(0), Low: electionID}, + Action: pb.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, + Config: &pb.ForwardingPipelineConfig{ + P4Info: p4Info, + Cookie: &pb.ForwardingPipelineConfig_Cookie{ + Cookie: 159, + }, + }, + }); err != nil { + return errors.New("errors seen when sending SetForwardingPipelineConfig") + } + return nil +} + +// getTracerouteParameter returns Traceroute related parameters for testPacketIn testcase. +func getTracerouteParameter(t *testing.T) PacketIO { + return &TraceroutePacketIO{ + PacketIOPacket: PacketIOPacket{ + TTL: &TTL1, + HopLimit: &HopLimit1, + }, + IngressPort: fmt.Sprint(portID), + EgressPort: fmt.Sprint(portID + 7), + } +} + +// testPacket setup p4RT client, table entry and send traffic and returns the +// percentage of packets sent out of each egress port +func testPacket(t *testing.T, args *testArgs, cs gosnappi.ControlState, flowValues []*flowArgs, EgressPortMap map[string]bool) []float64 { + dut := ondatra.DUT(t, "dut") + ctx := context.Background() + ate := ondatra.ATE(t, "ate") + top := args.otgConfig + top.Flows().Clear() + + configureDeviceID(ctx, t, dut) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + time.Sleep(30 * time.Second) + + args = &testArgs{ + ctx: ctx, + leader: args.leader, + follower: args.follower, + dut: dut, + ate: ate, + top: top, + } + + if err := setupP4RTClient(ctx, args); err != nil { + t.Fatalf("Could not setup p4rt client: %v", err) + } + args.packetIO = getTracerouteParameter(t) + return testPacketIn(ctx, t, args, true, cs, flowValues, EgressPortMap) +} + +type TraceroutePacketIO struct { + PacketIOPacket + IngressPort string + EgressPort string +} + +// GetTableEntry creates p4rtutils acl entry which is used to get the configured p4rt trap rules. +// A packet is sent to controller based on the trap rules +func (traceroute *TraceroutePacketIO) GetTableEntry(delete bool, IsIpv4 bool) []*p4rtutils.ACLWbbIngressTableEntryInfo { + if IsIpv4 { + actionType := pb.Update_INSERT + if delete { + actionType = pb.Update_DELETE + } + return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ + Type: actionType, + IsIpv4: 0x1, + TTL: 0x1, + TTLMask: 0xFF, + Priority: 1, + }, + { + Type: actionType, + IsIpv4: 0x1, + TTL: 0x0, + TTLMask: 0xFF, + Priority: 1, + }} + } + actionType := pb.Update_INSERT + if delete { + actionType = pb.Update_DELETE + } + return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ + Type: actionType, + IsIpv6: 0x1, + TTL: 0x1, + TTLMask: 0xFF, + Priority: 1, + }, + { + Type: actionType, + IsIpv6: 0x1, + TTL: 0x0, + TTLMask: 0xFF, + Priority: 1, + }} +} + +// GetPacketTemplate returns expected packets in PacketIn. +func (traceroute *TraceroutePacketIO) GetPacketTemplate() *PacketIOPacket { + return &traceroute.PacketIOPacket +} + +func (traceroute *TraceroutePacketIO) GetTrafficFlow(ate *ondatra.ATEDevice, dstMac string, isIpv4 bool, + TTL uint8, frameSize uint32, frameRate uint64, dstIP string, flowValues *flowArgs) gosnappi.Flow { + + flow := gosnappi.NewFlow() + flow.SetName(flowValues.flowName) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(tracerouteSrcMAC) + ethHeader.Dst().SetValue(dstMac) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(flowValues.outHdrSrcIP) + ipHeader.Dst().SetValue(flowValues.outHdrDstIP) + ipHeader.TimeToLive().SetValue(uint32(TTL)) + if len(flowValues.outHdrDscp) != 0 { + ipHeader.Priority().Dscp().Phb().SetValues(flowValues.outHdrDscp) + } + if flowValues.udp { + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + + if flowValues.proto != 0 { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Protocol().SetValue(flowValues.proto) + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + innerIPHdr.TimeToLive().SetValue(uint32(TTL)) + } else { + if flowValues.isInnHdrV4 { + innerIPHdr := flow.Packet().Add().Ipv4() + innerIPHdr.Src().SetValue(flowValues.InnHdrSrcIP) + innerIPHdr.Dst().SetValue(flowValues.InnHdrDstIP) + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } else { + innerIpv6Hdr := flow.Packet().Add().Ipv6() + innerIpv6Hdr.Src().SetValue(flowValues.InnHdrSrcIPv6) + innerIpv6Hdr.Dst().SetValue(flowValues.InnHdrDstIPv6) + UDPHeader := flow.Packet().Add().Udp() + UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) + } + } + + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(uint64(frameRate)) + return flow +} + +// GetIngressPort return expected ingress port info in Packetin. +func (traceroute *TraceroutePacketIO) GetIngressPort() string { + return traceroute.IngressPort +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go new file mode 100644 index 00000000000..1d757dab977 --- /dev/null +++ b/feature/experimental/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go @@ -0,0 +1,1141 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package traceroute_packetin_with_vrf_selection_test + +import ( + "context" + "flag" + "fmt" + "sort" + "strconv" + "testing" + "time" + + "github.com/cisco-open/go-p4/p4rt_client" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + baseScenario "github.com/openconfig/featureprofiles/internal/encapfrr" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/vrfpolicy" + "github.com/openconfig/gribigo/chk" + "github.com/openconfig/gribigo/constants" + "github.com/openconfig/gribigo/fluent" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +// Settings for configuring the baseline testbed with the test +// topology. +// +// ATE port-1 <------> port-1 DUT +// DUT port-2 <------> port-2 ATE +// DUT port-3 <------> port-3 ATE +// DUT port-4 <------> port-4 ATE +// DUT port-5 <------> port-5 ATE +// DUT port-6 <------> port-6 ATE +// DUT port-7 <------> port-7 ATE +// DUT port-8 <------> port-8 ATE + +const ( + plenIPv4 = 30 + plenIPv6 = 126 + dscpEncapA1 = 10 + dscpEncapB1 = 20 + dscpEncapNoMatch = 30 + ipv4OuterSrc111Addr = "198.51.100.111" + ipv4OuterSrc222Addr = "198.51.100.222" + ipv4OuterSrcAddr = "198.100.200.123" + ipv4OuterDst111 = "192.51.100.64" + ipv4OuterDst111WithMask = "192.51.100.0/24" + ipv4InnerDst = "138.0.11.8" + noMatchEncapDest = "20.0.0.1" + maskLen24 = "24" + maskLen126 = "124" + maskLen32 = "32" + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niEncapTeVrfB = "ENCAP_TE_VRF_B" + niTeVrf111 = "TE_VRF_111" + niTeVrf222 = "TE_VRF_222" + niRepairVrf = "REPAIR_VRF" + niTransitVRF = "TRANSIT_VRF" + tolerance = 0.02 + encapFlow = "encapFlow" + gribiIPv4EntryVRF1111 = "203.0.113.1" + gribiIPv4EntryVRF1112 = "203.0.113.2" + gribiIPv4EntryEncapVRF = "138.0.11.0" + gribiIPv6EntryEncapVRF = "2001:db8::138:0:11:0" + ipv6InnerDst = "2001:db8::138:0:11:8" + ipv6InnerDstNoEncap = "2001:db8::20:0:0:1" + + dutAreaAddress = "49.0001" + dutSysID = "1920.0000.2001" + otgSysID1 = "640000000001" + isisInstance = "DEFAULT" + otgIsisPort8LoopV4 = "203.0.113.10" + otgIsisPort8LoopV6 = "2001:db8::203:0:113:10" + dutAS = 65501 + peerGrpName1 = "BGP-PEER-GROUP1" + ipOverIPProtocol = 4 +) + +var ( + p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + streamName = "p4rt" + tracerouteSrcMAC = "00:01:00:02:00:03" + deviceID = uint64(1) + portID = uint32(10) + electionID = uint64(100) + MetadataIngressPort = uint32(1) + MetadataEgressPort = uint32(2) + TTL1 = uint8(1) + HopLimit1 = uint8(1) + + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + MAC: "02:00:01:01:01:01", + IPv6: "2001:db8::192:0:2:2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + MAC: "02:00:01:01:01:02", + IPv6: "2001:db8::192:0:2:6", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + IPv4: "192.0.2.9", + IPv6: "2001:db8::192:0:2:9", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort3 = attrs.Attributes{ + Name: "atePort3", + IPv4: "192.0.2.10", + MAC: "02:00:01:01:01:03", + IPv6: "2001:db8::192:0:2:a", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + IPv4: "192.0.2.13", + IPv6: "2001:db8::192:0:2:d", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort4 = attrs.Attributes{ + Name: "atePort4", + IPv4: "192.0.2.14", + MAC: "02:00:01:01:01:04", + IPv6: "2001:db8::192:0:2:e", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort5 = attrs.Attributes{ + Desc: "dutPort5", + IPv4: "192.0.2.17", + IPv6: "2001:db8::192:0:2:11", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort5 = attrs.Attributes{ + Name: "atePort5", + IPv4: "192.0.2.18", + MAC: "02:00:01:01:01:05", + IPv6: "2001:db8::192:0:2:12", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort6 = attrs.Attributes{ + Desc: "dutPort6", + IPv4: "192.0.2.21", + IPv6: "2001:db8::192:0:2:15", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort6 = attrs.Attributes{ + Name: "atePort6", + IPv4: "192.0.2.22", + MAC: "02:00:01:01:01:06", + IPv6: "2001:db8::192:0:2:16", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort7 = attrs.Attributes{ + Desc: "dutPort7", + IPv4: "192.0.2.25", + IPv6: "2001:db8::192:0:2:19", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort7 = attrs.Attributes{ + Name: "atePort7", + IPv4: "192.0.2.26", + MAC: "02:00:01:01:01:07", + IPv6: "2001:db8::192:0:2:1a", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort8 = attrs.Attributes{ + Desc: "dutPort8", + IPv4: "192.0.2.29", + IPv6: "2001:db8::192:0:2:1d", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort8 = attrs.Attributes{ + Name: "atePort8", + IPv4: "192.0.2.30", + MAC: "02:00:01:01:01:08", + IPv6: "2001:db8::192:0:2:1e", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + // loopbackIntfName string + // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 + // Below code will be uncommented once ixia issue is fixed. + // tolerance = 0.2 + + portsMap = map[string]attrs.Attributes{ + "dutPort1": dutPort1, + "atePort1": atePort1, + "dutPort2": dutPort2, + "atePort2": atePort2, + "dutPort3": dutPort3, + "atePort3": atePort3, + "dutPort4": dutPort4, + "atePort4": atePort4, + "dutPort5": dutPort5, + "atePort5": atePort5, + "dutPort6": dutPort6, + "atePort6": atePort6, + "dutPort7": dutPort7, + "atePort7": atePort7, + "dutPort8": dutPort8, + "atePort8": atePort8, + } + portsIPv4 = map[string]string{ + "dut:port1": "192.0.2.1", + "ate:port1": "192.0.2.2", + + "dut:port2": "192.0.2.5", + "ate:port2": "192.0.2.6", + + "dut:port3": "192.0.2.9", + "ate:port3": "192.0.2.10", + + "dut:port4": "192.0.2.13", + "ate:port4": "192.0.2.14", + + "dut:port5": "192.0.2.17", + "ate:port5": "192.0.2.18", + + "dut:port6": "192.0.2.21", + "ate:port6": "192.0.2.22", + + "dut:port7": "192.0.2.25", + "ate:port7": "192.0.2.26", + + "dut:port8": "192.0.2.29", + "ate:port8": "192.0.2.30", + } + portsIPv6 = map[string]string{ + "dut:port1": "2001:db8::192:0:2:1", + "ate:port1": "2001:db8::192:0:2:2", + + "dut:port2": "2001:db8::192:0:2:5", + "ate:port2": "2001:db8::192:0:2:6", + + "dut:port3": "2001:db8::192:0:2:9", + "ate:port3": "2001:db8::192:0:2:a", + + "dut:port4": "2001:db8::192:0:2:d", + "ate:port4": "2001:db8::192:0:2:e", + + "dut:port5": "2001:db8::192:0:2:11", + "ate:port5": "2001:db8::192:0:2:12", + + "dut:port6": "2001:db8::192:0:2:15", + "ate:port6": "2001:db8::192:0:2:16", + + "dut:port7": "2001:db8::192:0:2:19", + "ate:port7": "2001:db8::192:0:2:1a", + + "dut:port8": "2001:db8::192:0:2:1d", + "ate:port8": "2001:db8::192:0:2:1e", + } + otgPortDevices []gosnappi.Device + dutlo0Attrs = attrs.Attributes{ + Desc: "Loopback ip", + IPv4: "203.0.113.11", + IPv6: "2001:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + loopbackIntfName string + atePortNamelist []string +) + +// awaitTimeout calls a fluent client Await, adding a timeout to the context. +func awaitTimeout(ctx context.Context, t testing.TB, c *fluent.GRIBIClient, timeout time.Duration) error { + t.Helper() + subctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return c.Await(subctx, t) +} + +type testArgs struct { + ctx context.Context + client *fluent.GRIBIClient + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgConfig gosnappi.Config + top gosnappi.Config + electionID gribi.Uint128 + otg *otg.OTG + leader *p4rt_client.P4RTClient + follower *p4rt_client.P4RTClient + packetIO PacketIO +} + +type flowArgs struct { + flowName string + outHdrSrcIP, outHdrDstIP string + InnHdrSrcIP, InnHdrDstIP string + InnHdrSrcIPv6, InnHdrDstIPv6 string + udp, isInnHdrV4 bool + outHdrDscp []uint32 + proto uint32 +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func sortPorts(ports []*ondatra.Port) []*ondatra.Port { + sort.Slice(ports, func(i, j int) bool { + idi, idj := ports[i].ID(), ports[j].ID() + li, lj := len(idi), len(idj) + if li == lj { + return idi < idj + } + return li < lj // "port2" < "port10" + }) + return ports +} + +// dutInterface builds a DUT interface ygot struct for a given port +// according to portsIPv4. Returns nil if the port has no IP address +// mapping. +func dutInterface(p *ondatra.Port, dut *ondatra.DUTDevice, portIDx uint32) *oc.Interface { + id := fmt.Sprintf("%s:%s", p.Device().ID(), p.ID()) + i := &oc.Interface{ + Name: ygot.String(p.Name()), + Description: ygot.String(p.String()), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Id: ygot.Uint32(portIDx), + } + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + if p.PMD() == ondatra.PMD100GBASEFR { + e := i.GetOrCreateEthernet() + e.AutoNegotiate = ygot.Bool(false) + e.DuplexMode = oc.Ethernet_DuplexMode_FULL + e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } + + ipv4, ok := portsIPv4[id] + if !ok { + return nil + } + ipv6, ok := portsIPv6[id] + if !ok { + return nil + } + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + + a := s4.GetOrCreateAddress(ipv4) + a.PrefixLength = ygot.Uint8(plenIPv4) + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + a6 := s6.GetOrCreateAddress(ipv6) + a6.PrefixLength = ygot.Uint8(plenIPv6) + + return i +} + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice, dutPortList []*ondatra.Port) { + dc := gnmi.OC() + for idx, dp := range dutPortList { + portIDx := portID + uint32(idx) + if i := dutInterface(dp, dut, portIDx); i != nil { + gnmi.Replace(t, dut, dc.Interface(dp.Name()).Config(), i) + } else { + t.Fatalf("No address found for port %v", dp) + } + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + for _, dp := range dut.Ports() { + fptest.AssignToNetworkInstance(t, dut, dp.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + } + if deviations.ExplicitPortSpeed(dut) { + for _, dp := range dut.Ports() { + fptest.SetPortSpeed(t, dp) + } + } + + loopbackIntfName = netutil.LoopbackInterface(t, dut, 0) + lo0 := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + loop1 := dutlo0Attrs.NewOCInterface(loopbackIntfName, dut) + loop1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, dc.Interface(loopbackIntfName).Config(), loop1) + } else { + v4, ok := ipv4Addrs[0].Val() + if ok { + dutlo0Attrs.IPv4 = v4.GetIp() + } + v6, ok := ipv6Addrs[0].Val() + if ok { + dutlo0Attrs.IPv6 = v6.GetIp() + } + t.Logf("Got DUT IPv4 loopback address: %v", dutlo0Attrs.IPv4) + t.Logf("Got DUT IPv6 loopback address: %v", dutlo0Attrs.IPv6) + } +} + +// configureAdditionalGribiAft configures additional AFT entries for Gribi. +func configureAdditionalGribiAft(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3000).WithNextHopNetworkInstance(niRepairVrf), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3000).AddNextHop(3000, 1), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1111+"/"+maskLen32).WithNextHopGroup(1000), + + fluent.IPv4Entry().WithNetworkInstance(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(gribiIPv4EntryVRF1112+"/"+maskLen32).WithNextHopGroup(1001), + + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfA). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(101). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + defaultVRFIPList := []string{gribiIPv4EntryVRF1111, gribiIPv4EntryVRF1112} + for ip := range defaultVRFIPList { + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(defaultVRFIPList[ip]+"/32"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + } + // Programming AFT entries for prefixes in ENCAP_TE_VRF_A + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(200).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(200).AddNextHop(200, 1), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + args.client.Modify().AddEntry(t, + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(102).AddNextHop(101, 3).AddNextHop(102, 1).WithBackupNHG(200), + fluent.IPv4Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv4EntryEncapVRF+"/"+maskLen24).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + fluent.IPv6Entry().WithNetworkInstance(niEncapTeVrfB). + WithPrefix(gribiIPv6EntryEncapVRF+"/"+maskLen126).WithNextHopGroup(102). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv4Operation(gribiIPv4EntryEncapVRF+"/24"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult(). + WithIPv6Operation(gribiIPv6EntryEncapVRF+"/124"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB). + AsResult(), + chk.IgnoreOperationID(), + ) + +} + +// configureGribiRoute configures Gribi route for prefix +func configureGribiRoute(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs, prefWithMask string) { + t.Helper() + // Using gRIBI, install an IPv4Entry for the prefix 192.51.100.1/24 that points to a + // NextHopGroup that contains a single NextHop that specifies decapsulating the IPv4 + // header and specifies the DEFAULT network instance.This IPv4Entry should be installed + // into the DECAP_TE_VRF. + + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(3001).WithDecapsulateHeader(fluent.IPinIP), + fluent.NextHopGroupEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(3001).AddNextHop(3001, 1), + fluent.IPv4Entry().WithNetworkInstance(niDecapTeVrf). + WithPrefix(prefWithMask).WithNextHopGroup(3001). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + ) + if err := awaitTimeout(args.ctx, t, args.client, time.Minute); err != nil { + t.Logf("Could not program entries via client, got err, check error codes: %v", err) + } + + chk.HasResult(t, args.client.Results(t), + fluent.OperationResult().WithIPv4Operation(prefWithMask).WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInFIB).AsResult(), + chk.IgnoreOperationID(), + ) +} + +// configureISIS configures ISIS on the DUT. +func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName, dutAreaAddress, dutSysID string) { + t.Helper() + d := &oc.Root{} + netInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := netInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + globalISIS := isis.GetOrCreateGlobal() + + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.Instance = ygot.String(isisInstance) + } + globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", dutAreaAddress, dutSysID)} + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + + lspBit := globalISIS.GetOrCreateLspBit().GetOrCreateOverloadBit() + lspBit.SetBit = ygot.Bool(false) + isisLevel2 := isis.GetOrCreateLevel(2) + isisLevel2.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + + isisIntf := isis.GetOrCreateInterface(intfName) + isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(intfName) + isisIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + + if deviations.InterfaceRefConfigUnsupported(dut) { + isisIntf.InterfaceRef = nil + } + + isisIntf.Enabled = ygot.Bool(true) + isisIntf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + isisIntf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + isisIntf.Af = nil + } + isisIntfLevel := isisIntf.GetOrCreateLevel(2) + isisIntfLevel.Enabled = ygot.Bool(true) + + isisIntfLevelAfiv4 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Enabled = ygot.Bool(true) + isisIntfLevelAfiv6 := isisIntfLevel.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + + isisIntfLevelAfiv4.Metric = ygot.Uint32(20) + isisIntfLevelAfiv6.Metric = ygot.Uint32(20) + + isisIntfLevelAfiv6.Enabled = ygot.Bool(true) + if deviations.MissingIsisInterfaceAfiSafiEnable(dut) { + isisIntfLevelAfiv4.Enabled = nil + isisIntfLevelAfiv6.Enabled = nil + } + gnmi.Update(t, dut, gnmi.OC().Config(), d) +} + +// bgpCreateNbr creates BGP neighbor configuration +func bgpCreateNbr(localAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutlo0Attrs.IPv4) + global.As = ygot.Uint32(localAs) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + pg1 := bgp.GetOrCreatePeerGroup(peerGrpName1) + pg1.PeerAs = ygot.Uint32(localAs) + + bgpNbr := bgp.GetOrCreateNeighbor(otgIsisPort8LoopV4) + bgpNbr.PeerGroup = ygot.String(peerGrpName1) + bgpNbr.PeerAs = ygot.Uint32(localAs) + bgpNbr.Enabled = ygot.Bool(true) + bgpNbrT := bgpNbr.GetOrCreateTransport() + bgpNbrT.LocalAddress = ygot.String(dutlo0Attrs.IPv4) + af4 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + af6 := bgpNbr.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + + return niProto +} + +// verifyISISTelemetry verifies ISIS telemetry. +func verifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntf string) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + dutIntf = dutIntf + ".0" + } + nbrPath := statePath.Interface(dutIntf) + query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + return present && state == oc.Isis_IsisInterfaceAdjState_UP + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", dutIntf) + t.Fatal("No IS-IS adjacencies reported.") + } +} + +// verifyBgpTelemetry verifies BGP telemetry. +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + nbrPath := bgpPath.Neighbor(otgIsisPort8LoopV4) + // Get BGP adjacency state. + t.Logf("Waiting for BGP neighbor to establish...") + var status *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState] + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", otgIsisPort8LoopV4, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", otgIsisPort8LoopV4, state, want) + } +} + +// configureOTG configures the topology of the ATE. +func configureOTG(t testing.TB, otg *otg.OTG, atePorts []*ondatra.Port) gosnappi.Config { + t.Helper() + t.Logf("configureOTG") + config := gosnappi.NewConfig() + pmd100GFRPorts := []string{} + for i, ap := range atePorts { + if ap.PMD() == ondatra.PMD100GBASEFR { + pmd100GFRPorts = append(pmd100GFRPorts, ap.ID()) + } + // DUT and ATE ports are connected by the same names. + dutid := fmt.Sprintf("dut:%s", ap.ID()) + ateid := fmt.Sprintf("ate:%s", ap.ID()) + + port := config.Ports().Add().SetName(ap.ID()) + atePortNamelist = append(atePortNamelist, port.Name()) + portName := fmt.Sprintf("atePort%s", strconv.Itoa(i+1)) + dev := config.Devices().Add().SetName(portName) + macAddress := portsMap[portName].MAC + eth := dev.Ethernets().Add().SetName(portName + ".Eth").SetMac(macAddress) + eth.Connection().SetPortName(port.Name()) + eth.Ipv4Addresses().Add().SetName(portName + ".IPv4"). + SetAddress(portsIPv4[ateid]).SetGateway(portsIPv4[dutid]). + SetPrefix(plenIPv4) + eth.Ipv6Addresses().Add().SetName(portName + ".IPv6"). + SetAddress(portsIPv6[ateid]).SetGateway(portsIPv6[dutid]). + SetPrefix(plenIPv6) + + otgPortDevices = append(otgPortDevices, dev) + if i == 7 { + iDut8LoopV4 := dev.Ipv4Loopbacks().Add().SetName("Port8LoopV4").SetEthName(eth.Name()) + iDut8LoopV4.SetAddress(otgIsisPort8LoopV4) + iDut8LoopV6 := dev.Ipv6Loopbacks().Add().SetName("Port8LoopV6").SetEthName(eth.Name()) + iDut8LoopV6.SetAddress(otgIsisPort8LoopV6) + isisDut := dev.Isis().SetName("ISIS1").SetSystemId(otgSysID1) + isisDut.Basic().SetIpv4TeRouterId(portsIPv4[ateid]).SetHostname(isisDut.Name()).SetLearnedLspFilter(true) + isisDut.Interfaces().Add().SetEthName(dev.Ethernets().Items()[0].Name()). + SetName("devIsisInt1"). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + // Advertise OTG Port8 loopback address via ISIS. + isisPort2V4 := dev.Isis().V4Routes().Add().SetName("ISISPort8V4").SetLinkMetric(10) + isisPort2V4.Addresses().Add().SetAddress(otgIsisPort8LoopV4).SetPrefix(32) + isisPort2V6 := dev.Isis().V6Routes().Add().SetName("ISISPort8V6").SetLinkMetric(10) + isisPort2V6.Addresses().Add().SetAddress(otgIsisPort8LoopV6).SetPrefix(uint32(128)) + iDutBgp := dev.Bgp().SetRouterId(otgIsisPort8LoopV4) + iDutBgp4Peer := iDutBgp.Ipv4Interfaces().Add().SetIpv4Name(iDut8LoopV4.Name()).Peers().Add().SetName(ap.ID() + ".BGP4.peer") + iDutBgp4Peer.SetPeerAddress(dutlo0Attrs.IPv4).SetAsNumber(dutAS).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + iDutBgp4Peer.Capability().SetIpv4Unicast(true).SetIpv6Unicast(true) + iDutBgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + bgpNeti1Bgp4PeerRoutes := iDutBgp4Peer.V4Routes().Add().SetName(port.Name() + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(otgIsisPort8LoopV4). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(ipv4InnerDst).SetPrefix(32). + SetCount(1).SetStep(1) + bgpNeti1Bgp4PeerRoutes.Addresses().Add().SetAddress(noMatchEncapDest).SetPrefix(32). + SetCount(1).SetStep(1) + + bgpNeti1Bgp6PeerRoutes := iDutBgp4Peer.V6Routes().Add().SetName(atePort8.Name + ".BGP6.Route") + bgpNeti1Bgp6PeerRoutes.SetNextHopIpv6Address(otgIsisPort8LoopV6). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL). + Advanced().SetLocalPreference(100).SetIncludeLocalPreference(true) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDst).SetPrefix(128). + SetCount(1).SetStep(1) + bgpNeti1Bgp6PeerRoutes.Addresses().Add().SetAddress(ipv6InnerDstNoEncap).SetPrefix(128). + SetCount(1).SetStep(1) + + } + + } + config.Captures().Add().SetName("packetCapture"). + SetPortNames([]string{atePortNamelist[1], atePortNamelist[2], atePortNamelist[3], atePortNamelist[4], + atePortNamelist[5], atePortNamelist[6], atePortNamelist[7]}). + SetFormat(gosnappi.CaptureFormat.PCAP) + + // Disable FEC for 100G-FR ports because Novus does not support it. + if len(pmd100GFRPorts) > 0 { + l1Settings := config.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + pb, _ := config.Marshal().ToProto() + t.Log(pb.GetCaptures()) + return config +} + +func startCapture(t *testing.T, args *testArgs, capturePortList []string) gosnappi.ControlState { + t.Helper() + args.otgConfig.Captures().Clear() + args.otgConfig.Captures().Add().SetName("packetCapture"). + SetPortNames(capturePortList). + SetFormat(gosnappi.CaptureFormat.PCAP) + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + args.otg.SetControlState(t, cs) + return cs +} + +func verifyPortStatus(t *testing.T, args *testArgs, portList []string, portStatus bool) { + wantStatus := oc.Interface_OperStatus_UP + if !portStatus { + wantStatus = oc.Interface_OperStatus_DOWN + } + for _, port := range portList { + p := args.dut.Port(t, port) + t.Log("Check for port status") + gnmi.Await(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), 1*time.Minute, wantStatus) + operStatus := gnmi.Get(t, args.dut, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if operStatus != wantStatus { + t.Errorf("Get(DUT %v oper status): got %v, want %v", port, operStatus, wantStatus) + } + } +} + +func validateTrafficDistribution(t *testing.T, ate *ondatra.ATEDevice, wantWeights []float64, gotWeights []float64) { + t.Log("Verify packet load balancing as per the programmed weight") + t.Log("got ratio:", gotWeights) + t.Log("want ratio:", wantWeights) + if diff := cmp.Diff(wantWeights, gotWeights, cmpopts.EquateApprox(0, tolerance)); diff != "" { + t.Errorf("Packet distribution ratios -want,+got:\n%s", diff) + } +} + +// testGribiMatchNoSourceNoProtocolMacthDSCP is to test based on packet which doesn't match source IP and protocol +// but match DSCP value +// Test-1 +func testGribiMatchNoSourceNoProtocolMacthDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": true, "16": false, "17": false} + LoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrcAddr, outHdrDstIP: ipv4InnerDst, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, udp: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTunnelTrafficMatchDefaultTerm is to test Tunnel traffic match default term +// Test-2 +func testTunnelTrafficMatchDefaultTerm(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": false, "12": false, "13": false, "14": false, "15": false, "16": false, "17": true} + LoadBalancePercent := []float64{0, 0, 0, 0, 0, 0, 1} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrcAddr, outHdrDstIP: noMatchEncapDest, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: noMatchEncapDest, isInnHdrV4: true, udp: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testGribiDecapMatchSrcProtoDSCP is to test Gribi decap match src proto DSCP +// Test-3 +func testGribiDecapMatchSrcProtoDSCP(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": false, "16": false, "17": false} + LoadBalancePercent := []float64{0.0625, 0.1875, 0.75, 0, 0, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: gribiIPv4EntryVRF1111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTunnelTrafficNoDecap is to test Tunnel traffic no decap +// Test-6 +func testTunnelTrafficNoDecap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Helper() + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": false, "12": false, "13": false, "14": false, "15": false, "16": false, "17": true} + LoadBalancePercent := []float64{0, 0, 0, 0, 0, 0, 1} + + cases := []struct { + desc string + prefixWithMask string + }{{ + desc: "Mask Length 24", + prefixWithMask: "192.51.100.0/24", + }, { + desc: "Mask Length 32", + prefixWithMask: "192.51.100.64/32", + }, { + desc: "Mask Length 28", + prefixWithMask: "192.51.100.64/28", + }, { + desc: "Mask Length 22", + prefixWithMask: "192.51.100.0/22", + }} + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + + t.Run("Program gRIBi route for Prefix "+tc.prefixWithMask, func(t *testing.T) { + configureGribiRoute(ctx, t, dut, args, tc.prefixWithMask) + }) + t.Run("Create ip-in-ip and ipv6-in-ip flows, send traffic and verify decap functionality", + func(t *testing.T) { + // Send both 6in4 and 4in4 packets. Verify that the packets have their outer + // v4 header stripped and are forwarded according to the route in the DEFAULT + // VRF that matches the inner IP address. + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, + InnHdrSrcIP: ipv4OuterSrcAddr, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) + }) + }) + } +} + +// testTunnelTrafficDecapEncap is to test Tunnel traffic decap encap +// Test-9 +func testTunnelTrafficDecapEncap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(args.client); err != nil { + t.Fatal(err) + } + + // Configure GRIBi baseline AFTs. + baseScenario.ConfigureBaseGribiRoutes(ctx, t, dut, args.client) + configureAdditionalGribiAft(ctx, t, dut, args) + configureGribiRoute(ctx, t, dut, args, ipv4OuterDst111WithMask) + + baseCapturePortList := []string{atePortNamelist[1], atePortNamelist[5]} + EgressPortMap := map[string]bool{"11": true, "12": true, "13": true, "14": false, "15": true, "16": false, "17": false} + LoadBalancePercent := []float64{0.0156, 0.0468, 0.1875, 0, 0.75, 0, 0} + flow := []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc222Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState := startCapture(t, args, baseCapturePortList) + gotWeights := testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) + + LoadBalancePercent = []float64{0.0468, 0.1406, 0.5625, 0, 0.25, 0, 0} + flow = []*flowArgs{{flowName: "flow4in4", + outHdrSrcIP: ipv4OuterSrc111Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}, + {flowName: "flow6in4", + outHdrSrcIP: ipv4OuterSrc222Addr, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapB1}, + InnHdrSrcIPv6: atePort1.IPv6, InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false}} + captureState = startCapture(t, args, baseCapturePortList) + gotWeights = testPacket(t, args, captureState, flow, EgressPortMap) + validateTrafficDistribution(t, args.ate, LoadBalancePercent, gotWeights) +} + +// testTraceRoute is to test Test FRR behaviors with encapsulation scenarios +func TestTraceRoute(t *testing.T) { + ctx := context.Background() + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + gribic := dut.RawAPIs().GRIBI(t) + top := gosnappi.NewConfig() + dutPorts := sortPorts(dut.Ports())[0:8] + atePorts := sortPorts(ate.Ports())[0:8] + + t.Log("Configure Default Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.BackupNHGRequiresVrfWithDecap(dut) { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + pf := ni.GetOrCreatePolicyForwarding() + fp1 := pf.GetOrCreatePolicy("match-ipip") + fp1.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + fp1.GetOrCreateRule(1).GetOrCreateIpv4().Protocol = oc.UnionUint8(ipOverIPProtocol) + fp1.GetOrCreateRule(1).GetOrCreateAction().NetworkInstance = ygot.String(deviations.DefaultNetworkInstance(dut)) + p1 := dut.Port(t, "port1") + intf := pf.GetOrCreateInterface(p1.Name()) + intf.ApplyVrfSelectionPolicy = ygot.String("match-ipip") + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), pf) + } + + t.Run("Configure Interface on DUT", func(t *testing.T) { + configureDUT(t, dut, dutPorts) + }) + + t.Log("Apply vrf selection policy_c to DUT port-1") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyC) + + if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + // staticARPWithMagicUniversalIP(t, dut) + baseScenario.StaticARPWithMagicUniversalIP(t, dut) + } + + t.Log("Install BGP route resolved by ISIS.") + t.Log("Configure ISIS on DUT") + configureISIS(t, dut, dut.Port(t, "port8").Name(), dutAreaAddress, dutSysID) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + gnmi.Delete(t, dut, dutConfPath.Config()) + dutConf := bgpCreateNbr(dutAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + var otgConfig gosnappi.Config + t.Run("Configure OTG", func(t *testing.T) { + otgConfig = configureOTG(t, otg, atePorts) + }) + + // Connect gRIBI client to DUT referred to as gRIBI - using PRESERVE persistence and + // SINGLE_PRIMARY mode, with FIB ACK requested. Specify gRIBI as the leader. + client := fluent.NewClient() + client.Connection().WithStub(gribic).WithPersistence().WithInitialElectionID(1, 0). + WithFIBACK().WithRedundancyMode(fluent.ElectedPrimaryClient) + client.Start(ctx, t) + + client.StartSending(ctx, t) + if err := awaitTimeout(ctx, t, client, time.Minute); err != nil { + t.Fatalf("Await got error during session negotiation for clientA: %v", err) + } + eID := gribi.BecomeLeader(t, client) + + leader := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := leader.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + t.Fatalf("Could not initialize p4rt client: %v", err) + } + + follower := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := follower.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + t.Fatalf("Could not initialize p4rt client: %v", err) + } + args := &testArgs{ + ctx: ctx, + client: client, + dut: dut, + ate: ate, + otgConfig: otgConfig, + top: top, + electionID: eID, + otg: otg, + leader: leader, + follower: follower, + } + t.Log("Configure gRIBI routes") + t.Log("Flush existing gRIBI routes before test.") + if err := gribi.FlushAll(client); err != nil { + t.Fatal(err) + } + + t.Log("Verify whether the ports are in up state") + portList := []string{"port2", "port3", "port4", "port5", "port6", "port7", "port8"} + verifyPortStatus(t, args, portList, true) + + t.Log("Verify ISIS telemetry") + verifyISISTelemetry(t, dut, dut.Port(t, "port8").Name()) + t.Log("Verify BGP telemetry") + verifyBgpTelemetry(t, dut) + + t.Run("Test-1: Match on DSCP, no Source and no Protocol", func(t *testing.T) { + testGribiMatchNoSourceNoProtocolMacthDSCP(ctx, t, dut, args) + }) + + t.Log("Delete vrf selection policy C and Apply vrf selectioin policy W") + vrfpolicy.ConfigureVRFSelectionPolicy(t, dut, vrfpolicy.VRFPolicyW) + + t.Run("Test-2: Match on default term and send to default VRF", func(t *testing.T) { + testTunnelTrafficMatchDefaultTerm(ctx, t, dut, args) + }) + t.Run("Test-3: Match on source, protocol and DSCP, VRF_DECAP hit -> VRF_ENCAP_A miss -> DEFAULT", func(t *testing.T) { + testGribiDecapMatchSrcProtoDSCP(ctx, t, dut, args) + }) + // Below test case will implement later + /* + t.Run("Test-4: Tests that traceroute respects transit FRR", func(t *testing.T) { + + }) + t.Run("Test-5: Tests that traceroute respects transit FRR when the backup is also unviable.", func(t *testing.T) { + + })*/ + t.Run("Test-6: Tunneled traffic with no decap", func(t *testing.T) { + testTunnelTrafficNoDecap(ctx, t, dut, args) + }) + // Below test case will implement later + /* + t.Run("Test-7: Encap failure cases (TBD on confirmation)", func(t *testing.T) { + + }) + t.Run("Test-8: Tests that traceroute for a packet with a route lookup miss has an unset target_egress_port.", func(t *testing.T) { + + })*/ + t.Run("Test-9: Decap then encap", func(t *testing.T) { + testTunnelTrafficDecapEncap(ctx, t, dut, args) + }) +} diff --git a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/README.md b/feature/experimental/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/experimental/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/traceroute_packetout_test.go b/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go index 2b6cbf087ea..74b7b98b949 100644 --- a/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go +++ b/feature/experimental/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" @@ -238,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/policy/otg_tests/prefix_set_test/README.md b/feature/experimental/policy/otg_tests/prefix_set_test/README.md new file mode 100644 index 00000000000..20ccedcaaa8 --- /dev/null +++ b/feature/experimental/policy/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/experimental/policy/otg_tests/prefix_set_test/metadata.textproto b/feature/experimental/policy/otg_tests/prefix_set_test/metadata.textproto new file mode 100644 index 00000000000..b47a1749263 --- /dev/null +++ b/feature/experimental/policy/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/experimental/policy/otg_tests/prefix_set_test/prefix_set_test.go b/feature/experimental/policy/otg_tests/prefix_set_test/prefix_set_test.go new file mode 100644 index 00000000000..c52f4887069 --- /dev/null +++ b/feature/experimental/policy/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_base/feature.textproto b/feature/experimental/policy/policy_base/feature.textproto index e1e3a20a9e4..e0799b0980c 100644 --- a/feature/experimental/policy/policy_base/feature.textproto +++ b/feature/experimental/policy/policy_base/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: "experimental_policy_policy_base" diff --git a/feature/experimental/policy/policy_vrf_selection/feature.textproto b/feature/experimental/policy/policy_vrf_selection/feature.textproto index 8a0ef73150f..a5262ef5612 100644 --- a/feature/experimental/policy/policy_vrf_selection/feature.textproto +++ b/feature/experimental/policy/policy_vrf_selection/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: "experimental_policy_policy_vrf_selection" diff --git a/feature/experimental/policy/policy_vrf_selection/otg_tests/base_vrf_selection/README.md b/feature/experimental/policy/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/experimental/policy/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/metadata.textproto b/feature/experimental/policy/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/experimental/policy/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/experimental/policy/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/experimental/policy/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/experimental/policy/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/experimental/policy/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/route_redistribution/feature.textproto b/feature/experimental/route_redistribution/feature.textproto index b91b55c46c8..fa276b3ca6b 100644 --- a/feature/experimental/route_redistribution/feature.textproto +++ b/feature/experimental/route_redistribution/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: "experimental_route_redistribution" diff --git a/feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go b/feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go index 82508df4950..8581b9ed14c 100644 --- a/feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go +++ b/feature/experimental/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go @@ -50,9 +50,8 @@ func keyboardInteraction(password string) ssh.KeyboardInteractiveChallenge { } } -func gnmiClient(ctx context.Context, dut *ondatra.DUTDevice, gnmiAddr string) (gpb.GNMIClient, error) { - conn, err := grpc.DialContext( - ctx, +func gnmiClient(dut *ondatra.DUTDevice, gnmiAddr string) (gpb.GNMIClient, error) { + conn, err := grpc.NewClient( gnmiAddr, grpc.WithTransportCredentials( credentials.NewTLS(&tls.Config{ @@ -60,7 +59,7 @@ func gnmiClient(ctx context.Context, dut *ondatra.DUTDevice, gnmiAddr string) (g })), ) 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 } @@ -240,7 +239,7 @@ func TestAuthentication(t *testing.T) { context.Background(), "username", tc.user, "password", tc.pass) - gnmi, err := gnmiClient(ctx, dut, gnmiAddr) + gnmi, err := gnmiClient(dut, gnmiAddr) if err != nil { t.Fatal(err) } diff --git a/feature/experimental/system/gnmi/benchmarking/internal/setup/setup.go b/feature/experimental/system/gnmi/benchmarking/internal/setup/setup.go index cc2fa872227..1facbd3a15b 100644 --- a/feature/experimental/system/gnmi/benchmarking/internal/setup/setup.go +++ b/feature/experimental/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/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/README.md similarity index 100% rename from feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/README.md rename to feature/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/README.md diff --git a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go b/feature/experimental/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/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go index 925e9149c55..3b12bc1d5e5 100644 --- a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go +++ b/feature/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go @@ -17,7 +17,6 @@ package drained_configuration_convergence_time_test import ( - "net" "testing" "time" @@ -28,6 +27,7 @@ import ( "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" ) @@ -178,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. @@ -188,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") @@ -223,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) { @@ -244,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 @@ -252,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. @@ -303,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/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go similarity index 73% rename from feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go rename to feature/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go index e14f163c63f..e8bf2d5cfd2 100644 --- a/feature/experimental/system/gnmi/benchmarking/ate_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go +++ b/feature/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go @@ -25,6 +25,7 @@ import ( "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/experimental/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/experimental/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/metadata.textproto 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 index 671e98cab28..2589aeda5c4 100644 --- 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 @@ -17,9 +17,26 @@ 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 +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: -## Telemetry Parameter Coverage +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/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go index cf590178c90..e07d56bb4aa 100644 --- a/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go +++ b/feature/experimental/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go @@ -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/experimental/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/experimental/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/experimental/system/health/tests/system_generic_health_check/README.md b/feature/experimental/system/health/tests/system_generic_health_check/README.md index 7e7c8e7ab70..fbb026098c2 100644 --- a/feature/experimental/system/health/tests/system_generic_health_check/README.md +++ b/feature/experimental/system/health/tests/system_generic_health_check/README.md @@ -35,50 +35,41 @@ Generic Health Check N/A -## Telemetry Parameter Coverage +## OpenConfig Path and RPC 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 +```yaml +rpcs: + gnmi: + gNMI.Get: -## Protocol/RPC Parameter Coverage \ No newline at end of file +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/experimental/system/health/tests/system_generic_health_check/metadata.textproto index d45bb55810b..7c47220cea4 100644 --- a/feature/experimental/system/health/tests/system_generic_health_check/metadata.textproto +++ b/feature/experimental/system/health/tests/system_generic_health_check/metadata.textproto @@ -16,6 +16,7 @@ platform_exceptions: { 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/experimental/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/experimental/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/experimental/telemetry_only/feature.textproto b/feature/experimental/telemetry_only/feature.textproto index b8f75e48085..829203c9478 100644 --- a/feature/experimental/telemetry_only/feature.textproto +++ b/feature/experimental/telemetry_only/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: "experimental_telemetry_only" diff --git a/feature/gnmi/ate_tests/telemetry_port_speed_test/README.md b/feature/gnmi/ate_tests/telemetry_port_speed_test/README.md deleted file mode 100644 index c825def6c1d..00000000000 --- a/feature/gnmi/ate_tests/telemetry_port_speed_test/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# gNMI-1.5: Telemetry: Port Speed Test - -## Summary - -Validate port speed telemetry used by controller infrastructure. - -## Procedure - -* For each port speed to be supported by the device: - * Connect single port to ATE, validate that the port speed reported in - telemetry is the expected port speed. - * Turn port down at ATE, validate that operational status of the port is - reported as down. -* For each port speed to be supported by the device: - * Connect N ports between ATE and DUT configured as part of a LACP bundle. - Validate /interfaces/interface/aggregation/state/lag-speed is reported - as N*port speed. - * Disable each port at ATE and determine that the effective speed is - reduced by the expected amount. - * Turn ports sequentially up at the ATE, and determine that the effective - speed is increased as expected. - -## Config Parameter Coverage - -TBD - -## Telemetry Parameter Coverage - -/interfaces/interface/state/oper-status -/interfaces/interface/ethernet/state/port-speed -/interfaces/interface/aggregation/state/lag-speed - -## Protocol/RPC Parameter Coverage - -No new protocol coverage. - -## Minimum DUT platform requirement - -vRX diff --git a/feature/gnmi/ate_tests/telemetry_port_speed_test/telemetry_port_speed_test.go b/feature/gnmi/ate_tests/telemetry_port_speed_test/telemetry_port_speed_test.go deleted file mode 100644 index 894df7298eb..00000000000 --- a/feature/gnmi/ate_tests/telemetry_port_speed_test/telemetry_port_speed_test.go +++ /dev/null @@ -1,380 +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 telemetry_port_speed_test implements tests that cover port-speed related -// telemetry variables. -package telemetry_port_speed_test - -import ( - "fmt" - "sort" - "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/netutil" - "github.com/openconfig/ygnmi/ygnmi" - "github.com/openconfig/ygot/ygot" -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -// Test cases: -// - Validate that the port speed reported in telemetry is the expected port speed. -// - Turn port down at ATE, validate that operational status of the port is reported as down. -// - Connect N ports between ATE and DUT configured as part of a LACP bundle. -// Validate that the effective speed of the LAG is reported as N*port speed. -// - Disable each port at ATE and determine that the effective speed is reduced by the expected amount. -// - Turn ports sequentially up at the ATE, and determine that the effective speed is increased as expected. -// -// Topology: -// -// dut:port1 <--> ate:port1 -// dut:portN <--> ate:portN -const ( - plen4 = 30 - plen6 = 126 -) - -var ( - dutIPs = attrs.Attributes{ - Name: "dutip", - Desc: "LAG To ATE", - IPv4: "192.0.2.5", - IPv6: "2001:db8::5", - IPv4Len: plen4, - IPv6Len: plen6, - } - - ateIPs = attrs.Attributes{ - Name: "ateip", - IPv4: "192.0.2.6", - IPv6: "2001:db8::6", - IPv4Len: plen4, - IPv6Len: plen6, - } -) - -const ( - ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd - ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag - lagTypeLACP = oc.IfAggregate_AggregationType_LACP - lagTypeSTATIC = oc.IfAggregate_AggregationType_STATIC - minLink = 1 -) - -type testCase struct { - minlinks uint16 - lagType oc.E_IfAggregate_AggregationType - - dut *ondatra.DUTDevice - ate *ondatra.ATEDevice - top *ondatra.ATETopology - - dutPorts []*ondatra.Port - atePorts []*ondatra.Port - aggID string -} - -func (tc *testCase) configDUT(i *oc.Interface, a *attrs.Attributes) { - i.Description = ygot.String(a.Desc) - if deviations.InterfaceEnabled(tc.dut) { - i.Enabled = ygot.Bool(true) - } - - s := i.GetOrCreateSubinterface(0) - s4 := s.GetOrCreateIpv4() - if deviations.InterfaceEnabled(tc.dut) && !deviations.IPv4MissingEnabled(tc.dut) { - s4.Enabled = ygot.Bool(true) - } - s4.GetOrCreateAddress(a.IPv4).PrefixLength = ygot.Uint8((plen4)) - - s6 := s.GetOrCreateIpv6() - if deviations.InterfaceEnabled(tc.dut) { - s6.Enabled = ygot.Bool(true) - } - s6.GetOrCreateAddress(a.IPv6).PrefixLength = ygot.Uint8(plen6) -} - -func (tc *testCase) configAggregateDUT(i *oc.Interface, a *attrs.Attributes) { - tc.configDUT(i, a) - i.Type = ieee8023adLag - g := i.GetOrCreateAggregation() - g.LagType = tc.lagType - g.MinLinks = ygot.Uint16(tc.minlinks) -} - -var portSpeed = map[ondatra.Speed]oc.E_IfEthernet_ETHERNET_SPEED{ - ondatra.Speed10Gb: oc.IfEthernet_ETHERNET_SPEED_SPEED_10GB, - ondatra.Speed100Gb: oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB, - ondatra.Speed400Gb: oc.IfEthernet_ETHERNET_SPEED_SPEED_400GB, -} - -func (tc *testCase) configMemberDUT(i *oc.Interface, p *ondatra.Port) { - i.Description = ygot.String(p.String()) - i.Type = ethernetCsmacd - if deviations.InterfaceEnabled(tc.dut) { - i.Enabled = ygot.Bool(true) - } - e := i.GetOrCreateEthernet() - e.AggregateId = ygot.String(tc.aggID) -} - -func (tc *testCase) setupAggregateAtomically(t *testing.T) { - d := &oc.Root{} - - if tc.lagType == lagTypeLACP { - d.GetOrCreateLacp().GetOrCreateInterface(tc.aggID) - } - - agg := d.GetOrCreateInterface(tc.aggID) - agg.GetOrCreateAggregation().LagType = tc.lagType - agg.Type = ieee8023adLag - - for _, port := range tc.dutPorts { - i := d.GetOrCreateInterface(port.Name()) - i.GetOrCreateEthernet().AggregateId = ygot.String(tc.aggID) - i.Type = ethernetCsmacd - } - - fptest.LogQuery(t, fmt.Sprintf("%s to Update()", tc.dut), gnmi.OC().Config(), d) - gnmi.Update(t, tc.dut, gnmi.OC().Config(), d) -} - -func (tc *testCase) clearAggregateMembers(t *testing.T) { - for _, port := range tc.dutPorts { - gnmi.Delete(t, tc.dut, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) - } -} - -// sortPorts sorts the ports by the testbed port ID. -func sortPorts(ports []*ondatra.Port) []*ondatra.Port { - sort.SliceStable(ports, func(i, j int) bool { - return ports[i].ID() < ports[j].ID() - }) - return ports -} - -func (tc *testCase) configureDUT(t *testing.T) { - t.Logf("dut ports = %v", tc.dutPorts) - if len(tc.dutPorts) < 2 { - t.Fatalf("Testbed requires at least 2 ports, got %d", len(tc.dutPorts)) - } - - d := gnmi.OC() - - if deviations.AggregateAtomicUpdate(tc.dut) { - tc.clearAggregateMembers(t) - tc.setupAggregateAtomically(t) - } - - for _, port := range tc.dutPorts { - iName := port.Name() - i := &oc.Interface{Name: ygot.String(iName)} - tc.configMemberDUT(i, port) - iPath := d.Interface(iName) - fptest.LogQuery(t, port.String(), iPath.Config(), i) - gnmi.Replace(t, tc.dut, iPath.Config(), i) - if deviations.ExplicitPortSpeed(tc.dut) { - fptest.SetPortSpeed(t, port) - } - } - - if tc.lagType == lagTypeLACP { - lacp := &oc.Lacp_Interface{Name: ygot.String(tc.aggID)} - lacp.LacpMode = oc.Lacp_LacpActivityType_ACTIVE - - lacpPath := d.Lacp().Interface(tc.aggID) - fptest.LogQuery(t, "LACP", lacpPath.Config(), lacp) - gnmi.Replace(t, tc.dut, lacpPath.Config(), lacp) - t.Cleanup(func() { - gnmi.Delete(t, tc.dut, lacpPath.Config()) - }) - } - - agg := &oc.Interface{Name: ygot.String(tc.aggID)} - tc.configAggregateDUT(agg, &dutIPs) - aggPath := d.Interface(tc.aggID) - fptest.LogQuery(t, tc.aggID, aggPath.Config(), agg) - gnmi.Replace(t, tc.dut, aggPath.Config(), agg) - if deviations.ExplicitInterfaceInDefaultVRF(tc.dut) { - fptest.AssignToNetworkInstance(t, tc.dut, tc.aggID, deviations.DefaultNetworkInstance(tc.dut), 0) - } - t.Cleanup(func() { - gnmi.Delete(t, tc.dut, gnmi.OC().Interface(tc.aggID).Aggregation().MinLinks().Config()) - for _, port := range tc.dutPorts { - iName := port.Name() - iPath := d.Interface(iName) - gnmi.Replace(t, tc.dut, iPath.Config(), &oc.Interface{Name: ygot.String(iName), Type: ethernetCsmacd}) - } - if deviations.AggregateAtomicUpdate(tc.dut) { - resetBatch := &gnmi.SetBatch{} - if deviations.ExplicitInterfaceInDefaultVRF(tc.dut) { - gnmi.BatchDelete(resetBatch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(tc.dut)).Interface(tc.aggID+".0").Config()) - } - gnmi.BatchDelete(resetBatch, aggPath.Config()) - gnmi.BatchDelete(resetBatch, d.Lacp().Interface(tc.aggID).Config()) - resetBatch.Set(t, tc.dut) - } - gnmi.Delete(t, tc.dut, aggPath.Config()) - }) -} - -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) - - // Disable FEC for 100G-FR ports because Novus does not support it. - is100gfr := false - for _, p := range tc.atePorts { - if p.PMD() == ondatra.PMD100GBASEFR { - is100gfr = true - } - } - if is100gfr { - agg.Ethernet().FEC().WithEnabled(false) - } - - agg.IPv4(). - WithAddress(ateIPs.IPv4CIDR()). - WithDefaultGateway(dutIPs.IPv4) - agg.IPv6(). - WithAddress(ateIPs.IPv6CIDR()). - WithDefaultGateway(dutIPs.IPv6) - - 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 { - t.Errorf("Get(DUT port status): got %v, want %v", got, want) - } -} - -func TestGNMIPortSpeed(t *testing.T) { - dut := ondatra.DUT(t, "dut") - dutPort := dut.Port(t, "port1") - if got, want := gnmi.Get(t, dut, gnmi.OC().Interface(dutPort.Name()).Ethernet().PortSpeed().State()), portSpeed[dutPort.Speed()]; got != want { - t.Errorf("Get(DUT port1 status): got %v, want %v", got, want) - } -} - -func TestGNMIPortDown(t *testing.T) { - dut := ondatra.DUT(t, "dut") - 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()) - - if want := oc.Interface_OperStatus_DOWN; dutPortStatus != want { - t.Errorf("Get(DUT port1 status): got %v, want %v", dutPortStatus, want) - } - ate.Actions().NewSetPortState().WithPort(atePort).WithEnabled(true).Send(t) -} - -func TestGNMICombinedLACPSpeed(t *testing.T) { - dut := ondatra.DUT(t, "dut") - ate := ondatra.ATE(t, "ate") - - for _, lagType := range []oc.E_IfAggregate_AggregationType{lagTypeLACP, lagTypeSTATIC} { - t.Run(lagType.String(), func(t *testing.T) { - top := ate.Topology().New() - tc := &testCase{ - minlinks: minLink, - lagType: lagType, - - dut: dut, - ate: ate, - top: top, - - dutPorts: sortPorts(dut.Ports()), - atePorts: sortPorts(ate.Ports()), - aggID: netutil.NextAggregateInterface(t, dut), - } - tc.configureDUT(t) - tc.configureATE(t) - tc.verifyDUT(t, len(tc.dutPorts)) - }) - } -} - -func TestGNMIReducedLACPSpeed(t *testing.T) { - dut := ondatra.DUT(t, "dut") - ate := ondatra.ATE(t, "ate") - totalPort := len(ate.Ports()) - - for _, lagType := range []oc.E_IfAggregate_AggregationType{lagTypeLACP, lagTypeSTATIC} { - t.Run(lagType.String(), func(t *testing.T) { - top := ate.Topology().New() - tc := &testCase{ - minlinks: minLink, - lagType: lagType, - dut: dut, - ate: ate, - top: top, - - dutPorts: sortPorts(dut.Ports()), - atePorts: sortPorts(ate.Ports()), - aggID: netutil.NextAggregateInterface(t, dut), - } - tc.configureDUT(t) - tc.configureATE(t) - for _, port := range tc.atePorts { - totalPort-- - if totalPort < 1 { - break - } - ate.Actions().NewSetPortState().WithPort(port).WithEnabled(false).Send(t) - time.Sleep(10 * time.Second) - tc.verifyDUT(t, totalPort) - } - for _, port := range tc.atePorts { - totalPort++ - if totalPort > len(tc.atePorts)-1 { - break - } - ate.Actions().NewSetPortState().WithPort(port).WithEnabled(true).Send(t) - time.Sleep(10 * time.Second) - tc.verifyDUT(t, totalPort+1) - } - }) - } -} 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 a8da72bb3eb..e66ca0d9af4 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" @@ -749,7 +748,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() @@ -917,58 +919,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..693fd78a761 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 @@ -30,7 +30,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_port_speed_test/README.md b/feature/gnmi/otg_tests/telemetry_port_speed_test/README.md index c825def6c1d..8d3f35f8042 100644 --- a/feature/gnmi/otg_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/otg_tests/telemetry_port_speed_test/telemetry_port_speed_test.go b/feature/gnmi/otg_tests/telemetry_port_speed_test/telemetry_port_speed_test.go index a0efb524bd9..23cc5a9411b 100644 --- a/feature/gnmi/otg_tests/telemetry_port_speed_test/telemetry_port_speed_test.go +++ b/feature/gnmi/otg_tests/telemetry_port_speed_test/telemetry_port_speed_test.go @@ -267,8 +267,8 @@ func (tc *testCase) configureATE(t *testing.T) { } agg := tc.top.Lags().Add().SetName("lag") if tc.lagType == lagTypeSTATIC { - lagId, _ := strconv.Atoi(tc.aggID) - agg.Protocol().Static().SetLagId(uint32(lagId)) + 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) @@ -352,9 +352,10 @@ func TestGNMIPortDown(t *testing.T) { portStateAction.Port().Link().SetPortNames([]string{atePort.ID()}).SetState(gosnappi.StatePortLinkState.DOWN) ate.OTG().SetControlState(t, portStateAction) + 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 want := oc.Interface_OperStatus_DOWN; dutPortStatus != want { + if dutPortStatus != want { t.Errorf("Get(DUT port1 status): got %v, want %v", dutPortStatus, want) } 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/experimental/bgp/ate_tests/bgp_always_compare_med/metadata.textproto b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/metadata.textproto similarity index 52% rename from feature/experimental/bgp/ate_tests/bgp_always_compare_med/metadata.textproto rename to feature/gnmi/subscribe/tests/gnmi_sample_mode_test/metadata.textproto index ecf4c782d29..9f4c6a67ba7 100644 --- a/feature/experimental/bgp/ate_tests/bgp_always_compare_med/metadata.textproto +++ b/feature/gnmi/subscribe/tests/gnmi_sample_mode_test/metadata.textproto @@ -1,29 +1,28 @@ # 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: "abcc6890-c3e1-4b0e-984e-749c85b54e5a" +plan_id: "gNMI-1.27" +description: "gNMI Sample Mode Test" +testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { - vendor: NOKIA + vendor: ARISTA } deviations: { - explicit_interface_in_default_vrf: 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 } } platform_exceptions: { platform: { - vendor: ARISTA + vendor: NOKIA } deviations: { - 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 + explicit_interface_in_default_vrf: true } } -tags: TAGS_AGGREGATION 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 042bdabd8fd..b3af04b638b 100644 --- a/feature/gnoi/os/tests/osinstall/metadata.textproto +++ b/feature/gnoi/os/tests/osinstall/metadata.textproto @@ -7,18 +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: NOKIA + vendor: ARISTA } deviations: { - explicit_interface_in_default_vrf: true + default_network_instance: "default" interface_enabled: true } } 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 e0bbb248c81..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 @@ -5,14 +5,6 @@ uuid: "e98e61ec-03cd-4bff-8094-2fc69491962a" plan_id: "gNOI-2.1" description: "Packet-based Link Qualification" testbed: TESTBED_DUT_DUT_4LINKS -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - skip_plq_interface_oper_status_check: true - } -} platform_exceptions: { platform: { vendor: ARISTA @@ -33,3 +25,14 @@ platform_exceptions: { explicit_port_speed: true } } +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + 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/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/copying_debug_files_test/README.md b/feature/gnoi/system/tests/copying_debug_files_test/README.md index 3ba0ea4c2bf..4a18174032a 100644 --- a/feature/gnoi/system/tests/copying_debug_files_test/README.md +++ b/feature/gnoi/system/tests/copying_debug_files_test/README.md @@ -21,9 +21,10 @@ * 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). 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 response. - * Invoke ArtifactRequest to transfer the requested Artifact ID(s). - * Verify that the DUT returns the artifacts requested. + * 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 @@ -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 5fe2605d143..7a2bfcfc27a 100644 --- a/feature/gnoi/system/tests/copying_debug_files_test/copying_debug_files_test.go +++ b/feature/gnoi/system/tests/copying_debug_files_test/copying_debug_files_test.go @@ -19,16 +19,16 @@ import ( "time" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/system" hpb "github.com/openconfig/gnoi/healthz" spb "github.com/openconfig/gnoi/system" tpb "github.com/openconfig/gnoi/types" "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi" ) var ( processName = map[ondatra.Vendor]string{ - ondatra.NOKIA: "sr_bgp_mgr", + ondatra.NOKIA: "sr_qos_mgr", ondatra.ARISTA: "IpRib", ondatra.JUNIPER: "rpd", } @@ -53,7 +53,7 @@ func TestMain(m *testing.M) { // DUT // // Test notes: -//. Note: Initiating checkin to experimental +// Note: Initiating checkin to experimental // - KillProcess system call is used to kill a process. // - The healthz call needs to be modified to reflect the right component and its path. // @@ -67,10 +67,14 @@ func TestCopyingDebugFiles(t *testing.T) { if _, ok := processName[dut.Vendor()]; !ok { t.Fatalf("Please add support for vendor %v in var processName", dut.Vendor()) } + pID := system.FindProcessIDByName(t, dut, processName[dut.Vendor()]) + if pID == 0 { + t.Fatalf("process %v not found on device", processName[dut.Vendor()]) + } killProcessRequest := &spb.KillProcessRequest{ Signal: spb.KillProcessRequest_SIGNAL_KILL, Name: processName[dut.Vendor()], - Pid: findProcessByName(context.Background(), t, dut, processName[dut.Vendor()]), + Pid: uint32(pID), Restart: true, } processKillResponse, err := gnoiClient.System().KillProcess(context.Background(), killProcessRequest) @@ -109,19 +113,6 @@ func TestCopyingDebugFiles(t *testing.T) { } } -// findProcessByName uses telemetry to find out the PID of a process -func findProcessByName(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, pName string) uint32 { - pList := gnmi.GetAll(t, dut, gnmi.OC().System().ProcessAny().State()) - var pID uint32 - for _, proc := range pList { - if proc.GetName() == pName { - pID = uint32(proc.GetPid()) - t.Logf("Pid of daemon '%s' is '%d'", pName, pID) - } - } - return pID -} - func TestChassisComponentArtifacts(t *testing.T) { dut := ondatra.DUT(t, "dut") gnoiClient := dut.RawAPIs().GNOI(t) @@ -153,10 +144,10 @@ func TestChassisComponentArtifacts(t *testing.T) { t.Logf("Artifacts received for component %v: %v", componentName["name"], artifacts) // Fetch artifact details by executing ArtifactRequest and passing the artifact ID along. for _, artifact := range artifacts { - artId := artifact.GetId() - t.Logf("Executing ArtifactRequest for artifact ID %v", artId) + artID := artifact.GetId() + t.Logf("Executing ArtifactRequest for artifact ID %v", artID) artReq := &hpb.ArtifactRequest{ - Id: artId, + Id: artID, } // Verify that a valid response is received. artRes, err := gnoiClient.Healthz().Artifact(context.Background(), artReq) @@ -164,9 +155,9 @@ func TestChassisComponentArtifacts(t *testing.T) { t.Fatalf("Unexpected error on executing Healthz Artifact RPC: %v", err) } h1, err := artRes.Header() - t.Logf("Header of artifact %v: %v", artId, h1) + t.Logf("Header of artifact %v: %v", artID, h1) if err != nil { - t.Fatalf("Unexpected error when fetching the header of artifact %v: %v", artId, err) + t.Fatalf("Unexpected error when fetching the header of artifact %v: %v", artID, err) } } } diff --git a/feature/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/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/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/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/otg_tests/backup_ngp_multiple_nh_pbf_test/README.md b/feature/gribi/otg_tests/backup_ngp_multiple_nh_pbf_test/README.md new file mode 100644 index 00000000000..eeda23362c3 --- /dev/null +++ b/feature/gribi/otg_tests/backup_ngp_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_ngp_multiple_nh_pbf_test/backup_nhg_multiple_nh_pbf_test.go b/feature/gribi/otg_tests/backup_ngp_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_ngp_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/experimental/bgp/ate_tests/base_bgp_session_parameters/metadata.textproto b/feature/gribi/otg_tests/backup_ngp_multiple_nh_pbf_test/metadata.textproto similarity index 58% rename from feature/experimental/bgp/ate_tests/base_bgp_session_parameters/metadata.textproto rename to feature/gribi/otg_tests/backup_ngp_multiple_nh_pbf_test/metadata.textproto index b2db3691fa7..9c71cbd5fc6 100644 --- a/feature/experimental/bgp/ate_tests/base_bgp_session_parameters/metadata.textproto +++ b/feature/gribi/otg_tests/backup_ngp_multiple_nh_pbf_test/metadata.textproto @@ -1,17 +1,19 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "31ab1acc-3a50-4ce6-be63-d9e56ddc5813" -plan_id: "RT-1.1" -description: "Base BGP Session Parameters" -testbed: TESTBED_DUT_ATE_2LINKS +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 - connect_retry: true + interface_ref_interface_id_format: true + pf_require_match_default_rule: true + pf_require_sequential_order_pbr_rules: true } } platform_exceptions: { @@ -21,7 +23,6 @@ platform_exceptions: { deviations: { explicit_port_speed: true explicit_interface_in_default_vrf: true - missing_value_for_defaults: true interface_enabled: true } } @@ -30,12 +31,11 @@ platform_exceptions: { vendor: ARISTA } deviations: { - connect_retry: true omit_l2_mtu: true - network_instance_table_deletion_required: true - bgp_md5_requires_reset: true - missing_value_for_defaults: 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 9735755f0b0..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,29 +10,27 @@ 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. * Disable ATE port-3. Ensure that traffic for the destination is received at ATE port-4. -[TODO]: 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`. -* Replace the existing VRF selection policy with `vrf_selection_policy_w` as in - ## Config Parameter coverage * No new configuration covered. @@ -52,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 1a07fa7dbeb..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) 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/gribi/otg_tests/base_hierarchical_nhg_update/README.md b/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md index 0599873d933..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,128 +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 + 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 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`. +* 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 . -* 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. - -## 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. + in -## Summary -Validate NHG update in Drain Implementation Test. - -## 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 fde852f4890..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 @@ -164,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", @@ -275,39 +275,25 @@ func TestBaseHierarchicalNHGUpdate(t *testing.T) { }() } -func configureVrfSelectionPolicyW(t *testing.T, dut *ondatra.DUTDevice) { - t.Log("Delete existing vrf selection policy and Apply vrf selectioin policy W") - gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config()) - - p1 := dut.Port(t, "port1") - interfaceID := p1.Name() - if deviations.InterfaceRefInterfaceIDFormat(dut) { - interfaceID = interfaceID + ".0" - } - - niP := vrfpolicy.BuildVRFSelectionPolicyW(t, dut, deviations.DefaultNetworkInstance(dut)) - dutPolFwdPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() - gnmi.Replace(t, dut, dutPolFwdPath.Config(), niP) - - 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.Interface(interfaceID).Config(), intf) -} - 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) { - configureVrfSelectionPolicyW(t, args.dut) + 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. @@ -335,27 +321,19 @@ 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", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP2, Mac: pMAC, Dest: atePort2DummyIP.IPv4}) - } else { - 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}) + 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", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP2, Mac: pMAC}) - args.client.AddNHG(t, virtualIPNHGID, map[uint64]uint64{p2NHID: 1}, dni, fluent.InstalledInFIB) - args.client.AddIPv4(t, virtualPfx, virtualIPNHGID, dni, dni, fluent.InstalledInFIB) - - args.client.AddNH(t, dstNHID, virtualIP, dni, fluent.InstalledInFIB) - args.client.AddNHG(t, dstNHGID, map[uint64]uint64{dstNHID: 1}, dni, 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}) + if transit { args.client.AddIPv4(t, dstPfx, dstNHGID, niTeVrf111, dni, fluent.InstalledInFIB) } else { @@ -365,41 +343,24 @@ func testBaseHierarchialNHG(ctx context.Context, t *testing.T, args *testArgs) { 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", dni, fluent.InstalledInFIB, &gribi.NHOptions{Interface: dutP3, Mac: pMAC, Dest: atePort3DummyIP.IPv4}) - } else { - nh, op3 = gribi.NHEntry(p3NHID, "MACwithInterface", dni, 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", dni, 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}, dni, 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}, dni, 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}, dni, 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}, dni, 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}, dni, 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}, dni, 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) } @@ -471,10 +432,6 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { 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) @@ -994,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 8878487a2e5..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,12 +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. -[TODO]: 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`. -* Replace the existing VRF selection policy with `vrf_selection_policy_w` as in - +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. @@ -116,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 a2988705056..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() @@ -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/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..d5109eef5f1 --- /dev/null +++ b/feature/gribi/otg_tests/basic_encap_test/basic_encap_test.go @@ -0,0 +1,1147 @@ +// 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...) + } + + pbrRules = append(pbrRules, splitDefaultClassRules...) + + 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) + + // 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..29fb9f8def3 --- /dev/null +++ b/feature/gribi/otg_tests/basic_encap_test/metadata.textproto @@ -0,0 +1,41 @@ +# 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 + ttl_copy_unsupported: 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 + } +} +tags: TAGS_DATACENTER_EDGE 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..050b8bae94a --- /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 200 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..22b731e0879 --- /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 = 200 + 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 200 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/encap_frr/README.md b/feature/gribi/otg_tests/encap_frr/README.md similarity index 93% rename from feature/gribi/encap_frr/README.md rename to feature/gribi/otg_tests/encap_frr/README.md index e98ba8bc2f1..c16d78ca49d 100644 --- a/feature/gribi/encap_frr/README.md +++ b/feature/gribi/otg_tests/encap_frr/README.md @@ -6,18 +6,18 @@ 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 +- 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 +* Apply the following vrf selection policy to DUT port-1 ``` # DSCP value that will be matched to ENCAP_TE_VRF_A @@ -289,7 +289,7 @@ 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) { +NHG#1000 (DEFAULT VRF) { {NH#1000, DEFAULT VRF} } NH#1000 -> { @@ -304,6 +304,7 @@ NH#1000 -> { 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}, @@ -332,10 +333,19 @@ NH#1001 -> { } 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 @@ -430,12 +440,23 @@ 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" } +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} } -NHG#1001 (Default VRF) { {NH#1001, DEFAULT VRF} } NH#1001 -> { decapsulate_header: -OPENCONFIGAFTTYPESDECAPSULATIONHEADERTYPE_IPV4 network_instance: "DEFAULT" } ``` +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. @@ -448,7 +469,7 @@ 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 138.0.11.10, which should produce no +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. @@ -483,12 +504,19 @@ be routed to the DEFAULT VRF for further lookup. * network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/post-network-instance * network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decap-fallback-network-instance -## Protocol/RPC Parameter Coverage - -* gRIBI: - * Modify - * ModifyRequest +## OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Get: + gRIBI.Modify: + gRIBI.Flush: +``` ## Required DUT platform -vRX \ No newline at end of file +- 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..621a680fd91 --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr/encap_frr_test.go @@ -0,0 +1,1241 @@ +// 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) + } + + if p.PMD() == ondatra.PMD100GBASEFR && dut.Vendor() != ondatra.CISCO { + 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 _, 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..fdd5e3329a7 --- /dev/null +++ b/feature/gribi/otg_tests/encap_frr_with_reencap_vrf_test/encap_frr_with_reencap_vrf_test.go @@ -0,0 +1,1196 @@ +// 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" + 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 +) + +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) + } + + 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 _, 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, gribiIPv4EntryVRF2221).WithNextHopNetworkInstance(niTEVRF222), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(1301).WithDecapsulateHeader(fluent.IPinIP).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP(ipv4OuterSrc222Addr, gribiIPv4EntryVRF2222).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) + } + } + if tc.TestID == "teVrf111NoMatch" { + args.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(201).WithEncapsulateHeader(fluent.IPinIP).WithIPinIP(ipv4OuterSrc111Addr, "203.100.113.1"). + WithNextHopNetworkInstance(niTEVRF111), + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(202).WithEncapsulateHeader(fluent.IPinIP).WithIPinIP(ipv4OuterSrc111Addr, "203.100.113.2"). + 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) + } + } + + 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/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..2038b0e1899 --- /dev/null +++ b/feature/gribi/otg_tests/gribi_route_test/gribi_route_test.go @@ -0,0 +1,726 @@ +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) { + 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) + 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 c8ed4bf9571..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 @@ -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..981e65d1ab9 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 @@ -40,8 +40,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 398959eae6a..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 ( @@ -82,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) } @@ -177,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. @@ -225,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/ate_tests/hierarchical_weight_resolution_test/README.md b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/README.md similarity index 81% rename from feature/gribi/ate_tests/hierarchical_weight_resolution_test/README.md rename to feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/README.md index 6a7186d11d9..d2d65948ea1 100644 --- a/feature/gribi/ate_tests/hierarchical_weight_resolution_test/README.md +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/README.md @@ -1,4 +1,4 @@ -# TE-3.3: Hierarchical weight resolution +# TE-3.31: Hierarchical weight resolution with PBF ## Summary @@ -25,9 +25,18 @@ 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). + +* 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: @@ -110,35 +119,24 @@ 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. -[TODO]: 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`. -* Replace the existing VRF selection policy with `vrf_selection_policy_w` as in - -## Config Parameter Coverage - -N/A - -## Telemetry Parameter Coverage - -TODO: -/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight - -## Protocol/RPC Parameter coverage - -* gRIBI: - * Modify() - * ModifyRequest: - * AFTOperation: - * next_hop_group - * NextHopGroupKey: id - * NextHopGroup: weight +## 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/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/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/metadata.textproto b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/metadata.textproto similarity index 54% rename from feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/metadata.textproto rename to feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/metadata.textproto index 8f8943db228..18b3f8b387b 100644 --- a/feature/experimental/isis/ate_tests/isis_change_lsp_lifetime_test/metadata.textproto +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/metadata.textproto @@ -1,30 +1,39 @@ # 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" +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: NOKIA + vendor: CISCO } 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 + 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: CISCO + vendor: JUNIPER } deviations: { - ipv4_missing_enabled: true - isis_interface_level1_disable_required: true + hierarchical_weight_resolution_tolerance: 0.4 + explicit_interface_ref_definition: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_port_speed: true + explicit_interface_in_default_vrf: true + interface_enabled: true } } platform_exceptions: { @@ -32,12 +41,9 @@ 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_lsp_lifetime_interval_requires_lsp_refresh_interval: true } } -tags: TAGS_AGGREGATION +tags: TAGS_DATACENTER_EDGE diff --git a/feature/gribi/otg_tests/hierarchical_weight_resolution_test/README.md b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/README.md new file mode 100644 index 00000000000..63bfca04b3c --- /dev/null +++ b/feature/gribi/otg_tests/hierarchical_weight_resolution_test/README.md @@ -0,0 +1,144 @@ +# TE-3.3: Hierarchical weight resolution + +## 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). + +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. + +## Config Parameter Coverage + +N/A + +## 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: +``` + +## Minimum DUT platform requirement + +* vRX - virtual router device + 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 92% 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..a7f0ebfbfb0 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: { @@ -30,7 +29,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 +40,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 f9a4522b457..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{ 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 47f5f56309c..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 @@ -212,6 +212,11 @@ func configureGRIBIPrefixes(t *testing.T, dut *ondatra.DUTDevice, aggID string) 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{ @@ -402,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()) @@ -421,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_in_udp/README.md b/feature/gribi/otg_tests/mpls_in_udp/README.md new file mode 100644 index 00000000000..ad88513609d --- /dev/null +++ b/feature/gribi/otg_tests/mpls_in_udp/README.md @@ -0,0 +1,324 @@ +# 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. See [OC AFT Encap PR in progress](https://github.com/openconfig/public/pull/1153) +for the new OC AFT model nodes needed for this. + +TODO: The +[gRIBI v1 protobuf defintions](https://github.com/openconfig/gribi/blob/master/v1/proto/README.md) +will be generated from the afts tree. + +```proto +network_instances: { + network_instance: { + afts { + # + # entries used for "group_A" + ipv6_unicast { + ipv6_entry { + prefix: "inner_ipv6_dst_A" # this is an IPv6 entry for the origin/inner packet. + next_hop_group: 100 + } + } + ipv4_unicast { + ipv4_entry { + prefix: "ipv4_inner_dst_A" # this is an IPv4 entry for the origin/inner packet. + next_hop_group: 100 + } + } + next_hop_groups { + next_hop_group { + id: 100 + next_hops { # reference to a next-hop + next_hop: { + index: 100 + } + } + } + } + next_hops { + next_hop { + index: 100 + network_instance: "group_A" + encap-headers { + encap-header { + index: 1 + pushed_mpls_label_stack: [100,] + } + } + encap-headers { + encap-header { + index: 2 + 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" + } + } + } + } + # + # entries used for "group_B" + ipv6_unicast { + ipv6_entry { + prefix: "inner_ipv6_dst_B" + next_hop_group: 200 + } + } + ipv4_unicast { + ipv4_entry { + prefix: "ipv4_inner_dst_B" + next_hop_group: 200 + } + } + next_hop_groups { + next_hop_group { + id: 200 + next_hops { # reference to a next-hop + next_hop: { + index: 200 + } + } + } + } + next_hops { + next_hop { + index: 200 + network_instance: "group_B" + encap-headers { + encap-header { + index: 1 + type : OPENCONFIG_AFT_TYPES:MPLS + mpls { + pushed_mpls_label_stack: [200,] + } + } + } + encap-headers { + encap-header { + index: 2 + type: OPENCONFIG_AFT_TYPES:UDP + udp { + 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" + } + } + } + } + } + } + } +} +``` + +* Send traffic from ATE port 1 to DUT port 1 +* Validate afts next hop counters +* 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. + +```yaml +openconfig-network-instance: + network-instances: + - network-instance: "group_A" + afts: + policy-forwarding: + policies: + policy: "default encap rule" + config: + policy-id: "default encap rule" + type: PBR_POLICY + rules: + rule: 1 + config: + sequence-id: 1 + ipv6: + config: + destination-address: "inner_ipv6_default" + action: + encapsulate-mpls-in-gre: # TODO: add to OC model/PR in progress + targets: + target: "default_dst_1" + config: + id: "default_dst_1" + network-instance: "DEFAULT" + source-ip: "outer_ipv6_src" + destination-ip: "outer_ipv6_dst_def" + ip-ttl: outer_ip-ttl + dscp: outer_dscp + inner-ttl-min: 2 +``` + +* 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 + +### TE-18.1.5 - Policy forwarding to encap and forward for BGP packets + +TODO: Specify a solution for ensuring BGP packets are matched, encapsulated +and forwarding to a specified destination using OC policy-forwarding terms. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + +# afts state paths set via gRIBI + # TODO: https://github.com/openconfig/public/pull/1153 + + #/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id: + #/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/next-hop-group-id: + #/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/network-instance: + #/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/state/mpls/pushed-mpls-label-stack: + #/network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/udp/src-ip: + #/network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/udp/dst-ip: + #/network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/udp/dst-udp-port: + #/network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/udp/ip-ttl: + #/network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/udp/dscp: + +# 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: + + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true + gribi: + gRIBI.Modify: + network-instances:network-instance:afts:next-hops:next-hop:encapsulate_header: + network-instances:network-instance:afts:next-hops:next-hop:mpls-in-udp: + network-instances:network-instance:afts:next-hops:next-hop:decapsulate_header: + gRIBI.Flush: +``` + +## Required DUT platform + +* FFF 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/weighted_balancing_test/setup_test.go b/feature/gribi/otg_tests/weighted_balancing_test/setup_test.go index 26cc43e6fda..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()) @@ -222,6 +232,13 @@ func configureATE(t testing.TB, ate *ondatra.ATEDevice) gosnappi.Config { 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 } 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..1128fbf265a --- /dev/null +++ b/feature/interface/aggregate/otg_tests/aggregate_all_not_viable_test/aggregate_all_not_forwarding_viable_test.go @@ -0,0 +1,1207 @@ +// 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 = 2500000 + 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" +) + +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.Isis_IsisInterfaceAdjState_UP); !ok { + t.Fatal("ISIS Adjacency is Down on LAG_2") + } + configForwardingViable(t, dut, dutPortList[1:2], false) + // Ensure ISIS Adjacency is Down 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.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.Isis_IsisInterfaceAdjState_UP); !ok { + t.Fatal("ISIS Adjacency is Down on LAG_2") + } + configForwardingViable(t, dut, dutPortList[1:2], false) + 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.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} + 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 + 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(niRepairVrf).WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(pfx4AdvV4.ip+"/24").WithNextHopGroup(1000)) + + 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 prefixes Encap in Default VRF + tcArgs.client.Modify().AddEntry(t, + fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(tcArgs.dut)). + WithIndex(uint64(1)).WithEncapsulateHeader(fluent.IPinIP). + WithIPinIP("100.0.1.254", "100.0.4.254"). + 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 "100.0.4.254/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("100.0.4.254/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("100.0.4.254/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() + return v == state && ok + }).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_test/aggregate_test.go b/feature/interface/aggregate/otg_tests/aggregate_test/aggregate_test.go index 61367b17ef0..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" @@ -394,11 +395,11 @@ func (tc *testCase) verifyLACPTelemetry(t *testing.T) { 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 || *ateLACP.PartnerId != *dutLACP.SystemId { + 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 || *dutLACP.PartnerId != *ateLACP.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) } } 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/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 1039076c28b..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-9: 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 index 517f8f9b7e8..ac04a98cf85 100644 --- 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 @@ -12,11 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package topology_test configures just the ports on DUT and ATE, -// assuming that DUT port i is connected to ATE i. It detects the -// number of ports in the testbed and can be used with the 2, 4, 12 -// port variants of the atedut testbed. - package disable_ipv6_nd_ra_test import ( @@ -38,7 +33,7 @@ import ( "github.com/openconfig/ygot/ygot" ) -// Reserving the testbed and running tests +// Reserving the testbed and running tests. func TestMain(m *testing.M) { fptest.RunTests(m) } @@ -81,43 +76,40 @@ var ( } ) -// configureDUT configures port1 and port2 of the DUT. +// Configures port1 and port2 of the DUT. func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { d := gnmi.OC() p1 := dut.Port(t, "port1") - i1 := &oc.Interface{Name: ygot.String(p1.Name())} - gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutSrc, dut)) + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(p1, &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)) + 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) + } } -// configInterfaceDUT configures the given DUT interface. -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) +// 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) } - s := i.GetOrCreateSubinterface(0) - s6 := s.GetOrCreateIpv6() - if deviations.InterfaceEnabled(dut) { - s6.Enabled = ygot.Bool(true) - } - s6a := s6.GetOrCreateAddress(a.IPv6) - s6a.PrefixLength = ygot.Uint8(plen6) + s6 := i.GetOrCreateSubinterface(0).GetOrCreateIpv6() routerAdvert := s6.GetOrCreateRouterAdvertisement() - routerAdvert.SetInterval(*ygot.Uint32(routerAdvertisementTimeInterval)) + if !deviations.Ipv6RouterAdvertisementIntervalUnsupported(dut) { + routerAdvert.SetInterval(routerAdvertisementTimeInterval) + } if deviations.Ipv6RouterAdvertisementConfigUnsupported(dut) { - routerAdvert.SetSuppress(*ygot.Bool(routerAdvertisementDisabled)) + routerAdvert.SetSuppress(routerAdvertisementDisabled) } else { - routerAdvert.SetEnable(*ygot.Bool(false)) - routerAdvert.SetMode(oc.RouterAdvertisement_Mode_ALL) + routerAdvert.SetEnable(false) } return i } -// configureOTG configures OTG interfaces to send and recieve ipv6 packets +// 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") @@ -138,19 +130,23 @@ func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { 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 - Change the function Name verifyRATelemetry +// 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") - 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.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) { @@ -167,7 +163,7 @@ func verifyRATelemetry(t *testing.T, dut *ondatra.DUTDevice) { } } -// captureTrafficStats Captures traffic statistics and verifies for the loss +// 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) @@ -193,7 +189,7 @@ func verifyOTGPacketCaptureForRA(t *testing.T, ate *ondatra.ATEDevice, config go validatePackets(t, f.Name()) } -// To detect if the routerAdvertisement packet is found in the captured packets +// 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) @@ -213,7 +209,6 @@ func validatePackets(t *testing.T, fileName string) { if routerAdvert != nil { t.Fatalf("Error:Found a router advertisement packet!") } - } } } @@ -232,5 +227,4 @@ func TestIpv6NDRA(t *testing.T) { 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 96b04c598b8..7e07c4a34fd 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 @@ -2,7 +2,7 @@ # proto-message: Metadata uuid: "43776caf-2340-4f35-ab30-8c763feaadae" -plan_id: "RT-9" +plan_id: "RT-5.9" description: "Disable IPv6 ND Router Arvetisment" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { @@ -13,3 +13,21 @@ platform_exceptions: { 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 + } +} \ 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 5a8e6dcbbe0..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 @@ -46,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 index 45a19cd1024..cdced10d75a 100644 --- 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 @@ -128,7 +128,9 @@ func TestIPv6LinkLocal(t *testing.T) { 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.Await(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().State(), 30*time.Second, false) + t.Logf("Sleeping for 30 seconds") + time.Sleep(30 * time.Second) gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), true) otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") t.Run("Interface Telemetry", func(t *testing.T) { @@ -176,13 +178,34 @@ func configureDUTLinkLocalInterface(t *testing.T, dut *ondatra.DUTDevice) { p1 := dut.Port(t, "port1") srcIntf := dutSrc.NewOCInterface(p1.Name(), dut) - srcIntf.GetOrCreateSubinterface(0).GetOrCreateIpv6().GetOrCreateAddress(dutSrc.IPv6).SetType(oc.IfIp_Ipv6AddressType_LINK_LOCAL_UNICAST) + 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) - dstIntf.GetOrCreateSubinterface(0).GetOrCreateIpv6().GetOrCreateAddress(dutDst.IPv6).SetType(oc.IfIp_Ipv6AddressType_LINK_LOCAL_UNICAST) + 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) @@ -300,6 +323,10 @@ func verifyInterfaceTelemetry(t *testing.T, dut *ondatra.DUTDevice) { 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) @@ -310,5 +337,9 @@ func verifyInterfaceTelemetry(t *testing.T, dut *ondatra.DUTDevice) { 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/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto index 79291e1f1a5..8be2f6d388a 100644 --- a/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto +++ b/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto @@ -14,6 +14,14 @@ platform_exceptions: { default_network_instance: "default" } } +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + link_local_mask_len: true + } +} # CISCOXR platform_exceptions: { platform: { vendor: NOKIA 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/gnmi/ate_tests/telemetry_port_speed_test/metadata.textproto b/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto similarity index 60% rename from feature/gnmi/ate_tests/telemetry_port_speed_test/metadata.textproto rename to feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto index 5b465d17604..7cdc467c77e 100644 --- a/feature/gnmi/ate_tests/telemetry_port_speed_test/metadata.textproto +++ b/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto @@ -1,16 +1,29 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "352d6fc8-5245-419a-9ae9-7da432a46cdf" -plan_id: "gNMI-1.5" -description: "Telemetry: Port Speed Test" +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: { @@ -18,19 +31,15 @@ platform_exceptions: { vendor: NOKIA } deviations: { - explicit_port_speed: true - explicit_interface_in_default_vrf: true - aggregate_atomic_update: true interface_enabled: true + explicit_interface_in_default_vrf: true } } platform_exceptions: { platform: { - vendor: ARISTA + vendor: JUNIPER } deviations: { - aggregate_atomic_update: true interface_enabled: true - default_network_instance: "default" } } 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/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/singleton_test.go b/feature/interface/singleton/otg_tests/singleton_test/singleton_test.go index 999a36ac48f..4b38868f6ee 100644 --- a/feature/interface/singleton/otg_tests/singleton_test/singleton_test.go +++ b/feature/interface/singleton/otg_tests/singleton_test/singleton_test.go @@ -376,7 +376,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 { 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 f1597997a17..00000000000 --- a/feature/interface/staticarp/ate_tests/static_arp_test/static_arp_test.go +++ /dev/null @@ -1,292 +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) - if deviations.EnableFlowctrlFlag(dut) { - e.EnableFlowControl = 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(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/metadata.textproto b/feature/interface/staticarp/otg_tests/static_arp_test/metadata.textproto index 23329ae109b..2e8a9b92212 100644 --- a/feature/interface/staticarp/otg_tests/static_arp_test/metadata.textproto +++ b/feature/interface/staticarp/otg_tests/static_arp_test/metadata.textproto @@ -13,14 +13,6 @@ platform_exceptions: { ipv4_missing_enabled: true } } -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - enable_flowctrl_flag: true - } -} platform_exceptions: { platform: { vendor: NOKIA 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 a03fb89b463..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 @@ -127,9 +127,6 @@ func configInterfaceDUT(t *testing.T, p *ondatra.Port, me, peer *attrs.Attribute if me.MAC != "" { e := i.GetOrCreateEthernet() e.MacAddress = ygot.String(me.MAC) - if deviations.EnableFlowctrlFlag(dut) { - e.EnableFlowControl = ygot.Bool(true) - } } s := i.GetOrCreateSubinterface(0) @@ -296,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/static-route_isis_redistribution/README.md b/feature/isis/otg_tests/static_route_isis_redistribution/README.md similarity index 83% rename from feature/isis/static-route_isis_redistribution/README.md rename to feature/isis/otg_tests/static_route_isis_redistribution/README.md index 90d05ba7909..0d7f4913481 100644 --- a/feature/isis/static-route_isis_redistribution/README.md +++ b/feature/isis/otg_tests/static_route_isis_redistribution/README.md @@ -318,85 +318,70 @@ * 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/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 - - -## Telemetry parameter coverage - -* /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 - -## Protocol/RPC Parameter Coverage - -* gNMI - * Get - * Set +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. 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 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..bbced9e82ee --- /dev/null +++ b/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto @@ -0,0 +1,36 @@ +# 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 + } +} 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..231b7b19aa5 --- /dev/null +++ b/feature/isis/otg_tests/static_route_isis_redistribution/static_route_isis_redistribution_test.go @@ -0,0 +1,550 @@ +// 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" + tagValue = 100 +) + +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(getResponse) + + 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:"+ + "SRC PROTO GOT %v WANT STATIC\n"+ + "DST PRTO 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) + } + 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) + 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) + 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(tagValue)}) + 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(tagValue)}) + 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 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, + 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(tagValue)) + verifyRplConfig(t, ts.DUT, getTagSetName(ts.DUT, tc.RplName, v6Statement, "v6"), oc.UnionUint32(tagValue)) + }) + } + + 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..dee5ca53f52 --- /dev/null +++ b/feature/isis/otg_tests/weighted_ecmp_test/README.md @@ -0,0 +1,192 @@ +# 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 + +In the topology above, + +* Configure 1xLAG interface between ATE1<->DUT and 3xLAG interfaces between + DUT and ATE2. Each LAG interface is expected to be of 2x100Gbps + +* Configure IPv4 and IPv6 L2 adjacencies between DUT and ATE LAG bundles. + Therefore, DUT will have 1xIS-IS adjacency with ATE1 i.e. + DUT:LAG1<->ATE1:LAG1, and 3xIS-IS adjacencies with ATE2 i.e. + DUT:LAG2<->ATE2:LAG1, DUT:LAG3<->ATE2:LAG2 and DUT:LAG4<->ATE2:LAG3 + + * /network-instances/network-instance/protocols/protocol/isis/global/afi-safi + + * /network-instances/network-instance/protocols/protocol/isis/global/config/level-capability, + set to LEVEL_2 + + * /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style + set to 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-9.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-9.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 + +### Config paths + +### 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(OCPATH): Container path originally part of spec that needs to be separated +into leaves: /routing-policy/defined-sets/prefix-sets/prefix-set: + +```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..ce6638892c2 --- /dev/null +++ b/feature/isis/otg_tests/weighted_ecmp_test/weighted_ecmp_test.go @@ -0,0 +1,768 @@ +package weighted_ecmp_test + +import ( + "fmt" + "testing" + "time" + + "math/rand" + + "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 532595c9e28..e34e217dcd9 100644 --- a/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go +++ b/feature/lldp/otg_tests/core_lldp_tlv_population_test/core_lldp_tlv_population_test.go @@ -138,18 +138,21 @@ func TestLLDPDisabled(t *testing.T) { func configureDUT(t *testing.T, name string, lldpEnabled bool) (*ondatra.DUTDevice, *oc.Lldp) { node := ondatra.DUT(t, name) p := node.Port(t, portName) - lldp := gnmi.OC().Lldp() + d := &oc.Root{} + lldp := d.GetOrCreateLldp() + llint := lldp.GetOrCreateInterface(p.Name()) - gnmi.Replace(t, node, lldp.Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Enabled().Config(), lldpEnabled) if lldpEnabled { - gnmi.Replace(t, node, lldp.Interface(p.Name()).Enabled().Config(), lldpEnabled) + gnmi.Replace(t, node, gnmi.OC().Lldp().Interface(p.Name()).Config(), llint) } + if deviations.InterfaceEnabled(node) { gnmi.Replace(t, node, gnmi.OC().Interface(p.Name()).Enabled().Config(), true) } - return node, gnmi.Get(t, node, lldp.Config()) + return node, gnmi.Get(t, node, gnmi.OC().Lldp().Config()) } func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { diff --git a/feature/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_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 0f7304d92d5..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 @@ -153,7 +153,6 @@ func createFlow(flowName string, flowSize uint32, ipv string) gosnappi.Flow { SetRxNames([]string{fmt.Sprintf("%s.%s", ateDst.Name, ipv)}) ethHdr := flow.Packet().Add().Ethernet() ethHdr.Src().SetValue(ateSrc.MAC) - ethHdr.Dst().SetValue(ateDst.MAC) flow.SetSize(gosnappi.NewFlowSize().SetFixed(flowSize)) switch ipv { @@ -180,8 +179,8 @@ func runTest(t *testing.T, tt testDefinition, td testData, waitF func(t *testing 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) @@ -325,19 +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))} 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)). @@ -347,16 +344,16 @@ 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 { @@ -377,13 +374,13 @@ func TestLargeIPPacketTransmission(t *testing.T) { func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, lag *attrs.Attributes, bundleMembers []*ondatra.Port) string { bundleID := netutil.NextAggregateInterface(t, dut) ocRoot := &oc.Root{} - if deviations.AggregateAtomicUpdate(dut) { - gnmi.Delete(t, dut, gnmi.OC().Interface(bundleID).Aggregation().MinLinks().Config()) + deleteBatch := &gnmi.SetBatch{} + gnmi.BatchDelete(deleteBatch, gnmi.OC().Interface(bundleID).Aggregation().MinLinks().Config()) for _, port := range bundleMembers { - gnmi.Delete(t, dut, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) + gnmi.BatchDelete(deleteBatch, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) } - + deleteBatch.Set(t, dut) bundle := ocRoot.GetOrCreateInterface(bundleID) bundle.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_STATIC bundle.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag @@ -405,13 +402,6 @@ func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, lag *attrs.Attribu gnmi.Update(t, dut, gnmi.OC().Config(), ocRoot) } - lacp := &oc.Lacp_Interface{ - Name: ygot.String(bundleID), - LacpMode: oc.Lacp_LacpActivityType_UNSET, - } - lacpPath := gnmi.OC().Lacp().Interface(bundleID) - gnmi.Replace(t, dut, lacpPath.Config(), lacp) - agg := ocRoot.GetOrCreateInterface(bundleID) agg.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag agg.Description = ygot.String(fmt.Sprintf("dutLag-%s", bundleID)) @@ -566,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)) } 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 6a27032d6bd..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,6 +14,7 @@ platform_exceptions: { explicit_interface_in_default_vrf: true aggregate_atomic_update: true interface_enabled: true + omit_l2_mtu: true } } platform_exceptions: { @@ -26,5 +27,4 @@ platform_exceptions: { 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/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 e9dd420e736..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() @@ -299,11 +279,6 @@ func subscribeOnChangeInterfaceName(t *testing.T, dut *ondatra.DUTDevice, p *ond func TestP4RTDaemonFailure(t *testing.T) { dut := ondatra.DUT(t, "dut") - p4rtD, ok := p4rtDaemons[dut.Vendor()] - if !ok { - t.Fatalf("Please add support for vendor %v in var p4rtDaemons", dut.Vendor()) - } - t.Logf("Configure DUT") configureDUT(t, dut) @@ -334,23 +309,7 @@ func TestP4RTDaemonFailure(t *testing.T) { flow := startTraffic(t, ate, top) - pID, err := pidByName(t, dut, p4rtD) - if err != nil { - t.Fatal(err) - } - - c := dut.RawAPIs().GNOI(t) - req := &syspb.KillProcessRequest{ - Name: p4rtD, - Pid: uint32(pID), - Signal: syspb.KillProcessRequest_SIGNAL_TERM, - Restart: true, - } - resp, err := c.System().KillProcess(context.Background(), req) - t.Logf("Got kill process response: %v", resp) - if err != nil { - t.Fatalf("FAIL: to execute gNOI.KillProcess, error received: %v", err) - } + gnoi.KillProcess(t, dut, gnoi.P4RT, gnoi.SigTerm, true, true) // let traffic keep running for another 10 seconds. time.Sleep(10 * time.Second) @@ -359,7 +318,7 @@ func TestP4RTDaemonFailure(t *testing.T) { ate.OTG().StopTraffic(t) // Skip check for CISCO devices that use the same process for P4RT & gNMI. - if dut.Vendor() != ondatra.CISCO { + 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 { 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 6887a4d4d79..0f026d1ced4 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 @@ -34,7 +34,7 @@ This test if to verify that DUT supports gNMI Subscribe with ON_CHANGE for backp * Validate that we recieve a changed consumed-capacity metric matching the initial value -### gNMI-1.18.2 [TODO: https://github.com/openconfig/featureprofiles/issues/2323] +### gNMI-1.18.2 * Use gNMI subscribe with "ON_CHANGE" mode for the below telemetry paths @@ -70,26 +70,34 @@ This test if to verify that DUT supports gNMI Subscribe with ON_CHANGE for backp * /components/component/integrated-circuit/backplane-facing-capacity/state/total -## 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 - -## Telemetry parameter coverage - -* /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 - -## Protocol/RPC Parameter Coverage - -* 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 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 index 52f4163134e..099686c7f73 100644 --- 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 @@ -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: "ef804c64-84dd-432d-ab38-630e9c82b42d" @@ -13,15 +13,6 @@ platform_exceptions: { ipv4_missing_enabled: true } } -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - explicit_interface_ref_definition: true - backplane_facing_capacity_unsupported: true - } -} platform_exceptions: { platform: { vendor: NOKIA @@ -32,7 +23,6 @@ platform_exceptions: { explicit_interface_in_default_vrf: true qos_queue_requires_id: true missing_value_for_defaults: true - backplane_facing_capacity_unsupported: true } } platform_exceptions: { 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 index 4c1c707c0c6..651bb61d582 100644 --- 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 @@ -117,6 +117,19 @@ func TestOnChangeBackplaneCapacityCounters(t *testing.T) { 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()) } @@ -126,13 +139,24 @@ func TestOnChangeBackplaneCapacityCounters(t *testing.T) { 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) @@ -145,7 +169,8 @@ func TestOnChangeBackplaneCapacityCounters(t *testing.T) { 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 { @@ -164,7 +189,7 @@ func TestOnChangeBackplaneCapacityCounters(t *testing.T) { 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: + case v1 != v2 || v1 != v3: t.Errorf("BackplaneFacingCapacity Total are not valid: v1 %d, v2 %d, v3 %d", v1, v2, v3) } @@ -184,7 +209,7 @@ func TestOnChangeBackplaneCapacityCounters(t *testing.T) { switch { case !ok1 || !ok2 || !ok3: t.Errorf("BackplaneFacingCapacity AvailablePct not present: ok1 %t, ok2 %t, ok3 %t", ok1, ok2, ok3) - case v1 <= v2 || v1 != v3: + case v1 != 0 && (v1 <= v2 || v1 != v3): t.Errorf("BackplaneFacingCapacity AvailablePct are not valid: v1 %d, v2 %d, v3 %d", v1, v2, v3) } }) 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_test/README.md b/feature/platform/integrated_circuit/otg_tests/utilization_test/README.md index 3aaeaef989b..42c2fa101f5 100644 --- a/feature/platform/integrated_circuit/otg_tests/utilization_test/README.md +++ b/feature/platform/integrated_circuit/otg_tests/utilization_test/README.md @@ -31,19 +31,29 @@ Test `used-threshold-upper` configuration and telemetry for hardware resources. * Get utilization percentages again and validate decrease in utilization. -## 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 -* /components/component/integrated_circuit/utilization/resources/resource/state/used -* /components/component/integrated_circuit/utilization/resources/resource/state/free -* /components/component/integrated_circuit/utilization/resources/resource/state/used-threshold-upper -* /components/component/integrated_circuit/utilization/resources/resource/state/used-threshold-upper-clear +## 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/platform/integrated_circuit/otg_tests/utilization_test/metadata.textproto b/feature/platform/integrated_circuit/otg_tests/utilization_test/metadata.textproto index 1fe40a1b24b..45d28a4dcdf 100644 --- a/feature/platform/integrated_circuit/otg_tests/utilization_test/metadata.textproto +++ b/feature/platform/integrated_circuit/otg_tests/utilization_test/metadata.textproto @@ -17,4 +17,13 @@ platform_exceptions: { missing_hardware_resource_telemetry_before_config: true } } +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} 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 index 3cc22db7bf5..db1719b5c3f 100644 --- a/feature/platform/integrated_circuit/otg_tests/utilization_test/utilization_test.go +++ b/feature/platform/integrated_circuit/otg_tests/utilization_test/utilization_test.go @@ -20,6 +20,7 @@ import ( "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" @@ -45,6 +46,7 @@ const ( var ( fibResource = map[ondatra.Vendor]string{ ondatra.ARISTA: "Routing/Resource6", + ondatra.NOKIA: "ip-lpm-routes", } dutPort1 = attrs.Attributes{ Desc: "dutPort1", @@ -105,58 +107,22 @@ func TestResourceUtilization(t *testing.T) { otgV6Peer, otgPort1, otgConfig := configureOTG(t, otg) verifyBgpTelemetry(t, dut) - - val, ok := gnmi.Watch(t, dut, gnmi.OC().System().Utilization().Resource(fibResource[dut.Vendor()]).ActiveComponentList().State(), time.Minute, func(v *ygnmi.Value[[]string]) bool { - cs, present := v.Val() - return present && len(cs) > 0 - }).Await(t) - if !ok { - switch { - case deviations.MissingHardwareResourceTelemetryBeforeConfig(dut): - t.Log("FIB resource is not active in any available components") - default: - t.Fatalf("FIB resource is not active in any available components") - } - } - comps, _ := val.Val() - 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), }) - - val, ok = gnmi.Watch(t, dut, gnmi.OC().System().Utilization().Resource(fibResource[dut.Vendor()]).ActiveComponentList().State(), time.Minute, func(v *ygnmi.Value[[]string]) bool { - cs, present := v.Val() - return present && len(cs) > 0 - }).Await(t) - if !ok { - t.Fatalf("FIB resource is not active in any available components") - } - comps, _ = val.Val() - + 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 Components in active-component-list") + t.Fatalf("Couldn't retrieve Utilization information for all Active Components") } - t.Run("Utilization Thresholds per Component", func(t *testing.T) { - for _, c := range comps { - t.Run(c, func(t *testing.T) { - if got, want := beforeUtzs[c].upperThreshold, usedThresholdUpper; got != want { - t.Errorf("used-upper-threshold mismatch for component: %s, got: %d, want: %d", c, got, want) - } - if got, want := beforeUtzs[c].upperThresholdClear, usedThresholdUpperClear; got != want { - t.Errorf("used-upper-threshold-clear mismatch for component: %s, got: %d, want: %d", c, got, want) - } - }) - } - }) 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 Components in active-component-list") + t.Fatalf("Couldn't retrieve Utilization information for all Active Components") } t.Run("Utilization after BGP route installation", func(t *testing.T) { @@ -174,7 +140,7 @@ func TestResourceUtilization(t *testing.T) { afterClearUtzs := componentUtilizations(t, dut, comps) if len(afterClearUtzs) != len(comps) { - t.Fatalf("Couldn't retrieve Utilization information for all Components in active-component-list") + t.Fatalf("Couldn't retrieve Utilization information for all Active Components") } t.Run("Utilization after BGP route clear", func(t *testing.T) { 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/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..22212c8ffd7 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().Config()) + 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().Config()) + 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().Config()) + 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/zr_fec_uncorrectable_frames_test/README.md b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md similarity index 78% rename from feature/platform/transceiver/zr_fec_uncorrectable_frames_test/README.md rename to feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md index 751768fde32..49966402fd2 100644 --- a/feature/platform/transceiver/zr_fec_uncorrectable_frames_test/README.md +++ b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md @@ -29,11 +29,20 @@ This is a post-FEC decoder error metric. 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. -## Config Parameter coverage - -* /components/component/transceiver/config/enabled -* /interfaces/interface/config/enabled - -## Telemetry Parameter coverage - -* /terminal-device/logical-channels/channel/otn/state/fec-uncorrectable-blocks +## 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/zr_laser_bias_current_test/README.md b/feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md similarity index 81% rename from feature/platform/transceiver/zr_laser_bias_current_test/README.md rename to feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md index cc87c088fd5..05823ee7047 100644 --- a/feature/platform/transceiver/zr_laser_bias_current_test/README.md +++ b/feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md @@ -86,14 +86,27 @@ specified operating temperature and voltage. updated to the value in the normal range again. * Typical measurement range 0 to 131 mA. -## Config Parameter coverage - -* /components/component/transceiver/config/enabled -* /interfaces/interface/config/enabled - -## Telemetry Parameter coverage - -* /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 \ No newline at end of file +## 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..caa9410e80a --- /dev/null +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/metadata.textproto @@ -0,0 +1,16 @@ +# 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 + } +} 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..06dca4f91ed --- /dev/null +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/zr_logical_channels_test.go @@ -0,0 +1,246 @@ +package zr_logical_channels_test + +import ( + "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/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 + dp16QAM = 1 + 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, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func Test400ZRLogicalChannels(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 := 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, dp16QAM) + cfgplugins.ConfigOTNChannel(t, dut, oc1, otnIndex1, ethernetIndex1) + cfgplugins.ConfigETHChannel(t, dut, p1.Name(), tr1, otnIndex1, ethernetIndex1) + cfgplugins.ConfigOpticalChannel(t, dut, oc2, frequency, targetOutputPower, dp16QAM) + 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, otnIndex1, ethernetIndex1, ethChan1) + validateEthernetChannelTelemetry(t, otnIndex2, ethernetIndex2, ethChan2) + validateOTNChannelTelemetry(t, otnIndex1, ethernetIndex1, oc1, otnChan1) + validateOTNChannelTelemetry(t, otnIndex2, ethernetIndex2, oc2, otnChan2) +} + +func validateEthernetChannelTelemetry(t *testing.T, 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 := []struct { + desc string + got any + want any + }{ + { + 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(), + }, + { + 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(), + }, + } + 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, 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 := []struct { + desc string + got any + want any + }{ + { + 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(), + }, + { + desc: "Optical Channel 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(), + }, + { + 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(), + }, + } + + 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/experimental/bgp/ate_tests/bgp_remove_private_as/metadata.textproto b/feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto similarity index 50% rename from feature/experimental/bgp/ate_tests/bgp_remove_private_as/metadata.textproto rename to feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto index 4a298ab13bd..011a0b7bb63 100644 --- a/feature/experimental/bgp/ate_tests/bgp_remove_private_as/metadata.textproto +++ b/feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto @@ -1,10 +1,10 @@ # 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" -testbed: TESTBED_DUT_ATE_2LINKS +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 @@ -12,13 +12,6 @@ platform_exceptions: { deviations: { interface_enabled: true default_network_instance: "default" - } -} -platform_exceptions: { - platform: { - vendor: NOKIA - } - deviations: { - interface_enabled: true + 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..b8bdc687167 --- /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/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) +} + +// 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")) + + 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) + + opInst := samplestream.New(t, dut, gnmi.OC().Component(tr).OpticalChannel().OutputPower().Instant().State(), samplingInterval) + defer opInst.Close() + if opInstN := opInst.Next(); opInstN != nil { + if _, ok := opInstN.Val(); ok { + 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(tr).OpticalChannel().OutputPower().Avg().State(), samplingInterval) + defer opAvg.Close() + if opAvgN := opAvg.Next(); opAvgN != nil { + if _, ok := opAvgN.Val(); ok { + 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(tr).OpticalChannel().OutputPower().Min().State(), samplingInterval) + defer opMin.Close() + if opMinN := opMin.Next(); opMinN != nil { + if _, ok := opMinN.Val(); ok { + 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(tr).OpticalChannel().OutputPower().Max().State(), samplingInterval) + defer opMax.Close() + if opMaxN := opMax.Next(); opMaxN != nil { + if _, ok := opMaxN.Val(); ok { + 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..91557f86ff6 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_pm_test/metadata.textproto @@ -0,0 +1,15 @@ +# 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" + } +} 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..455bf38a6cf --- /dev/null +++ b/feature/platform/transceiver/tests/zr_pm_test/zr_pm_test.go @@ -0,0 +1,212 @@ +package zr_pm_test + +import ( + "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 ( + dp16QAM = uint16(1) + 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 + flapInterval = 30 * time.Second + otnIndexBase = uint32(4000) + ethernetIndexBase = uint32(40000) +) + +var ( + frequencies = []uint64{191400000, 196100000} + targetOpticalPowers = []float64{-9, -13} +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestPM(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + 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, dp16QAM) + 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, 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, 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 { + 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/zr_temperature_test/README.md b/feature/platform/transceiver/tests/zr_temperature_test/README.md similarity index 79% rename from feature/platform/transceiver/zr_temperature_test/README.md rename to feature/platform/transceiver/tests/zr_temperature_test/README.md index 57a4ab52bbb..750acf0bcb5 100644 --- a/feature/platform/transceiver/zr_temperature_test/README.md +++ b/feature/platform/transceiver/tests/zr_temperature_test/README.md @@ -54,13 +54,26 @@ https://www.oiforum.com/wp-content/uploads/CMIS5p0_Third_Party_Spec.pdf any invalid string values like "nil" or "-inf". * Reported temperature value must always be of type decimal64. -## Config Parameter coverage +## OpenConfig Path and RPC Coverage -* /interfaces/interface/config/enabled +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 ## + /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: +``` -* /platform/components/component/state/temperature/instant -* /platform/components/component/state/temperature/min -* /platform/components/component/state/temperature/max -* /platform/components/component/state/temperature/avg \ No newline at end of file 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..e90ee70f530 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_tunable_parameters_test/metadata.textproto @@ -0,0 +1,16 @@ +# 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 + } +} \ 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..fe01013e2be --- /dev/null +++ b/feature/platform/transceiver/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go @@ -0,0 +1,288 @@ +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) { + gnmi.Replace(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(tc.targetOutputPower), + Frequency: ygot.Uint64(freq), + OperationalMode: ygot.Uint16(dp16QAM), + }) + gnmi.Replace(t, dut, gnmi.OC().Component(oc2).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(tc.targetOutputPower), + Frequency: ygot.Uint64(freq), + OperationalMode: ygot.Uint16(dp16QAM), + }) + 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) { + gnmi.Replace(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(top), + Frequency: ygot.Uint64(tc.frequency), + OperationalMode: ygot.Uint16(dp16QAM), + }) + gnmi.Replace(t, dut, gnmi.OC().Component(oc2).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(top), + Frequency: ygot.Uint64(tc.frequency), + OperationalMode: ygot.Uint16(dp16QAM), + }) + 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) + gnmi.Replace(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(targetPower), + Frequency: ygot.Uint64(frequency), + OperationalMode: ygot.Uint16(dp16QAM), + }) + gnmi.Replace(t, dut, gnmi.OC().Component(oc2).OpticalChannel().Config(), &oc.Component_OpticalChannel{ + TargetOutputPower: ygot.Float64(targetPower), + Frequency: ygot.Uint64(frequency), + OperationalMode: ygot.Uint16(dp16QAM), + }) + 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 { + 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/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 ba98e083401..2e8e158c370 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 @@ -432,6 +432,7 @@ func TestBurstyTraffic(t *testing.T) { 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) 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_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/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/qos_policy_config_test.go b/feature/qos/tests/qos_policy_config_test/qos_policy_config_test.go index b43b04be089..e035af3e562 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 @@ -601,8 +601,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 +620,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 { 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/RecordHistoryTruncation/README.md b/feature/security/gnsi/acctz/RecordHistoryTruncation/README.md index fecccd415f6..a8d705b31bd 100644 --- a/feature/security/gnsi/acctz/RecordHistoryTruncation/README.md +++ b/feature/security/gnsi/acctz/RecordHistoryTruncation/README.md @@ -11,20 +11,22 @@ Test Record Response Truncation boolean is set - 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 +## 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 -## 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 = CommandService": true +``` ## Minimum DUT vRX diff --git a/feature/security/gnsi/acctz/RecordPayloadTruncation/README.md b/feature/security/gnsi/acctz/RecordPayloadTruncation/README.md index 5127ff66578..8bf0782019c 100644 --- a/feature/security/gnsi/acctz/RecordPayloadTruncation/README.md +++ b/feature/security/gnsi/acctz/RecordPayloadTruncation/README.md @@ -12,13 +12,19 @@ Test how large payload is handled. 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. -## Telemetry Coverage +## OpenConfig Path and RPC Coverage -Accounting does not currently support any telemetry; see where it might become /system/aaa/acctz/XXX +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 +TODO(OCRPC): Record may not be complete -gnsi.acctz.v1 +```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 diff --git a/feature/security/gnsi/acctz/RecordSubscribeFull/README.md b/feature/security/gnsi/acctz/RecordSubscribeFull/README.md index c0dea6911f7..31e90a3d563 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeFull/README.md +++ b/feature/security/gnsi/acctz/RecordSubscribeFull/README.md @@ -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 - -### Parameter: -RecordRequest.timestamp=0 -RecordResponse.service_request = GrpcService - -## 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 = GrpcService": true +``` ## 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/RecordSubscribeNongrpc/README.md b/feature/security/gnsi/acctz/RecordSubscribeNongrpc/README.md index f21cb5714ba..74a7e7aaad7 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeNongrpc/README.md +++ b/feature/security/gnsi/acctz/RecordSubscribeNongrpc/README.md @@ -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/RecordSubscribePartial/README.md b/feature/security/gnsi/acctz/RecordSubscribePartial/README.md index ecfa9120c44..69e685d4b76 100644 --- a/feature/security/gnsi/acctz/RecordSubscribePartial/README.md +++ b/feature/security/gnsi/acctz/RecordSubscribePartial/README.md @@ -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/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/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 4042f2abc92..92cb5022af9 100644 --- a/feature/security/gnsi/authz/tests/authz/README.md +++ b/feature/security/gnsi/authz/tests/authz/README.md @@ -400,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/certz/client_certificates/README.md b/feature/security/gnsi/certz/client_certificates/README.md index cea7ebc84ee..f34513e3eac 100644 --- a/feature/security/gnsi/certz/client_certificates/README.md +++ b/feature/security/gnsi/certz/client_certificates/README.md @@ -102,13 +102,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 -## 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. + +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/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/trust_bundle/README.md b/feature/security/gnsi/certz/trust_bundle/README.md index bc15e6aa10c..eda1bf030de 100644 --- a/feature/security/gnsi/certz/trust_bundle/README.md +++ b/feature/security/gnsi/certz/trust_bundle/README.md @@ -68,13 +68,18 @@ 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 d26f1e77d58..08442078f8d 100644 --- a/feature/security/gnsi/credentialz/tests/README.md +++ b/feature/security/gnsi/credentialz/tests/README.md @@ -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 ``` @@ -204,7 +217,7 @@ and * 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) @@ -281,3 +294,36 @@ and * Ensure that access rejects telemetry counter is incremented `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-rejects` +### Credentialz-6, GLOME Configuration + +#### Setup +* Create a glome key with `glome` following [these + instructions](https://github.com/google/glome?tab=readme-ov-file#getting-started). +* Send a RotateHostParameters GlomeRequest message, with key, key_version, and + prefix_url. + +#### Pass case +* Attempt a console connection. + * Prompt must include a GLOME challenge. + * Use the `glome` binary along with your generated key to generate an + authorization code. + * Use the authorization code at the console prompt. + * Authorization must succeed. + * Ensure telemetry values for version and enabled match what was set in Setup. + +#### Fail case +* Attempt a console connection. + * Enter `fake-authorization-code` in the prompt. + * Authentication must fail. +======= +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. + +TODO(OCRPC): Record may not be complete + +```yaml +rpcs: + gnsi: + credentialz.v1.Credentialz.RotateAccountCredentials: +``` diff --git a/feature/sflow/feature.textproto b/feature/sflow/feature.textproto 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 78c6dc422dd..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" @@ -34,5 +34,6 @@ platform_exceptions: { 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 0d780bc4302..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,41 +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{ - NetworkInstance: "DEFAULT", + 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{ @@ -59,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 + +const ( + IPv4 = "IPv4" + IPv6 = "IPv6" +) - return i +func TestMain(m *testing.M) { + fptest.RunTests(m) } // configureDUTBaseline configures port1 and port2 on the DUT. @@ -98,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) @@ -114,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) + } + + 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) + } - // configure interfaces on DUT - // TODO: consider refactoring interface configs into cfgplugins - configureDUTBaseline(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()) @@ -147,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) @@ -158,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 index eecbe83c336..1127cffe981 100644 --- a/feature/staticroute/otg_tests/basic_static_route_support_test/README.md +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/README.md @@ -16,8 +16,8 @@ #### Initial Setup: -* Connect DUT port-1, port-2 and port-3 to ATE port-1, port-2 and port-3 - respectively +* 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 @@ -57,7 +57,7 @@ #### Test to validate static route metric -* Configure metric of ipv4-route-b and ipv6-route-b to 1000 +* 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 @@ -69,7 +69,7 @@ #### Test to validate static route preference -* Configure preference of ipv4-route-a and ipv6-route-a to 200 +* 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 @@ -90,9 +90,9 @@ #### Test to validate IPv6 static route with IPv4 next-hop -* Remove metric of 1000 from ipv4-route-b and ipv6-route-b +* 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 200 from ipv4-route-a and ipv6-route-a +* 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 @@ -177,32 +177,51 @@ 203.0.113.0/24` and `ipv6-network 2001:db8:128:128::/64` * Validate that traffic is NOT received from DUT -## Config parameter coverage - -* /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 - -## Telemetry parameter coverage - -* /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 - -## Protocol/RPC Parameter Coverage - -* gNMI - * Get - * Set +### 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 diff --git a/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go b/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go index 14259f34278..932e28281af 100644 --- a/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go @@ -2,7 +2,6 @@ package basic_static_route_support_test import ( "fmt" - "math" "net" "strings" "testing" @@ -22,31 +21,34 @@ import ( ) 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" + 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 ( @@ -100,6 +102,23 @@ var ( 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) { @@ -207,8 +226,97 @@ func TestBasicStaticRouteSupport(t *testing.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") @@ -250,6 +358,7 @@ func TestDisableRecursiveNextHopResolution(t *testing.T) { 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) @@ -268,9 +377,11 @@ func (td *testData) testRecursiveNextHopResolution(t *testing.T) { "0": oc.UnionString(td.advertisedIPv4.address), }, } - if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, td.dut); err != nil { + 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. @@ -281,16 +392,31 @@ func (td *testData) testRecursiveNextHopResolution(t *testing.T) { "0": oc.UnionString(td.advertisedIPv6.address), }, } - if _, err := cfgplugins.NewStaticRouteCfg(b, sV6, td.dut); err != nil { + 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)) - 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)) + + _, 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 { @@ -308,8 +434,8 @@ func (td *testData) testRecursiveNextHopResolution(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 10*time.Second) - lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 10*time.Second) + 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) @@ -332,14 +458,20 @@ func (td *testData) testRecursiveNextHopResolutionDisabled(t *testing.T) { t.Run("Telemetry", func(t *testing.T) { - 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 static route next-hop recursive lookup is disabled - if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).NextHop("0").Recurse().State()), false; got != want { - t.Errorf("IPv4 Static Route next hop: got: %v, want: %v", got, want) + _, 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") } - if got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv6.cidr(t)).NextHop("0").Recurse().State()), false; got != want { - t.Errorf("IPv6 Static Route next hop: got: %v, want: %v", got, want) + + _, 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) { @@ -349,8 +481,8 @@ func (td *testData) testRecursiveNextHopResolutionDisabled(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 10*time.Second) - lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 10*time.Second) + 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) @@ -363,7 +495,7 @@ func (td *testData) testRecursiveNextHopResolutionDisabled(t *testing.T) { }) } -func (td *testData) testStaticRouteECMP(t *testing.T) { +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 @@ -403,29 +535,64 @@ func (td *testData) testStaticRouteECMP(t *testing.T) { 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(), 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 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) + 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) + } } }) @@ -436,8 +603,8 @@ func (td *testData) testStaticRouteECMP(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 10*time.Second) - lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 10*time.Second) + 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 { @@ -492,17 +659,32 @@ func (td *testData) testStaticRouteECMP(t *testing.T) { } func (td *testData) testStaticRouteWithMetric(t *testing.T) { - const port2Metric = uint32(1000) + 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 1000 + // 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 @@ -521,8 +703,8 @@ func (td *testData) testStaticRouteWithMetric(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 10*time.Second) - lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 10*time.Second) + 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 { @@ -533,46 +715,120 @@ func (td *testData) testStaticRouteWithMetric(t *testing.T) { } // 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, 10*time.Second) + _, 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 got, want := float64(port1Counter)*100/float64(rxV4), float64(100); got+lossTolerance < want { - t.Errorf("IPv4 traffic on port1, got: %v, want: %v", got, want) + + 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, 10*time.Second) + _, 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 got, want := float64(port1Counter)*100/float64(rxV6), float64(100); got+lossTolerance < want { - t.Errorf("IPv6 traffic on port1, got: %v, want: %v", got, want) + 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) { - const port1Preference = uint32(200) + 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 preference of ipv4-route-a and ipv6-route-a to 200 + + // 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("0").Preference().Config(), port1Preference) - gnmi.BatchReplace(batch, sp.Static(td.staticIPv6.cidr(t)).NextHop("0").Preference().Config(), port1Preference) + 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 got, want := gnmi.Get(t, td.dut, sp.Static(td.staticIPv4.cidr(t)).NextHop("1").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("1").Preference().State()), port1Preference; got != want { - t.Errorf("IPv6 Static Route preference for NextHop 0, got: %d, want: %d", got, want) + 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) + } } }) @@ -583,8 +839,8 @@ func (td *testData) testStaticRouteWithPreference(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 10*time.Second) - lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 10*time.Second) + 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 { @@ -595,7 +851,7 @@ func (td *testData) testStaticRouteWithPreference(t *testing.T) { } // 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, 10*time.Second) + _, 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) @@ -605,7 +861,7 @@ func (td *testData) testStaticRouteWithPreference(t *testing.T) { } // 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, 10*time.Second) + _, 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) @@ -618,6 +874,7 @@ func (td *testData) testStaticRouteWithPreference(t *testing.T) { 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{ @@ -650,6 +907,8 @@ func (td *testData) testStaticRouteSetTag(t *testing.T) { 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)) @@ -665,31 +924,51 @@ func (td *testData) testStaticRouteSetTag(t *testing.T) { } func (td *testData) testIPv6StaticRouteWithIPv4NextHop(t *testing.T) { - b := &gnmi.SetBatch{} - // Remove metric of 1000 from ipv4-route-b and ipv6-route-b + // 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 200 from ipv4-route-a and ipv6-route-a + // 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 - 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 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()) @@ -708,7 +987,7 @@ func (td *testData) testIPv6StaticRouteWithIPv4NextHop(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 10*time.Second) + lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 20*time.Second) otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) @@ -739,12 +1018,53 @@ func (td *testData) testIPv6StaticRouteWithIPv4NextHop(t *testing.T) { }) } +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), @@ -758,17 +1078,32 @@ func (td *testData) testIPv4StaticRouteWithIPv6NextHop(t *testing.T) { } 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)) - 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) + + 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) + } } }) @@ -779,7 +1114,7 @@ func (td *testData) testIPv4StaticRouteWithIPv6NextHop(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 10*time.Second) + lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 20*time.Second) otgutils.LogFlowMetrics(t, td.ate.OTG(), td.top) @@ -811,7 +1146,9 @@ func (td *testData) testIPv4StaticRouteWithIPv6NextHop(t *testing.T) { } 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 @@ -844,7 +1181,12 @@ func (td *testData) testStaticRouteWithDropNextHop(t *testing.T) { } 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)) @@ -868,8 +1210,8 @@ func (td *testData) testStaticRouteWithDropNextHop(t *testing.T) { time.Sleep(trafficDuration) td.ate.OTG().StopTraffic(t) - lossV4 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v4Flow, 10*time.Second) - lossV6 := otgutils.GetFlowLossPct(t, td.ate.OTG(), v6Flow, 10*time.Second) + 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 @@ -917,6 +1259,10 @@ func (td *testData) configureOTGFlows(t *testing.T) { 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) @@ -930,7 +1276,11 @@ func (td *testData) configureOTGFlows(t *testing.T) { v6FIP := v6F.Packet().Add().Ipv6() v6FIP.Src().SetValue(srcV6.Address()) - v6FIP.Dst().Increment().SetStart(v6TrafficStart).SetCount(math.MaxInt32) + 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() @@ -961,16 +1311,29 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { p1 := dut.Port(t, "port1") p2 := dut.Port(t, "port2") p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") 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)) - gnmi.BatchReplace(b, gnmi.OC().Interface(p3.Name()).Config(), dutPort3.NewOCInterface(p3.Name(), dut)) + 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) @@ -979,6 +1342,7 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { 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) } } @@ -987,11 +1351,13 @@ func configureOTG(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config) []g 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) - return []gosnappi.Device{d1, d2, d3} + d4 := atePort4.AddToOTG(top, p4, &dutPort4) + return []gosnappi.Device{d1, d2, d3, d4} } func (td *testData) advertiseRoutesWithISIS(t *testing.T) { @@ -1012,7 +1378,11 @@ func (td *testData) advertiseRoutesWithISIS(t *testing.T) { 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) - isis.GetOrCreateLevel(2).SetMetricStyle(oc.Isis_MetricStyle_WIDE_METRIC) + 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() diff --git a/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto b/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto index e552d95cdc9..af7f010bc52 100644 --- a/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto @@ -18,6 +18,34 @@ platform_exceptions: { 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/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/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/large_set_consistency_test/large_set_consistency_test.go b/feature/system/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go index 0073f8a2b67..84b7e7af943 100644 --- 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 @@ -139,14 +139,16 @@ func buildGNMIUpdate(t *testing.T, yPath ygnmi.PathStruct, val any) *gpb.Update } // extractMetadataAnnotation extracts the metadata protobuf message from a gNMI GetResponse. -func extractMetadataAnnotation(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUTDevice) string { +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 { @@ -185,7 +187,7 @@ func extractMetadataAnnotation(t *testing.T, gnmiClient gpb.GNMIClient, dut *ond if err := proto.Unmarshal(decoded, msg); err != nil { t.Fatalf("cannot unmarshal received proto any msg, err: %v", err) } - return msg.GetName() + return msg.GetName(), getRespTimeStamp } // buildGNMISetRequest builds gnmi set request with protobuf-metadata @@ -238,24 +240,28 @@ func getNotificationsUsingGNMIGet(t *testing.T, gnmiClient gpb.GNMIClient, dut * return getResponse.GetNotification() } -// checkMetadata checks protobuf-metadata -func checkMetadata(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUTDevice, done *atomic.Bool) { +func checkMetadata1(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUTDevice, done *atomic.Int64) { t.Helper() - - got := extractMetadataAnnotation(t, gnmiClient, dut) - + got, getRespTimeStamp := extractMetadataAnnotation(t, gnmiClient, dut) want := metadata1 - if done.Load() { - want = metadata2 + t.Logf("getResp: %v ", getRespTimeStamp) + if got != want && done.Load() == 0 { + 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.Bool{} - done.Store(false) + done := &atomic.Int64{} dut := ondatra.DUT(t, "dut") // configuring basic interface and network instance as some devices only populate OC after configuration @@ -278,7 +284,7 @@ func TestLargeSetConsistency(t *testing.T) { if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { t.Fatalf("gnmi.Set unexpected error: %v", err) } - checkMetadata(t, gnmiClient, dut, done) + checkMetadata1(t, gnmiClient, dut, done) var wg sync.WaitGroup ch := make(chan struct{}, 1) @@ -289,15 +295,15 @@ func TestLargeSetConsistency(t *testing.T) { go func() { defer wg.Done() t.Log("gnmiClient Set 2nd large config") - if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + setResp, err := gnmiClient.Set(context.Background(), gpbSetRequest) + if err != nil { t.Errorf("gnmi.Set unexpected error: %v", err) return } close(ch) - done.Store(true) + done.Store(setResp.GetTimestamp()) }() - // sending 4 Get requests concurrently every 5 seconds. for i := 0; i < 4; i++ { wg.Add(1) go func(i int) { @@ -309,14 +315,14 @@ func TestLargeSetConsistency(t *testing.T) { return default: t.Logf("[%d - running] checking config protobuf-metadata", i) - checkMetadata(t, gnmiClient, dut, done) - time.Sleep(5 * time.Second) + time.Sleep(5 * time.Millisecond) + checkMetadata1(t, gnmiClient, dut, done) } } }(i) } wg.Wait() - - checkMetadata(t, gnmiClient, dut, done) + time.Sleep(5 * time.Second) + checkMetadata2(t, gnmiClient, dut) } diff --git a/feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto b/feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto index 80bdcf384d9..cae8c3c1a46 100644 --- a/feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto +++ b/feature/system/gnmi/metadata/tests/large_set_consistency_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: "e9cdd09c-14b9-4e3c-b38c-584ef9611c3f" 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 98aa53351ce..f87f4045c14 100644 --- a/feature/system/gnmi/set/tests/gnmi_set_test/README.md +++ b/feature/system/gnmi/set/tests/gnmi_set_test/README.md @@ -196,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 5f5d489e81a..8152a803887 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" @@ -31,6 +33,7 @@ import ( "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" "github.com/openconfig/ygot/ytypes" @@ -53,7 +56,11 @@ var ( 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. + cannotDeleteVRF = flag.Bool("cannot_delete_vrf", true, "Device cannot delete VRF.") // See "Note about cannotDeleteVRF" below. + 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.") ) var ( @@ -74,6 +81,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 +138,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 +174,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) } }) @@ -157,12 +189,8 @@ func TestDeleteInterface(t *testing.T) { config.DeleteInterface(p1.Name()) config.DeleteInterface(p2.Name()) - for _, iname := range scope.interfaces { - iface := config.GetInterface(iname) - if iface == nil { - config.Interface = nil - - } + if len(config.Interface) == 0 { + config.Interface = nil } op.push(t, dut, config, scope) @@ -208,6 +236,10 @@ func TestReuseIP(t *testing.T) { forEachPushOp(t, dut, func(t *testing.T, op pushOp, config *oc.Root) { t.Log("Initialize") + if deviations.SkipMacaddressCheck(dut) { + *setEthernetFromState = false + } + config.DeleteInterface(p1.Name()) config.DeleteInterface(agg1) configMember(config.GetOrCreateInterface(p1.Name()), agg1, dut) @@ -321,6 +353,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) @@ -328,8 +364,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) @@ -341,7 +377,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) @@ -369,6 +408,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()}, @@ -387,11 +427,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 { @@ -404,9 +451,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. }) @@ -416,18 +465,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) { @@ -438,7 +506,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) @@ -447,6 +519,9 @@ 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() } @@ -487,8 +562,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) @@ -600,7 +675,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) @@ -673,8 +752,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) } } @@ -683,9 +762,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) @@ -695,8 +774,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) @@ -704,12 +783,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)) - id = fmt.Sprintf("%s.%d", id, sub) + if deviations.InterfaceRefInterfaceIDFormat(dut) { + id = fmt.Sprintf("%s.%d", id, sub) + } return id } @@ -718,8 +799,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) @@ -789,6 +870,56 @@ func getDeviceConfig(t testing.TB, dev gnmi.DeviceOrOpts) *oc.Root { config := gnmi.Get[*oc.Root](t, dev, gnmi.OC().Config()) fptest.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. @@ -810,16 +941,39 @@ func getDeviceConfig(t testing.TB, dev gnmi.DeviceOrOpts) *oc.Root { // 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) + 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 } } @@ -887,7 +1041,14 @@ func (op rootOp) push(t testing.TB, dev gnmi.DeviceOrOpts, config *oc.Root, _ *p 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 @@ -905,6 +1066,22 @@ func (op containerOp) push(t testing.TB, dev gnmi.DeviceOrOpts, config *oc.Root, 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) @@ -1034,3 +1211,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 d6df13be2ef..13cf7f56ecd 100644 --- a/feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto +++ b/feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto @@ -10,7 +10,10 @@ platform_exceptions: { 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: { @@ -19,6 +22,7 @@ platform_exceptions: { } deviations: { skip_static_nexthop_check: true + interface_ref_interface_id_format: true } } platform_exceptions: { 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..9a92d63f2ce --- /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) { + createFlowV6(t, bs) + 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) + 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 baf1e745196..8e901db64e0 100644 --- a/feature/system/tests/system_base_test/g_protocol_test.go +++ b/feature/system/tests/system_base_test/g_protocol_test.go @@ -24,6 +24,8 @@ import ( "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/binding/introspect" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" gpb "github.com/openconfig/gnmi/proto/gnmi" spb "github.com/openconfig/gnoi/system" @@ -56,7 +58,7 @@ func TestGNMIClient(t *testing.T) { dut := ondatra.DUT(t, "dut") 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) } } @@ -76,9 +78,16 @@ func TestGNSIClient(t *testing.T) { dut := ondatra.DUT(t, "dut") conn := dialConn(t, dut, introspect.GNSI, 9339) c := authzpb.NewAuthzClient(conn) - if _, err := c.Get(context.Background(), &authzpb.GetRequest{}); err != nil { - t.Fatalf("gnsi.authz.Get failed: %v", err) + rsp, err := c.Get(context.Background(), &authzpb.GetRequest{}) + if err != nil { + 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 gRIBI Port. diff --git a/go.mod b/go.mod index a97dfb792f4..c93984c9271 100644 --- a/go.mod +++ b/go.mod @@ -3,92 +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.35.1 + 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.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.5.0 + github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 - github.com/open-traffic-generator/snappi/gosnappi v0.13.7 + 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.4 + 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.5 github.com/openconfig/gribi v1.0.0 - github.com/openconfig/gribigo v0.0.0-20231213034307-d0abeba7f432 - 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.5.0 + 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.11.1 - github.com/openconfig/ygot v0.29.18 + 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 + 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 - golang.org/x/crypto v0.18.0 - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a - golang.org/x/text v0.14.0 - google.golang.org/api v0.153.0 - google.golang.org/grpc v1.60.1 - google.golang.org/protobuf v1.32.0 + github.com/yuin/goldmark v1.4.13 + golang.org/x/crypto v0.27.0 + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 + golang.org/x/text v0.18.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.6.1 // indirect - golang.org/x/oauth2 v0.15.0 + golang.org/x/oauth2 v0.21.0 ) require ( - cloud.google.com/go v0.111.0 // indirect - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // 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.0.2 // 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/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect - github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect - github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // 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/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.11.1 // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.2 // 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.4 // 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.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // 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 @@ -97,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 @@ -109,16 +119,18 @@ 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.1 // 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/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/sagikazarmark/locafero v0.4.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 @@ -128,38 +140,34 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect - github.com/srl-labs/srl-controller v0.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/otel v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.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/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // 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 207e248fc3f..454fb06c5bc 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= @@ -41,18 +45,28 @@ cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x 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.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= +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= @@ -62,6 +76,13 @@ cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQ 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= @@ -69,18 +90,31 @@ cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9R 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= @@ -90,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= @@ -104,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= @@ -113,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= @@ -120,28 +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= @@ -158,6 +225,9 @@ cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB 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= @@ -166,6 +236,11 @@ cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhh 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= @@ -173,16 +248,27 @@ cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/ 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= @@ -191,11 +277,18 @@ cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb 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= @@ -204,6 +297,9 @@ cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6 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= @@ -221,19 +317,28 @@ cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/ cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= 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.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +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= @@ -242,12 +347,20 @@ cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8 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= @@ -260,24 +373,42 @@ cloud.google.com/go/datacatalog v1.14.0/go.mod h1:h0PrGtlihoutNMp/uvwhawLQ9+c63K 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= @@ -285,15 +416,28 @@ cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJ 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= @@ -302,6 +446,7 @@ cloud.google.com/go/datastore v1.12.0/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1 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= @@ -310,12 +455,21 @@ cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZ 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= @@ -327,10 +481,20 @@ cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz 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= @@ -340,35 +504,58 @@ cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb 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= @@ -377,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= @@ -388,26 +578,42 @@ cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2H 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= @@ -421,8 +627,11 @@ cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBa 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.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +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= @@ -430,15 +639,24 @@ cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQX 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= @@ -450,8 +668,11 @@ cloud.google.com/go/kms v1.11.0/go.mod h1:hwdiYC0xjnWsKQQCQQmIQnS9asjYVSK6jtXm+z cloud.google.com/go/kms v1.12.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= 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.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= +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= @@ -459,38 +680,62 @@ cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEy 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= @@ -498,12 +743,20 @@ cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U 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= @@ -512,16 +765,26 @@ cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9T 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= @@ -530,20 +793,33 @@ cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2 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= @@ -551,16 +827,27 @@ 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= @@ -568,11 +855,17 @@ cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/l 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= @@ -582,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= @@ -597,10 +892,19 @@ 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= @@ -608,33 +912,53 @@ cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph10 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= @@ -642,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= @@ -655,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= @@ -662,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= @@ -675,6 +1013,9 @@ cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxF 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= @@ -687,11 +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= @@ -700,6 +1050,10 @@ cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDF 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= @@ -712,32 +1066,48 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi 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/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= -cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= -cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +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= @@ -746,6 +1116,10 @@ cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV 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= @@ -755,12 +1129,18 @@ cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxE 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= @@ -769,30 +1149,48 @@ 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= @@ -800,6 +1198,9 @@ cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT 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= @@ -827,9 +1228,10 @@ 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= @@ -851,8 +1253,9 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= 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= @@ -866,7 +1269,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX 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= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -877,8 +1279,10 @@ github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= 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= @@ -892,6 +1296,8 @@ 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= @@ -916,20 +1322,25 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go. github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= 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 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= 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= github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= 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 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= 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= @@ -961,10 +1372,11 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb 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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 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= @@ -978,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= @@ -986,8 +1400,9 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU 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/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= 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= @@ -1019,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= @@ -1047,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= @@ -1075,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= @@ -1087,14 +1506,17 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ 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.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +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.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= @@ -1111,8 +1533,9 @@ 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.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -1123,6 +1546,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vb 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= @@ -1130,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= @@ -1141,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= @@ -1152,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= @@ -1190,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= @@ -1210,26 +1643,33 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m 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/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/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/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.7 h1:qrisl9OcqHdhUXVIJ0BXUAO8MDWf2qraNn+Oic+b8JM= -github.com/open-traffic-generator/snappi/gosnappi v0.13.7/go.mod h1:3pBcoJfsRp2B7IEcGq1w/8BwsBs7w2u6sYVqiThabmw= +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.4 h1:oHdSFP1CpP+mfv6IOKWefHpbW3Fy9ZOSHgPgpCb8EDU= -github.com/openconfig/gnsi v1.2.4/go.mod h1:jzPF4rVWPHhIG0F3t910Hh2VqqTbSfv18shbgE4AXhw= +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= @@ -1240,20 +1680,20 @@ github.com/openconfig/goyang v1.4.5/go.mod h1:sdNZi/wdTZyLNBNfgLzmmbi7kISm7FskMD 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-20231213034307-d0abeba7f432 h1:LADwzfGipdbqKnvHtbvN/MSZ3ttE9sli2B5VAAA8hpk= -github.com/openconfig/gribigo v0.0.0-20231213034307-d0abeba7f432/go.mod h1:fW2+Z2NiQ5L3hY/wrDsZBIvGYrM5ryHIzjHeVTiPzuM= +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.5.0 h1:Fva5haO/NrBFQ/+5NQrgIdgm9fr77YggUWpUQbfA66g= -github.com/openconfig/ondatra v0.5.0/go.mod h1:S0bcTPw3p4Ln8ptETUnqJuBh9bSIoQ9RtDOY0Lpt8dE= +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= @@ -1263,9 +1703,11 @@ github.com/openconfig/ygnmi v0.11.1/go.mod h1:naCxQR+/wBItM82ilJXWgapCRkrx8bphBm 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.18 h1:vgG2r7RVwaVDXgHtpsCNW+qdSGSdxqRxUfRN2rPCy7M= -github.com/openconfig/ygot v0.29.18/go.mod h1:sp6roPPmVDcTCF2E3qTjILA+jzJMkZ9d6spC9KLMqpc= +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= @@ -1274,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.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/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= @@ -1288,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= @@ -1296,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= @@ -1315,8 +1761,8 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz 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.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +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= @@ -1331,8 +1777,8 @@ 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/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= @@ -1341,6 +1787,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO 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/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= 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= @@ -1349,10 +1796,10 @@ 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.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= -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= @@ -1360,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= @@ -1371,16 +1819,15 @@ 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= @@ -1394,9 +1841,12 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de 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= @@ -1406,17 +1856,32 @@ 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/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +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/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +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/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +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/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +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.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= @@ -1425,8 +1890,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i 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.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -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.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= @@ -1440,15 +1905,23 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.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.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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 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= @@ -1464,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-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +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= @@ -1510,8 +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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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= @@ -1575,9 +2049,16 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 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.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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +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= @@ -1608,9 +2089,13 @@ golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I 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.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 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= 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= @@ -1630,8 +2115,10 @@ 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/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.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= @@ -1719,9 +2206,15 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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= @@ -1733,9 +2226,15 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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.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.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +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.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 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= @@ -1753,9 +2252,12 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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.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 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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= @@ -1831,8 +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.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +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= @@ -1840,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= @@ -1915,8 +2419,12 @@ google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2 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.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= -google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4= -google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= +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= @@ -1924,7 +2432,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww 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/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 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= @@ -2069,41 +2576,80 @@ google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFl 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-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-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +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= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= 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-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-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o= +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= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= 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-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-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +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= @@ -2149,10 +2695,15 @@ google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3 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.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +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/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= @@ -2172,8 +2723,10 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw 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/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= 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= @@ -2214,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 a502e35c4ab..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 { diff --git a/internal/cfgplugins/bgp.go b/internal/cfgplugins/bgp.go index eed48a62302..b1b69e1b003 100644 --- a/internal/cfgplugins/bgp.go +++ b/internal/cfgplugins/bgp.go @@ -41,15 +41,15 @@ const ( RPLPermitAll = "PERMIT-ALL" // DutAS dut AS - DutAS = uint32(65656) + DutAS = uint32(65501) // AteAS1 for ATE port1 - AteAS1 = uint32(65536) + AteAS1 = uint32(65511) // AteAS2 for ATE port2 - AteAS2 = uint32(65537) + AteAS2 = uint32(65512) // AteAS3 for ATE port3 - AteAS3 = uint32(65538) + AteAS3 = uint32(65513) // AteAS4 for ATE port4 - AteAS4 = uint32(65539) + AteAS4 = uint32(65514) // BGPPeerGroup1 for ATE port1 BGPPeerGroup1 = "BGP-PEER-GROUP1" @@ -93,7 +93,7 @@ var ( Name: "port4", IPv4: "192.0.2.13", IPv4Len: plenIPv4, - IPv6: "2001:0db8::192:0:2:13", + IPv6: "2001:0db8::192:0:2:d", IPv6Len: plenIPv6, } @@ -149,15 +149,16 @@ type BGPSession struct { DUTConf *oc.Root ATETop gosnappi.Config - DUTPorts []*attrs.Attributes - ATEPorts []*attrs.Attributes - aftType oc.E_BgpTypes_AFI_SAFI_TYPE + 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) *BGPSession { +func NewBGPSession(t *testing.T, pc PortCount, ni *string) *BGPSession { conf := &BGPSession{ DUT: ondatra.DUT(t, "dut"), DUTConf: &oc.Root{}, @@ -186,15 +187,25 @@ func NewBGPSession(t *testing.T, pc PortCount) *BGPSession { 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, aftype oc.E_BgpTypes_AFI_SAFI_TYPE, isSamePG, isSameAS bool) *BGPSession { - if aftype != oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST && aftype != oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST { - t.Fatalf("Unsupported AFI type: %v", bs.aftType) +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.aftType = aftype + bs.afiTypes = afiTypes asNumbers := []uint32{AteAS1, AteAS2, AteAS3, AteAS4} if isSameAS { @@ -205,34 +216,34 @@ func (bs *BGPSession) WithEBGP(t *testing.T, aftype oc.E_BgpTypes_AFI_SAFI_TYPE, 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) - switch aftype { - 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(uint32(asNumbers[i])) - bgp4Peer.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) - bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) - bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(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(uint32(asNumbers[i])) - bgp6Peer.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) - bgp6Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true).SetExtendedNextHopEncoding(true) - bgp6Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + 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(uint32(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(uint32(asNumbers[i])) + bgp6Peer.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + bgp6Peer.LearnedInformationFilter().SetUnicastIpv6Prefix(true) + } } } - dni := bs.DUTConf.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(bs.DUT)) - dni.SetType(oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) - - niProtocol := dni.GetOrCreateProtocol(PTBGP, bgpName) - neighborConfig := BuildNeigborConfig(isSamePG, isSameAS, len(bs.DUTPorts)) - niProtocol.Bgp = BuildBGPOCConfig(t, bs.DUT, dutPort1.IPv4, aftype, neighborConfig) + 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 { @@ -273,7 +284,7 @@ func (bs *BGPSession) PushDUT(t testing.TB) error { if deviations.ExplicitInterfaceInDefaultVRF(bs.DUT) { for i := 0; i < len(bs.DUTPorts); i++ { - fptest.AssignToNetworkInstance(t, bs.DUT, bs.OndatraDUTPorts[i].Name(), deviations.DefaultNetworkInstance(bs.DUT), 0) + fptest.AssignToNetworkInstance(t, bs.DUT, bs.OndatraDUTPorts[i].Name(), bs.networkInstance, 0) } } if deviations.ExplicitPortSpeed(bs.DUT) { @@ -291,19 +302,21 @@ func (bs *BGPSession) PushAndStartATE(t testing.TB) { otg.PushConfig(t, bs.ATETop) otg.StartProtocols(t) - switch bs.aftType { - 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") + 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 (bs *BGPSession) VerifyDUTBGPEstablished(t *testing.T) { - dni := deviations.DefaultNetworkInstance(bs.DUT) +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, bs.DUT, nSessionState, 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + 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 @@ -317,9 +330,9 @@ func (bs *BGPSession) VerifyDUTBGPEstablished(t *testing.T) { } // VerifyOTGBGPEstablished verifies on OTG BGP peer establishment -func (bs *BGPSession) VerifyOTGBGPEstablished(t *testing.T) { +func VerifyOTGBGPEstablished(t *testing.T, ate *ondatra.ATEDevice) { pSessionState := gnmi.OTG().BgpPeerAny().SessionState().State() - watch := gnmi.WatchAll(t, bs.ATE.OTG(), pSessionState, 2*time.Minute, func(val *ygnmi.Value[otgtelemetry.E_BgpPeer_SessionState]) bool { + 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 @@ -334,74 +347,74 @@ func (bs *BGPSession) VerifyOTGBGPEstablished(t *testing.T) { // NeighborConfig to hold neighbor specific config type NeighborConfig struct { + Name string IPv4Neighbor string IPv6Neighbor string PeerGroup string AS uint32 } -// BuildNeigborConfig builds neighbor config based on given flags -func BuildNeigborConfig(isSamePG, isSameAS bool, portCount int) []*NeighborConfig { - nc := []*NeighborConfig{ - { - IPv4Neighbor: atePort1.IPv4, - IPv6Neighbor: atePort1.IPv6, - PeerGroup: BGPPeerGroup1, - AS: AteAS1, - }, - { - IPv4Neighbor: atePort2.IPv4, - IPv6Neighbor: atePort2.IPv6, - PeerGroup: BGPPeerGroup2, - AS: AteAS2, - }, - } - - if portCount == int(PortCount4) { - nc = append( - nc, - &NeighborConfig{ - IPv4Neighbor: atePort3.IPv4, - IPv6Neighbor: atePort3.IPv6, - PeerGroup: BGPPeerGroup3, - AS: AteAS3, - }, - &NeighborConfig{ - IPv4Neighbor: atePort4.IPv4, - IPv6Neighbor: atePort4.IPv6, - PeerGroup: BGPPeerGroup4, - AS: AteAS4, - }, - ) +// 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} + + validNC := []*NeighborConfig{} + for _, nc := range ncAll[:len(bs.DUTPorts)] { + if containsValue(bgpPorts, nc.Name) { + validNC = append(validNC, nc) + } } if isSamePG { - for _, n := range nc { - n.PeerGroup = BGPPeerGroup1 + for _, nc := range validNC { + nc.PeerGroup = BGPPeerGroup1 } } if isSameAS { - for _, n := range nc { - n.AS = AteAS1 + for _, nc := range validNC { + nc.AS = AteAS1 } } - return nc + 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, aftType 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{ - aftType: { - AfiSafiName: aftType, - Enabled: ygot.Bool(true), - }, - } - afiSafiNeighbor := map[oc.E_BgpTypes_AFI_SAFI_TYPE]*oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi{ - aftType: { - AfiSafiName: aftType, +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{ @@ -414,23 +427,30 @@ func BuildBGPOCConfig(t *testing.T, dut *ondatra.DUTDevice, routerID string, aft peerGroups := make(map[string]*oc.NetworkInstance_Protocol_Bgp_PeerGroup) var neighbor string for _, nc := range neighborConfig { - switch aftType { - 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", aftType) + 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) } - - neighbors[neighbor] = &oc.NetworkInstance_Protocol_Bgp_Neighbor{ - PeerAs: ygot.Uint32(nc.AS), - PeerGroup: ygot.String(nc.PeerGroup), - NeighborAddress: ygot.String(neighbor), - AfiSafi: afiSafiNeighbor, - } - - peerGroups[nc.PeerGroup] = getPeerGroup(nc.PeerGroup, dut, aftType) } return &oc.NetworkInstance_Protocol_Bgp{ @@ -441,7 +461,7 @@ func BuildBGPOCConfig(t *testing.T, dut *ondatra.DUTDevice, routerID string, aft } // getPeerGroup build peer-config -func getPeerGroup(pgn string, dut *ondatra.DUTDevice, aftype oc.E_BgpTypes_AFI_SAFI_TYPE) *oc.NetworkInstance_Protocol_Bgp_PeerGroup { +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) @@ -454,10 +474,21 @@ func getPeerGroup(pgn string, dut *ondatra.DUTDevice, aftype oc.E_BgpTypes_AFI_S } // policy under peer group AFI - afisafi := pg.GetOrCreateAfiSafi(aftype) - afisafi.Enabled = ygot.Bool(true) - rpl := afisafi.GetOrCreateApplyPolicy() - rpl.SetExportPolicy([]string{RPLPermitAll}) - rpl.SetImportPolicy([]string{RPLPermitAll}) + 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 +} 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..bf0086daba0 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 { + 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 aa6f135712d..73022d52a41 100644 --- a/internal/cfgplugins/staticroute.go +++ b/internal/cfgplugins/staticroute.go @@ -52,6 +52,7 @@ func NewStaticRouteCfg(batch *gnmi.SetBatch, cfg *StaticRouteCfg, d *ondatra.DUT 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/internal/cntrsrv/build/cntr_build.sh b/internal/cntrsrv/build/cntr_build.sh new file mode 100755 index 00000000000..9da4d016218 --- /dev/null +++ b/internal/cntrsrv/build/cntr_build.sh @@ -0,0 +1,33 @@ +#!/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. +# 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. + +# 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 . + +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..c2e6a8b2896 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()) @@ -175,3 +198,27 @@ func FindStandbyRP(t *testing.T, dut *ondatra.DUTDevice, supervisors []string) ( 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/deviations.go b/internal/deviations/deviations.go index 8aa56dd6b19..3e2a221ac20 100644 --- a/internal/deviations/deviations.go +++ b/internal/deviations/deviations.go @@ -146,12 +146,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,11 +179,6 @@ 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() @@ -377,6 +366,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,11 +386,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() -} - // 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() @@ -731,11 +720,6 @@ func SkipStaticNexthopCheck(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetSkipStaticNexthopCheck() } -// EnableFlowctrlFlag returns if device needs set leaf specific enable flag. -func EnableFlowctrlFlag(dut *ondatra.DUTDevice) bool { - return lookupDUTDeviations(dut).GetEnableFlowctrlFlag() -} - // Ipv6RouterAdvertisementConfigUnsupported returns true for devices which don't support Ipv6 RouterAdvertisement configuration func Ipv6RouterAdvertisementConfigUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetIpv6RouterAdvertisementConfigUnsupported() @@ -745,3 +729,439 @@ func Ipv6RouterAdvertisementConfigUnsupported(dut *ondatra.DUTDevice) bool { 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 ethertype 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 atleast >= 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 atleast >= 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 unsported ipv6 with ipv4 nexthop +func IPv6StaticRouteWithIPv4NextHopUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIpv6StaticRouteWithIpv4NhUnsupported() +} + +// StaticRouteWithDropNhUnsupported unsuported drop nexthop +func StaticRouteWithDropNhUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticRouteWithDropNh() +} + +// StaticRouteWithExplicitMetric set explict 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 explicity 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 backup 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() +} + +// 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() +} 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/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 608a77465e0..14d819c91d0 100644 --- a/internal/fptest/runtests.go +++ b/internal/fptest/runtests.go @@ -75,6 +75,7 @@ func testbedPathFromMetadata() (string, error) { 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..48eec464b83 --- /dev/null +++ b/internal/gnoi/gnoi.go @@ -0,0 +1,133 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gnoi provides utilities for interacting with the gNOI API. +package gnoi + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/system" + gpb "github.com/openconfig/gnmi/proto/gnmi" + spb "github.com/openconfig/gnoi/system" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +var ( + daemonProcessNames = map[ondatra.Vendor]map[Daemon]string{ + ondatra.ARISTA: { + GRIBI: "Gribi", + OCAGENT: "Octa", + P4RT: "P4Runtime", + ROUTING: "Bgp-main", + }, + ondatra.CISCO: { + GRIBI: "emsd", + P4RT: "emsd", + ROUTING: "emsd", + }, + ondatra.JUNIPER: { + GRIBI: "rpd", + P4RT: "p4-switch", + ROUTING: "rpd", + }, + ondatra.NOKIA: { + GRIBI: "sr_grpc_server", + OCAGENT: "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/helpers/helpers.go b/internal/helpers/helpers.go index cb2fefb4228..0667886c46e 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -16,6 +16,7 @@ package helpers import ( + "context" "fmt" "sort" "strings" @@ -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/internal/isissession/isissession.go b/internal/isissession/isissession.go index 11b43dce24b..3fc1dae70a8 100644 --- a/internal/isissession/isissession.go +++ b/internal/isissession/isissession.go @@ -58,8 +58,6 @@ 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 = &attrs.Attributes{ Desc: "DUT to ATE with IS-IS", diff --git a/internal/otgutils/actions.go b/internal/otgutils/actions.go index af0feab727c..9ed8f136f98 100644 --- a/internal/otgutils/actions.go +++ b/internal/otgutils/actions.go @@ -37,9 +37,12 @@ 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 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..34611fb2384 100644 --- a/internal/p4rtutils/p4rtutils.go +++ b/internal/p4rtutils/p4rtutils.go @@ -25,8 +25,6 @@ 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" @@ -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/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/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/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/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 index e5a0399a5eb..09d687b26a3 100644 --- a/internal/vrfpolicy/vrfpolicy.go +++ b/internal/vrfpolicy/vrfpolicy.go @@ -18,6 +18,7 @@ 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" @@ -25,12 +26,17 @@ import ( ) 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" - vrfPolW = "vrf_selection_policy_w" + niRepairVrf = "REPAIR_VRF" niDefault = "DEFAULT" dscpEncapA1 = 10 dscpEncapA2 = 18 @@ -44,7 +50,7 @@ const ( decapFlowSrc = "198.51.100.111" ) -type ipv4 struct { +type ipInfo struct { protocol oc.UnionUint8 dscpSet []uint8 sourceAddr string @@ -54,12 +60,14 @@ type action struct { decapNI string postDecapNI string decapFallbackNI string + networkInstance string } type policyFwRule struct { seqID uint32 - ipv4 ipv4 - action action + ipv4 *ipInfo + ipv6 *ipInfo + action *action } // configureNetworkInstance configures vrfs DECAP_TE_VRF, ENCAP_TE_VRF_A, ENCAP_TE_VRF_B, @@ -67,7 +75,7 @@ type policyFwRule struct { func configNonDefaultNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { t.Helper() c := &oc.Root{} - vrfs := []string{niDecapTeVrf, niEncapTeVrfA, niEncapTeVrfB, niEncapTeVrfC, niEncapTeVrfD, niTeVrf111, niTeVrf222} + 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 @@ -78,70 +86,69 @@ func configNonDefaultNetworkInstance(t *testing.T, dut *ondatra.DUTDevice) { // 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 { - d := &oc.Root{} configNonDefaultNetworkInstance(t, dut) pfRule1 := &policyFwRule{ seqID: 1, - ipv4: ipv4{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, } pfRule2 := &policyFwRule{ seqID: 2, - ipv4: ipv4{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf222}, } pfRule3 := &policyFwRule{ seqID: 3, - ipv4: ipv4{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, } pfRule4 := &policyFwRule{ seqID: 4, - ipv4: ipv4{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapA1, dscpEncapA2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfA, decapFallbackNI: niTeVrf111}, } pfRule5 := &policyFwRule{ seqID: 5, - ipv4: ipv4{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, } pfRule6 := &policyFwRule{ seqID: 6, - ipv4: ipv4{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf222}, } pfRule7 := &policyFwRule{ seqID: 7, - ipv4: ipv4{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, + ipv4: &ipInfo{protocol: 4, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, } pfRule8 := &policyFwRule{ seqID: 8, - ipv4: ipv4{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, + ipv4: &ipInfo{protocol: 41, dscpSet: []uint8{dscpEncapB1, dscpEncapB2}, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niEncapTeVrfB, decapFallbackNI: niTeVrf111}, } pfRule9 := &policyFwRule{ seqID: 9, - ipv4: ipv4{protocol: 4, sourceAddr: ipv4OuterSrc222WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, + ipv4: &ipInfo{protocol: 4, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, } pfRule10 := &policyFwRule{ seqID: 10, - ipv4: ipv4{protocol: 41, sourceAddr: ipv4OuterSrc222WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, + ipv4: &ipInfo{protocol: 41, sourceAddr: ipv4OuterSrc222WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf222}, } pfRule11 := &policyFwRule{ seqID: 11, - ipv4: ipv4{protocol: 4, sourceAddr: ipv4OuterSrc111WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, + ipv4: &ipInfo{protocol: 4, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, } pfRule12 := &policyFwRule{ seqID: 12, - ipv4: ipv4{protocol: 41, sourceAddr: ipv4OuterSrc111WithMask}, - action: action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, + ipv4: &ipInfo{protocol: 41, sourceAddr: ipv4OuterSrc111WithMask}, + action: &action{decapNI: niDecapTeVrf, postDecapNI: niDefault, decapFallbackNI: niTeVrf111}, } pfRuleList := []*policyFwRule{ @@ -149,28 +156,245 @@ func BuildVRFSelectionPolicyW(t *testing.T, dut *ondatra.DUTDevice, niName strin pfRule7, pfRule8, pfRule9, pfRule10, pfRule11, pfRule12, } - ni := d.GetOrCreateNetworkInstance(niName) + 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(vrfPolW) + niPf := niP.GetOrCreatePolicy(policyName) niPf.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) - for _, pfRule := range pfRuleList { + for _, pfRule := range pfRules { pfR := niPf.GetOrCreateRule(pfRule.seqID) - pfRProtoIPv4 := pfR.GetOrCreateIpv4() - pfRProtoIPv4.Protocol = oc.UnionUint8(pfRule.ipv4.protocol) - if pfRule.ipv4.dscpSet != nil { - pfRProtoIPv4.DscpSet = pfRule.ipv4.dscpSet + if pfRule.ipv4 != nil { + pfRProtoIP := pfR.GetOrCreateIpv4() + if pfRule.ipv4.dscpSet != nil { + pfRProtoIP.DscpSet = pfRule.ipv4.dscpSet + } + if pfRule.ipv4.protocol != 0 { + pfRProtoIP.Protocol = oc.UnionUint8(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 + } } - pfRProtoIPv4.SourceAddress = ygot.String(pfRule.ipv4.sourceAddr) + pfRAction := pfR.GetOrCreateAction() - pfRAction.DecapNetworkInstance = ygot.String(pfRule.action.decapNI) - pfRAction.PostDecapNetworkInstance = ygot.String(pfRule.action.postDecapNI) - pfRAction.DecapFallbackNetworkInstance = ygot.String(pfRule.action.decapFallbackNI) + 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) + } } - pfR := niPf.GetOrCreateRule(13) - pfRAction := pfR.GetOrCreateAction() - pfRAction.NetworkInstance = ygot.String(niDefault) - 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 103e3d4ad8b..9d5973aaa2e 100644 --- a/proto/metadata.proto +++ b/proto/metadata.proto @@ -37,6 +37,7 @@ message Metadata { 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; @@ -143,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; @@ -156,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; @@ -278,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; @@ -297,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,12 +311,13 @@ message Metadata { // Devices do not support telemetry for isis counter: part-changes. // 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. @@ -330,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 @@ -339,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 @@ -361,8 +363,8 @@ 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 @@ -378,30 +380,249 @@ message Metadata { 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 + // 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 + // 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; - // Devices which needs to enable leaf specific flag - // Juniper: b/319202763 - bool enable_flowctrl_flag = 137; // Device doesn't support router advertisement enable and mode config - // Juniper: b/316173974 + // 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; // Reserved field numbers and identifiers. - reserved 84, 9, 28, 20, 90, 97, 55, 89, 19; + reserved 84, 9, 28, 20, 90, 97, 55, 89, 19, 36, 35, 40, 173; } message PlatformExceptions { @@ -424,4 +645,9 @@ 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 f5a6d9d8679..2472d35b23b 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 v3.14.0 +// protoc-gen-go v1.34.2 +// protoc v5.28.0 // source: metadata.proto package metadata_go_proto @@ -48,6 +48,7 @@ const ( 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. @@ -61,6 +62,7 @@ var ( 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, @@ -71,6 +73,7 @@ var ( "TESTBED_DUT_ATE_9LINKS_LAG": 5, "TESTBED_DUT_DUT_ATE_2LINKS": 6, "TESTBED_DUT_ATE_8LINKS": 7, + "TESTBED_DUT_400ZR": 8, } ) @@ -176,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() { @@ -252,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 @@ -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. @@ -583,12 +592,13 @@ type Metadata_Deviations struct { // Devices do not support telemetry for isis counter: part-changes. // 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,8 +644,8 @@ 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 @@ -647,27 +661,248 @@ type Metadata_Deviations struct { 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 + // 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 + // 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"` - // Devices which needs to enable leaf specific flag - // Juniper: b/319202763 - EnableFlowctrlFlag bool `protobuf:"varint,137,opt,name=enable_flowctrl_flag,json=enableFlowctrlFlag,proto3" json:"enable_flowctrl_flag,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"` } func (x *Metadata_Deviations) Reset() { @@ -912,20 +1147,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 @@ -947,13 +1168,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 @@ -1542,23 +1756,597 @@ func (x *Metadata_Deviations) GetSkipStaticNexthopCheck() bool { return false } -func (x *Metadata_Deviations) GetEnableFlowctrlFlag() bool { +func (x *Metadata_Deviations) GetIpv6RouterAdvertisementConfigUnsupported() bool { if x != nil { - return x.EnableFlowctrlFlag + return x.Ipv6RouterAdvertisementConfigUnsupported } return false } -func (x *Metadata_Deviations) GetIpv6RouterAdvertisementConfigUnsupported() bool { +func (x *Metadata_Deviations) GetPrefixLimitExceededTelemetryUnsupported() bool { if x != nil { - return x.Ipv6RouterAdvertisementConfigUnsupported + return x.PrefixLimitExceededTelemetryUnsupported } return false } -func (x *Metadata_Deviations) GetPrefixLimitExceededTelemetryUnsupported() bool { +func (x *Metadata_Deviations) GetSkipSettingAllowMultipleAs() bool { if x != nil { - return x.PrefixLimitExceededTelemetryUnsupported + 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 } @@ -1626,7 +2414,7 @@ var file_metadata_proto_rawDesc = []byte{ 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, 0xc1, 0x4d, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe6, 0x7d, 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, @@ -1645,609 +2433,995 @@ var file_metadata_proto_rawDesc = []byte{ 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, 0xd9, 0x45, 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, 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, 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, 0x31, 0x0a, 0x14, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x74, 0x72, 0x6c, 0x5f, 0x66, 0x6c, - 0x61, 0x67, 0x18, 0x89, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x46, 0x6c, 0x6f, 0x77, 0x63, 0x74, 0x72, 0x6c, 0x46, 0x6c, 0x61, 0x67, 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, 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, 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, 0xe3, 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, 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, + 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, 0xb9, 0x75, 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, 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 ( @@ -2264,7 +3438,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 @@ -2293,7 +3467,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 @@ -2305,7 +3479,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 @@ -2317,7 +3491,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 @@ -2329,7 +3503,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/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 3c1b5b0493e..533758476f2 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,46 @@ 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: "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 +217,18 @@ 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.2" description: "QoS policy feature config" @@ -115,6 +241,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,12 +263,29 @@ 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: "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" 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: { id: "IC-1" description: "Integrated Circuit Utilization and Thresholds" @@ -140,9 +293,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 +334,129 @@ 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/experimental/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/experimental/p4rt/tests/p4rt_election/README.md" +} +test: { + id: "P4RT-2.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/tests/metadata_validation_test/README.md" +} +test: { + id: "P4RT-3.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/README.md" +} +test: { + id: "P4RT-3.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/README.md" +} +test: { + id: "P4RT-5.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/otg_tests/traceroute_packetin_test/README.md" +} +test: { + id: "P4RT-5.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/otg_tests/traceroute_packetout_test/README.md" +} +test: { + id: "P4RT-6.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/otg_tests/performance_test/README.md" +} +test: { + id: "P4RT-7.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/otg_tests/lldp_packetin_test/README.md" +} +test: { + id: "P4RT-7.2" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/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: "PLT-1.1" + description: "Interface breakout Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/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" @@ -207,26 +501,20 @@ 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" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/bgp/ate_tests/bgp_2byte_4byte_asn/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" + description: "BGP 2-Byte and 4-Byte ASN support" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/bgp/otg_tests/bgp_2byte_4byte_asn/README.md" exec: " " } test: { @@ -250,7 +538,7 @@ test: { test: { id: "RT-1.24" description: "BGP 2-byte and 4-byte ASN support with policy" - readme: "" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/README.md" exec: " " } test: { @@ -268,7 +556,7 @@ test: { test: { id: "RT-1.27" description: "Static route to BGP redistribution" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/static-route_bgp_redistribution/README.md" + 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: { @@ -278,22 +566,43 @@ 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.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.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.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.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.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: { @@ -309,82 +618,115 @@ test: { 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.53" + description: "Prefix-set test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/policy/prefix_set/README.md" exec: " " } test: { - id: "RT-1.8" - description: "BGP Route Reflector test at scale" - readme: "" + 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.9" - description: "BGP Transport Parameters test" - readme: "" + id: "RT-1.56" + description: "BGP extended Next-hop support (RFC 5549)" + readme: " " exec: " " } test: { - id: "RT-2.2" - description: "BGP Policy AS Path Set" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/aspath_test/README.md" + 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-2.3" - description: "BGP Policy Community Set" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/policybase/otg_tests/community_test/README.md" + id: "RT-1.58" + description: "Support for learning IPv6 ULA/64 address space over [E|I]BGP sessions" + readme: " " exec: " " } test: { - id: "RT-2.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" + id: "RT-1.59" + description: "Support for configuring static IPv6 ULA/64 routes with higher administrative distance" + readme: " " exec: " " } - test: { - id: "RT-2.6" - description: "IS-IS Hello-Padding enabled at interface level" - readme: "" + id: "RT-1.60" + description: "ECMP hashing support for [E|I]BGP learnt ULA/64 address space" + readme: " " exec: " " } test: { - id: "RT-2.7" - description: "IS-IS Passive is enabled at interface level" + 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-2.10" + description: "IS-IS change LSP lifetime" readme: "" exec: " " } test: { - id: "RT-2.8" - description: "IS-IS Passive is enabled at the area level" + id: "RT-2.11" + description: "IS-IS metric style wide not enabled" readme: "" exec: " " } test: { - id: "RT-2.9" - description: "IS-IS metric style wide enabled" + 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/experimental/isis/otg_tests/isis_drain_test/README.md" + exec: " " +} +test: { + id: "RT-2.2" + description: "IS-IS LSP Updates" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/isis/otg_tests/lsp_updates_test/README.md" +} +test: { + id: "RT-2.6" + description: "IS-IS Hello-Padding enabled at interface level" readme: "" exec: " " } test: { - id: "RT-2.10" - description: "IS-IS change LSP lifetime" + id: "RT-2.7" + description: "IS-IS Passive is enabled at interface level" readme: "" exec: " " } test: { - id: "RT-2.11" - description: "IS-IS metric style wide not enabled" + id: "RT-2.8" + description: "IS-IS Passive is enabled at the area level" 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" + id: "RT-2.9" + description: "IS-IS metric style wide enabled" + readme: "" exec: " " } test: { @@ -417,6 +759,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" @@ -454,10 +802,9 @@ test: { 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: " " + id: "RT-5.6" + description: "Interface Loopback mode" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md" } test: { id: "RT-5.7" @@ -471,6 +818,12 @@ test: { 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: { id: "RT-6.1" description: "Core LLDP TLV Population" @@ -483,18 +836,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/experimental/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/experimental/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" @@ -507,6 +942,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" @@ -543,6 +983,24 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/ate_tests/gribigo_compliance_test/README.md" exec: " " } +test: { + id: "TE-16.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/basic_encap_test/README.md" +} +test: { + id: "TE-16.2" + description: "gRIBI encapsulation FRR scenarios" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/encap_frr/README.md" +} +test: { + id: "TE-17.1" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/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-2.1" description: "gRIBI IPv4 Entry" @@ -693,20 +1151,15 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/ate_tests/supervisor_failure_test/README.md" exec: " " } -test: { - id: "TE-9" - description: "MPLS based forwarding Static LSP" - 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" } 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" @@ -720,6 +1173,66 @@ test: { 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" @@ -804,6 +1317,152 @@ 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: "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" @@ -849,7 +1508,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: " " } @@ -886,9 +1544,19 @@ test: { test: { id: "gNMI-1.20" description: "Telemetry: Optics Thresholds" - readme: "https://github.com/openconfig/featureprofiles/feature/experimental/platform/tests/optics_thresholds_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/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" @@ -974,56 +1642,78 @@ 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: "TRANSCEIVER-8" - description: "400ZR Optics module temperature streaming" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_temperature_test/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: "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" + 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: "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" + 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: "PLT-1.1" - description: "Interface breakout Test" - readme: "https://github.com/openconfig/featureprofiles/feature/experimental/platform/tests/breakout_configuration/README.md" + 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: " " } 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 376b7dff14b..e2d6602d550 100755 --- a/tools/check_ip_addresses.pl +++ b/tools/check_ip_addresses.pl @@ -43,15 +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 =~ /20\.0\./; # 20.0.0.1/15 - next if $ip =~ /30\.0\./; # 30.0.0.1/15 - next if $ip =~ /100\.0\./; # 100.0.0.1/12 - next if $ip =~ /138\.0\.11\./; # 138.0.11.0/24 - next if $ip =~ /192\.51\.100\./; # 192.51.100.1/24 - next if $ip =~ /192\.51\.128\./; # 192.51.129.0/22 - next if $ip =~ /192\.55\.200\./; # 192.55.200.3/32 - next if $ip =~ /198\.100\.200\./; # 198.100.200.123/24 + 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; } @@ -63,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/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_test.go b/tools/internal/ocpaths/ocpaths_test.go index f4582df917f..237e8947b59 100644 --- a/tools/internal/ocpaths/ocpaths_test.go +++ b/tools/internal/ocpaths/ocpaths_test.go @@ -164,7 +164,7 @@ func TestValidatePath(t *testing.T) { err := validatePath(ocpath, root) if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Errorf(diff) + t.Error(diff) } if err != nil { diff --git a/tools/internal/ocrpcs/ocrpcs.go b/tools/internal/ocrpcs/ocrpcs.go index b948c9778a0..5e535b90928 100644 --- a/tools/internal/ocrpcs/ocrpcs.go +++ b/tools/internal/ocrpcs/ocrpcs.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 ocrpcs contains utilities related to ocrpcs.proto. package ocrpcs @@ -15,12 +29,22 @@ import ( "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 { @@ -36,7 +60,11 @@ func cloneAPIRepo(downloadPath, api string) (string, error) { return repoPath, nil } -func readAllRPCs(downloadPath, api string) (map[string]struct{}, error) { +// 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 @@ -87,7 +115,7 @@ func ValidateRPCs(downloadPath string, protocols map[string]*rpb.OCProtocol) (ui var errs errlist.List errs.Separator = "\n" for api, protocol := range protocols { - rpcs, err := readAllRPCs(downloadPath, api) + rpcs, err := Read(downloadPath, api) if err != nil { return 0, err } diff --git a/tools/nosimage/example/generate_example.go b/tools/nosimage/example/generate_example.go index 00871279ffc..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, valid bool) error { +func generateExample(invalidPaths, invalidProtocols, invalidSoftwareName, invalidHardwareName bool) ([]byte, error) { componentPrefix := "/components/component" softwareComponent := "OPERATING_SYSTEM" interfaceLeafName := "name" - if !valid { + 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", @@ -96,7 +112,7 @@ func generateExample(filepath string, valid bool) error { }, Ocrpcs: &rpb.OCRPCs{ OcProtocols: func() map[string]*rpb.OCProtocol { - if valid { + if !invalidProtocols { return map[string]*rpb.OCProtocol{ "gnmi": { Version: "0.10.0", @@ -143,10 +159,6 @@ func generateExample(filepath string, valid bool) error { }(), }, }) - if err != nil { - return err - } - return os.WriteFile(filepath, bs, 0664) } func formatTxtpb(msg proto.Message) ([]byte, error) { @@ -171,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 100% rename from tools/nosimage/example/example_nosimageprofile.textproto rename to tools/nosimage/example/invalid-hw-name_example_nosimageprofile.textproto diff --git a/tools/nosimage/example/example_nosimageprofile_invalid.textproto b/tools/nosimage/example/invalid-path_example_nosimageprofile.textproto similarity index 95% rename from tools/nosimage/example/example_nosimageprofile_invalid.textproto rename to tools/nosimage/example/invalid-path_example_nosimageprofile.textproto index 6cfeb5348ad..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 } @@ -41,14 +42,12 @@ ocrpcs: { 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" 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 614fdbe8694..a43203d4e4d 100644 --- a/tools/nosimage/validate/validate.go +++ b/tools/nosimage/validate/validate.go @@ -18,11 +18,9 @@ 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" @@ -58,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", "https://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") @@ -92,7 +69,6 @@ func unmarshalFile(filePath string) (*npb.NOSImageProfile, error) { return nil, err } return profile, nil - } func main() { @@ -104,10 +80,18 @@ func main() { os.Exit(1) } + 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)) } - publicPath, err := clonePublicRepo(config.DownloadPath, "v"+profile.Ocpaths.GetVersion()) + publicPath, err := ocpaths.ClonePublicRepo(config.DownloadPath, "v"+profile.Ocpaths.GetVersion()) if err != nil { fmt.Println(err) os.Exit(1) 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/binding/binding.go b/topologies/binding/binding.go index dcabc88167f..d47f7ce18eb 100644 --- a/topologies/binding/binding.go +++ b/topologies/binding/binding.go @@ -23,32 +23,30 @@ import ( "net" "net/http" "os" - "strings" "time" grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" "github.com/open-traffic-generator/snappi/gosnappi" + 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" - - bindpb "github.com/openconfig/featureprofiles/topologies/proto/binding" - gpb "github.com/openconfig/gnmi/proto/gnmi" - grpb "github.com/openconfig/gribi/v1/proto/service" - opb "github.com/openconfig/ondatra/proto" - p4pb "github.com/p4lang/p4runtime/go/p4/v1" ) var ( // To be stubbed out by unit tests. - grpcDialContextFn = grpc.DialContext + grpcDialContextFn = grpc.NewClient gosnappiNewAPIFn = gosnappi.NewApi ) @@ -91,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 } @@ -142,7 +140,7 @@ func (d *staticDUT) Dialer(svc introspect.Service) (*introspect.Dialer, error) { return nil, fmt.Errorf("no known DUT service %v", svc) } bopts := d.r.grpc(d.dev, params) - return makeDialer(d.Name(), params, bopts) + return makeDialer(params, bopts) } func (d *staticDUT) reset(ctx context.Context) error { @@ -239,7 +237,7 @@ func (a *staticATE) Dialer(svc introspect.Service) (*introspect.Dialer, error) { return nil, fmt.Errorf("no known ATE service %v", svc) } bopts := a.r.grpc(a.dev, params) - return makeDialer(a.Name(), params, bopts) + return makeDialer(params, bopts) } func (a *staticATE) DialGNMI(ctx context.Context, opts ...grpc.DialOption) (gpb.GNMIClient, error) { @@ -276,167 +274,118 @@ func (a *staticATE) DialIxNetwork(ctx context.Context) (*binding.IxNetwork, erro 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)) + } + 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)) } - dims := &binding.Dims{ + + 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 { @@ -524,7 +473,7 @@ func dialConn(ctx context.Context, dev introspect.Introspector, svc introspect.S } func dialOpts(bopts *bindpb.Options) ([]grpc.DialOption, error) { - opts := []grpc.DialOption{grpc.WithBlock()} + opts := []grpc.DialOption{grpc.WithDisableRetry()} switch { case bopts.Insecure: tc := insecure.NewCredentials() @@ -564,7 +513,7 @@ func dialOpts(bopts *bindpb.Options) ([]grpc.DialOption, error) { return opts, nil } -func makeDialer(name string, params *svcParams, bopts *bindpb.Options) (*introspect.Dialer, error) { +func makeDialer(params *svcParams, bopts *bindpb.Options) (*introspect.Dialer, error) { opts, err := dialOpts(bopts) if err != nil { return nil, err @@ -574,12 +523,12 @@ func makeDialer(name string, params *svcParams, bopts *bindpb.Options) (*introsp DialFunc: func(ctx context.Context, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { if bopts.Timeout != 0 { var cancelFunc context.CancelFunc - ctx, cancelFunc = context.WithTimeout(ctx, time.Duration(bopts.Timeout)*time.Second) + _, cancelFunc = context.WithTimeout(ctx, time.Duration(bopts.Timeout)*time.Second) defer cancelFunc() } - return grpcDialContextFn(ctx, bopts.Target, opts...) + return grpcDialContextFn(target, opts...) }, - DialTarget: fmt.Sprintf("%s:%d", name, params.port), + DialTarget: bopts.Target, DialOpts: opts, }, nil } diff --git a/topologies/binding/binding_test.go b/topologies/binding/binding_test.go index 03158e34de6..5a13b066457 100644 --- a/topologies/binding/binding_test.go +++ b/topologies/binding/binding_test.go @@ -16,6 +16,7 @@ package binding import ( "context" + "fmt" "strings" "testing" "time" @@ -24,6 +25,7 @@ import ( "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" @@ -51,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", @@ -96,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) } @@ -147,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{{ @@ -164,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", }}, @@ -176,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", @@ -199,25 +213,27 @@ 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) } } } @@ -225,13 +241,10 @@ func TestReservation_Error(t *testing.T) { func TestDialOTGTimeout(t *testing.T) { const timeoutSecs = 42 a := &staticATE{ - AbstractATE: &binding.AbstractATE{Dims: &binding.Dims{Name: "my_ate"}}, - r: resolver{&bindpb.Binding{}}, - dev: &bindpb.Device{Otg: &bindpb.Options{ - Timeout: timeoutSecs, - }}, + r: resolver{&bindpb.Binding{}}, + dev: &bindpb.Device{Otg: &bindpb.Options{Timeout: timeoutSecs}}, } - grpcDialContextFn = func(context.Context, string, ...grpc.DialOption) (*grpc.ClientConn, error) { + grpcDialContextFn = func(string, ...grpc.DialOption) (*grpc.ClientConn, error) { return nil, nil } gosnappiNewAPIFn = func() gosnappi.Api { @@ -256,3 +269,36 @@ 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 c0eccfcfab6..ffa7460410f 100644 --- a/topologies/binding/options.go +++ b/topologies/binding/options.go @@ -88,26 +88,6 @@ 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 -} - 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)) diff --git a/topologies/binding/options_test.go b/topologies/binding/options_test.go index e7f5b1efe11..e4ff9b13c7a 100644 --- a/topologies/binding/options_test.go +++ b/topologies/binding/options_test.go @@ -152,66 +152,6 @@ 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_Options(t *testing.T) { r := resolverBinding cases := []struct { 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