Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ func (o *OpResult) String() string {
}

if v := o.OperationID; v != 0 {
buf.WriteString(fmt.Sprintf(" AFTOperation { ID: %d, Status: %s }", v, o.ProgrammingResult))
buf.WriteString(fmt.Sprintf(" AFTOperation { ID: %d, Type: %s, Status: %s }", v, o.Details.Type, o.ProgrammingResult))
}

if v := o.SessionParameters; v != nil {
Expand Down Expand Up @@ -613,6 +613,12 @@ func (c *Client) StartSending() {
c.qs.sendq = []*spb.ModifyRequest{}
}

// StopSending toggles the client to stop sending messages to the server, meaning
// that entries that are enqueued will be stored until StartSending is called.
func (c *Client) StopSending() {
c.qs.sending.Store(false)
}

// handleModifyRequest performs any required post-processing after having sent a
// ModifyRequest onto the gRPC channel to the server. Particularly, this ensures
// that pending transactions are enqueued into the pending queue where they have
Expand Down
282 changes: 280 additions & 2 deletions compliance/compliance.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ type Test struct {
Description string
// ShortName is a short description of the test for use in test output.
ShortName string
// Reference is a unique reference to external data (e.g., test plans) used for the test.
Reference string
}

// TestSpec is a description of a test.
Expand Down Expand Up @@ -79,11 +81,13 @@ var (
}, {
In: Test{
Fn: AddIPv4EntryRIBACK,
Reference: "TE-2.1.1.1",
ShortName: "Add IPv4 entry that can be programmed on the server - with RIB ACK",
},
}, {
In: Test{
Fn: AddIPv4EntryFIBACK,
Reference: "TE-2.1.1.2",
ShortName: "Add IPv4 entry that can be programmed on the server - with FIB ACK",
},
}, {
Expand All @@ -101,6 +105,43 @@ var (
Fn: AddIPv4EntryRandom,
ShortName: "Add IPv4 entries that are resolved by NHG and NH, in random order",
},
}, {
In: Test{
Fn: AddIPv4ToMultipleNHsSingleRequest,
ShortName: "Add IPv4 entries that are resolved to a next-hop-group containing multiple next-hops (single ModifyRequest)",
Reference: "TE-2.1.2.1",
},
}, {
In: Test{
Fn: AddIPv4ToMultipleNHsMultipleRequests,
ShortName: "Add IPv4 entries that are resolved to a next-hop-group containing multiple next-hops (multiple ModifyRequests)",
Reference: "TE-2.1.2.2",
},
}, {
In: Test{
Fn: DeleteIPv4Entry,
ShortName: "Delete IPv4 entry within default network instance",
},
}, {
In: Test{
Fn: DeleteReferencedNHGFailure,
ShortName: "Delete NHG entry that is referenced - failure",
},
}, {
In: Test{
Fn: DeleteReferencedNHFailure,
ShortName: "Delete NH entry that is referenced - failure",
},
}, {
In: Test{
Fn: DeleteNextHopGroup,
ShortName: "Delete NHG entry successfully",
},
}, {
In: Test{
Fn: DeleteNextHop,
ShortName: "Delete NH entry successfully",
},
}}
)

Expand Down Expand Up @@ -234,8 +275,6 @@ func addIPv4Internal(c *fluent.GRIBIClient, t testing.TB, wantACK fluent.Program
WithProgrammingResult(wantACK).
AsResult(),
)

// TODO(robjs): add gNMI subscription using generated telemetry library.
}

// addIPv4Random adds an IPv4 Entry, shuffling the order of the entries, and
Expand Down Expand Up @@ -357,3 +396,242 @@ func addNextHopGroupInternal(c *fluent.GRIBIClient, t testing.TB, wantACK fluent
AsResult(),
)
}

// For the following tests, the base topology shown below is assumed.
//
// Topology: ________
// | |
// -----port1----- | DUT |-----port2----
// 192.0.2.0/31 | | 192.0.2.2/31
// | |
// | |----port3-----
// | | 192.0.2.4/31
// |________|
//
// -------------------1.0.0.0/8-------------->
//
// As the dataplane implementation is added, the input configuration
// within the test will cover the configuration of these ports, however,
// at this time the diagram above is illustrative for tracking the tests.

// baseTopologyEntries creates the entries shown in the diagram above using
// separate ModifyRequests for each entry.
func baseTopologyEntries(c *fluent.GRIBIClient, t testing.TB) {
c.Modify().AddEntry(t, fluent.NextHopEntry().WithNetworkInstance(server.DefaultNetworkInstanceName).WithIndex(1).WithIPAddress("192.0.2.3"))
c.Modify().AddEntry(t, fluent.NextHopEntry().WithNetworkInstance(server.DefaultNetworkInstanceName).WithIndex(2).WithIPAddress("192.0.2.5"))
c.Modify().AddEntry(t, fluent.NextHopGroupEntry().WithNetworkInstance(server.DefaultNetworkInstanceName).WithID(1).AddNextHop(1, 1).AddNextHop(2, 1))
c.Modify().AddEntry(t, fluent.IPv4Entry().WithPrefix("1.0.0.0/8").WithNetworkInstance(server.DefaultNetworkInstanceName).WithNextHopGroup(1))
}

// validateBaseEntries checks that the entries in the base topology are correctly
// installed.
func validateBaseTopologyEntries(res []*client.OpResult, t testing.TB) {
// Check for next-hops 1 and 2.
for _, nhopID := range []uint64{1, 2} {
chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopOperation(nhopID).
WithProgrammingResult(fluent.InstalledInFIB).
WithOperationType(constants.Add).
AsResult(),
chk.IgnoreOperationID(),
)
}

// Check for next-hop-group 1.
chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopGroupOperation(1).
WithProgrammingResult(fluent.InstalledInFIB).
WithOperationType(constants.Add).
AsResult(),
chk.IgnoreOperationID(),
)

// Check for 1/8.
chk.HasResult(t, res,
fluent.OperationResult().
WithIPv4Operation("1.0.0.0/8").
WithProgrammingResult(fluent.InstalledInFIB).
WithOperationType(constants.Add).
AsResult(),
chk.IgnoreOperationID(),
)
}

// AddIPv4ToMultipleNHsSingleRequest creates an IPv4 entry which references a NHG containing
// 2 NHs within a single ModifyRequest, validating that they are installed in the FIB.
func AddIPv4ToMultipleNHsSingleRequest(c *fluent.GRIBIClient, t testing.TB) {

ops := []func(){
func() {
c.Modify().AddEntry(t,
fluent.NextHopEntry().WithNetworkInstance(server.DefaultNetworkInstanceName).WithIndex(1).WithIPAddress("192.0.2.3"),
fluent.NextHopEntry().WithNetworkInstance(server.DefaultNetworkInstanceName).WithIndex(2).WithIPAddress("192.0.2.5"),
fluent.NextHopGroupEntry().WithNetworkInstance(server.DefaultNetworkInstanceName).WithID(1).AddNextHop(1, 1).AddNextHop(2, 1),
fluent.IPv4Entry().WithPrefix("1.0.0.0/8").WithNetworkInstance(server.DefaultNetworkInstanceName).WithNextHopGroup(1))
},
}

validateBaseTopologyEntries(doOps(c, t, ops, fluent.InstalledInFIB, false), t)
}

// AddIPv4ToMultipleNHsMultipleRequests creates an IPv4 entry which references a NHG containing
// 2 NHs within multiple ModifyReqests, validating that they are installed in the FIB.
func AddIPv4ToMultipleNHsMultipleRequests(c *fluent.GRIBIClient, t testing.TB) {

ops := []func(){
func() { baseTopologyEntries(c, t) },
}
validateBaseTopologyEntries(doOps(c, t, ops, fluent.InstalledInFIB, false), t)
}

// DeleteIPv4Entry deletes an IPv4 entry from the server's RIB.
func DeleteIPv4Entry(c *fluent.GRIBIClient, t testing.TB) {
ops := []func(){
func() { baseTopologyEntries(c, t) },
func() {
c.Modify().DeleteEntry(t, fluent.IPv4Entry().WithPrefix("1.0.0.0/8").WithNetworkInstance(server.DefaultNetworkInstanceName))
},
}
res := doOps(c, t, ops, fluent.InstalledInFIB, false)
validateBaseTopologyEntries(res, t)

chk.HasResult(t, res,
fluent.OperationResult().
WithIPv4Operation("1.0.0.0/8").
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.InstalledInFIB).
AsResult(),
chk.IgnoreOperationID(),
)
}

// DeleteReferencedNHGFailure attempts to delete a NextHopGroup entry that is referenced
// from the RIB, and expects a failure.
func DeleteReferencedNHGFailure(c *fluent.GRIBIClient, t testing.TB) {
ops := []func(){
func() { baseTopologyEntries(c, t) },
func() {
c.Modify().DeleteEntry(t, fluent.NextHopGroupEntry().WithID(1).WithNetworkInstance(server.DefaultNetworkInstanceName))
},
}
res := doOps(c, t, ops, fluent.InstalledInFIB, false)
validateBaseTopologyEntries(res, t)

chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopGroupOperation(1).
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.ProgrammingFailed).
AsResult(),
chk.IgnoreOperationID())
}

// DeleteReferencedNHFailure attempts to delete a NH entry that is referened from the RIB
// and expects a failure.
func DeleteReferencedNHFailure(c *fluent.GRIBIClient, t testing.TB) {
ops := []func(){
func() { baseTopologyEntries(c, t) },
func() {
c.Modify().DeleteEntry(t, fluent.NextHopEntry().WithIndex(1).WithNetworkInstance(server.DefaultNetworkInstanceName))
},
func() {
c.Modify().DeleteEntry(t, fluent.NextHopEntry().WithIndex(2).WithNetworkInstance(server.DefaultNetworkInstanceName))
},
}
res := doOps(c, t, ops, fluent.InstalledInFIB, false)
validateBaseTopologyEntries(res, t)

for _, i := range []uint64{1, 2} {
chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopOperation(i).
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.ProgrammingFailed).
AsResult(),
chk.IgnoreOperationID())
}
}

// DeleteNextHopGroup attempts to delete a NHG entry that is not referenced and expects
// success.
func DeleteNextHopGroup(c *fluent.GRIBIClient, t testing.TB) {
ops := []func(){
func() { baseTopologyEntries(c, t) },
func() {
c.Modify().DeleteEntry(t,
fluent.IPv4Entry().WithPrefix("1.0.0.0/8").WithNetworkInstance(server.DefaultNetworkInstanceName),
fluent.NextHopGroupEntry().WithID(1).WithNetworkInstance(server.DefaultNetworkInstanceName),
)
},
}

res := doOps(c, t, ops, fluent.InstalledInFIB, false)
validateBaseTopologyEntries(res, t)

chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopGroupOperation(1).
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.InstalledInFIB).
AsResult(),
chk.IgnoreOperationID())

chk.HasResult(t, res,
fluent.OperationResult().
WithIPv4Operation("1.0.0.0/8").
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.InstalledInFIB).
AsResult(),
chk.IgnoreOperationID())
}

// DeleteNextHop attempts to delete the NH entris within the base topology and expects
// success.
//
// TODO(robjs): When traffic and AFT validation is added, ensure that a partial delete
// scenario keeps traffic routed via the remaining NH.
func DeleteNextHop(c *fluent.GRIBIClient, t testing.TB) {
ops := []func(){
func() { baseTopologyEntries(c, t) },
func() {
c.Modify().DeleteEntry(t,
fluent.IPv4Entry().WithPrefix("1.0.0.0/8").WithNetworkInstance(server.DefaultNetworkInstanceName),
fluent.NextHopGroupEntry().WithID(1).WithNetworkInstance(server.DefaultNetworkInstanceName),
fluent.NextHopEntry().WithIndex(1).WithNetworkInstance(server.DefaultNetworkInstanceName),
fluent.NextHopEntry().WithIndex(2).WithNetworkInstance(server.DefaultNetworkInstanceName),
)
},
}

res := doOps(c, t, ops, fluent.InstalledInFIB, false)
validateBaseTopologyEntries(res, t)

for _, i := range []uint64{1, 2} {
chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopOperation(i).
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.InstalledInFIB).
AsResult(),
chk.IgnoreOperationID())

}

chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopGroupOperation(1).
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.InstalledInFIB).
AsResult(),
chk.IgnoreOperationID())

chk.HasResult(t, res,
fluent.OperationResult().
WithIPv4Operation("1.0.0.0/8").
WithOperationType(constants.Delete).
WithProgrammingResult(fluent.InstalledInFIB).
AsResult(),
chk.IgnoreOperationID())
}
12 changes: 11 additions & 1 deletion fluent/fluent.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,12 @@ func (g *GRIBIClient) Start(ctx context.Context, t testing.TB) {
g.ctx = ctx
}

// Stop specifies that the gRIBI client should stop sending operations,
// and subsequently disconnect from the server.
func (g *GRIBIClient) Stop(t testing.TB) {
g.c.StopSending()
if err := g.c.Close(); err != nil {
t.Fatalf("cannot disconnect from client, %v", err)
t.Fatalf("cannot disconnect from server, %v", err)
}
}

Expand Down Expand Up @@ -445,6 +448,13 @@ func (i *ipv4Entry) WithNextHopGroup(u uint64) *ipv4Entry {
return i
}

// WithNextHopGroupNetworkInstance specifies the network-instance within which
// the next-hop-group for the IPv4 entry should be resolved.
func (i *ipv4Entry) WithNextHopGroupNetworkInstance(n string) *ipv4Entry {
i.pb.Ipv4Entry.NextHopGroupNetworkInstance = &wpb.StringValue{Value: n}
return i
}

// opproto implements the gRIBIEntry interface, returning a gRIBI AFTOperation. ID
// and ElectionID are explicitly not populated such that they can be populated by
// the function (e.g., AddEntry) to which they are an argument.
Expand Down
13 changes: 12 additions & 1 deletion gnmit/gnmit.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ func periodic(period time.Duration, fn func()) {
}
}

// GNMIServer implements the gNMI server interface.
type GNMIServer struct {
// The subscribe Server implements only Subscribe for gNMI.
*subscribe.Server
}

// New returns a new collector that listens on the specified addr (in the form host:port),
// supporting a single downstream target named hostname. sendMeta controls whether the
// metadata *other* than meta/sync and meta/connected is sent by the collector.
Expand Down Expand Up @@ -91,7 +97,12 @@ func New(ctx context.Context, addr string, hostname string, sendMeta bool, opts
if err != nil {
return nil, "", fmt.Errorf("could not instantiate gNMI server: %v", err)
}
gpb.RegisterGNMIServer(srv, subscribeSrv)

gnmiserver := &GNMIServer{
Server: subscribeSrv, // use the 'subscribe' implementation.
}

gpb.RegisterGNMIServer(srv, gnmiserver)
// Forward streaming updates to clients.
c.cache.SetClient(subscribeSrv.Update)
// Register listening port and start serving.
Expand Down
Loading