diff --git a/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go b/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go index 07b702db3360040de4c4d190a16fc4cabfdb2a38..2b4dff95575b2df72036642f40bd69cfedb4e5ab 100644 --- a/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go +++ b/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go @@ -12,16 +12,18 @@ import ( // AdjRIBOutAddPath represents an Adjacency RIB Out with BGP add path type AdjRIBOutAddPath struct { routingtable.ClientManager - rt *routingtable.RoutingTable - neighbor *routingtable.Neighbor - mu sync.RWMutex + rt *routingtable.RoutingTable + neighbor *routingtable.Neighbor + pathIDManager *pathIDManager + mu sync.RWMutex } // New creates a new Adjacency RIB Out with BGP add path func New(neighbor *routingtable.Neighbor) *AdjRIBOutAddPath { a := &AdjRIBOutAddPath{ - rt: routingtable.NewRoutingTable(), - neighbor: neighbor, + rt: routingtable.NewRoutingTable(), + neighbor: neighbor, + pathIDManager: newPathIDManager(), } a.ClientManager = routingtable.NewClientManager(a) return a @@ -41,8 +43,12 @@ func (a *AdjRIBOutAddPath) AddPath(pfx net.Prefix, p *route.Path) error { a.mu.Lock() defer a.mu.Unlock() - p.BGPPath.PathIdentifier = 7 + pathID, err := a.pathIDManager.getNewID() + if err != nil { + return fmt.Errorf("Unable to get path ID: %v", err) + } + p.BGPPath.PathIdentifier = pathID a.rt.AddPath(pfx, p) for _, client := range a.ClientManager.Clients() { @@ -66,6 +72,7 @@ func (a *AdjRIBOutAddPath) RemovePath(pfx net.Prefix, p *route.Path) bool { } a.rt.RemovePath(pfx, p) + a.pathIDManager.releaseID(p.BGPPath.PathIdentifier) a.removePathFromClients(pfx, p) return true } diff --git a/routingtable/adjRIBOutAddPath/path_id_manager.go b/routingtable/adjRIBOutAddPath/path_id_manager.go new file mode 100644 index 0000000000000000000000000000000000000000..0d79924941424b3cb7a772a6ca3df07ce914fb6e --- /dev/null +++ b/routingtable/adjRIBOutAddPath/path_id_manager.go @@ -0,0 +1,47 @@ +package adjRIBOutAddPath + +import ( + "fmt" +) + +var maxUint32 = ^uint32(0) + +// pathIDManager manages BGP path identifiers for add-path. This is no thread safe (and doesn't need to be). +type pathIDManager struct { + ids map[uint32]struct{} + last uint32 + used uint32 +} + +func newPathIDManager() *pathIDManager { + return &pathIDManager{ + ids: make(map[uint32]struct{}), + } +} + +func (fm *pathIDManager) getNewID() (uint32, error) { + if fm.used == maxUint32 { + return 0, fmt.Errorf("Out of path IDs") + } + + fm.last++ + for { + if _, exists := fm.ids[fm.last]; exists { + fm.last++ + continue + } + break + } + + ret := fm.last + fm.used++ + + return ret, nil +} + +func (fm *pathIDManager) releaseID(id uint32) { + if _, exists := fm.ids[id]; exists { + delete(fm.ids, id) + fm.used-- + } +} diff --git a/routingtable/adjRIBOutAddPath/path_id_manager_test.go b/routingtable/adjRIBOutAddPath/path_id_manager_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c1cff4e6060b26075c99c957ae500fc74484f613 --- /dev/null +++ b/routingtable/adjRIBOutAddPath/path_id_manager_test.go @@ -0,0 +1,109 @@ +package adjRIBOutAddPath + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetNewID(t *testing.T) { + tests := []struct { + name string + maxIDs uint32 + count int + wantFail bool + }{ + { + name: "Out of path IDs", + maxIDs: 10, + count: 11, + wantFail: true, + }, + { + name: "Success", + maxIDs: 10, + count: 10, + wantFail: false, + }, + } + +X: + for _, test := range tests { + maxUint32 = test.maxIDs + m := newPathIDManager() + for i := 0; i < test.count; i++ { + _, err := m.getNewID() + if err != nil { + if test.wantFail { + continue X + } + + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue X + } + } + + if test.wantFail { + t.Errorf("Unexpected success for test %q", test.name) + continue + } + } +} + +func TestReleaseID(t *testing.T) { + tests := []struct { + name string + pm *pathIDManager + release uint32 + expected *pathIDManager + }{ + { + name: "Release existent", + pm: &pathIDManager{ + ids: map[uint32]struct{}{ + 0: struct{}{}, + 1: struct{}{}, + 2: struct{}{}, + }, + last: 2, + used: 3, + }, + release: 1, + expected: &pathIDManager{ + ids: map[uint32]struct{}{ + 0: struct{}{}, + 2: struct{}{}, + }, + last: 2, + used: 2, + }, + }, + { + name: "Release non-existent", + pm: &pathIDManager{ + ids: map[uint32]struct{}{ + 0: struct{}{}, + 1: struct{}{}, + 2: struct{}{}, + }, + last: 2, + used: 3, + }, + release: 3, + expected: &pathIDManager{ + ids: map[uint32]struct{}{ + 0: struct{}{}, + 1: struct{}{}, + 2: struct{}{}, + }, + last: 2, + used: 3, + }, + }, + } + + for _, test := range tests { + test.pm.releaseID(test.release) + assert.Equalf(t, test.expected, test.pm, "%s", test.name) + } +}