diff --git a/Dockerfile b/Dockerfile index 685baba00cacc0280fa4e86aa10c0680a46f0859..dd077fc7c5b790e77d29ca9352c98bc4e59dd05c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,7 @@ COPY ./cmd ./cmd COPY ./interfaces ./interfaces COPY ./northbound ./northbound COPY ./nucleus ./nucleus +COPY ./store ./store RUN GOOS=linux go build $BUILDARGS ./cmd/gosdn diff --git a/api/api_test.toml b/api/api_test.toml new file mode 100644 index 0000000000000000000000000000000000000000..fb66a37541a7332465914a8f0f5102e5ba4a49a4 --- /dev/null +++ b/api/api_test.toml @@ -0,0 +1 @@ +cli_pnd = "2043519e-46d1-4963-9a8e-d99007e104b8" diff --git a/api/initialise_test.go b/api/initialise_test.go index bbac6dad0f48872079d548671b9a52e8bbe5ed0a..7a7429a96db3b2997f70b4ef3e4bddcfcb336ea9 100644 --- a/api/initialise_test.go +++ b/api/initialise_test.go @@ -14,6 +14,7 @@ import ( nbi "code.fbi.h-da.de/danet/gosdn/northbound/server" "code.fbi.h-da.de/danet/gosdn/nucleus" "code.fbi.h-da.de/danet/gosdn/nucleus/util/proto" + "code.fbi.h-da.de/danet/gosdn/store" "code.fbi.h-da.de/danet/yang-models/generated/openconfig" "github.com/google/uuid" log "github.com/sirupsen/logrus" @@ -36,8 +37,8 @@ const changeID = "0992d600-f7d4-4906-9559-409b04d59a5f" const sbiID = "f6fd4b35-f039-4111-9156-5e4501bb8a5a" const ondID = "7e0ed8cc-ebf5-46fa-9794-741494914883" -var pndStore *nucleus.PndStore -var sbiStore *nucleus.SbiStore +var pndStore *store.PndStore +var sbiStore *store.SbiStore var lis *bufconn.Listener var pndUUID uuid.UUID var sbiUUID uuid.UUID @@ -49,8 +50,8 @@ func bootstrapUnitTest() { } lis = bufconn.Listen(bufSize) s := grpc.NewServer() - pndStore = nucleus.NewPndStore() - sbiStore = nucleus.NewSbiStore() + pndStore = store.NewPndStore() + sbiStore = store.NewSbiStore() changeUUID, err := uuid.Parse(changeID) if err != nil { diff --git a/controller.go b/controller.go index a4c1b4f77ab098a7d1377a2233765dd03a4a2632..a9bfa2493040ddb4f4b06fbe84e33ebdad308903 100644 --- a/controller.go +++ b/controller.go @@ -20,6 +20,7 @@ import ( spb "code.fbi.h-da.de/danet/api/go/gosdn/southbound" "code.fbi.h-da.de/danet/gosdn/interfaces/southbound" nbi "code.fbi.h-da.de/danet/gosdn/northbound/server" + "code.fbi.h-da.de/danet/gosdn/store" "code.fbi.h-da.de/danet/gosdn/nucleus" ) @@ -29,7 +30,7 @@ var coreOnce sync.Once // Core is the representation of the controller's core type Core struct { - pndc *nucleus.PndStore + pndc *store.PndStore httpServer *http.Server grpcServer *grpc.Server nbi *nbi.NorthboundInterface @@ -42,7 +43,7 @@ var c *Core func init() { c = &Core{ - pndc: nucleus.NewPndStore(), + pndc: store.NewPndStore(), stopChan: make(chan os.Signal, 1), } @@ -141,7 +142,7 @@ func shutdown() error { return stopHttpServer() } -func callback(id uuid.UUID, ch chan nucleus.DeviceDetails) { +func callback(id uuid.UUID, ch chan store.DeviceDetails) { if ch != nil { c.pndc.AddPendingChannel(id, ch) log.Infof("pending channel %v added", id) diff --git a/northbound/server/csbi.go b/northbound/server/csbi.go index 975e3ea225fa6c9c699ac7664a64b050b40d0b9d..00f0fa47bbc0acf7f4fb826ff1794d5c57b829a0 100644 --- a/northbound/server/csbi.go +++ b/northbound/server/csbi.go @@ -7,7 +7,7 @@ import ( log "github.com/sirupsen/logrus" cpb "code.fbi.h-da.de/danet/api/go/gosdn/csbi" - "code.fbi.h-da.de/danet/gosdn/nucleus" + "code.fbi.h-da.de/danet/gosdn/store" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -17,12 +17,12 @@ type csbi struct { } func (s csbi) Hello(ctx context.Context, syn *cpb.Syn) (*cpb.Ack, error) { - ch, err := pndc.PendingChannels(nucleus.FromString(syn.Id)) + ch, err := pndc.PendingChannels(store.FromString(syn.Id)) if err != nil { log.Error(err) return nil, status.Errorf(codes.Aborted, "pending channel %v", err) } - ch <- nucleus.DeviceDetails{ + ch <- store.DeviceDetails{ ID: syn.Id, Address: syn.Address, } diff --git a/northbound/server/nbi.go b/northbound/server/nbi.go index e99888d64cf7bd7dc74dc3e2109711544d0b89fb..e110d68bcabcb099756ac219a021c6e35ec5f45f 100644 --- a/northbound/server/nbi.go +++ b/northbound/server/nbi.go @@ -1,10 +1,10 @@ package server import ( - "code.fbi.h-da.de/danet/gosdn/nucleus" + "code.fbi.h-da.de/danet/gosdn/store" ) -var pndc *nucleus.PndStore +var pndc *store.PndStore // NorthboundInterface is the representation of the // gRPC services used provided. @@ -15,7 +15,7 @@ type NorthboundInterface struct { } // NewNBI receives a PndStore and returns a new gRPC *NorthboundInterface -func NewNBI(pnds *nucleus.PndStore) *NorthboundInterface { +func NewNBI(pnds *store.PndStore) *NorthboundInterface { pndc = pnds return &NorthboundInterface{ Pnd: &pndServer{}, diff --git a/northbound/server/pnd.go b/northbound/server/pnd.go index 46f4505e5ceefab4c9df7730d40f713e3435cca4..3e06a57809cf9f0306d8b54372fb665d1aae4db3 100644 --- a/northbound/server/pnd.go +++ b/northbound/server/pnd.go @@ -11,7 +11,6 @@ import ( ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd" spb "code.fbi.h-da.de/danet/api/go/gosdn/southbound" "code.fbi.h-da.de/danet/gosdn/interfaces/networkdomain" - "code.fbi.h-da.de/danet/gosdn/nucleus" "code.fbi.h-da.de/danet/gosdn/nucleus/errors" "github.com/google/uuid" "github.com/openconfig/ygot/ygot" @@ -174,7 +173,7 @@ func handleGetPath(pid uuid.UUID, req *ppb.GetRequest_Path) (*ppb.GetResponse, e func fillSbis(pnd networkdomain.NetworkDomain, all bool, sid ...string) ([]*spb.SouthboundInterface, error) { var sbiList []uuid.UUID - sbiStore := pnd.GetSBIs().(*nucleus.SbiStore) + sbiStore := pnd.GetSBIs() switch all { case true: sbiList = sbiStore.UUIDs() @@ -194,14 +193,13 @@ func fillSbis(pnd networkdomain.NetworkDomain, all bool, sid ...string) ([]*spb. } sbis := make([]*spb.SouthboundInterface, len(sbiList)) for i, id := range sbiList { - sbi, err := sbiStore.GetSBI(id) + _, err := sbiStore.Get(id) if err != nil { log.Error(err) return nil, status.Errorf(codes.Aborted, "%v", err) } sbis[i] = &spb.SouthboundInterface{ - Id: id.String(), - Type: sbi.Type(), + Id: id.String(), } } return sbis, nil diff --git a/northbound/server/pnd_test.go b/northbound/server/pnd_test.go index 541f29260cccfe81b6c4c4786da5c0e1e5322af5..8fa00231d05a455079263b8d9ff8d60213621cb2 100644 --- a/northbound/server/pnd_test.go +++ b/northbound/server/pnd_test.go @@ -13,6 +13,7 @@ import ( "code.fbi.h-da.de/danet/gosdn/interfaces/device" "code.fbi.h-da.de/danet/gosdn/mocks" "code.fbi.h-da.de/danet/gosdn/nucleus" + "code.fbi.h-da.de/danet/gosdn/store" "code.fbi.h-da.de/danet/yang-models/generated/openconfig" "github.com/google/uuid" log "github.com/sirupsen/logrus" @@ -32,7 +33,7 @@ var committedChangeUUID uuid.UUID var deviceUUID uuid.UUID var mockPnd *mocks.NetworkDomain var mockDevice device.Device -var sbiStore *nucleus.SbiStore +var sbiStore *store.SbiStore func TestMain(m *testing.M) { log.SetReportCaller(true) @@ -71,7 +72,7 @@ func TestMain(m *testing.M) { mockDevice.(*nucleus.CommonDevice).SetSBI(nucleus.NewSBI(spb.Type_OPENCONFIG)) mockDevice.(*nucleus.CommonDevice).SetTransport(&mocks.Transport{}) mockDevice.(*nucleus.CommonDevice).SetName(hostname) - sbiStore = nucleus.NewSbiStore() + sbiStore = store.NewSbiStore() if err := sbiStore.Add(mockDevice.SBI()); err != nil { log.Fatal(err) } @@ -95,7 +96,7 @@ func TestMain(m *testing.M) { mockPnd.On("Confirm", mock.Anything).Return(nil) mockPnd.On("ChangeOND", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(uuid.Nil, nil) - pndc = nucleus.NewPndStore() + pndc = store.NewPndStore() if err := pndc.Add(mockPnd); err != nil { log.Fatal(err) } diff --git a/nucleus/initialise_test.go b/nucleus/initialise_test.go index 093d08b716ddb974d2e4e96c635b58fda07a8afc..36c205ac052a1f20ca06580c63a4f933d0cf73e0 100644 --- a/nucleus/initialise_test.go +++ b/nucleus/initialise_test.go @@ -6,6 +6,7 @@ import ( "testing" "code.fbi.h-da.de/danet/gosdn/interfaces/device" + "code.fbi.h-da.de/danet/gosdn/store" tpb "code.fbi.h-da.de/danet/api/go/gosdn/transport" @@ -151,9 +152,9 @@ func newPnd() pndImplementation { return pndImplementation{ name: "default", description: "default test pnd", - sbic: SbiStore{genericStore{}}, - devices: NewDeviceStore(), - changes: ChangeStore{genericStore{}}, + sbic: store.NewSbiStore(), + devices: store.NewDeviceStore(), + changes: store.NewChangeStore(), id: defaultPndID, } } diff --git a/nucleus/principalNetworkDomain.go b/nucleus/principalNetworkDomain.go index b01b663f6a9d041792664f4d2955182fb91a0f6f..b0238a095dfb2766e9712b7974614229f560a314 100644 --- a/nucleus/principalNetworkDomain.go +++ b/nucleus/principalNetworkDomain.go @@ -17,9 +17,11 @@ import ( "code.fbi.h-da.de/danet/gosdn/interfaces/device" "code.fbi.h-da.de/danet/gosdn/interfaces/networkdomain" "code.fbi.h-da.de/danet/gosdn/interfaces/southbound" - "code.fbi.h-da.de/danet/gosdn/interfaces/store" + si "code.fbi.h-da.de/danet/gosdn/interfaces/store" "code.fbi.h-da.de/danet/gosdn/nucleus/errors" + "code.fbi.h-da.de/danet/gosdn/store" + "github.com/google/uuid" "github.com/openconfig/ygot/ygot" "github.com/openconfig/ygot/ytypes" @@ -27,18 +29,19 @@ import ( ) // NewPND creates a Principle Network Domain -func NewPND(name, description string, id uuid.UUID, sbi southbound.SouthboundInterface, c cpb.CsbiClient, callback func(uuid.UUID, chan DeviceDetails)) (networkdomain.NetworkDomain, error) { +func NewPND(name, description string, id uuid.UUID, sbi southbound.SouthboundInterface, c cpb.CsbiClient, callback func(uuid.UUID, chan store.DeviceDetails)) (networkdomain.NetworkDomain, error) { pnd := &pndImplementation{ name: name, description: description, - sbic: SbiStore{genericStore{}}, - devices: NewDeviceStore(), - changes: ChangeStore{genericStore{}}, + sbic: store.NewSbiStore(), + devices: store.NewDeviceStore(), + changes: store.NewChangeStore(), id: id, csbiClient: c, callback: callback, } + if err := pnd.sbic.Add(sbi); err != nil { return nil, err } @@ -48,13 +51,13 @@ func NewPND(name, description string, id uuid.UUID, sbi southbound.SouthboundInt type pndImplementation struct { name string description string - sbic SbiStore - devices *DeviceStore - changes ChangeStore + sbic *store.SbiStore + devices *store.DeviceStore + changes *store.ChangeStore id uuid.UUID csbiClient cpb.CsbiClient - callback func(uuid.UUID, chan DeviceDetails) + callback func(uuid.UUID, chan store.DeviceDetails) } func (pnd *pndImplementation) PendingChanges() []uuid.UUID { @@ -113,8 +116,8 @@ func (pnd *pndImplementation) GetDescription() string { } // GetSBIs returns the registered SBIs -func (pnd *pndImplementation) GetSBIs() store.Store { - return &pnd.sbic +func (pnd *pndImplementation) GetSBIs() si.Store { + return pnd.sbic } // Destroy destroys the PND @@ -154,7 +157,7 @@ func (pnd *pndImplementation) AddDevice(name string, opt *tpb.TransportOption, s } func (pnd *pndImplementation) GetDevice(identifier string) (device.Device, error) { - d, err := pnd.devices.GetDevice(FromString(identifier)) + d, err := pnd.devices.GetDevice(store.FromString(identifier)) if err != nil { return nil, err } @@ -211,7 +214,7 @@ func (pnd *pndImplementation) removeDevice(id uuid.UUID) error { } func (pnd *pndImplementation) MarshalDevice(identifier string) (string, error) { - foundDevice, err := pnd.devices.GetDevice(FromString(identifier)) + foundDevice, err := pnd.devices.GetDevice(store.FromString(identifier)) if err != nil { return "", err } @@ -231,7 +234,7 @@ func (pnd *pndImplementation) MarshalDevice(identifier string) (string, error) { // Request sends a get request to a specific device func (pnd *pndImplementation) Request(uuid uuid.UUID, path string) (proto.Message, error) { - d, err := pnd.devices.GetDevice(FromString(uuid.String())) + d, err := pnd.devices.GetDevice(store.FromString(uuid.String())) if err != nil { return nil, err } @@ -360,13 +363,6 @@ func (pnd *pndImplementation) handleCsbiEnrolment(name string, opt *tpb.Transpor return nil } -// DeviceDetails contains details of a device used by the cSBI mechanism -type DeviceDetails struct { - ID string - Address string - TransportOption *tpb.TransportOption -} - func (pnd *pndImplementation) createCsbiDevice(name string, d *cpb.Deployment, opt *tpb.TransportOption) error { defer func() { if r := recover(); r != nil { @@ -377,7 +373,7 @@ func (pnd *pndImplementation) createCsbiDevice(name string, d *cpb.Deployment, o if err != nil { return err } - ch := make(chan DeviceDetails, 1) + ch := make(chan store.DeviceDetails, 1) pnd.callback(id, ch) go func() { deviceDetails := <-ch @@ -393,7 +389,7 @@ func (pnd *pndImplementation) createCsbiDevice(name string, d *cpb.Deployment, o panic(err) } d.(*CsbiDevice).UUID = id - ch <- DeviceDetails{TransportOption: opt} + ch <- store.DeviceDetails{TransportOption: opt} if err := pnd.devices.Add(d, d.Name()); err != nil { panic(err) } diff --git a/nucleus/principalNetworkDomain_test.go b/nucleus/principalNetworkDomain_test.go index 2c01bf1b917a53e7cf13492a7a03c90e5cb37154..d14fd7f76b3a18f26e0d6134401ac47512768b6d 100644 --- a/nucleus/principalNetworkDomain_test.go +++ b/nucleus/principalNetworkDomain_test.go @@ -12,6 +12,7 @@ import ( "code.fbi.h-da.de/danet/gosdn/interfaces/networkdomain" "code.fbi.h-da.de/danet/gosdn/interfaces/southbound" "code.fbi.h-da.de/danet/gosdn/mocks" + "code.fbi.h-da.de/danet/gosdn/store" "code.fbi.h-da.de/danet/yang-models/generated/openconfig" "github.com/google/uuid" gpb "github.com/openconfig/gnmi/proto/gnmi" @@ -110,7 +111,7 @@ func Test_pndImplementation_AddDevice(t *testing.T) { t.Error(err) } if tt.name == "already exists" { - pnd.devices.genericStore[did] = tt.args.device.(device.Device) + pnd.devices.Store[did] = tt.args.device.(device.Device) } err := pnd.AddDevice(tt.args.name, tt.args.opts, defaultSbiID) if (err != nil) != tt.wantErr { @@ -118,7 +119,7 @@ func Test_pndImplementation_AddDevice(t *testing.T) { } if tt.name != "fails wrong type" { if err == nil { - d, err := pnd.devices.GetDevice(FromString(tt.args.name)) + d, err := pnd.devices.GetDevice(store.FromString(tt.args.name)) if err != nil { t.Errorf("AddDevice() error = %v", err) return @@ -135,6 +136,7 @@ func Test_pndImplementation_AddDevice(t *testing.T) { } } +// TODO: refactor test to use store interface instead if direct access. func Test_pndImplementation_AddSbi(t *testing.T) { type args struct { sbi southbound.SouthboundInterface @@ -167,7 +169,7 @@ func Test_pndImplementation_AddSbi(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pnd := newPnd() if tt.name == "already exists" { - pnd.sbic.genericStore[defaultSbiID] = tt.args.sbi + pnd.sbic.Store[defaultSbiID] = tt.args.sbi } err := pnd.AddSbi(tt.args.sbi) if (err != nil) != tt.wantErr { @@ -175,7 +177,7 @@ func Test_pndImplementation_AddSbi(t *testing.T) { } if tt.name != "fails wrong type" { if err == nil { - _, ok := pnd.sbic.genericStore[defaultSbiID] + _, ok := pnd.sbic.Store[defaultSbiID] if !ok { t.Errorf("AddSbi() SBI %v not in device store %v", tt.args.sbi, pnd.GetSBIs()) @@ -234,8 +236,8 @@ func Test_pndImplementation_Destroy(t *testing.T) { type fields struct { name string description string - sbi SbiStore - devices *DeviceStore + sbi *store.SbiStore + devices *store.DeviceStore } tests := []struct { name string @@ -297,9 +299,9 @@ func Test_pndImplementation_GetSBIs(t *testing.T) { pnd := newPnd() tests := []struct { name string - want *SbiStore + want *store.SbiStore }{ - {name: "default", want: &pnd.sbic}, + {name: "default", want: pnd.sbic}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -404,8 +406,8 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) { pnd := &pndImplementation{ name: "test-remove-sbi", description: "test-remove-sbi", - sbic: SbiStore{genericStore{}}, - devices: NewDeviceStore(), + sbic: store.NewSbiStore(), + devices: store.NewDeviceStore(), id: defaultPndID, } if tt.name != "fails empty" { @@ -624,7 +626,7 @@ func Test_pndImplementation_ChangeOND(t *testing.T) { return } - did, ok := pnd.devices.deviceNameToUUIDLookup["testdevice"] + did, ok := pnd.devices.DeviceNameToUUIDLookup["testdevice"] if !ok { err := errors.New("error fetching device") t.Error(err) @@ -637,8 +639,8 @@ func Test_pndImplementation_ChangeOND(t *testing.T) { return } if !tt.wantErr { - if len(pnd.changes.genericStore) != 1 { - t.Errorf("ChangeOND() unexpected change count. got %v, want 1", len(pnd.changes.genericStore)) + if len(pnd.changes.Store) != 1 { + t.Errorf("ChangeOND() unexpected change count. got %v, want 1", len(pnd.changes.Store)) } } }) @@ -797,7 +799,7 @@ func Test_pndImplementation_PendingChanges(t *testing.T) { return nil } - store := NewChangeStore() + store := store.NewChangeStore() pending := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) if err := store.Add(pending); err != nil { t.Error(err) @@ -815,7 +817,7 @@ func Test_pndImplementation_PendingChanges(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pnd := newPnd() - pnd.changes = *store + pnd.changes = store if got := pnd.PendingChanges(); !reflect.DeepEqual(got, tt.want) { t.Errorf("pndImplementation.PendingChanges() = %v, want %v", got, tt.want) } @@ -830,7 +832,7 @@ func Test_pndImplementation_CommittedChanges(t *testing.T) { return nil } - store := NewChangeStore() + store := store.NewChangeStore() committed := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) if err := committed.Commit(); err != nil { t.Error(err) @@ -852,7 +854,7 @@ func Test_pndImplementation_CommittedChanges(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pnd := newPnd() - pnd.changes = *store + pnd.changes = store if got := pnd.CommittedChanges(); !reflect.DeepEqual(got, tt.want) { t.Errorf("pndImplementation.CommittedChanges() = %v, want %v", got, tt.want) } @@ -866,7 +868,7 @@ func Test_pndImplementation_ConfirmedChanges(t *testing.T) { log.Infof("callback in test %v", testName) return nil } - store := NewChangeStore() + store := store.NewChangeStore() confirmed := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) if err := confirmed.Commit(); err != nil { t.Error(err) @@ -892,7 +894,7 @@ func Test_pndImplementation_ConfirmedChanges(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pnd := newPnd() - pnd.changes = *store + pnd.changes = store if got := pnd.ConfirmedChanges(); !reflect.DeepEqual(got, tt.want) { t.Errorf("pndImplementation.ConfirmedChanges() = %v, want %v", got, tt.want) } diff --git a/nucleus/store.go b/nucleus/store.go deleted file mode 100644 index 1d37668d72ca9e080609c3d840d2efc1150bbda5..0000000000000000000000000000000000000000 --- a/nucleus/store.go +++ /dev/null @@ -1,345 +0,0 @@ -package nucleus - -import ( - "fmt" - "reflect" - "sync" - - ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd" - - "code.fbi.h-da.de/danet/gosdn/interfaces/device" - "code.fbi.h-da.de/danet/gosdn/interfaces/networkdomain" - "code.fbi.h-da.de/danet/gosdn/interfaces/southbound" - "code.fbi.h-da.de/danet/gosdn/interfaces/store" - "code.fbi.h-da.de/danet/gosdn/nucleus/errors" - - "github.com/google/uuid" - log "github.com/sirupsen/logrus" -) - -var storeLock sync.RWMutex - -// NewStore returns a generic Store -func NewStore() store.Store { - return genericStore{} -} - -// NewPndStore returns a PndStore -func NewPndStore() *PndStore { - return &PndStore{ - genericStore: genericStore{}, - pendingChannels: make(map[uuid.UUID]chan DeviceDetails, 0), - } -} - -// NewSbiStore returns a SbiStore -func NewSbiStore() *SbiStore { - return &SbiStore{genericStore{}} -} - -// NewDeviceStore returns a DeviceStore -func NewDeviceStore() *DeviceStore { - return &DeviceStore{genericStore: genericStore{}, deviceNameToUUIDLookup: make(map[string]uuid.UUID)} -} - -// NewChangeStore returns a ChangeStore -func NewChangeStore() *ChangeStore { - return &ChangeStore{genericStore{}} -} - -type genericStore map[uuid.UUID]store.Storable - -// Exists takes a Storable's UUID and checks its existence in the store. -func (s genericStore) Exists(id uuid.UUID) bool { - storeLock.RLock() - defer storeLock.RUnlock() - _, ok := s[id] - return ok -} - -// Add adds a Storable to the Store -func (s genericStore) Add(item store.Storable) error { - if s.Exists(item.ID()) { - return &errors.ErrAlreadyExists{Item: item} - } - storeLock.Lock() - s[item.ID()] = item - storeLock.Unlock() - log.WithFields(log.Fields{ - "type": reflect.TypeOf(item), - "uuid": item.ID(), - }).Debug("storable was added") - return nil -} - -// Get takes a Storable's UUID and returns the Storable. If the requested -// Storable does not exist an error is returned. Get is only type safe for -// this Storable interface. For type safe get operations on specialised stores -// use GetDevice, GetPND, GetSBI, or GetChange respectively. -func (s genericStore) Get(id uuid.UUID) (store.Storable, error) { - if !s.Exists(id) { - return nil, &errors.ErrNotFound{ID: id} - } - storeLock.RLock() - defer storeLock.RUnlock() - return s[id], nil -} - -// Delete takes a Storable's UUID and deletes it. If the specified UUID does not -// exist in the Store an error is returned. -func (s genericStore) Delete(id uuid.UUID) error { - if !s.Exists(id) { - return &errors.ErrNotFound{ID: id} - } - storeLock.Lock() - delete(s, id) - storeLock.Unlock() - log.WithFields(log.Fields{ - "uuid": id, - }).Debug("storable was deleted") - return nil -} - -// UUIDs returns all UUIDs in the store. -func (s genericStore) UUIDs() []uuid.UUID { - storeLock.RLock() - defer storeLock.RUnlock() - keys := make([]uuid.UUID, len(s)) - i := 0 - for k := range s { - keys[i] = k - i++ - } - return keys -} - -// SbiStore is used to store SouthboundInterfaces -type SbiStore struct { - genericStore -} - -// GetSBI takes a SouthboundInterface's UUID and returns the SouthboundInterface. If the requested -// SouthboundInterface does not exist an error is returned. -func (s SbiStore) GetSBI(id uuid.UUID) (southbound.SouthboundInterface, error) { - item, err := s.genericStore.Get(id) - if err != nil { - return nil, err - } - sbi, ok := item.(southbound.SouthboundInterface) - if !ok { - return nil, &errors.ErrInvalidTypeAssertion{ - Value: sbi, - Type: "SouthboundInterface", - } - } - log.WithFields(log.Fields{ - "uuid": id, - }).Debug("southbound interface was accessed") - return sbi, nil -} - -// PndStore is used to store PrincipalNetworkDomains -type PndStore struct { - genericStore - pendingChannels map[uuid.UUID]chan DeviceDetails -} - -// GetPND takes a PrincipalNetworkDomain's UUID and returns the PrincipalNetworkDomain. If the requested -// PrincipalNetworkDomain does not exist an error is returned. -func (s PndStore) GetPND(id uuid.UUID) (networkdomain.NetworkDomain, error) { - item, err := s.genericStore.Get(id) - if err != nil { - return nil, err - } - pnd, ok := item.(networkdomain.NetworkDomain) - if !ok { - return nil, &errors.ErrInvalidTypeAssertion{ - Value: pnd, - Type: "PrincipalNetworkDomain", - } - } - log.WithFields(log.Fields{ - "uuid": id, - }).Debug("principal network domain was accessed") - return pnd, nil -} - -// PendingChannels holds channels used communicate with pending -// cSBI deployments -func (s PndStore) PendingChannels(id uuid.UUID, parseErrors ...error) (chan DeviceDetails, error) { - ch, ok := s.pendingChannels[id] - if !ok { - return nil, &errors.ErrNotFound{ID: id} - } - return ch, nil -} - -// AddPendingChannel adds a pending channel to the map -func (s PndStore) AddPendingChannel(id uuid.UUID, ch chan DeviceDetails) { - s.pendingChannels[id] = ch -} - -// RemovePendingChannel removes a pending channel from the map -func (s PndStore) RemovePendingChannel(id uuid.UUID) { - delete(s.pendingChannels, id) -} - -// DeviceStore is used to store Devices -type DeviceStore struct { - deviceNameToUUIDLookup map[string]uuid.UUID - genericStore -} - -// GetDevice takes a Device's UUID and returns the Device. If the requested -// Device does not exist an error is returned. -func (s DeviceStore) GetDevice(id uuid.UUID, parseErrors ...error) (device.Device, error) { - var foundID uuid.UUID - - foundID = id - - for _, parseErrs := range parseErrors { - if parseErrs != nil { - switch e := parseErrs.(type) { - case *errors.ErrInvalidUUID: - myID, ok := s.deviceNameToUUIDLookup[e.DeviceName] - if !ok { - log.Debug(fmt.Sprintf("no device named %s found", foundID)) - return nil, &errors.ErrNotFound{} - } - - foundID = myID - } - } - } - - item, err := s.genericStore.Get(foundID) - if err != nil { - return nil, err - } - d, ok := item.(device.Device) - if !ok { - return nil, &errors.ErrInvalidTypeAssertion{ - Value: d, - Type: reflect.TypeOf((device.Device)(nil)), - } - } - log.WithFields(log.Fields{ - "uuid": foundID, - "name": d.Name(), - }).Debug("device was accessed") - - return d, nil -} - -// FromString is a helper to check if a provided string as a valid UUID or a name. -func FromString(id string) (uuid.UUID, error) { - idAsUUID, err := uuid.Parse(id) - - // id is no UUID therefore it could be a device name. - // The name will be returned within the error. - if err != nil { - log.WithFields(log.Fields{ - "identifier": id, - }).Debug(err) - return uuid.Nil, &errors.ErrInvalidUUID{DeviceName: id} - } - - return idAsUUID, nil -} - -// Add adds a device to the device store. -// It also adds the name of the device to the lookup table. -func (s DeviceStore) Add(item store.Storable, name string) error { - if s.Exists(item.ID()) { - return &errors.ErrAlreadyExists{Item: item} - } - - s.deviceNameToUUIDLookup[name] = item.ID() - - storeLock.Lock() - s.genericStore[item.ID()] = item - storeLock.Unlock() - - log.WithFields(log.Fields{ - "type": reflect.TypeOf(item), - "uuid": item.ID(), - }).Debug("storable was added") - - return nil -} - -// Delete deletes a device from the device store. -func (s DeviceStore) Delete(id uuid.UUID) error { - if !s.Exists(id) { - return &errors.ErrNotFound{ID: id} - } - - storeLock.Lock() - delete(s.genericStore, id) - storeLock.Unlock() - - for key, value := range s.deviceNameToUUIDLookup { - if value == id { - delete(s.deviceNameToUUIDLookup, key) - } - } - - log.WithFields(log.Fields{ - "uuid": id, - }).Debug("storable was deleted") - - return nil -} - -// ChangeStore is used to store Changes -type ChangeStore struct { - genericStore -} - -// GetChange takes a Change's UUID and returns the Change. If the requested -// Change does not exist an error is returned. -func (s ChangeStore) GetChange(id uuid.UUID) (*Change, error) { - item, err := s.genericStore.Get(id) - if err != nil { - return nil, err - } - change, ok := item.(*Change) - if !ok { - return nil, &errors.ErrInvalidTypeAssertion{ - Value: change, - Type: reflect.TypeOf(&Change{}), - } - } - log.WithFields(log.Fields{ - "uuid": id, - }).Debug("change was accessed") - return change, nil -} - -// Pending returns the UUIDs of all pending changes -func (s ChangeStore) Pending() []uuid.UUID { - return filterChanges(s, ppb.Change_PENDING) -} - -// Committed returns the UUIDs of all pending changes -func (s ChangeStore) Committed() []uuid.UUID { - return filterChanges(s, ppb.Change_COMMITTED) -} - -// Confirmed returns the UUIDs of all pending changes -func (s ChangeStore) Confirmed() []uuid.UUID { - return filterChanges(s, ppb.Change_CONFIRMED) -} - -func filterChanges(store ChangeStore, state ppb.Change_State) []uuid.UUID { - changes := make([]uuid.UUID, 0) - for _, ch := range store.genericStore { - switch change := ch.(type) { - case *Change: - if change.State() == state { - changes = append(changes, change.cuid) - } - } - } - return changes -} diff --git a/nucleus/store_test.go b/nucleus/store_test.go deleted file mode 100644 index bc22db429bdf3b54da943b1ef5a1e66b24069a7d..0000000000000000000000000000000000000000 --- a/nucleus/store_test.go +++ /dev/null @@ -1,613 +0,0 @@ -package nucleus - -import ( - "reflect" - "sort" - "testing" - - ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd" - "code.fbi.h-da.de/danet/yang-models/generated/openconfig" - - "code.fbi.h-da.de/danet/gosdn/interfaces/device" - "code.fbi.h-da.de/danet/gosdn/interfaces/networkdomain" - "code.fbi.h-da.de/danet/gosdn/interfaces/southbound" - "code.fbi.h-da.de/danet/gosdn/interfaces/store" - - "code.fbi.h-da.de/danet/gosdn/mocks" - "github.com/google/uuid" - "github.com/openconfig/ygot/ygot" - log "github.com/sirupsen/logrus" -) - -func Test_Store_add(t *testing.T) { - type args struct { - item store.Storable - } - tests := []struct { - name string - s genericStore - args args - wantErr bool - }{ - { - name: "default", - s: genericStore{}, - args: args{ - item: &mocks.Storable{}, - }, - }, - { - name: "already exists", - s: genericStore{ - iid: &mocks.Storable{}, - }, - args: args{ - item: &mocks.Storable{}, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.args.item.(*mocks.Storable).On("ID").Return(iid) - switch tt.name { - case "already exixts": - _ = tt.s.Add(tt.args.item) - default: - } - if err := tt.s.Add(tt.args.item); (err != nil) != tt.wantErr { - t.Errorf("Add() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_Store_delete(t *testing.T) { - type args struct { - id uuid.UUID - } - tests := []struct { - name string - s genericStore - args args - wantErr bool - }{ - { - name: "default", - s: genericStore{ - iid: &mocks.Storable{}, - }, - args: args{id: iid}, - wantErr: false, - }, - { - name: "not found empty", - s: genericStore{}, - args: args{id: iid}, - wantErr: true, - }, - { - name: "not found", - s: genericStore{ - iid: &mocks.Storable{}, - }, - args: args{id: altIid}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.s.Delete(tt.args.id); (err != nil) != tt.wantErr { - t.Errorf("delete() error = %v, wantErr %v", err, tt.wantErr) - } - if tt.name == "default" { - item, ok := tt.s[iid] - if ok { - t.Errorf("delete() item %v still in genericStore %v", item, tt.s) - } - } - }) - } -} - -func Test_Store_exists(t *testing.T) { - type args struct { - id uuid.UUID - } - tests := []struct { - name string - s genericStore - args args - want bool - }{ - { - name: "default", - s: genericStore{ - iid: &mocks.Storable{}, - }, - args: args{id: iid}, - want: true, - }, - { - name: "not found empty", - s: genericStore{}, - args: args{id: iid}, - want: false, - }, - { - name: "not found", - s: genericStore{ - iid: &mocks.Storable{}, - }, - args: args{id: altIid}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.s.Exists(tt.args.id); got != tt.want { - t.Errorf("exists() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Store_get(t *testing.T) { - type args struct { - id uuid.UUID - } - tests := []struct { - name string - s genericStore - args args - want store.Storable - wantErr bool - }{ - { - name: "exists", - s: genericStore{ - iid: &mocks.Storable{}, - }, - args: args{id: iid}, - want: &mocks.Storable{}, - wantErr: false, - }, - { - name: "not found", - s: genericStore{ - iid: &mocks.Storable{}, - }, - args: args{id: altIid}, - want: nil, - wantErr: true, - }, - { - name: "not found empty", - s: genericStore{}, - args: args{id: iid}, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.s.Get(tt.args.id) - if (err != nil) != tt.wantErr { - t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("get() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Store_UUIDs(t *testing.T) { - tests := []struct { - name string - s genericStore - want []uuid.UUID - }{ - { - name: "default", - s: genericStore{ - iid: &mocks.Storable{}, - altIid: &mocks.Storable{}, - }, - want: []uuid.UUID{iid, altIid}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sort.Slice(tt.want, func(i, j int) bool { - return tt.want[i].String() < tt.want[j].String() - }) - got := tt.s.UUIDs() - sort.Slice(got, func(i, j int) bool { - return got[i].String() < got[j].String() - }) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("UUIDs() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_sbiStore_get(t *testing.T) { - type fields struct { - genericStore genericStore - } - type args struct { - id uuid.UUID - } - tests := []struct { - name string - fields fields - args args - want southbound.SouthboundInterface - wantErr bool - }{ - { - name: "exists", - fields: fields{ - genericStore: genericStore{ - defaultSbiID: &OpenConfig{id: defaultSbiID}, - }, - }, - args: args{id: defaultSbiID}, - want: &OpenConfig{id: defaultSbiID}, - wantErr: false, - }, - { - name: "fails", - fields: fields{ - genericStore: genericStore{ - defaultSbiID: &OpenConfig{id: defaultSbiID}, - }, - }, - args: args{id: iid}, - wantErr: true, - }, - { - name: "fails empty", - fields: fields{ - genericStore: genericStore{}, - }, - args: args{id: defaultSbiID}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := SbiStore{ - genericStore: tt.fields.genericStore, - } - got, err := s.Get(tt.args.id) - if (err != nil) != tt.wantErr { - t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("get() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_pndStore_get(t *testing.T) { - type fields struct { - genericStore genericStore - } - type args struct { - id uuid.UUID - } - tests := []struct { - name string - fields fields - args args - want networkdomain.NetworkDomain - wantErr bool - }{ - { - name: "exists", - fields: fields{ - genericStore: genericStore{ - defaultPndID: &pndImplementation{id: defaultPndID}, - }, - }, - args: args{id: defaultPndID}, - want: &pndImplementation{id: defaultPndID}, - wantErr: false, - }, - { - name: "fails", - fields: fields{ - genericStore: genericStore{ - defaultPndID: &pndImplementation{id: defaultPndID}, - }, - }, - args: args{id: iid}, - wantErr: true, - }, - { - name: "fails empty", - fields: fields{ - genericStore: genericStore{}, - }, - args: args{id: defaultPndID}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := PndStore{ - genericStore: tt.fields.genericStore, - } - got, err := s.Get(tt.args.id) - if (err != nil) != tt.wantErr { - t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("get() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_deviceStore_get(t *testing.T) { - type fields struct { - genericStore genericStore - } - type args struct { - id uuid.UUID - } - tests := []struct { - name string - fields fields - args args - want device.Device - wantErr bool - }{ - { - name: "exists", - fields: fields{ - genericStore: genericStore{ - defaultPndID: &CommonDevice{UUID: did}}}, - args: args{id: defaultPndID}, - want: &CommonDevice{ - UUID: did, - }, - wantErr: false, - }, - { - name: "fails", - fields: fields{ - genericStore: genericStore{ - defaultPndID: &CommonDevice{UUID: did}}}, - args: args{id: iid}, - wantErr: true, - }, - { - name: "fails empty", - fields: fields{ - genericStore: genericStore{}, - }, - args: args{id: defaultPndID}, - wantErr: true, - }, - { - name: "fails wrong type", - fields: fields{ - genericStore: genericStore{ - defaultPndID: &pndImplementation{id: defaultPndID}}}, - args: args{id: defaultPndID}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := DeviceStore{ - genericStore: tt.fields.genericStore, - deviceNameToUUIDLookup: make(map[string]uuid.UUID), - } - - got, err := s.GetDevice(FromString(tt.args.id.String())) - if (err != nil) != tt.wantErr { - t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("get() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestChangeStore_Pending(t *testing.T) { - testName := t.Name() - callback := func(first ygot.GoStruct, second ygot.GoStruct) error { - log.Infof("callback in test %v", testName) - return nil - } - - store := NewChangeStore() - pending := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) - if err := store.Add(pending); err != nil { - t.Error(err) - return - } - tests := []struct { - name string - want []uuid.UUID - }{ - { - name: "default", - want: []uuid.UUID{pending.cuid}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := store - if got := s.Pending(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Pending() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestChangeStore_Committed(t *testing.T) { - testName := t.Name() - callback := func(first ygot.GoStruct, second ygot.GoStruct) error { - log.Infof("callback in test %v", testName) - return nil - } - - store := NewChangeStore() - committed := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) - if err := committed.Commit(); err != nil { - t.Error(err) - return - } - if err := store.Add(committed); err != nil { - t.Error(err) - return - } - tests := []struct { - name string - want []uuid.UUID - }{ - { - name: "default", - want: []uuid.UUID{committed.cuid}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := store - if got := s.Committed(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Committed() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestChangeStore_Confirmed(t *testing.T) { - testName := t.Name() - callback := func(first ygot.GoStruct, second ygot.GoStruct) error { - log.Infof("callback in test %v", testName) - return nil - } - - store := NewChangeStore() - confirmed := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) - if err := confirmed.Commit(); err != nil { - t.Error(err) - return - } - if err := confirmed.Confirm(); err != nil { - t.Error(err) - return - } - if err := store.Add(confirmed); err != nil { - t.Error(err) - return - } - - tests := []struct { - name string - want []uuid.UUID - }{ - { - name: "default", - want: []uuid.UUID{confirmed.cuid}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := store - if got := s.Confirmed(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Confirmed() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_filterChanges(t *testing.T) { - testName := t.Name() - callback := func(first ygot.GoStruct, second ygot.GoStruct) error { - log.Infof("callback in test %v", testName) - return nil - } - - store := NewChangeStore() - pending := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) - committed := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) - if err := committed.Commit(); err != nil { - t.Error(err) - return - } - confirmed := NewChange(did, &openconfig.Device{}, &openconfig.Device{}, callback) - if err := confirmed.Commit(); err != nil { - t.Error(err) - return - } - if err := confirmed.Confirm(); err != nil { - t.Error(err) - return - } - if err := store.Add(pending); err != nil { - t.Error(err) - return - } - if err := store.Add(committed); err != nil { - t.Error(err) - return - } - if err := store.Add(confirmed); err != nil { - t.Error(err) - return - } - type args struct { - store ChangeStore - state ppb.Change_State - } - tests := []struct { - name string - args args - want []uuid.UUID - }{ - { - name: "pending", - args: args{ - store: *store, - state: ppb.Change_PENDING, - }, - want: []uuid.UUID{pending.cuid}, - }, - { - name: "committed", - args: args{ - store: *store, - state: ppb.Change_COMMITTED, - }, - want: []uuid.UUID{committed.cuid}, - }, - { - name: "confirmed", - args: args{ - store: *store, - state: ppb.Change_CONFIRMED, - }, - want: []uuid.UUID{confirmed.cuid}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := filterChanges(tt.args.store, tt.args.state); !reflect.DeepEqual(got, tt.want) { - t.Errorf("filterChanges() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/store/changeStores.go b/store/changeStores.go new file mode 100644 index 0000000000000000000000000000000000000000..974c6f13eff8e453ee9d05e85639d3d6f7b8e7b0 --- /dev/null +++ b/store/changeStores.go @@ -0,0 +1,68 @@ +package store + +import ( + "code.fbi.h-da.de/danet/gosdn/interfaces/change" + "code.fbi.h-da.de/danet/gosdn/nucleus/errors" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + + ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd" +) + +// ChangeStore is used to store Changes +type ChangeStore struct { + *genericStore +} + +// NewChangeStore returns a ChangeStore +func NewChangeStore() *ChangeStore { + return &ChangeStore{genericStore: newGenericStore()} +} + +// GetChange takes a Change's UUID and returns the Change. If the requested +// Change does not exist an error is returned. +func (s *ChangeStore) GetChange(id uuid.UUID) (change.Change, error) { + item, err := s.genericStore.Get(id) + if err != nil { + return nil, err + } + change, ok := item.(change.Change) + if !ok { + return nil, &errors.ErrInvalidTypeAssertion{ + Value: change, + Type: "change", + } + } + log.WithFields(log.Fields{ + "uuid": id, + }).Debug("change was accessed") + return change, nil +} + +// Pending returns the UUIDs of all pending changes +func (s *ChangeStore) Pending() []uuid.UUID { + return filterChanges(s, ppb.Change_PENDING) +} + +// Committed returns the UUIDs of all pending changes +func (s *ChangeStore) Committed() []uuid.UUID { + return filterChanges(s, ppb.Change_COMMITTED) +} + +// Confirmed returns the UUIDs of all pending changes +func (s *ChangeStore) Confirmed() []uuid.UUID { + return filterChanges(s, ppb.Change_CONFIRMED) +} + +func filterChanges(store *ChangeStore, state ppb.Change_State) []uuid.UUID { + changes := make([]uuid.UUID, 0) + for _, ch := range store.Store { + switch change := ch.(type) { + case change.Change: + if change.State() == state { + changes = append(changes, change.ID()) + } + } + } + return changes +} diff --git a/store/change_store_test.go b/store/change_store_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fee6c99bd07c75114c41eac9648ef77396dc6256 --- /dev/null +++ b/store/change_store_test.go @@ -0,0 +1,226 @@ +package store + +import ( + "log" + "reflect" + "testing" + + "code.fbi.h-da.de/danet/api/go/gosdn/pnd" + ppb "code.fbi.h-da.de/danet/api/go/gosdn/pnd" + "code.fbi.h-da.de/danet/gosdn/mocks" + "github.com/google/uuid" +) + +func TestChangeStore_Pending(t *testing.T) { + changeMock := &mocks.Change{} + changeMock.On("ID").Return(cuid) + changeMock.On("State").Return(pnd.Change_PENDING) + + store := NewChangeStore() + pending := changeMock + if err := store.Add(pending); err != nil { + t.Error(err) + return + } + tests := []struct { + name string + want []uuid.UUID + }{ + { + name: "default", + want: []uuid.UUID{cuid}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := store + if got := s.Pending(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Pending() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestChangeStore_Committed(t *testing.T) { + changeMock := &mocks.Change{} + changeMock.On("ID").Return(cuid) + changeMock.On("State").Return(pnd.Change_COMMITTED) + changeMock.On("Commit").Return(nil) + + store := NewChangeStore() + committed := changeMock + if err := committed.Commit(); err != nil { + t.Error(err) + return + } + if err := store.Add(committed); err != nil { + t.Error(err) + return + } + tests := []struct { + name string + want []uuid.UUID + }{ + { + name: "default", + want: []uuid.UUID{cuid}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := store + if got := s.Committed(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Committed() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestChangeStore_Confirmed(t *testing.T) { + changeMock := &mocks.Change{} + changeMock.On("ID").Return(cuid) + changeMock.On("State").Return(pnd.Change_CONFIRMED) + changeMock.On("Commit").Return(nil) + changeMock.On("Confirm").Return(nil) + + store := NewChangeStore() + confirmed := changeMock + if err := confirmed.Commit(); err != nil { + t.Error(err) + return + } + if err := confirmed.Confirm(); err != nil { + t.Error(err) + return + } + if err := store.Add(confirmed); err != nil { + t.Error(err) + return + } + + tests := []struct { + name string + want []uuid.UUID + }{ + { + name: "default", + want: []uuid.UUID{cuid}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := store + if got := s.Confirmed(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Confirmed() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_filterChanges(t *testing.T) { + var pendingCUID uuid.UUID + var committedCUID uuid.UUID + var confirmedCUID uuid.UUID + var err error + + pendingCUID, err = uuid.Parse("3e8219b0-e926-400d-8660-217f2a25a7c7") + if err != nil { + log.Fatal(err) + } + committedCUID, err = uuid.Parse("3e8219b0-e926-400d-8660-217f2a25a7c8") + if err != nil { + log.Fatal(err) + } + confirmedCUID, err = uuid.Parse("3e8219b0-e926-400d-8660-217f2a25a7c9") + if err != nil { + log.Fatal(err) + } + + changeMockPending := &mocks.Change{} + changeMockPending.On("ID").Return(pendingCUID) + changeMockPending.On("State").Return(pnd.Change_PENDING) + changeMockPending.On("Commit").Return(nil) + changeMockPending.On("Confirm").Return(nil) + + changeMockCommited := &mocks.Change{} + changeMockCommited.On("ID").Return(committedCUID) + changeMockCommited.On("State").Return(pnd.Change_COMMITTED) + changeMockCommited.On("Commit").Return(nil) + changeMockCommited.On("Confirm").Return(nil) + + changeMockConfirmed := &mocks.Change{} + changeMockConfirmed.On("ID").Return(confirmedCUID) + changeMockConfirmed.On("State").Return(pnd.Change_CONFIRMED) + changeMockConfirmed.On("Commit").Return(nil) + changeMockConfirmed.On("Confirm").Return(nil) + + store := NewChangeStore() + pending := changeMockPending + committed := changeMockCommited + if err := committed.Commit(); err != nil { + t.Error(err) + return + } + confirmed := changeMockConfirmed + if err := confirmed.Commit(); err != nil { + t.Error(err) + return + } + if err := confirmed.Confirm(); err != nil { + t.Error(err) + return + } + if err := store.Add(pending); err != nil { + t.Error(err) + return + } + if err := store.Add(committed); err != nil { + t.Error(err) + return + } + if err := store.Add(confirmed); err != nil { + t.Error(err) + return + } + type args struct { + store *ChangeStore + state ppb.Change_State + } + tests := []struct { + name string + args args + want []uuid.UUID + }{ + { + name: "pending", + args: args{ + store: store, + state: ppb.Change_PENDING, + }, + want: []uuid.UUID{pendingCUID}, + }, + { + name: "committed", + args: args{ + store: store, + state: ppb.Change_COMMITTED, + }, + want: []uuid.UUID{committedCUID}, + }, + { + name: "confirmed", + args: args{ + store: store, + state: ppb.Change_CONFIRMED, + }, + want: []uuid.UUID{confirmedCUID}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := filterChanges(tt.args.store, tt.args.state); !reflect.DeepEqual(got, tt.want) { + t.Errorf("filterChanges() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/store/deviceStore.go b/store/deviceStore.go new file mode 100644 index 0000000000000000000000000000000000000000..e4bf0e419b193be57b3cc786c1f4c69721f5d9cf --- /dev/null +++ b/store/deviceStore.go @@ -0,0 +1,109 @@ +package store + +import ( + "fmt" + "reflect" + + "code.fbi.h-da.de/danet/gosdn/interfaces/device" + "code.fbi.h-da.de/danet/gosdn/interfaces/store" + "code.fbi.h-da.de/danet/gosdn/nucleus/errors" + + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +// DeviceStore is used to store Devices +type DeviceStore struct { + DeviceNameToUUIDLookup map[string]uuid.UUID + *genericStore +} + +// NewDeviceStore returns a DeviceStore +func NewDeviceStore() *DeviceStore { + return &DeviceStore{genericStore: newGenericStore(), DeviceNameToUUIDLookup: make(map[string]uuid.UUID)} +} + +// GetDevice takes a Device's UUID and returns the Device. If the requested +// Device does not exist an error is returned. +func (s *DeviceStore) GetDevice(id uuid.UUID, parseErrors ...error) (device.Device, error) { + var foundID uuid.UUID + + foundID = id + + for _, parseErrs := range parseErrors { + if parseErrs != nil { + switch e := parseErrs.(type) { + case *errors.ErrInvalidUUID: + myID, ok := s.DeviceNameToUUIDLookup[e.DeviceName] + if !ok { + log.Debug(fmt.Sprintf("no device named %s found", foundID)) + return nil, &errors.ErrNotFound{} + } + + foundID = myID + } + } + } + + item, err := s.genericStore.Get(foundID) + if err != nil { + return nil, err + } + d, ok := item.(device.Device) + if !ok { + return nil, &errors.ErrInvalidTypeAssertion{ + Value: d, + Type: reflect.TypeOf((device.Device)(nil)), + } + } + log.WithFields(log.Fields{ + "uuid": foundID, + "name": d.Name(), + }).Debug("device was accessed") + + return d, nil +} + +// Add adds a device to the device store. +// It also adds the name of the device to the lookup table. +func (s *DeviceStore) Add(item store.Storable, name string) error { + if s.Exists(item.ID()) { + return &errors.ErrAlreadyExists{Item: item} + } + + s.DeviceNameToUUIDLookup[name] = item.ID() + + s.storeLock.Lock() + s.genericStore.Store[item.ID()] = item + s.storeLock.Unlock() + + log.WithFields(log.Fields{ + "type": reflect.TypeOf(item), + "uuid": item.ID(), + }).Debug("storable was added") + + return nil +} + +// Delete deletes a device from the device store. +func (s *DeviceStore) Delete(id uuid.UUID) error { + if !s.Exists(id) { + return &errors.ErrNotFound{ID: id} + } + + s.storeLock.Lock() + delete(s.genericStore.Store, id) + s.storeLock.Unlock() + + for key, value := range s.DeviceNameToUUIDLookup { + if value == id { + delete(s.DeviceNameToUUIDLookup, key) + } + } + + log.WithFields(log.Fields{ + "uuid": id, + }).Debug("storable was deleted") + + return nil +} diff --git a/store/device_store_test.go b/store/device_store_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ad2257632b5f1570d4985307a0ec237506c61252 --- /dev/null +++ b/store/device_store_test.go @@ -0,0 +1,97 @@ +package store + +import ( + "reflect" + "sync" + "testing" + + "code.fbi.h-da.de/danet/gosdn/interfaces/device" + "code.fbi.h-da.de/danet/gosdn/interfaces/store" + "code.fbi.h-da.de/danet/gosdn/mocks" + "github.com/google/uuid" +) + +func Test_deviceStore_get(t *testing.T) { + deviceMock := &mocks.Device{} + deviceMock.On("ID").Return(did) + deviceMock.On("Name").Return("did") + pndMock := &mocks.NetworkDomain{} + pndMock.On("ID").Return(did) + + type fields struct { + genericStore *genericStore + } + type args struct { + id uuid.UUID + } + tests := []struct { + name string + fields fields + args args + want device.Device + wantErr bool + }{ + { + name: "exists", + fields: fields{ + &genericStore{ + Store: map[uuid.UUID]store.Storable{ + defaultPndID: deviceMock, + }, + storeLock: sync.RWMutex{}, + }, + }, + args: args{id: defaultPndID}, + want: deviceMock, + wantErr: false, + }, + { + name: "fails", + fields: fields{ + &genericStore{ + Store: map[uuid.UUID]store.Storable{ + defaultPndID: deviceMock, + }, + storeLock: sync.RWMutex{}, + }, + }, + args: args{id: iid}, + wantErr: true, + }, + { + name: "fails empty", + fields: fields{ + genericStore: &genericStore{}, + }, + args: args{id: defaultPndID}, + wantErr: true, + }, + { + name: "fails wrong type", + fields: fields{ + &genericStore{ + Store: map[uuid.UUID]store.Storable{ + defaultPndID: pndMock, + }, + storeLock: sync.RWMutex{}, + }, + }, + args: args{id: defaultPndID}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := DeviceStore{genericStore: tt.fields.genericStore} + + got, err := s.GetDevice(FromString(tt.args.id.String())) + if (err != nil) != tt.wantErr { + t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("get() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/store/genericStore.go b/store/genericStore.go new file mode 100644 index 0000000000000000000000000000000000000000..83d6bb9333403bc868611c17c29d70c271bfd337 --- /dev/null +++ b/store/genericStore.go @@ -0,0 +1,89 @@ +package store + +import ( + "reflect" + "sync" + + "code.fbi.h-da.de/danet/gosdn/interfaces/store" + "code.fbi.h-da.de/danet/gosdn/nucleus/errors" + + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +// newGenericStore returns a genericStore +func newGenericStore() *genericStore { + return &genericStore{Store: make(map[uuid.UUID]store.Storable), storeLock: sync.RWMutex{}} +} + +type genericStore struct { + Store map[uuid.UUID]store.Storable + storeLock sync.RWMutex +} + +// Exists takes a Storable's UUID and checks its existence in the store. +func (s *genericStore) Exists(id uuid.UUID) bool { + s.storeLock.RLock() + defer s.storeLock.RUnlock() + _, ok := s.Store[id] + return ok +} + +// Add adds a Storable to the Store +func (s *genericStore) Add(item store.Storable) error { + if s.Exists(item.ID()) { + return &errors.ErrAlreadyExists{Item: item} + } + s.storeLock.Lock() + s.Store[item.ID()] = item + s.storeLock.Unlock() + log.WithFields(log.Fields{ + "type": reflect.TypeOf(item), + "uuid": item.ID(), + }).Debug("storable was added") + return nil +} + +// Get takes a Storable's UUID and returns the Storable. If the requested +// Storable does not exist an error is returned. Get is only type safe for +// this Storable interface. For type safe get operations on specialised stores +// use GetDevice, GetPND, GetSBI, or GetChange respectively. +func (s *genericStore) Get(id uuid.UUID) (store.Storable, error) { + if !s.Exists(id) { + return nil, &errors.ErrNotFound{ID: id} + } + log.WithFields(log.Fields{ + "uuid": id, + }).Debug("storable was accessed") + s.storeLock.RLock() + defer s.storeLock.RUnlock() + return s.Store[id], nil +} + +// Delete takes a Storable's UUID and deletes it. If the specified UUID does not +// exist in the Store an error is returned. +func (s *genericStore) Delete(id uuid.UUID) error { + if !s.Exists(id) { + return &errors.ErrNotFound{ID: id} + } + s.storeLock.Lock() + delete(s.Store, id) + s.storeLock.Unlock() + log.WithFields(log.Fields{ + "uuid": id, + }).Debug("storable was deleted") + return nil +} + +// UUIDs returns all UUIDs in the store. +func (s *genericStore) UUIDs() []uuid.UUID { + s.storeLock.RLock() + defer s.storeLock.RUnlock() + keys := make([]uuid.UUID, len(s.Store)) + i := 0 + for k := range s.Store { + keys[i] = k + i++ + } + return keys +} diff --git a/store/generic_store_test.go b/store/generic_store_test.go new file mode 100644 index 0000000000000000000000000000000000000000..feb55d5b1dd68fbc48fc4e54648601253ff42e66 --- /dev/null +++ b/store/generic_store_test.go @@ -0,0 +1,242 @@ +package store + +import ( + "reflect" + "sort" + "testing" + + "code.fbi.h-da.de/danet/gosdn/interfaces/store" + "code.fbi.h-da.de/danet/gosdn/mocks" + "github.com/google/uuid" +) + +func Test_Store_add(t *testing.T) { + type args struct { + item store.Storable + } + tests := []struct { + name string + s *genericStore + args args + wantErr bool + }{ + { + name: "default", + s: newGenericStore(), + args: args{ + item: &mocks.Storable{}, + }, + }, + { + name: "already exists", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + }, + }, + args: args{ + item: &mocks.Storable{}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.item.(*mocks.Storable).On("ID").Return(iid) + switch tt.name { + case "already exixts": + _ = tt.s.Add(tt.args.item) + default: + } + if err := tt.s.Add(tt.args.item); (err != nil) != tt.wantErr { + t.Errorf("Add() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_Store_delete(t *testing.T) { + type args struct { + id uuid.UUID + } + tests := []struct { + name string + s *genericStore + args args + wantErr bool + }{ + { + name: "default", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + }, + }, + args: args{id: iid}, + wantErr: false, + }, + { + name: "not found empty", + s: newGenericStore(), + args: args{id: iid}, + wantErr: true, + }, + { + name: "not found", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + }, + }, + args: args{id: altIid}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Delete(tt.args.id); (err != nil) != tt.wantErr { + t.Errorf("delete() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.name == "default" { + item, ok := tt.s.Store[iid] + if ok { + t.Errorf("delete() item %v still in genericStore %v", item, tt.s) + } + } + }) + } +} + +func Test_Store_exists(t *testing.T) { + type args struct { + id uuid.UUID + } + tests := []struct { + name string + s *genericStore + args args + want bool + }{ + { + name: "default", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + }, + }, + args: args{id: iid}, + want: true, + }, + { + name: "not found empty", + s: newGenericStore(), + args: args{id: iid}, + want: false, + }, + { + name: "not found", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + }, + }, + args: args{id: altIid}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Exists(tt.args.id); got != tt.want { + t.Errorf("exists() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_Store_get(t *testing.T) { + type args struct { + id uuid.UUID + } + tests := []struct { + name string + s *genericStore + args args + want store.Storable + wantErr bool + }{ + { + name: "exists", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + }, + }, + args: args{id: iid}, + want: &mocks.Storable{}, + wantErr: false, + }, + { + name: "not found", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + }, + }, + args: args{id: altIid}, + want: nil, + wantErr: true, + }, + { + name: "not found empty", + s: newGenericStore(), + args: args{id: iid}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Get(tt.args.id) + if (err != nil) != tt.wantErr { + t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("get() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_Store_UUIDs(t *testing.T) { + tests := []struct { + name string + s *genericStore + want []uuid.UUID + }{ + { + name: "default", + s: &genericStore{ + Store: map[uuid.UUID]store.Storable{ + iid: &mocks.Storable{}, + altIid: &mocks.Storable{}, + }, + }, + want: []uuid.UUID{iid, altIid}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sort.Slice(tt.want, func(i, j int) bool { + return tt.want[i].String() < tt.want[j].String() + }) + got := tt.s.UUIDs() + sort.Slice(got, func(i, j int) bool { + return got[i].String() < got[j].String() + }) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("UUIDs() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/store/initialise_test.go b/store/initialise_test.go new file mode 100644 index 0000000000000000000000000000000000000000..732bad161a89a0fe8a7d9b7e92bf261a027e6031 --- /dev/null +++ b/store/initialise_test.go @@ -0,0 +1,60 @@ +package store + +import ( + "os" + "testing" + + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +// UUIDs for test cases +var mdid uuid.UUID +var defaultPndID uuid.UUID +var cuid uuid.UUID +var did uuid.UUID +var iid uuid.UUID +var altIid uuid.UUID +var defaultSbiID uuid.UUID + +func TestMain(m *testing.M) { + log.SetReportCaller(true) + + if os.Getenv("GOSDN_LOG") == "nolog" { + log.SetLevel(log.PanicLevel) + } + readTestUUIDs() + os.Exit(m.Run()) +} + +func readTestUUIDs() { + var err error + mdid, err = uuid.Parse("688a264e-5f85-40f8-bd13-afc42fcd5c7a") + if err != nil { + log.Fatal(err) + } + defaultPndID, err = uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + if err != nil { + log.Fatal(err) + } + cuid, err = uuid.Parse("3e8219b0-e926-400d-8660-217f2a25a7c6") + if err != nil { + log.Fatal(err) + } + did, err = uuid.Parse("4d8246f8-e884-41d6-87f5-c2c784df9e44") + if err != nil { + log.Fatal(err) + } + iid, err = uuid.Parse("8495a8ac-a1e8-418e-b787-10f5878b2690") + if err != nil { + log.Fatal(err) + } + altIid, err = uuid.Parse("edc5de93-2d15-4586-b2a7-fb1bc770986b") + if err != nil { + log.Fatal(err) + } + defaultSbiID, err = uuid.Parse("b70c8425-68c7-4d4b-bb5e-5586572bd64b") + if err != nil { + log.Fatal(err) + } +} diff --git a/store/pndStore.go b/store/pndStore.go new file mode 100644 index 0000000000000000000000000000000000000000..32aa935413896a409e6a8287248fa8b85e7bb549 --- /dev/null +++ b/store/pndStore.go @@ -0,0 +1,67 @@ +package store + +import ( + tpb "code.fbi.h-da.de/danet/api/go/gosdn/transport" + "code.fbi.h-da.de/danet/gosdn/interfaces/networkdomain" + "code.fbi.h-da.de/danet/gosdn/nucleus/errors" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +// DeviceDetails contains details of a device used by the cSBI mechanism +type DeviceDetails struct { + ID string + Address string + TransportOption *tpb.TransportOption +} + +// PndStore is used to store PrincipalNetworkDomains +type PndStore struct { + pendingChannels map[uuid.UUID]chan DeviceDetails + *genericStore +} + +// NewPndStore returns a PndStore +func NewPndStore() *PndStore { + return &PndStore{genericStore: newGenericStore()} +} + +// GetPND takes a PrincipalNetworkDomain's UUID and returns the PrincipalNetworkDomain. If the requested +// PrincipalNetworkDomain does not exist an error is returned. +func (s *PndStore) GetPND(id uuid.UUID) (networkdomain.NetworkDomain, error) { + item, err := s.genericStore.Get(id) + if err != nil { + return nil, err + } + pnd, ok := item.(networkdomain.NetworkDomain) + if !ok { + return nil, &errors.ErrInvalidTypeAssertion{ + Value: pnd, + Type: "PrincipalNetworkDomain", + } + } + log.WithFields(log.Fields{ + "uuid": id, + }).Debug("principal network domain was accessed") + return pnd, nil +} + +// PendingChannels holds channels used communicate with pending +// cSBI deployments +func (s *PndStore) PendingChannels(id uuid.UUID, parseErrors ...error) (chan DeviceDetails, error) { + ch, ok := s.pendingChannels[id] + if !ok { + return nil, &errors.ErrNotFound{ID: id} + } + return ch, nil +} + +// AddPendingChannel adds a pending channel to the map +func (s *PndStore) AddPendingChannel(id uuid.UUID, ch chan DeviceDetails) { + s.pendingChannels[id] = ch +} + +// RemovePendingChannel removes a pending channel from the map +func (s *PndStore) RemovePendingChannel(id uuid.UUID) { + delete(s.pendingChannels, id) +} diff --git a/store/pnd_store_test.go b/store/pnd_store_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5cd8bbbbfa42bb2aa4c44c2812bf1bf22abd73d7 --- /dev/null +++ b/store/pnd_store_test.go @@ -0,0 +1,82 @@ +package store + +import ( + "reflect" + "sync" + "testing" + + "code.fbi.h-da.de/danet/gosdn/interfaces/networkdomain" + "code.fbi.h-da.de/danet/gosdn/interfaces/store" + "code.fbi.h-da.de/danet/gosdn/mocks" + "github.com/google/uuid" +) + +func Test_pndStore_get(t *testing.T) { + pndMock := &mocks.NetworkDomain{} + pndMock.On("ID").Return(defaultPndID) + + type fields struct { + genericStore *genericStore + } + type args struct { + id uuid.UUID + } + tests := []struct { + name string + fields fields + args args + want networkdomain.NetworkDomain + wantErr bool + }{ + { + name: "exists", + fields: fields{ + &genericStore{ + Store: map[uuid.UUID]store.Storable{ + defaultPndID: pndMock, + }, + storeLock: sync.RWMutex{}, + }, + }, + args: args{id: defaultPndID}, + want: pndMock, + wantErr: false, + }, + { + name: "fails", + fields: fields{ + &genericStore{ + Store: map[uuid.UUID]store.Storable{ + defaultPndID: pndMock, + }, + storeLock: sync.RWMutex{}, + }, + }, + args: args{id: iid}, + wantErr: true, + }, + { + name: "fails empty", + fields: fields{ + genericStore: &genericStore{}, + }, + args: args{id: defaultPndID}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := PndStore{ + genericStore: tt.fields.genericStore, + } + got, err := s.Get(tt.args.id) + if (err != nil) != tt.wantErr { + t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("get() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/store/sbiStore.go b/store/sbiStore.go new file mode 100644 index 0000000000000000000000000000000000000000..fd8310bfd64577c822748afdbb14b2744a6028de --- /dev/null +++ b/store/sbiStore.go @@ -0,0 +1,39 @@ +package store + +import ( + "code.fbi.h-da.de/danet/gosdn/interfaces/southbound" + "code.fbi.h-da.de/danet/gosdn/nucleus/errors" + + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +// SbiStore is used to store SouthboundInterfaces +type SbiStore struct { + *genericStore +} + +// NewSbiStore returns a SbiStore +func NewSbiStore() *SbiStore { + return &SbiStore{genericStore: newGenericStore()} +} + +// GetSBI takes a SouthboundInterface's UUID and returns the SouthboundInterface. If the requested +// SouthboundInterface does not exist an error is returned. +func (s *SbiStore) GetSBI(id uuid.UUID) (southbound.SouthboundInterface, error) { + item, err := s.Get(id) + if err != nil { + return nil, err + } + sbi, ok := item.(southbound.SouthboundInterface) + if !ok { + return nil, &errors.ErrInvalidTypeAssertion{ + Value: sbi, + Type: "SouthboundInterface", + } + } + log.WithFields(log.Fields{ + "uuid": id, + }).Debug("southbound interface was accessed") + return sbi, nil +} diff --git a/store/sbi_store_test.go b/store/sbi_store_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2dc294639a6947f6c31374a97b75034297647027 --- /dev/null +++ b/store/sbi_store_test.go @@ -0,0 +1,82 @@ +package store + +import ( + "reflect" + "sync" + "testing" + + "code.fbi.h-da.de/danet/gosdn/interfaces/southbound" + "code.fbi.h-da.de/danet/gosdn/interfaces/store" + "code.fbi.h-da.de/danet/gosdn/mocks" + + "github.com/google/uuid" +) + +func Test_sbiStore_get(t *testing.T) { + openConfig := &mocks.SouthboundInterface{} + openConfig.On("ID").Return(defaultSbiID) + + type fields struct { + genericStore *genericStore + } + type args struct { + id uuid.UUID + } + tests := []struct { + name string + fields fields + args args + want southbound.SouthboundInterface + wantErr bool + }{ + { + name: "exists", + fields: fields{ + &genericStore{ + Store: map[uuid.UUID]store.Storable{ + defaultSbiID: openConfig, + }, + storeLock: sync.RWMutex{}, + }, + }, + args: args{id: defaultSbiID}, + want: openConfig, + wantErr: false, + }, + { + name: "fails", + fields: fields{ + &genericStore{ + Store: map[uuid.UUID]store.Storable{ + defaultSbiID: openConfig, + }, + storeLock: sync.RWMutex{}, + }, + }, + args: args{id: iid}, + wantErr: true, + }, + { + name: "fails empty", + fields: fields{ + genericStore: &genericStore{}, + }, + args: args{id: defaultSbiID}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := SbiStore{genericStore: tt.fields.genericStore} + + got, err := s.Get(tt.args.id) + if (err != nil) != tt.wantErr { + t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("get() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/store/utils.go b/store/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..8478836672cb1c450bf083c2246e97c294bfcf1c --- /dev/null +++ b/store/utils.go @@ -0,0 +1,23 @@ +package store + +import ( + "code.fbi.h-da.de/danet/gosdn/nucleus/errors" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +// FromString is a helper to check if a provided string as a valid UUID or a name. +func FromString(id string) (uuid.UUID, error) { + idAsUUID, err := uuid.Parse(id) + + // id is no UUID therefore it could be a device name. + // The name will be returned within the error. + if err != nil { + log.WithFields(log.Fields{ + "identifier": id, + }).Debug(err) + return uuid.Nil, &errors.ErrInvalidUUID{DeviceName: id} + } + + return idAsUUID, nil +}