Skip to content

Commit

Permalink
Merge pull request #251 from openconfig/ipv4-prefix-set-integration
Browse files Browse the repository at this point in the history
Add IPv4 prefix set integration test
  • Loading branch information
wenovus authored Sep 6, 2023
2 parents 2a0ac3b + d307437 commit bcd2dc3
Show file tree
Hide file tree
Showing 9 changed files with 902 additions and 15 deletions.
25 changes: 17 additions & 8 deletions gnmi/gnmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func newServer(ctx context.Context, targetName string, enableSet bool, recs ...r
if err := ygot.PruneConfigFalse(configSchema.RootSchema(), configSchema.Root); err != nil {
return nil, fmt.Errorf("gnmi: %v", err)
}
if err := updateCache(c.cache, configSchema.Root, emptySchema.Root, targetName, OpenConfigOrigin, true, time.Now().UnixNano(), "", nil); err != nil {
if err := updateCache(c.cache, configSchema.Root, emptySchema.Root, targetName, OpenConfigOrigin, true, 0, "", nil); err != nil {
return nil, fmt.Errorf("gnmi newServer: %v", err)
}
}
Expand All @@ -140,7 +140,7 @@ func newServer(ctx context.Context, targetName string, enableSet bool, recs ...r
if err := setupSchema(stateSchema, false); err != nil {
return nil, err
}
if err := updateCache(c.cache, stateSchema.Root, emptySchema.Root, targetName, OpenConfigOrigin, true, time.Now().UnixNano(), "", nil); err != nil {
if err := updateCache(c.cache, stateSchema.Root, emptySchema.Root, targetName, OpenConfigOrigin, true, 0, "", nil); err != nil {
return nil, fmt.Errorf("gnmi newServer: %v", err)
}
}
Expand Down Expand Up @@ -193,21 +193,27 @@ func setupSchema(schema *ytypes.Schema, config bool) error {
func updateCache(cache *cache.Cache, dirtyRoot, root ygot.GoStruct, target, origin string, preferShadowPath bool, timestamp int64, user string, auth PathAuth) error {
var nos []*gpb.Notification
if root == nil {
if timestamp == 0 {
timestamp = time.Now().UnixNano()
}
var err error
if nos, err = ygot.TogNMINotifications(dirtyRoot, timestamp, ygot.GNMINotificationsConfig{
UsePathElem: true,
}); err != nil {
return fmt.Errorf("gnmi: %v", err)
}
} else {
ns, err := ygot.DiffWithAtomic(root, dirtyRoot, &ygot.DiffPathOpt{PreferShadowPath: preferShadowPath})
if err != nil {
var err error
if nos, err = ygot.DiffWithAtomic(root, dirtyRoot, &ygot.DiffPathOpt{PreferShadowPath: preferShadowPath}); err != nil {
return fmt.Errorf("gnmi: error while creating update notification for Set: %v", err)
}
for _, n := range ns {
if timestamp == 0 {
// Set timestamp here in order to minimize latency and reduce change for "update is stale" error.
timestamp = time.Now().UnixNano()
}
for _, n := range nos {
n.Timestamp = timestamp
}
nos = append(nos, ns...)
}

if auth != nil && auth.IsInitialized() {
Expand Down Expand Up @@ -338,7 +344,7 @@ func unmarshalSetRequest(schema *ytypes.Schema, req *gpb.SetRequest, preferShado
// set returns a gRPC error with the correct code and shouldn't be wrapped again.
//
// - timestamp specifies the timestamp of the values that are to be updated in
// the gNMI cache.
// the gNMI cache. If zero, then time.Now().UnixNano() is used.
// - auth adds authorization to before writing vals to the cache, if set to nil, not authorization is checked.
func set(schema *ytypes.Schema, cache *cache.Cache, target string, req *gpb.SetRequest, preferShadowPath bool, validators []func(*oc.Root) error, timestamp int64, user string, auth PathAuth) error {
// skip diffing and deepcopy for performance when handling state update paths.
Expand All @@ -356,6 +362,9 @@ func set(schema *ytypes.Schema, cache *cache.Cache, target string, req *gpb.SetR
Unmarshal: schema.Unmarshal,
}
unmarshalSetRequest(tempSchema, req, preferShadowPath)
if timestamp == 0 {
timestamp = time.Now().UnixNano()
}
notifs, err := ygot.TogNMINotifications(tempSchema.Root, timestamp, ygot.GNMINotificationsConfig{UsePathElem: true})
if err != nil {
return err
Expand Down Expand Up @@ -476,7 +485,7 @@ const (
// is to support cases where the data comes from an externally-timestamped
// source.
func (s *Server) Set(ctx context.Context, req *gpb.SetRequest) (*gpb.SetResponse, error) {
timestamp := time.Now().UnixNano()
var timestamp int64
// Use ConfigMode by default so that external users don't need to set metadata.
gnmiMode := ConfigMode
md, ok := metadata.FromIncomingContext(ctx)
Expand Down
8 changes: 8 additions & 0 deletions integration_tests/dut_policy_tests/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
filegroup(
name = "topology_testbed",
srcs = [
"testbed.pb.txt",
"topology.pb.txt",
],
visibility = ["//visibility:public"],
)
19 changes: 19 additions & 0 deletions integration_tests/dut_policy_tests/prefix_set/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")

go_test(
name = "prefix_set_test",
size = "enormous",
srcs = ["prefix_set_test.go"],
data = ["//integration_tests/dut_policy_tests:topology_testbed"],
tags = [
"exclusive",
],
deps = [
"//bgp/tests/proto/policyval",
"//internal/binding",
"//policytest",
"@com_github_openconfig_ondatra//:ondatra",
"@com_github_openconfig_ondatra//gnmi",
"@com_github_openconfig_ondatra//gnmi/oc",
],
)
165 changes: 165 additions & 0 deletions integration_tests/dut_policy_tests/prefix_set/prefix_set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
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 integration_test

import (
"testing"

"github.com/openconfig/lemming/internal/binding"
"github.com/openconfig/lemming/policytest"
"github.com/openconfig/ondatra"
"github.com/openconfig/ondatra/gnmi"
"github.com/openconfig/ondatra/gnmi/oc"

valpb "github.com/openconfig/lemming/bgp/tests/proto/policyval"
)

func TestMain(m *testing.M) {
ondatra.RunTests(m, binding.Get(".."))
}

func TestPrefixSet(t *testing.T) {
installPolicies := func(t *testing.T, pair12, pair52, pair23 *policytest.DevicePair, invert bool) {
t.Log("Installing test policies")
dut2 := pair12.Second
port1 := pair12.FirstPort

prefix1 := "10.33.0.0/16"
prefix2 := "10.34.0.0/16"

// Policy to reject routes with the given prefix set
policyName := "def1"

// Create prefix set
prefixSetName := "reject-" + prefix1
prefix1Path := policytest.RoutingPolicyPath.DefinedSets().PrefixSet(prefixSetName).Prefix(prefix1, "exact").IpPrefix()
gnmi.Replace(t, dut2, prefix1Path.Config(), prefix1)
prefix2Path := policytest.RoutingPolicyPath.DefinedSets().PrefixSet(prefixSetName).Prefix(prefix2, "16..23").IpPrefix()
gnmi.Replace(t, dut2, prefix2Path.Config(), prefix2)

policy := &oc.RoutingPolicy_PolicyDefinition_Statement_OrderedMap{}
stmt, err := policy.AppendNew("stmt1")
if err != nil {
t.Fatalf("Cannot append new BGP policy statement: %v", err)
}
// Match on prefix set & reject route
stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetPrefixSet(prefixSetName)
if invert {
stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_INVERT)
} else {
stmt.GetOrCreateConditions().GetOrCreateMatchPrefixSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsRestrictedType_ANY)
}
stmt.GetOrCreateActions().SetPolicyResult(oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE)
// Install policy
gnmi.Replace(t, dut2, policytest.RoutingPolicyPath.PolicyDefinition(policyName).Config(), &oc.RoutingPolicy_PolicyDefinition{Statement: policy})
gnmi.Replace(t, dut2, policytest.BGPPath.Neighbor(port1.IPv4).ApplyPolicy().ImportPolicy().Config(), []string{policyName})
}

invertResult := func(result valpb.RouteTestResult, invert bool) valpb.RouteTestResult {
if invert {
switch result {
case valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT:
return valpb.RouteTestResult_ROUTE_TEST_RESULT_DISCARD
case valpb.RouteTestResult_ROUTE_TEST_RESULT_DISCARD:
return valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT
default:
}
}
return result
}

getspec := func(invert bool) *valpb.PolicyTestCase {
spec := &valpb.PolicyTestCase{
Description: "Test that one prefix gets accepted and the other rejected via an ANY prefix-set.",
RouteTests: []*valpb.RouteTestCase{{
Description: "Exact match",
Input: &valpb.TestRoute{
ReachPrefix: "10.33.0.0/16",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_DISCARD, invert),
}, {
Description: "Not exact match",
Input: &valpb.TestRoute{
ReachPrefix: "10.33.0.0/17",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT, invert),
}, {
Description: "No match with any prefix",
Input: &valpb.TestRoute{
ReachPrefix: "10.3.0.0/16",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT, invert),
}, {
Description: "mask length too short",
Input: &valpb.TestRoute{
ReachPrefix: "10.34.0.0/15",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT, invert),
}, {
Description: "Lower end of mask length",
Input: &valpb.TestRoute{
ReachPrefix: "10.34.0.0/16",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_DISCARD, invert),
}, {
Description: "Middle of mask length",
Input: &valpb.TestRoute{
ReachPrefix: "10.34.0.0/20",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_DISCARD, invert),
}, {
Description: "Upper end of mask length",
Input: &valpb.TestRoute{
ReachPrefix: "10.34.0.0/23",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_DISCARD, invert),
}, {
Description: "mask length too long",
Input: &valpb.TestRoute{
ReachPrefix: "10.34.0.0/24",
},
ExpectedResultBeforePolicy: valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT,
ExpectedResult: invertResult(valpb.RouteTestResult_ROUTE_TEST_RESULT_ACCEPT, invert),
}},
}
return spec
}

t.Run("ANY", func(t *testing.T) {
policytest.TestPolicy(t, policytest.TestCase{
Spec: getspec(false),
InstallPolicies: func(t *testing.T, pair12, pair52, pair23 *policytest.DevicePair) {
installPolicies(t, pair12, pair52, pair23, false)
},
})
})
t.Run("INVERT", func(t *testing.T) {
policytest.TestPolicy(t, policytest.TestCase{
Spec: getspec(true),
InstallPolicies: func(t *testing.T, pair12, pair52, pair23 *policytest.DevicePair) {
installPolicies(t, pair12, pair52, pair23, true)
},
})
})
}
106 changes: 106 additions & 0 deletions integration_tests/dut_policy_tests/testbed.pb.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# proto-file: github.com/openconfig/ondatra/blob/main/proto/testbed.proto
# proto-message: ondatra.Testbed

# PolicyTestCase contains the specifications for a DUT-only policy test.
#
# Topology:
#
# DUT1 (AS 64500) -> DUT2 (AS 64500) -> DUT3 (AS 64501)
# ^
# |
# DUT4 (AS 64502) -> DUT5 (AS 64500)
#
# Additionally, DUT0 is present as a neighbour for DUT1, DUT4, and DUT5
# to allow a static route to be resolvable.
#
# Currently by convention, all policies are installed on DUT1 (export), DUT5
# (export), and DUT2 (import). This is because GoBGP only withdraws routes on
# import policy change after a soft reset:
# https://github.com/osrg/gobgp/blob/master/docs/sources/policy.md#policy-and-soft-reset

# This DUT connects to DUT1, DUT4, and DUT5 for resolving static routes.
duts {
id: "dut0"
ports {
id: "port1"
}
ports {
id: "port2"
}
ports {
id: "port3"
}
}

duts {
id: "dut1"
ports {
id: "port0"
}
ports {
id: "port1"
}
}

duts {
id: "dut2"
ports {
id: "port1"
}
ports {
id: "port2"
}
ports {
id: "port3"
}
}

duts {
id: "dut3"
ports {
id: "port1"
}
}

duts {
id: "dut4"
ports {
id: "port0"
}
ports {
id: "port1"
}
}

duts {
id: "dut5"
ports {
id: "port0"
}
ports {
id: "port1"
}
ports {
id: "port2"
}
}

links {
a: "dut1:port1"
b: "dut2:port1"
}

links {
a: "dut2:port2"
b: "dut3:port1"
}

links {
a: "dut4:port1"
b: "dut5:port1"
}

links {
a: "dut5:port2"
b: "dut2:port3"
}
Loading

0 comments on commit bcd2dc3

Please sign in to comment.