diff --git a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md index b74ba3fad48..aa276698ab4 100644 --- a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/README.md @@ -6,7 +6,8 @@ BGP Override AS-path split-horizon ## Topology - ATE Port1 (AS 65502) --- DUT Port1 (AS 65501) DUT Port2 ---eBGP --- ATE Port2 (AS 65503) +ATE Port1 (AS 65502) --- eBGP --------- DUT Port1 (DUT Local AS 65501) +ATE Port2 (AS 65503) --- eBGP --------- DUT Port2 (DUT Local AS 64513) ## Procedure diff --git a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go new file mode 100644 index 00000000000..4bb980a2bd3 --- /dev/null +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go @@ -0,0 +1,581 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bgp_override_as_path_split_horizon_test + +import ( + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + otg "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + advertisedRoutesv4CIDR = "203.0.113.1/32" + advertisedRoutesv4Net = "203.0.113.1" + advertisedRoutesv4Prefix = 32 + peerGrpName1 = "BGP-PEER-GROUP1" + peerGrpName2 = "BGP-PEER-GROUP2" + dutGlobalAS = 64512 + dutLocalAS1 = 65501 + dutLocalAS2 = 64513 + ateAS1 = 65502 + ateAS2 = 65503 + plenIPv4 = 30 + plenIPv6 = 126 + policyName = "ALLOW" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE Port1", + IPv4: "192.0.2.1", + IPv6: "2001:db8::192:0:2:1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort1 = attrs.Attributes{ + Name: "atePort1", + IPv4: "192.0.2.2", + IPv6: "2001:db8::192:0:2:2", + MAC: "02:00:01:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE Port2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::192:0:2:5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + IPv4: "192.0.2.6", + IPv6: "2001:db8::192:0:2:6", + MAC: "02:00:02:01:01:01", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + nbr1 = &bgpNeighbor{localAS: dutLocalAS1, peerAS: ateAS1, neighborip: atePort1.IPv4, isV4: true, peerGrp: peerGrpName1} + nbr2 = &bgpNeighbor{localAS: dutLocalAS2, peerAS: ateAS2, neighborip: atePort2.IPv4, isV4: true, peerGrp: peerGrpName2} + + otgPort1V4Peer = "atePort1.BGP4.peer" + otgPort2V4Peer = "atePort2.BGP4.peer" +) + +// configureDUT configures all the interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + i1 := dutPort1.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + i2 := dutPort2.NewOCInterface(dut.Port(t, "port2").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i2.GetName()).Config(), i2) +} + +// 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 atePort1 and atePort2 +func bgpCreateNbr(t *testing.T, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + t.Helper() + dutOcRoot := &oc.Root{} + ni1 := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + global := bgp.GetOrCreateGlobal() + global.RouterId = ygot.String(dutPort2.IPv4) + global.As = ygot.Uint32(dutGlobalAS) + + // Note: we have to define the peer group even if we aren't setting any policy because it's + // invalid OC for the neighbor to be part of a peer group that doesn't exist. + + for _, nbr := range []*bgpNeighbor{nbr1, nbr2} { + + pg := bgp.GetOrCreatePeerGroup(nbr.peerGrp) + pg.PeerAs = ygot.Uint32(nbr.peerAS) + pg.LocalAs = ygot.Uint32(nbr.localAS) + pg.PeerGroupName = ygot.String(nbr.peerGrp) + + nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.PeerGroup = ygot.String(nbr.peerGrp) + nv4.PeerAs = ygot.Uint32(nbr.peerAS) + nv4.Enabled = ygot.Bool(true) + af4 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + + if deviations.RoutePolicyUnderAFIUnsupported(dut) { + rpl := pg.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{policyName} + rpl.ExportPolicy = []string{policyName} + } else { + pgaf := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pgaf.Enabled = ygot.Bool(true) + rpl := pgaf.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{policyName} + rpl.ExportPolicy = []string{policyName} + } + } + return niProto +} + +// verifyBgpTelemetry checks that the dut has an established BGP session with reasonable settings. +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbrsList []*bgpNeighbor) { + t.Helper() + t.Logf("Verifying BGP state.") + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + for _, nbr := range nbrsList { + nbrPath := bgpPath.Neighbor(nbr.neighborip) + t.Logf("Waiting for BGP neighbor to establish...") + status, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + return ok && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + t.Fatal("No BGP neighbor formed") + } + state, _ := status.Val() + t.Logf("BGP adjacency for %s: %v", nbr.neighborip, state) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; state != want { + t.Errorf("BGP peer %s status got %d, want %d", nbr.neighborip, state, want) + } + } +} + +// configureOTG configures the interfaces and BGP protocols on an ATE. +func configureOTG(t *testing.T, otg *otg.OTG) (gosnappi.BgpV4Peer, gosnappi.DeviceIpv4, gosnappi.Config) { + t.Helper() + config := gosnappi.NewConfig() + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + iDut1Dev := config.Devices().Add().SetName(atePort1.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + iDut1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + iDut2Dev := config.Devices().Add().SetName(atePort2.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + iDut2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + iDut2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + iDut1Bgp := iDut1Dev.Bgp().SetRouterId(iDut1Ipv4.Address()) + iDut2Bgp := iDut2Dev.Bgp().SetRouterId(iDut2Ipv4.Address()) + + iDut1Bgp4Peer := iDut1Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut1Ipv4.Name()).Peers().Add().SetName(otgPort1V4Peer) + iDut1Bgp4Peer.SetPeerAddress(iDut1Ipv4.Gateway()).SetAsNumber(ateAS1).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + iDut2Bgp4Peer := iDut2Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut2Ipv4.Name()).Peers().Add().SetName(otgPort2V4Peer) + iDut2Bgp4Peer.SetPeerAddress(iDut2Ipv4.Gateway()).SetAsNumber(ateAS2).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut2Bgp4Peer.Capability().SetIpv4UnicastAddPath(true).SetIpv6UnicastAddPath(true) + iDut2Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true).SetUnicastIpv6Prefix(true) + + t.Logf("Pushing config to OTG and starting protocols...") + otg.PushConfig(t, config) + time.Sleep(30 * time.Second) + otg.StartProtocols(t) + time.Sleep(30 * time.Second) + + return iDut1Bgp4Peer, iDut1Ipv4, config +} + +// 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) +} + +// Verify BGP capabilities like route refresh as32 and mpbgp. +func verifyBGPCapabilities(t *testing.T, dut *ondatra.DUTDevice, nbrs []*bgpNeighbor) { + t.Log("Verifying BGP capabilities") + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + for _, nbr := range nbrs { + nbrPath := statePath.Neighbor(nbr.neighborip) + + capabilities := map[oc.E_BgpTypes_BGP_CAPABILITY]bool{ + oc.BgpTypes_BGP_CAPABILITY_ROUTE_REFRESH: false, + oc.BgpTypes_BGP_CAPABILITY_ASN32: false, + oc.BgpTypes_BGP_CAPABILITY_MPBGP: false, + } + for _, cap := range gnmi.Get(t, dut, nbrPath.SupportedCapabilities().State()) { + capabilities[cap] = true + } + for cap, present := range capabilities { + if !present { + t.Errorf("Capability not reported: %v", cap) + } + } + } +} + +func advBGPRouteFromOTG(t *testing.T, args *otgTestArgs, asSeg []uint32) { + + args.otgBgpPeer.V4Routes().Clear() + + bgpNeti1Bgp4PeerRoutes := args.otgBgpPeer.V4Routes().Add().SetName(atePort1.Name + ".BGP4.Route") + bgpNeti1Bgp4PeerRoutes.SetNextHopIpv4Address(args.otgIPv4Device.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + bgpNeti1Bgp4PeerRoutes.Addresses().Add(). + SetAddress(advertisedRoutesv4Net). + SetPrefix(uint32(advertisedRoutesv4Prefix)). + SetCount(1) + + bgpNeti1AsPath := bgpNeti1Bgp4PeerRoutes.AsPath().SetAsSetMode(gosnappi.BgpAsPathAsSetMode.INCLUDE_AS_SET) + bgpNeti1AsPath.Segments().Add().SetAsNumbers(asSeg).SetType(gosnappi.BgpAsPathSegmentType.AS_SEQ) + + t.Logf("Pushing config to OTG and starting protocols...") + args.otg.PushConfig(t, args.otgConfig) + time.Sleep(30 * time.Second) + args.otg.StartProtocols(t) + time.Sleep(30 * time.Second) +} + +// verifyPrefixesTelemetry confirms that the dut shows the correct numbers of installed, +// sent and received IPv4 prefixes. +func verifyPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr string, wantInstalled, wantSent uint32) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + prefixesv4 := statePath.Neighbor(nbr).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes() + if gotInstalled := gnmi.Get(t, dut, prefixesv4.Installed().State()); gotInstalled != wantInstalled { + t.Errorf("Installed prefixes mismatch: got %v, want %v", gotInstalled, wantInstalled) + } + if gotSent := gnmi.Get(t, dut, prefixesv4.Sent().State()); gotSent != wantSent { + t.Errorf("Sent prefixes mismatch: got %v, want %v", gotSent, wantSent) + } +} + +// configreRoutePolicy adds route-policy config. +func configureRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, name string, pr oc.E_RoutingPolicy_PolicyResultType) { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + 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) +} + +// verifyOTGPrefixTelemetry is to Validate prefix received on OTG por2. +func verifyOTGPrefixTelemetry(t *testing.T, otg *otg.OTG, wantPrefix bool) { + t.Helper() + _, ok := gnmi.WatchAll(t, otg, gnmi.OTG().BgpPeer(atePort2.Name+".BGP4.peer").UnicastIpv4PrefixAny().State(), + time.Minute, func(v *ygnmi.Value[*otgtelemetry.BgpPeer_UnicastIpv4Prefix]) bool { + return v.IsPresent() + }).Await(t) + + if ok { + bgpPrefixes := gnmi.GetAll(t, otg, gnmi.OTG().BgpPeer(atePort2.Name+".BGP4.peer").UnicastIpv4PrefixAny().State()) + for _, prefix := range bgpPrefixes { + if prefix.GetAddress() == advertisedRoutesv4Net { + if wantPrefix { + gotASPath := prefix.AsPath[len(prefix.AsPath)-1].GetAsNumbers() + t.Logf("Received prefix %v on otg as expected with AS-PATH %v", prefix.GetAddress(), gotASPath) + } else { + t.Errorf("Prefix %v is not received on otg", prefix.GetAddress()) + } + } + } + } +} + +func testSplitHorizonNoAllowOwnIn(t *testing.T, args *otgTestArgs) { + t.Log("Baseline Test No allow-own-in") + + t.Log("Advertise a prefix from the ATE with an AS-path that includes AS dutLocalAS1 (DUT's AS) in the middle (e.g., AS-path: 65500 dutLocalAS1 65499") + advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + verifyBGPCapabilities(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the ATE Port2 doesn't receive the route. due to the presence of its own AS in the path.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 0, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 0) + verifyOTGPrefixTelemetry(t, args.otg, false) +} + +func testSplitHorizonAllowOwnAs1(t *testing.T, args *otgTestArgs) { + t.Log("Test allow-own-as 1, Enable allow-own-as 1 on the DUT.") + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + if deviations.BgpAllowownasDiffDefaultValue(args.dut) { + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 2) + } else { + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 1) + } + + t.Log("Re-advertise the prefix from the ATE with the same AS-path.") + advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + verifyBGPCapabilities(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + +} + +func testSplitHorizonAllowOwnAs3(t *testing.T, args *otgTestArgs) { + t.Log("Test allow-own-as 3, Enable allow-own-as 3 on the DUT.") + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + if deviations.BgpAllowownasDiffDefaultValue(args.dut) { + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 4) + } else { + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 3) + } + + t.Run("Re-advertise the prefix from the ATE with 1 Occurrence: 65500 dutLocalAS1 65499", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + verifyBGPCapabilities(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 3 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 4 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499 (Should be rejected)", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + verifyBGPCapabilities(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 0, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 0) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, false) + }) +} + +func testSplitHorizonAllowOwnAs4(t *testing.T, args *otgTestArgs) { + t.Log("Test allow-own-as 4, Enable allow-own-as 4 on the DUT.") + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + if deviations.BgpAllowownasDiffDefaultValue(args.dut) { + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 5) + } else { + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 4) + } + + t.Run("Re-advertise the prefix from the ATE with 1 Occurrence: 65500, dutLocalAS1, 65499", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + verifyBGPCapabilities(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 3 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) + + t.Run("Re-advertise the prefix from the ATE with 4 Occurrences: dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499 (Should be accepted)", func(t *testing.T) { + advBGPRouteFromOTG(t, args, []uint32{dutLocalAS1, dutLocalAS1, dutLocalAS1, dutLocalAS1, 65499}) + + t.Log("Validate session state and capabilities received on DUT using telemetry.") + verifyBgpTelemetry(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + verifyBGPCapabilities(t, args.dut, []*bgpNeighbor{nbr1, nbr2}) + + t.Log("Verify that the DUT accepts the route.") + verifyPrefixesTelemetry(t, args.dut, nbr1.neighborip, 1, 0) + verifyPrefixesTelemetry(t, args.dut, nbr2.neighborip, 0, 1) + + t.Log("Verify that the ATE Port2 receives the route.") + verifyOTGPrefixTelemetry(t, args.otg, true) + }) +} + +type bgpNeighbor struct { + localAS uint32 + peerAS uint32 + neighborip string + isV4 bool + peerGrp string +} + +type otgTestArgs struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + otgBgpPeer gosnappi.BgpV4Peer + otgIPv4Device gosnappi.DeviceIpv4 + otgConfig gosnappi.Config + otg *otg.OTG +} + +// TestBGPOverrideASPathSplitHorizon validates BGP Override AS-path split-horizon. +func TestBGPOverrideASPathSplitHorizon(t *testing.T) { + t.Logf("Start DUT config load.") + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + t.Run("Configure DUT interfaces", func(t *testing.T) { + configureDUT(t, dut) + }) + + t.Run("Configure DEFAULT network instance", func(t *testing.T) { + 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) { + configureRoutePolicy(t, dut, policyName, oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + bgpClearConfig(t, dut) + dutConf := bgpCreateNbr(t, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + }) + + otg := ate.OTG() + var otgConfig gosnappi.Config + var otgBgpPeer gosnappi.BgpV4Peer + var otgIPv4Device gosnappi.DeviceIpv4 + otgBgpPeer, otgIPv4Device, otgConfig = configureOTG(t, otg) + + args := &otgTestArgs{ + dut: dut, + ate: ate, + otgBgpPeer: otgBgpPeer, + otgIPv4Device: otgIPv4Device, + otgConfig: otgConfig, + otg: otg, + } + + t.Run("Verify port status on DUT", func(t *testing.T) { + verifyPortsUp(t, dut.Device) + }) + + t.Run("Verify BGP telemetry", func(t *testing.T) { + verifyBgpTelemetry(t, dut, []*bgpNeighbor{nbr1, nbr2}) + verifyBGPCapabilities(t, dut, []*bgpNeighbor{nbr1, nbr2}) + }) + + cases := []struct { + desc string + funcName func() + skipMsg string + }{{ + desc: " Baseline Test No allow-own-in", + funcName: func() { testSplitHorizonNoAllowOwnIn(t, args) }, + }, { + desc: " Test allow-own-as 1", + funcName: func() { testSplitHorizonAllowOwnAs1(t, args) }, + }, { + desc: " Test allow-own-as 3", + funcName: func() { testSplitHorizonAllowOwnAs3(t, args) }, + }, { + desc: " Test allow-own-as 4", + funcName: func() { testSplitHorizonAllowOwnAs4(t, args) }, + }} + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + tc.funcName() + }) + } +} diff --git a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto new file mode 100644 index 00000000000..b4b418d51e2 --- /dev/null +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto @@ -0,0 +1,37 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "97f0e45a-2970-4227-9409-3003e7c7cdd7" +plan_id: "RT-1.54" +description: "BGP Override AS-path split-horizon" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + route_policy_under_afi_unsupported: true + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + bgp_set_med_requires_equal_ospf_set_metric: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_allowownas_diff_default_value: true + } +} +tags: TAGS_AGGREGATION diff --git a/internal/deviations/deviations.go b/internal/deviations/deviations.go index 16757a9ef72..2d07cee768e 100644 --- a/internal/deviations/deviations.go +++ b/internal/deviations/deviations.go @@ -1171,3 +1171,8 @@ func EthChannelIngressParametersUnsupported(dut *ondatra.DUTDevice) bool { func EthChannelAssignmentCiscoNumbering(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetEthChannelAssignmentCiscoNumbering() } + +// Device have different default value for allow own as. +func BgpAllowownasDiffDefaultValue(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpAllowownasDiffDefaultValue() +} diff --git a/proto/metadata.proto b/proto/metadata.proto index bc6bd7fb913..8396b4e1f08 100644 --- a/proto/metadata.proto +++ b/proto/metadata.proto @@ -623,6 +623,10 @@ message Metadata { bool eth_channel_assignment_cisco_numbering = 223; // Devices needs time to update interface counters. bool interface_counters_update_delayed = 224; + // Device have different default value for allow own as. + // Juniper : b/373559004 + bool bgp_allowownas_diff_default_value = 225; + // Reserved field numbers and identifiers. reserved 84, 9, 28, 20, 90, 97, 55, 89, 19, 36, 35, 40, 173; } diff --git a/proto/metadata_go_proto/metadata.pb.go b/proto/metadata_go_proto/metadata.pb.go index 139b2058c16..13bf2c082ef 100644 --- a/proto/metadata_go_proto/metadata.pb.go +++ b/proto/metadata_go_proto/metadata.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.6.1 // source: metadata.proto package metadata_go_proto @@ -905,6 +905,9 @@ type Metadata_Deviations struct { EthChannelAssignmentCiscoNumbering bool `protobuf:"varint,223,opt,name=eth_channel_assignment_cisco_numbering,json=ethChannelAssignmentCiscoNumbering,proto3" json:"eth_channel_assignment_cisco_numbering,omitempty"` // Devices needs time to update interface counters. InterfaceCountersUpdateDelayed bool `protobuf:"varint,224,opt,name=interface_counters_update_delayed,json=interfaceCountersUpdateDelayed,proto3" json:"interface_counters_update_delayed,omitempty"` + // Device have different default value for allow own as. + // Juniper : b/373559004 + BgpAllowownasDiffDefaultValue bool `protobuf:"varint,225,opt,name=bgp_allowownas_diff_default_value,json=bgpAllowownasDiffDefaultValue,proto3" json:"bgp_allowownas_diff_default_value,omitempty"` } func (x *Metadata_Deviations) Reset() { @@ -2360,6 +2363,13 @@ func (x *Metadata_Deviations) GetInterfaceCountersUpdateDelayed() bool { return false } +func (x *Metadata_Deviations) GetBgpAllowownasDiffDefaultValue() bool { + if x != nil { + return x.BgpAllowownasDiffDefaultValue + } + return false +} + type Metadata_PlatformExceptions struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2423,7 +2433,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, 0xb2, 0x7e, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x7e, 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, @@ -2457,7 +2467,7 @@ var file_metadata_proto_rawDesc = []byte{ 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, 0x85, 0x76, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x1a, 0xd0, 0x76, 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, @@ -3396,46 +3406,50 @@ var file_metadata_proto_rawDesc = []byte{ 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0xe0, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 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, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x49, 0x0a, + 0x21, 0x62, 0x67, 0x70, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x77, 0x6e, 0x61, 0x73, 0x5f, + 0x64, 0x69, 0x66, 0x66, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0xe1, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x62, 0x67, 0x70, 0x41, 0x6c, + 0x6c, 0x6f, 0x77, 0x6f, 0x77, 0x6e, 0x61, 0x73, 0x44, 0x69, 0x66, 0x66, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 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, 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, + 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 (