From bab0ac041391b659541ffdfdcd606cf3f3cb5b13 Mon Sep 17 00:00:00 2001 From: Rob Shakir Date: Sat, 4 Nov 2023 18:41:19 -0700 Subject: [PATCH] Add fluent support for IPv6 entries. * (M) fluent/fluent.go - Add IPv6 entry support within the gRIBIgo fluent library. * (M) fluent/fluent_test.go - Add testing for IPv6 builder, and additional coverage for MPLS. --- fluent/fluent.go | 91 +++++++++++++++++++++++++++++++++++++++++++ fluent/fluent_test.go | 37 ++++++++++++++++-- 2 files changed, 125 insertions(+), 3 deletions(-) diff --git a/fluent/fluent.go b/fluent/fluent.go index aaac71f6..6b0ef318 100644 --- a/fluent/fluent.go +++ b/fluent/fluent.go @@ -311,6 +311,8 @@ const ( NextHopGroup // NextHop references the NextHop AFT. NextHop + // IPv6 references the IPv6Entry AFT. + IPv6 ) // aftMap provides mapping between the AFT enumerated type within the fluent @@ -320,6 +322,7 @@ var aftMap = map[AFT]spb.AFTType{ IPv4: spb.AFTType_IPV4, NextHopGroup: spb.AFTType_NEXTHOP_GROUP, NextHop: spb.AFTType_NEXTHOP, + IPv6: spb.AFTType_IPV6, } // WithAFT specifies the AFT for which the Get request is made. The AllAFTs @@ -603,6 +606,94 @@ func (i *ipv4Entry) EntryProto() (*spb.AFTEntry, error) { }, nil } +// ipv6Entry is the internal representation of a gRIBI IPv6Entry. +type ipv6Entry struct { + // pb is the gRIBI IPv4Entry that is being composed. + pb *aftpb.Afts_Ipv6EntryKey + // ni is the network instance to which the IPv4Entry is applied. + ni string + // electionID is an explicit election ID to be used for an + // operation using the entry. + electionID *spb.Uint128 +} + +// IPv6Entry returns a new gRIBI IPv6Entry builder. +func IPv6Entry() *ipv6Entry { + return &ipv6Entry{ + pb: &aftpb.Afts_Ipv6EntryKey{ + Ipv6Entry: &aftpb.Afts_Ipv6Entry{}, + }, + } +} + +// WithPrefix sets the prefix of the IPv6Entry to the specified value, which +// must be a valid IPv6 prefix in the form prefix/mask. +func (i *ipv6Entry) WithPrefix(p string) *ipv6Entry { + i.pb.Prefix = p + return i +} + +// WithNetworkInstance specifies the network instance to which the IPv6Entry +// is being applied. +func (i *ipv6Entry) WithNetworkInstance(n string) *ipv6Entry { + i.ni = n + return i +} + +// WithNextHopGroup specifies the next-hop group that the IPv6Entry points to. +func (i *ipv6Entry) WithNextHopGroup(u uint64) *ipv6Entry { + i.pb.Ipv6Entry.NextHopGroup = &wpb.UintValue{Value: u} + return i +} + +// WithNextHopGroupNetworkInstance specifies the network-instance within which +// the next-hop-group for the IPv6 entry should be resolved. +func (i *ipv6Entry) WithNextHopGroupNetworkInstance(n string) *ipv6Entry { + i.pb.Ipv6Entry.NextHopGroupNetworkInstance = &wpb.StringValue{Value: n} + return i +} + +// WithMetadata specifies a byte slice that is stored as metadata alongside +// the IPV6 entry on the gRIBI server. +func (i *ipv6Entry) WithMetadata(b []byte) *ipv6Entry { + i.pb.Ipv6Entry.EntryMetadata = &wpb.BytesValue{Value: b} + return i +} + +// WithElectionID specifies an explicit election ID to be used for the Entry. +// The election ID is made up of the concatenation of the low and high uint64 +// values provided. +func (i *ipv6Entry) WithElectionID(low, high uint64) *ipv6Entry { + i.electionID = &spb.Uint128{ + Low: low, + High: high, + } + return i +} + +// OpProto implements the gRIBIEntry interface, returning a gRIBI AFTOperation. ID +// is explicitly not populated such that they can be populated by +// the function (e.g., AddEntry) to which they are an argument. +func (i *ipv6Entry) OpProto() (*spb.AFTOperation, error) { + return &spb.AFTOperation{ + NetworkInstance: i.ni, + Entry: &spb.AFTOperation_Ipv6{ + Ipv6: proto.Clone(i.pb).(*aftpb.Afts_Ipv6EntryKey), + }, + ElectionId: i.electionID, + }, nil +} + +// EntryProto implements the GRIBIEntry interface, building a gRIBI AFTEntry. +func (i *ipv6Entry) EntryProto() (*spb.AFTEntry, error) { + return &spb.AFTEntry{ + NetworkInstance: i.ni, + Entry: &spb.AFTEntry_Ipv6{ + Ipv6: proto.Clone(i.pb).(*aftpb.Afts_Ipv6EntryKey), + }, + }, nil +} + // labelEntry is the internal representation of a MPLS label entry in gRIBI. type labelEntry struct { // ni is the network instance that the MPLS label entry is within. diff --git a/fluent/fluent_test.go b/fluent/fluent_test.go index f0182c75..168afffc 100644 --- a/fluent/fluent_test.go +++ b/fluent/fluent_test.go @@ -385,13 +385,14 @@ func TestEntry(t *testing.T) { }, }, { desc: "mpls entry", - in: LabelEntry().WithNetworkInstance("DEFAULT").WithLabel(42).WithNextHopGroupNetworkInstance("DEFAULT").WithPoppedLabelStack(10, 20), + in: LabelEntry().WithNetworkInstance("DEFAULT").WithLabel(42).WithNextHopGroup(1).WithNextHopGroupNetworkInstance("DEFAULT").WithPoppedLabelStack(10, 20), wantOpProto: &spb.AFTOperation{ NetworkInstance: "DEFAULT", Entry: &spb.AFTOperation_Mpls{ Mpls: &aftpb.Afts_LabelEntryKey{ Label: &aftpb.Afts_LabelEntryKey_LabelUint64{LabelUint64: 42}, LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: 1}, NextHopGroupNetworkInstance: &wpb.StringValue{Value: "DEFAULT"}, PoppedMplsLabelStack: []*aftpb.Afts_LabelEntry_PoppedMplsLabelStackUnion{ {PoppedMplsLabelStackUint64: 10}, @@ -407,6 +408,7 @@ func TestEntry(t *testing.T) { Mpls: &aftpb.Afts_LabelEntryKey{ Label: &aftpb.Afts_LabelEntryKey_LabelUint64{LabelUint64: 42}, LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: 1}, NextHopGroupNetworkInstance: &wpb.StringValue{Value: "DEFAULT"}, PoppedMplsLabelStack: []*aftpb.Afts_LabelEntry_PoppedMplsLabelStackUnion{ {PoppedMplsLabelStackUint64: 10}, @@ -416,6 +418,35 @@ func TestEntry(t *testing.T) { }, }, }, + }, { + desc: "ipv6 entry", + in: IPv6Entry().WithNetworkInstance("DEFAULT").WithPrefix("2001:db8::/42").WithNextHopGroup(1).WithNextHopGroupNetworkInstance("DEFAULT").WithMetadata([]byte{1, 2, 3, 4}), + wantOpProto: &spb.AFTOperation{ + NetworkInstance: "DEFAULT", + Entry: &spb.AFTOperation_Ipv6{ + Ipv6: &aftpb.Afts_Ipv6EntryKey{ + Prefix: "2001:db8::/42", + Ipv6Entry: &aftpb.Afts_Ipv6Entry{ + NextHopGroup: &wpb.UintValue{Value: 1}, + NextHopGroupNetworkInstance: &wpb.StringValue{Value: "DEFAULT"}, + EntryMetadata: &wpb.BytesValue{Value: []byte{1, 2, 3, 4}}, + }, + }, + }, + }, + wantEntryProto: &spb.AFTEntry{ + NetworkInstance: "DEFAULT", + Entry: &spb.AFTEntry_Ipv6{ + Ipv6: &aftpb.Afts_Ipv6EntryKey{ + Prefix: "2001:db8::/42", + Ipv6Entry: &aftpb.Afts_Ipv6Entry{ + NextHopGroup: &wpb.UintValue{Value: 1}, + NextHopGroupNetworkInstance: &wpb.StringValue{Value: "DEFAULT"}, + EntryMetadata: &wpb.BytesValue{Value: []byte{1, 2, 3, 4}}, + }, + }, + }, + }, }} for _, tt := range tests { @@ -425,7 +456,7 @@ func TestEntry(t *testing.T) { t.Fatalf("did not get expected error for op, got: %v, wantErr? %v", err, tt.wantOpErr) } if diff := cmp.Diff(gotop, tt.wantOpProto, protocmp.Transform()); diff != "" { - t.Fatalf("did not get expected proto, diff(-got,+want):\n%s", diff) + t.Fatalf("did not get expected Operation proto, diff(-got,+want):\n%s", diff) } gotent, err := tt.in.EntryProto() @@ -433,7 +464,7 @@ func TestEntry(t *testing.T) { t.Fatalf("did not get expected error for entry, got: %v, wantErr? %v", err, tt.wantEntryErr) } if diff := cmp.Diff(gotent, tt.wantEntryProto, protocmp.Transform()); diff != "" { - t.Fatalf("did not get expexcted proto, diff(-got,+want)\n%s", diff) + t.Fatalf("did not get expected Entry proto, diff(-got,+want)\n%s", diff) } }) }