From 8233dbea619df5667339907cb5778a8b7bfd3f9f Mon Sep 17 00:00:00 2001 From: Boris Glimcher Date: Fri, 8 Sep 2023 20:22:21 +0300 Subject: [PATCH] feat(db): add repository package for persistence This is not full implementation yet, just the start Signed-off-by: Boris Glimcher --- cmd/main.go | 12 ++++++ go.mod | 4 ++ go.sum | 8 ++++ pkg/repository/errors.go | 37 ++++++++++++++++ pkg/repository/memory.go | 47 +++++++++++++++++++++ pkg/repository/models_svi.go | 38 +++++++++++++++++ pkg/repository/models_vrf.go | 35 ++++++++++++++++ pkg/repository/redis.go | 57 +++++++++++++++++++++++++ pkg/repository/repository.go | 24 +++++++++++ pkg/repository/repository_svi.go | 72 ++++++++++++++++++++++++++++++++ pkg/repository/repository_vrf.go | 72 ++++++++++++++++++++++++++++++++ 11 files changed, 406 insertions(+) create mode 100644 pkg/repository/errors.go create mode 100644 pkg/repository/memory.go create mode 100644 pkg/repository/models_svi.go create mode 100644 pkg/repository/models_vrf.go create mode 100644 pkg/repository/redis.go create mode 100644 pkg/repository/repository.go create mode 100644 pkg/repository/repository_svi.go create mode 100644 pkg/repository/repository_vrf.go diff --git a/cmd/main.go b/cmd/main.go index 1297e8d6..28c0d81e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,6 +13,7 @@ import ( pe "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" "github.com/opiproject/opi-evpn-bridge/pkg/evpn" + "github.com/opiproject/opi-evpn-bridge/pkg/repository" "google.golang.org/grpc" "google.golang.org/grpc/reflection" @@ -28,6 +29,17 @@ func main() { if err != nil { log.Fatalf("failed to listen: %v", err) } + // Provision for persistent storage here + dbvrf, err := repository.VrfFactory("memory") + if err != nil { + log.Fatalf("failed to create database: %v", err) + } + dbvsvi, err := repository.SviFactory("memory") + if err != nil { + log.Fatalf("failed to create database: %v", err) + } + log.Printf("todo: use db in opi server (%v) and (%v)", dbvrf, dbvsvi) + s := grpc.NewServer() opi := evpn.NewServer() diff --git a/go.mod b/go.mod index a838da9f..d145c76b 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,14 @@ require ( ) require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-redis/redis v6.15.9+incompatible // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/redis/go-redis/v9 v9.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect golang.org/x/net v0.14.0 // indirect diff --git a/go.sum b/go.sum index b5a7f873..2beb7c57 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,12 @@ +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 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= @@ -18,6 +24,8 @@ github.com/opiproject/opi-api v0.0.0-20230905130004-eac732ac240b h1:jAEYUnARydKp github.com/opiproject/opi-api v0.0.0-20230905130004-eac732ac240b/go.mod h1:92pv4ulvvPMuxCJ9ND3aYbmBfEMLx0VCjpkiR7ZTqPY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= +github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= diff --git a/pkg/repository/errors.go b/pkg/repository/errors.go new file mode 100644 index 00000000..449acc64 --- /dev/null +++ b/pkg/repository/errors.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +// OperationError when cannot perform a given operation on database (SET, GET or DELETE) +type OperationError struct { + operation string +} + +func (err *OperationError) Error() string { + return "Could not perform the " + err.operation + " operation." +} + +// DownError when its not a redis.Nil response, in this case the database is down +type DownError struct{} + +func (dbe *DownError) Error() string { + return "Database is down" +} + +// CreateDatabaseError when cannot perform set on database +type CreateDatabaseError struct{} + +func (err *CreateDatabaseError) Error() string { + return "Could not create Databse" +} + +// NotImplementedDatabaseError when user tries to create a not implemented database +type NotImplementedDatabaseError struct { + database string +} + +func (err *NotImplementedDatabaseError) Error() string { + return err.database + " not implemented" +} diff --git a/pkg/repository/memory.go b/pkg/repository/memory.go new file mode 100644 index 00000000..d2585e80 --- /dev/null +++ b/pkg/repository/memory.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +import ( + "fmt" + "sync" +) + +type memoryDatabase struct { + data map[string]string + lock sync.RWMutex +} + +func newMemoryDatabase() *memoryDatabase { + return &memoryDatabase{ + data: make(map[string]string), + // lock: &sync.RWMutex{}, + } +} + +func (repo *memoryDatabase) Set(key string, value string) (string, error) { + repo.lock.RLock() + defer repo.lock.RUnlock() + repo.data[key] = value + return key, nil +} + +func (repo *memoryDatabase) Get(key string) (string, error) { + repo.lock.RLock() + defer repo.lock.RUnlock() + value, ok := repo.data[key] + if !ok { + // TODO: use our own errors, maybe OperationError ? + return "", fmt.Errorf("value does not exist for key: %s", key) + } + return value, nil +} + +func (repo *memoryDatabase) Delete(key string) (string, error) { + repo.lock.RLock() + defer repo.lock.RUnlock() + delete(repo.data, key) + return key, nil +} diff --git a/pkg/repository/models_svi.go b/pkg/repository/models_svi.go new file mode 100644 index 00000000..dd7f1b18 --- /dev/null +++ b/pkg/repository/models_svi.go @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +import ( + "encoding/binary" + "net" + + pb "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" +) + +// Svi object, separate from protobuf for decoupling +type Svi struct { + VrfRefKey string + LogicalBridgeRefKey string + MacAddress net.HardwareAddr + GwIP []net.IPNet +} + +// NewSvi creates new SVI object from protobuf message +func NewSvi(in *pb.Svi) *Svi { + mac := net.HardwareAddr(in.Spec.MacAddress) + gwIPList := []net.IPNet{} + for _, item := range in.Spec.GwIpPrefix { + myip := make(net.IP, 4) + binary.BigEndian.PutUint32(myip, item.Addr.GetV4Addr()) + gip := net.IPNet{IP: myip, Mask: net.CIDRMask(int(item.Len), 32)} + gwIPList = append(gwIPList, gip) + } + return &Svi{VrfRefKey: in.Spec.Vrf, LogicalBridgeRefKey: in.Spec.LogicalBridge, MacAddress: mac, GwIP: gwIPList} +} + +// ToPb transforms SVI object to protobuf message +func (in *Svi) ToPb() (*pb.Svi, error) { + return &pb.Svi{Spec: nil, Status: nil}, nil +} diff --git a/pkg/repository/models_vrf.go b/pkg/repository/models_vrf.go new file mode 100644 index 00000000..1916293e --- /dev/null +++ b/pkg/repository/models_vrf.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +import ( + "encoding/binary" + "net" + + pb "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" +) + +// Vrf object, separate from protobuf for decoupling +type Vrf struct { + Vni uint32 + LoopbackIP net.IPNet + VtepIP net.IPNet +} + +// NewVrf creates new VRF object from protobuf message +func NewVrf(in *pb.Vrf) *Vrf { + loopip := make(net.IP, 4) + binary.BigEndian.PutUint32(loopip, in.Spec.LoopbackIpPrefix.Addr.GetV4Addr()) + lip := net.IPNet{IP: loopip, Mask: net.CIDRMask(int(in.Spec.LoopbackIpPrefix.Len), 32)} + vtepip := make(net.IP, 4) + binary.BigEndian.PutUint32(vtepip, in.Spec.VtepIpPrefix.Addr.GetV4Addr()) + vip := net.IPNet{IP: vtepip, Mask: net.CIDRMask(int(in.Spec.VtepIpPrefix.Len), 32)} + return &Vrf{Vni: *in.Spec.Vni, LoopbackIP: lip, VtepIP: vip} +} + +// ToPb transforms VRF object to protobuf message +func (in *Vrf) ToPb() (*pb.Vrf, error) { + return &pb.Vrf{Spec: nil, Status: nil}, nil +} diff --git a/pkg/repository/redis.go b/pkg/repository/redis.go new file mode 100644 index 00000000..23708607 --- /dev/null +++ b/pkg/repository/redis.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +import "github.com/go-redis/redis" + +type redisDatabase struct { + client *redis.Client +} + +func newRedisDatabase() (*redisDatabase, error) { + // TODO: pass address + url := "redis://redis:6379?password=&protocol=3/vrf" + opts, err := redis.ParseURL(url) + if err != nil { + return nil, &CreateDatabaseError{} + } + client := redis.NewClient(opts) + _, err = client.Ping().Result() // makes sure database is connected + if err != nil { + return nil, &CreateDatabaseError{} + } + return &redisDatabase{client: client}, nil +} + +func (r *redisDatabase) Set(key string, value string) (string, error) { + _, err := r.client.Set(key, value, 0).Result() + if err != nil { + return generateError("set", err) + } + return key, nil +} + +func (r *redisDatabase) Get(key string) (string, error) { + value, err := r.client.Get(key).Result() + if err != nil { + return generateError("get", err) + } + return value, nil +} + +func (r *redisDatabase) Delete(key string) (string, error) { + _, err := r.client.Del(key).Result() + if err != nil { + return generateError("delete", err) + } + return key, nil +} + +func generateError(operation string, err error) (string, error) { + if err == redis.Nil { + return "", &OperationError{operation} + } + return "", &DownError{} +} diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go new file mode 100644 index 00000000..48c28ffa --- /dev/null +++ b/pkg/repository/repository.go @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +// IKeyValueStore abstraction +type IKeyValueStore interface { + Set(key string, value string) (string, error) + Get(key string) (string, error) + Delete(key string) (string, error) +} + +// Factory pattern to create new IKeyValueStore +func Factory(databaseImplementation string) (IKeyValueStore, error) { + switch databaseImplementation { + case "redis": + return newRedisDatabase() + case "memory": + return newMemoryDatabase(), nil + default: + return nil, &NotImplementedDatabaseError{databaseImplementation} + } +} diff --git a/pkg/repository/repository_svi.go b/pkg/repository/repository_svi.go new file mode 100644 index 00000000..d32e4fc5 --- /dev/null +++ b/pkg/repository/repository_svi.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +import ( + "encoding/json" +) + +// ISviRepository abstraction +type ISviRepository interface { + SetSvi(resourceName string, svi *Svi) error + GetSvi(resourceName string) (*Svi, error) + DeleteSvi(resourceName string) error +} + +// Factory pattern to create new ISviRepository +func SviFactory(databaseImplementation string) (ISviRepository, error) { + kvstore, err := Factory(databaseImplementation) + if err != nil { + // TODO: use our own errors, maybe OperationError ? + return nil, err + } + return newSviDatabase(kvstore), nil +} + +// sviDatabase implements ISviRepository interface +type sviDatabase struct { + kvstore IKeyValueStore +} + +func newSviDatabase(kvstore IKeyValueStore) *sviDatabase { + return &sviDatabase{ + kvstore: kvstore, + } +} + +func (repo *sviDatabase) GetSvi(resourceName string) (*Svi, error) { + value, err := repo.kvstore.Get(resourceName) + if err != nil { + // TODO: use our own errors, maybe OperationError ? + return nil, err + } + svi := &Svi{} + err = json.Unmarshal([]byte(value), svi) + if err != nil { + // TODO: use our own errors, maybe OperationError ? + return nil, err + } + return svi, nil +} + +func (repo *sviDatabase) SetSvi(resourceName string, svi *Svi) error { + value, err := json.Marshal(svi) + if err != nil { + return err + } + _, err = repo.kvstore.Set(resourceName, string(value)) + if err != nil { + return err + } + return nil +} + +func (repo *sviDatabase) DeleteSvi(resourceName string) error { + _, err := repo.kvstore.Delete(resourceName) + if err != nil { + return err + } + return nil +} diff --git a/pkg/repository/repository_vrf.go b/pkg/repository/repository_vrf.go new file mode 100644 index 00000000..512e1f42 --- /dev/null +++ b/pkg/repository/repository_vrf.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries. + +// Package repository is the database abstraction implementing repository design pattern +package repository + +import ( + "encoding/json" +) + +// IVrfRepository abstraction +type IVrfRepository interface { + SetVrf(resourceName string, vrf *Vrf) error + GetVrf(resourceName string) (*Vrf, error) + DeleteVrf(resourceName string) error +} + +// Factory pattern to create new IVrfRepository +func VrfFactory(databaseImplementation string) (IVrfRepository, error) { + kvstore, err := Factory(databaseImplementation) + if err != nil { + // TODO: use our own errors, maybe OperationError ? + return nil, err + } + return newVrfDatabase(kvstore), nil +} + +// sviDatabase implements IVrfRepository interface +type vrfDatabase struct { + kvstore IKeyValueStore +} + +func newVrfDatabase(kvstore IKeyValueStore) *vrfDatabase { + return &vrfDatabase{ + kvstore: kvstore, + } +} + +func (repo *vrfDatabase) GetVrf(resourceName string) (*Vrf, error) { + value, err := repo.kvstore.Get(resourceName) + if err != nil { + // TODO: use our own errors, maybe OperationError ? + return nil, err + } + vrf := &Vrf{} + err = json.Unmarshal([]byte(value), vrf) + if err != nil { + // TODO: use our own errors, maybe OperationError ? + return nil, err + } + return vrf, nil +} + +func (repo *vrfDatabase) SetVrf(resourceName string, vrf *Vrf) error { + value, err := json.Marshal(vrf) + if err != nil { + return err + } + _, err = repo.kvstore.Set(resourceName, string(value)) + if err != nil { + return err + } + return nil +} + +func (repo *vrfDatabase) DeleteVrf(resourceName string) error { + _, err := repo.kvstore.Delete(resourceName) + if err != nil { + return err + } + return nil +}