Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(db): add repository package for persistence #170

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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()

Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand All @@ -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=
Expand Down
37 changes: 37 additions & 0 deletions pkg/repository/errors.go
Original file line number Diff line number Diff line change
@@ -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"
}
47 changes: 47 additions & 0 deletions pkg/repository/memory.go
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions pkg/repository/models_svi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2022-2023 Dell Inc, or its subsidiaries.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to a separate PR #267


// 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
}
35 changes: 35 additions & 0 deletions pkg/repository/models_vrf.go
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to a separate PR #267

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
}
57 changes: 57 additions & 0 deletions pkg/repository/redis.go
Original file line number Diff line number Diff line change
@@ -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)
Fixed Show fixed Hide fixed
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{}
}
24 changes: 24 additions & 0 deletions pkg/repository/repository.go
Original file line number Diff line number Diff line change
@@ -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}
}
}
72 changes: 72 additions & 0 deletions pkg/repository/repository_svi.go
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 18 in pkg/repository/repository_svi.go

View workflow job for this annotation

GitHub Actions / call / golangci

exported: comment on exported function SviFactory should be of the form "SviFactory ..." (revive)
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
}
Loading
Loading