From 8d3e81bab4958c446daab4030a1a94b978249eb0 Mon Sep 17 00:00:00 2001 From: Igor Shishkin Date: Sun, 2 Jun 2024 10:39:50 +0300 Subject: [PATCH 1/3] Add to the configuration Signed-off-by: Igor Shishkin --- config/config.go | 1 + config/config_test.go | 1 + config/testdata/sample.json | 1 + config/testdata/sample.yaml | 1 + 4 files changed, 4 insertions(+) diff --git a/config/config.go b/config/config.go index 288fe9a..779b768 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ import ( ) type Announcer struct { + RouterID string `json:"router_id"` LocalAddress string `json:"local_address"` LocalAS uint32 `json:"local_as"` Routes []string `json:"routes"` diff --git a/config/config_test.go b/config/config_test.go index c9ca670..5a6a304 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -19,6 +19,7 @@ func TestConfig(t *testing.T) { sampleConfig := Config{ Announcer: Announcer{ + RouterID: "10.3.3.3", LocalAddress: "10.0.0.1", LocalAS: 65999, Routes: []string{"10.0.0.128/32"}, diff --git a/config/testdata/sample.json b/config/testdata/sample.json index 1e28218..f6ce57c 100644 --- a/config/testdata/sample.json +++ b/config/testdata/sample.json @@ -1,5 +1,6 @@ { "announcer": { + "router_id": "10.3.3.3", "local_address": "10.0.0.1", "local_as": 65999, "routes": [ diff --git a/config/testdata/sample.yaml b/config/testdata/sample.yaml index d9f484f..a3f7d00 100644 --- a/config/testdata/sample.yaml +++ b/config/testdata/sample.yaml @@ -1,5 +1,6 @@ --- announcer: + router_id: 10.3.3.3 local_address: 10.0.0.1 local_as: 65999 routes: From 48dfd001c3b503d0a48764372623d0d14da7bebf Mon Sep 17 00:00:00 2001 From: Igor Shishkin Date: Sun, 2 Jun 2024 10:43:17 +0300 Subject: [PATCH 2/3] Add announcer Signed-off-by: Igor Shishkin --- announcer/announcer.go | 115 ++++++++++++++++++++++++++++++++++++ announcer/announcer_test.go | 34 +++++++++++ announcer/gobgp_mock.go | 28 +++++++++ announcer/logger.go | 42 +++++++++++++ announcer/mock.go | 27 +++++++++ go.mod | 9 ++- go.sum | 24 +++++++- 7 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 announcer/announcer.go create mode 100644 announcer/announcer_test.go create mode 100644 announcer/gobgp_mock.go create mode 100644 announcer/logger.go create mode 100644 announcer/mock.go diff --git a/announcer/announcer.go b/announcer/announcer.go new file mode 100644 index 0000000..e92be93 --- /dev/null +++ b/announcer/announcer.go @@ -0,0 +1,115 @@ +package announcer + +import ( + "context" + "strconv" + "strings" + + api "github.com/osrg/gobgp/v3/api" + apb "google.golang.org/protobuf/types/known/anypb" +) + +type GoBGPServer interface { + AddPath(ctx context.Context, r *api.AddPathRequest) (*api.AddPathResponse, error) + DeletePath(ctx context.Context, r *api.DeletePathRequest) error +} + +type Announcer interface { + Announce(ctx context.Context) error + Denounce(ctx context.Context) error +} + +type Config struct { + GoBGP GoBGPServer + Prefixes []string + NextHop string +} + +type announcer struct { + gobgp GoBGPServer + prefixes []string + nexthop string +} + +func New(cfg Config) Announcer { + return &announcer{ + gobgp: cfg.GoBGP, + prefixes: cfg.Prefixes, + nexthop: cfg.NextHop, + } +} + +func (a *announcer) Announce(ctx context.Context) error { + pp, err := a.newPathList() + if err != nil { + return err + } + + for _, p := range pp { + _, err = a.gobgp.AddPath(ctx, &api.AddPathRequest{ + Path: p, + }) + if err != nil { + return err + } + } + + return nil +} + +func (a *announcer) Denounce(ctx context.Context) error { + pp, err := a.newPathList() + if err != nil { + return err + } + + for _, p := range pp { + err := a.gobgp.DeletePath(ctx, &api.DeletePathRequest{ + Path: p, + }) + if err != nil { + return err + } + } + return nil +} + +func (a *announcer) newPathList() ([]*api.Path, error) { + prefixes := []*api.Path{} + for _, p := range a.prefixes { + l := strings.SplitN(p, "/", 2) + prefixLen, err := strconv.ParseUint(l[1], 10, 32) + if err != nil { + return nil, err + } + + nlri, _ := apb.New(&api.IPAddressPrefix{ + Prefix: l[0], + PrefixLen: uint32(prefixLen), + }) + + a1, _ := apb.New(&api.OriginAttribute{ + Origin: 0, + }) + a2, _ := apb.New(&api.NextHopAttribute{ + NextHop: a.nexthop, + }) + a3, _ := apb.New(&api.AsPathAttribute{ + Segments: []*api.AsSegment{ + { + Type: 2, + Numbers: []uint32{6762, 39919, 65000, 35753, 65000}, + }, + }, + }) + attrs := []*apb.Any{a1, a2, a3} + + prefixes = append(prefixes, &api.Path{ + Family: &api.Family{Afi: api.Family_AFI_IP, Safi: api.Family_SAFI_UNICAST}, + Nlri: nlri, + Pattrs: attrs, + }) + } + + return prefixes, nil +} diff --git a/announcer/announcer_test.go b/announcer/announcer_test.go new file mode 100644 index 0000000..c8cfcd8 --- /dev/null +++ b/announcer/announcer_test.go @@ -0,0 +1,34 @@ +package announcer + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAnnouncer(t *testing.T) { + r := require.New(t) + + goBgpM := newGoBGPMock() + call1 := goBgpM.On( + "AddPath", + "[type.googleapis.com/apipb.IPAddressPrefix]:{prefix_len:32 prefix:\"172.16.38.43\"}", + ).Return([]byte("123456"), nil).Once() + goBgpM.On( + "DeletePath", + "[type.googleapis.com/apipb.IPAddressPrefix]:{prefix_len:32 prefix:\"172.16.38.43\"}", + ).Return(nil).NotBefore(call1).Once() + + a := New(Config{ + GoBGP: goBgpM, + Prefixes: []string{"172.16.38.43/32"}, + NextHop: "172.12.33.14", + }) + + err := a.Announce(context.Background()) + r.NoError(err) + + err = a.Denounce(context.Background()) + r.NoError(err) +} diff --git a/announcer/gobgp_mock.go b/announcer/gobgp_mock.go new file mode 100644 index 0000000..4ee91e5 --- /dev/null +++ b/announcer/gobgp_mock.go @@ -0,0 +1,28 @@ +package announcer + +import ( + "context" + + api "github.com/osrg/gobgp/v3/api" + "github.com/stretchr/testify/mock" +) + +type goBGPMock struct { + mock.Mock +} + +func newGoBGPMock() *goBGPMock { + return &goBGPMock{} +} + +func (m *goBGPMock) AddPath(_ context.Context, r *api.AddPathRequest) (*api.AddPathResponse, error) { + args := m.Called(r.GetPath().GetNlri().String()) + return &api.AddPathResponse{ + Uuid: args.Get(0).([]byte), + }, args.Error(1) +} + +func (m *goBGPMock) DeletePath(ctx context.Context, r *api.DeletePathRequest) error { + args := m.Called(r.GetPath().GetNlri().String()) + return args.Error(0) +} diff --git a/announcer/logger.go b/announcer/logger.go new file mode 100644 index 0000000..a371cae --- /dev/null +++ b/announcer/logger.go @@ -0,0 +1,42 @@ +package announcer + +import ( + gobgpLog "github.com/osrg/gobgp/v3/pkg/log" + log "github.com/sirupsen/logrus" +) + +type Logger struct { + Logger *log.Logger +} + +func (l *Logger) Panic(msg string, fields gobgpLog.Fields) { + l.Logger.WithFields(log.Fields(fields)).Panic(msg) +} + +func (l *Logger) Fatal(msg string, fields gobgpLog.Fields) { + l.Logger.WithFields(log.Fields(fields)).Fatal(msg) +} + +func (l *Logger) Error(msg string, fields gobgpLog.Fields) { + l.Logger.WithFields(log.Fields(fields)).Error(msg) +} + +func (l *Logger) Warn(msg string, fields gobgpLog.Fields) { + l.Logger.WithFields(log.Fields(fields)).Warn(msg) +} + +func (l *Logger) Info(msg string, fields gobgpLog.Fields) { + l.Logger.WithFields(log.Fields(fields)).Info(msg) +} + +func (l *Logger) Debug(msg string, fields gobgpLog.Fields) { + l.Logger.WithFields(log.Fields(fields)).Debug(msg) +} + +func (l *Logger) SetLevel(level gobgpLog.LogLevel) { + l.Logger.SetLevel(log.Level(level)) +} + +func (l *Logger) GetLevel() gobgpLog.LogLevel { + return gobgpLog.LogLevel(l.Logger.GetLevel()) +} diff --git a/announcer/mock.go b/announcer/mock.go new file mode 100644 index 0000000..86f2b1f --- /dev/null +++ b/announcer/mock.go @@ -0,0 +1,27 @@ +package announcer + +import ( + "context" + + "github.com/stretchr/testify/mock" +) + +var _ Announcer = (*Mock)(nil) + +type Mock struct { + mock.Mock +} + +func NewMock() *Mock { + return &Mock{} +} + +func (m *Mock) Announce(ctx context.Context) error { + args := m.Called() + return args.Error(0) +} + +func (m *Mock) Denounce(ctx context.Context) error { + args := m.Called() + return args.Error(0) +} diff --git a/go.mod b/go.mod index 26ef7e6..0c434c4 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,23 @@ go 1.22.3 require ( github.com/go-ozzo/ozzo-validation/v4 v4.3.0 + github.com/osrg/gobgp/v3 v3.26.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 + google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/grpc v1.56.3 // indirect ) diff --git a/go.sum b/go.sum index b60f1f6..3ffb61b 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/osrg/gobgp/v3 v3.26.0 h1:/iHaQKNgp0dRI3/RGt/j60aUeoGng6CL0VATVfQXEPE= +github.com/osrg/gobgp/v3 v3.26.0/go.mod h1:ZGeSti9mURR/o5hf5R6T1FM5g1yiEBZbhP+TuqYJUpI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -18,8 +26,22 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 0a662869eb72e173c1f99345eac5f5639effbfc5 Mon Sep 17 00:00:00 2001 From: Igor Shishkin Date: Sun, 2 Jun 2024 10:44:52 +0300 Subject: [PATCH 3/3] Add check mocks Signed-off-by: Igor Shishkin --- checkers/checkers.go | 12 +++++++++++- checkers/mock.go | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 checkers/mock.go diff --git a/checkers/checkers.go b/checkers/checkers.go index a1f4fa7..9419089 100644 --- a/checkers/checkers.go +++ b/checkers/checkers.go @@ -3,6 +3,7 @@ package checkers import ( "context" "encoding/json" + "sync" "github.com/pkg/errors" ) @@ -11,9 +12,15 @@ type Checker interface { Check(ctx context.Context) error } -var registry = map[string]func(in json.RawMessage) (Checker, error){} +var ( + registry = map[string]func(in json.RawMessage) (Checker, error){} + registryMutex = &sync.RWMutex{} +) func NewCheckerByKind(kind string, spec json.RawMessage) (Checker, error) { + registryMutex.RLock() + defer registryMutex.RUnlock() + c, ok := registry[kind] if !ok { return nil, errors.Errorf("checker with kind `%s` is not registered", kind) @@ -23,6 +30,9 @@ func NewCheckerByKind(kind string, spec json.RawMessage) (Checker, error) { } func Register(kind string, fn func(in json.RawMessage) (Checker, error)) error { + registryMutex.Lock() + defer registryMutex.Unlock() + if _, ok := registry[kind]; ok { return errors.Errorf("checker with kind `%s` already registered", kind) } diff --git a/checkers/mock.go b/checkers/mock.go new file mode 100644 index 0000000..41710cf --- /dev/null +++ b/checkers/mock.go @@ -0,0 +1,22 @@ +package checkers + +import ( + "context" + + "github.com/stretchr/testify/mock" +) + +var _ Checker = (*Mock)(nil) + +type Mock struct { + mock.Mock +} + +func NewMock() *Mock { + return &Mock{} +} + +func (m *Mock) Check(context.Context) error { + args := m.Called() + return args.Error(0) +}