diff --git a/client/gribiclient.go b/client/gribiclient.go index 33d67494..89332d36 100644 --- a/client/gribiclient.go +++ b/client/gribiclient.go @@ -702,6 +702,8 @@ type OpDetailsResults struct { NextHopGroupID uint64 // IPv4Prefix is the IPv4 prefix modified by the operation. IPv4Prefix string + // IPv6Prefix is the IPv6 prefix modified by the operation. + IPv6Prefix string // MPLSLabel is the MPLS label that was modified by the operation. MPLSLabel uint64 } @@ -720,6 +722,8 @@ func (o *OpDetailsResults) String() string { buf.WriteString(fmt.Sprintf("NHG ID: %d", o.NextHopGroupID)) case o.IPv4Prefix != "": buf.WriteString(fmt.Sprintf("IPv4: %s", o.IPv4Prefix)) + case o.IPv6Prefix != "": + buf.WriteString(fmt.Sprintf("IPv6: %s", o.IPv6Prefix)) case o.MPLSLabel != 0: buf.WriteString(fmt.Sprintf("MPLS: %d", o.MPLSLabel)) } @@ -949,6 +953,8 @@ func (c *Client) clearPendingOp(op *spb.AFTResult) (*OpResult, error) { switch opEntry := v.Op.Entry.(type) { case *spb.AFTOperation_Ipv4: det.IPv4Prefix = opEntry.Ipv4.GetPrefix() + case *spb.AFTOperation_Ipv6: + det.IPv6Prefix = opEntry.Ipv6.GetPrefix() case *spb.AFTOperation_Mpls: det.MPLSLabel = opEntry.Mpls.GetLabelUint64() case *spb.AFTOperation_NextHopGroup: diff --git a/client/gribiclient_test.go b/client/gribiclient_test.go index bf487566..80e0b735 100644 --- a/client/gribiclient_test.go +++ b/client/gribiclient_test.go @@ -1038,6 +1038,66 @@ func TestGet(t *testing.T) { }, }}, }, + }, { + desc: "single IPv6 entry in Server - specific NI", + inOperations: []*spb.AFTOperation{{ + NetworkInstance: server.DefaultNetworkInstanceName, + Entry: &spb.AFTOperation_Ipv6{ + Ipv6: &aftpb.Afts_Ipv6EntryKey{ + Prefix: "2001:db8::/32", + Ipv6Entry: &aftpb.Afts_Ipv6Entry{}, + }, + }, + }}, + inGetRequest: &spb.GetRequest{ + NetworkInstance: &spb.GetRequest_Name{ + Name: server.DefaultNetworkInstanceName, + }, + Aft: spb.AFTType_ALL, + }, + wantResponse: &spb.GetResponse{ + Entry: []*spb.AFTEntry{{ + NetworkInstance: server.DefaultNetworkInstanceName, + Entry: &spb.AFTEntry_Ipv6{ + Ipv6: &aftpb.Afts_Ipv6EntryKey{ + Prefix: "2001:db8::/32", + Ipv6Entry: &aftpb.Afts_Ipv6Entry{}, + }, + }, + }}, + }, + }, { + desc: "single MPLS entry in Server - specific NI", + inOperations: []*spb.AFTOperation{{ + NetworkInstance: server.DefaultNetworkInstanceName, + Entry: &spb.AFTOperation_Mpls{ + Mpls: &aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: 42, + }, + LabelEntry: &aftpb.Afts_LabelEntry{}, + }, + }, + }}, + inGetRequest: &spb.GetRequest{ + NetworkInstance: &spb.GetRequest_Name{ + Name: server.DefaultNetworkInstanceName, + }, + Aft: spb.AFTType_ALL, + }, + wantResponse: &spb.GetResponse{ + Entry: []*spb.AFTEntry{{ + NetworkInstance: server.DefaultNetworkInstanceName, + Entry: &spb.AFTEntry_Mpls{ + Mpls: &aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: 42, + }, + LabelEntry: &aftpb.Afts_LabelEntry{}, + }, + }, + }}, + }, }, { desc: "multiple entries - single NI", inOperations: []*spb.AFTOperation{{ diff --git a/compliance/compliance.go b/compliance/compliance.go index fc6ff0ba..887365f4 100644 --- a/compliance/compliance.go +++ b/compliance/compliance.go @@ -111,6 +111,8 @@ type Test struct { RequiresNonDefaultNINHG bool // RequiresMPLS marks a test that requires MPLS support in the gRIBI server. RequiresMPLS bool + // RequiresIPv6 marks a test that requires IPv6 support in the gRIBI server. + RequiresIPv6 bool } // TestSpec is a description of a test. @@ -503,6 +505,25 @@ var ( ShortName: "MPLS add entry with NH label stack", RequiresMPLS: true, }, + }, { + In: Test{ + Fn: makeTestWithACK(AddIPv6Entry, fluent.InstalledInRIB), + ShortName: "Add IPv6 entry that can be programmed on the server - with RIB ACK", + RequiresIPv6: true, + }, + }, { + In: Test{ + Fn: makeTestWithACK(AddIPv6Entry, fluent.InstalledInFIB), + ShortName: "Add IPv6 entry that can be programmed on the server - with FIB ACK", + RequiresFIBACK: true, + RequiresIPv6: true, + }, + }, { + In: Test{ + Fn: AddIPv6Metadata, + ShortName: "Add IPv6 entry with metadata", + RequiresIPv6: true, + }, }} ) @@ -1784,3 +1805,89 @@ func AddDeleteAdd(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t tes AsResult(), chk.IgnoreOperationID()) } + +// AddIPv6Entry adds a fully referenced IPv4Entry and checks whether the specified ACK +// type (wantACK) is returned. +func AddIPv6Entry(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) { + defer flushServer(c, t) + + ops := []func(){ + func() { + c.Modify().AddEntry(t, fluent.NextHopEntry().WithNetworkInstance(defaultNetworkInstanceName).WithIndex(1).WithIPAddress("192.0.2.1")) + }, + func() { + c.Modify().AddEntry(t, fluent.NextHopGroupEntry().WithNetworkInstance(defaultNetworkInstanceName).WithID(42).AddNextHop(1, 1)) + }, + func() { + c.Modify().AddEntry(t, fluent.IPv6Entry().WithPrefix("2001:db8::/32").WithNetworkInstance(defaultNetworkInstanceName).WithNextHopGroup(42)) + }, + } + + res := DoModifyOps(c, t, ops, wantACK, false) + + // Check the three entries in order. + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationID(1). + WithProgrammingResult(wantACK). + AsResult(), + ) + + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationID(2). + WithProgrammingResult(wantACK). + AsResult(), + ) + + chk.HasResult(t, res, + fluent.OperationResult(). + WithOperationID(3). + WithProgrammingResult(wantACK). + AsResult(), + ) +} + +// AddIPv6Metadata adds an IPv6 Entry (and its dependencies) with metadata alongside the +// entry. +func AddIPv6Metadata(c *fluent.GRIBIClient, t testing.TB, _ ...TestOpt) { + defer flushServer(c, t) + ops := []func(){ + func() { + c.Modify().AddEntry(t, fluent.NextHopEntry().WithIndex(1).WithNetworkInstance(defaultNetworkInstanceName).WithIPAddress("192.0.2.3")) + c.Modify().AddEntry(t, fluent.NextHopGroupEntry().WithID(1).WithNetworkInstance(defaultNetworkInstanceName).AddNextHop(1, 1)) + c.Modify().AddEntry(t, fluent.IPv6Entry(). + WithPrefix("2001:db8::1/128"). + WithNetworkInstance(defaultNetworkInstanceName). + WithNextHopGroup(1). + WithMetadata([]byte{1, 2, 3, 4, 5, 6, 7, 8}), + ) + }, + } + + res := DoModifyOps(c, t, ops, fluent.InstalledInRIB, false) + + chk.HasResult(t, res, + fluent.OperationResult(). + WithIPv6Operation("2001:db8::1/128"). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInRIB). + AsResult(), + chk.IgnoreOperationID()) + + chk.HasResult(t, res, + fluent.OperationResult(). + WithNextHopGroupOperation(1). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInRIB). + AsResult(), + chk.IgnoreOperationID()) + + chk.HasResult(t, res, + fluent.OperationResult(). + WithNextHopOperation(1). + WithOperationType(constants.Add). + WithProgrammingResult(fluent.InstalledInRIB). + AsResult(), + chk.IgnoreOperationID()) +} diff --git a/fluent/fluent.go b/fluent/fluent.go index 6b0ef318..2da31b84 100644 --- a/fluent/fluent.go +++ b/fluent/fluent.go @@ -1158,6 +1158,17 @@ func (o *opResult) WithIPv4Operation(p string) *opResult { return o } +// WithIPv6Operation indicates that the result corresponds to +// an operation impacting the IPv6 prefix p which is of the form +// prefix/mask. +func (o *opResult) WithIPv6Operation(p string) *opResult { + if o.r.Details == nil { + o.r.Details = &client.OpDetailsResults{} + } + o.r.Details.IPv6Prefix = p + return o +} + // WithNextHopGroupOperation indicates that the result correspodns to // an operation impacting the next-hop-group with index i. func (o *opResult) WithNextHopGroupOperation(i uint64) *opResult {