From e6f4419f2b1682e766c058d545029807949e8980 Mon Sep 17 00:00:00 2001 From: Rob Shakir Date: Tue, 24 Oct 2023 14:26:27 -0700 Subject: [PATCH] Add support for MPLS reconciliation. (#201) * Add support for MPLS reconciliation. * (M) rib/helpers.go * (M) rib/helpers_test.go - Add fake RIB helper methods for MPLS. * (M) rib/reconciler/reconcile.go * (M) rib/reconciler/reconcile_test.go - Add support for reconciliation of label entries. * Add support for `ALL` address families in explicitReplace. * (M) rib/reconciler/reconcile.go * (M) rib/reconciler/reconcile_test.go - Ensure that the `ALL` `AFTType` is mapped to the correct address families when reconciling. --- rib/helpers.go | 31 +++- rib/helpers_test.go | 27 +++ rib/reconciler/reconcile.go | 62 +++++++ rib/reconciler/reconcile_test.go | 305 +++++++++++++++++++++++++++++++ 4 files changed, 421 insertions(+), 4 deletions(-) diff --git a/rib/helpers.go b/rib/helpers.go index 5768493e..e68ce94e 100644 --- a/rib/helpers.go +++ b/rib/helpers.go @@ -112,17 +112,17 @@ func (f *fakeRIB) InjectIPv4(ni, pfx string, nhg uint64) error { } // InjectNHG adds a next-hop-group entry to network instance ni, with the specified -// ID (nhgId). The next-hop-group contains the next hops specified in the nhs map, +// ID (nhgID). The next-hop-group contains the next hops specified in the nhs map, // with the key of the map being the next-hop ID and the value being the weight within // the group. -func (f *fakeRIB) InjectNHG(ni string, nhgId uint64, nhs map[uint64]uint64) error { +func (f *fakeRIB) InjectNHG(ni string, nhgID uint64, nhs map[uint64]uint64) error { niR, ok := f.r.NetworkInstanceRIB(ni) if !ok { return fmt.Errorf("unknown NI, %s", ni) } nhg := &aftpb.Afts_NextHopGroupKey{ - Id: nhgId, + Id: nhgID, NextHopGroup: &aftpb.Afts_NextHopGroup{}, } for nh, weight := range nhs { @@ -142,7 +142,8 @@ func (f *fakeRIB) InjectNHG(ni string, nhgId uint64, nhs map[uint64]uint64) erro } // InjectNH adds a next-hop entry to network instance ni, with the specified -// index (nhIdx). An error is returned if it cannot be added. +// index (nhIdx), and interface ref to intName. An error is returned if it cannot +// be added. func (f *fakeRIB) InjectNH(ni string, nhIdx uint64, intName string) error { niR, ok := f.r.NetworkInstanceRIB(ni) if !ok { @@ -162,3 +163,25 @@ func (f *fakeRIB) InjectNH(ni string, nhIdx uint64, intName string) error { return nil } + +// InjectMPLS adds an MPLS (Label) entry to network instance ni, with the +// specified next-hop-group. An error is returned if it cannot be added. +func (f *fakeRIB) InjectMPLS(ni string, label, nhg uint64) error { + niR, ok := f.r.NetworkInstanceRIB(ni) + if !ok { + return fmt.Errorf("unknown NI, %s", ni) + } + + if _, _, err := niR.AddMPLS(&aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: label, + }, + LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: nhg}, + }, + }, false); err != nil { + return fmt.Errorf("cannot add MPLS entry, err: %v", err) + } + + return nil +} diff --git a/rib/helpers_test.go b/rib/helpers_test.go index 49398e07..2c9ab32b 100644 --- a/rib/helpers_test.go +++ b/rib/helpers_test.go @@ -316,6 +316,33 @@ func TestFakeRIB(t *testing.T) { }, }, }, + }, { + desc: "mpls only", + inBuild: func() *fakeRIB { + f := NewFake(dn, DisableRIBCheckFn()) + if err := f.InjectMPLS(dn, 42, 1); err != nil { + t.Fatalf("cannot add MPLS, err: %v", err) + } + return f + }, + wantRIB: &RIB{ + defaultName: dn, + niRIB: map[string]*RIBHolder{ + dn: { + name: dn, + r: &aft.RIB{ + Afts: &aft.Afts{ + LabelEntry: map[aft.Afts_LabelEntry_Label_Union]*aft.Afts_LabelEntry{ + aft.UnionUint32(42): { + Label: aft.UnionUint32(42), + NextHopGroup: ygot.Uint64(1), + }, + }, + }, + }, + }, + }, + }, }, { desc: "nhg only", inBuild: func() *fakeRIB { diff --git a/rib/reconciler/reconcile.go b/rib/reconciler/reconcile.go index 78b23ff6..63c7f995 100644 --- a/rib/reconciler/reconcile.go +++ b/rib/reconciler/reconcile.go @@ -165,6 +165,17 @@ func diff(src, dst *rib.RIB, explicitReplace map[spb.AFTType]bool) (*reconcileOp if src == nil || dst == nil { return nil, fmt.Errorf("invalid nil input RIBs, src: %v, dst: %v", src, dst) } + + // Re-map ALL into the supported address families. + if _, ok := explicitReplace[spb.AFTType_ALL]; ok { + explicitReplace = map[spb.AFTType]bool{ + spb.AFTType_IPV4: true, + spb.AFTType_MPLS: true, + spb.AFTType_NEXTHOP: true, + spb.AFTType_NEXTHOP_GROUP: true, + } + } + srcContents, err := src.RIBContents() if err != nil { return nil, fmt.Errorf("cannot copy source RIB contents, err: %v", err) @@ -210,6 +221,28 @@ func diff(src, dst *rib.RIB, explicitReplace map[spb.AFTType]bool) (*reconcileOp } } + for lbl, srcE := range srcNIEntries.GetAfts().LabelEntry { + if dstE, ok := dstNIEntries.GetAfts().LabelEntry[lbl]; !ok || !reflect.DeepEqual(srcE, dstE) { + opType := spb.AFTOperation_ADD + if ok && explicitReplace[spb.AFTType_MPLS] { + opType = spb.AFTOperation_REPLACE + } + id++ + op, err := mplsOperation(opType, srcNI, lbl, id, srcE) + if err != nil { + return nil, err + } + + // If this entry already exists then this is an addition rather than a replace. + switch ok { + case true: + ops.Replace.TopLevel = append(ops.Replace.TopLevel, op) + case false: + ops.Add.TopLevel = append(ops.Add.TopLevel, op) + } + } + } + for nhgID, srcE := range srcNIEntries.GetAfts().NextHopGroup { if dstE, ok := dstNIEntries.GetAfts().NextHopGroup[nhgID]; !ok || !reflect.DeepEqual(srcE, dstE) { opType := spb.AFTOperation_ADD @@ -266,6 +299,17 @@ func diff(src, dst *rib.RIB, explicitReplace map[spb.AFTType]bool) (*reconcileOp } } + for lbl, dstE := range dstNIEntries.GetAfts().LabelEntry { + if _, ok := srcNIEntries.GetAfts().LabelEntry[lbl]; !ok { + id++ + op, err := mplsOperation(spb.AFTOperation_DELETE, srcNI, lbl, id, dstE) + if err != nil { + return nil, err + } + ops.Delete.TopLevel = append(ops.Delete.TopLevel, op) + } + } + for nhgID, dstE := range dstNIEntries.GetAfts().NextHopGroup { if _, ok := srcNIEntries.GetAfts().NextHopGroup[nhgID]; !ok { id++ @@ -345,3 +389,21 @@ func nhOperation(method spb.AFTOperation_Operation, ni string, nhID, id uint64, }, }, nil } + +// mplsOperation builds a gRIBI LabelEntry operation with the specified method corresponding to +// the MPLS label entry lbl. The operation is targeted at network instance ni, and uses the specified +// ID. The contents of the operation are the entry e. +func mplsOperation(method spb.AFTOperation_Operation, ni string, lbl aft.Afts_LabelEntry_Label_Union, id uint64, e *aft.Afts_LabelEntry) (*spb.AFTOperation, error) { + p, err := rib.ConcreteMPLSProto(e) + if err != nil { + return nil, fmt.Errorf("cannot create operation for label %d, %v", lbl, err) + } + return &spb.AFTOperation{ + Id: id, + NetworkInstance: ni, + Op: method, + Entry: &spb.AFTOperation_Mpls{ + Mpls: p, + }, + }, nil +} diff --git a/rib/reconciler/reconcile_test.go b/rib/reconciler/reconcile_test.go index e4e87f25..32ce206a 100644 --- a/rib/reconciler/reconcile_test.go +++ b/rib/reconciler/reconcile_test.go @@ -668,6 +668,311 @@ func TestDiff(t *testing.T) { }, { desc: "nil input", wantErr: true, + }, { + desc: "MPLS added", + inSrc: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 1); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + return r.RIB() + }(), + inDst: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + return r.RIB() + }(), + wantOps: &reconcileOps{ + Add: &ops{ + TopLevel: []*spb.AFTOperation{{ + Id: 1, + NetworkInstance: dn, + Op: spb.AFTOperation_ADD, + Entry: &spb.AFTOperation_Mpls{ + Mpls: &aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: 42, + }, + LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: 1}, + }, + }, + }, + }}, + }, + }, + }, { + desc: "MPLS delete", + inSrc: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + return r.RIB() + }(), + inDst: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 1); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + return r.RIB() + }(), + wantOps: &reconcileOps{ + Delete: &ops{ + TopLevel: []*spb.AFTOperation{{ + Id: 1, + NetworkInstance: dn, + Op: spb.AFTOperation_DELETE, + Entry: &spb.AFTOperation_Mpls{ + Mpls: &aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: 42, + }, + LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: 1}, + }, + }, + }, + }}, + }, + }, + }, { + desc: "MPLS implicit replace", + inSrc: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectNHG(dn, 2, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 2); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + return r.RIB() + }(), + inDst: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 2, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 1); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + return r.RIB() + }(), + wantOps: &reconcileOps{ + Replace: &ops{ + TopLevel: []*spb.AFTOperation{{ + Id: 1, + NetworkInstance: dn, + Op: spb.AFTOperation_ADD, + Entry: &spb.AFTOperation_Mpls{ + Mpls: &aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: 42, + }, + LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: 2}, + }, + }, + }, + }}, + }, + }, + }, { + desc: "MPLS explicit replace", + inSrc: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectNHG(dn, 2, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 2); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + return r.RIB() + }(), + inDst: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectNHG(dn, 2, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 1); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + return r.RIB() + }(), + inExplicitReplace: map[spb.AFTType]bool{ + spb.AFTType_MPLS: true, + }, + wantOps: &reconcileOps{ + Replace: &ops{ + TopLevel: []*spb.AFTOperation{{ + Id: 1, + NetworkInstance: dn, + Op: spb.AFTOperation_REPLACE, + Entry: &spb.AFTOperation_Mpls{ + Mpls: &aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: 42, + }, + LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: 2}, + }, + }, + }, + }}, + }, + }, + }, { + desc: "explicit replace for all types", + inSrc: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int42"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + // NHG ID = 1 is unchanged. + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectNHG(dn, 2, map[uint64]uint64{1: 64}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 2); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + if err := r.InjectIPv4(dn, "1.0.0.0/24", 2); err != nil { + t.Fatalf("cannot add IPv4 entry, %v", err) + } + return r.RIB() + }(), + inDst: func() *rib.RIB { + r := rib.NewFake(dn) + if err := r.InjectNH(dn, 1, "int10"); err != nil { + t.Fatalf("cannot add NH, %v", err) + } + if err := r.InjectNHG(dn, 1, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectNHG(dn, 2, map[uint64]uint64{1: 1}); err != nil { + t.Fatalf("cannot add NHG, %v", err) + } + if err := r.InjectMPLS(dn, 42, 1); err != nil { + t.Fatalf("cannot add label entry, %v", err) + } + if err := r.InjectIPv4(dn, "1.0.0.0/24", 1); err != nil { + t.Fatalf("cannot add IPv4 entry, %v", err) + } + return r.RIB() + }(), + inExplicitReplace: map[spb.AFTType]bool{ + spb.AFTType_ALL: true, + }, + wantOps: &reconcileOps{ + Replace: &ops{ + TopLevel: []*spb.AFTOperation{{ + Id: 1, + NetworkInstance: dn, + Op: spb.AFTOperation_REPLACE, + Entry: &spb.AFTOperation_Ipv4{ + Ipv4: &aftpb.Afts_Ipv4EntryKey{ + Prefix: "1.0.0.0/24", + Ipv4Entry: &aftpb.Afts_Ipv4Entry{ + NextHopGroup: &wpb.UintValue{Value: 2}, + }, + }, + }, + }, { + Id: 2, + NetworkInstance: dn, + Op: spb.AFTOperation_REPLACE, + Entry: &spb.AFTOperation_Mpls{ + Mpls: &aftpb.Afts_LabelEntryKey{ + Label: &aftpb.Afts_LabelEntryKey_LabelUint64{ + LabelUint64: 42, + }, + LabelEntry: &aftpb.Afts_LabelEntry{ + NextHopGroup: &wpb.UintValue{Value: 2}, + }, + }, + }, + }}, + NHG: []*spb.AFTOperation{{ + Id: 3, + NetworkInstance: dn, + Op: spb.AFTOperation_REPLACE, + Entry: &spb.AFTOperation_NextHopGroup{ + NextHopGroup: &aftpb.Afts_NextHopGroupKey{ + Id: 2, + NextHopGroup: &aftpb.Afts_NextHopGroup{ + NextHop: []*aftpb.Afts_NextHopGroup_NextHopKey{{ + Index: 1, + NextHop: &aftpb.Afts_NextHopGroup_NextHop{ + Weight: &wpb.UintValue{Value: 64}, + }, + }}, + }, + }, + }, + }}, + NH: []*spb.AFTOperation{{ + Id: 4, + NetworkInstance: dn, + Op: spb.AFTOperation_REPLACE, + Entry: &spb.AFTOperation_NextHop{ + NextHop: &aftpb.Afts_NextHopKey{ + Index: 1, + NextHop: &aftpb.Afts_NextHop{ + InterfaceRef: &aftpb.Afts_NextHop_InterfaceRef{ + Interface: &wpb.StringValue{Value: "int42"}, + }, + }, + }, + }, + }}, + }, + }, }} for _, tt := range tests {