From 32a5f82665c065b9c0353722f94874f8b6ef34d5 Mon Sep 17 00:00:00 2001
From: Manuel Kieweg <mail@manuelkieweg.de>
Date: Fri, 19 Feb 2021 15:21:17 +0000
Subject: [PATCH] New storage concept drafted and tested

---
 mocks/PrincipalNetworkDomain.go        | 207 ++++++++++++
 mocks/SouthboundInterface.go           |  18 +
 mocks/Storable.go                      |  30 ++
 nucleus/cli-handling.go                |  68 +++-
 nucleus/clientConfig.go                |   2 +-
 nucleus/controller.go                  |  25 +-
 nucleus/controller_test.go             |  85 -----
 nucleus/device.go                      |   4 +
 nucleus/errors.go                      |   9 +
 nucleus/gnmi_transport_test.go         |   2 +
 nucleus/principalNetworkDomain.go      | 135 ++++----
 nucleus/principalNetworkDomain_test.go | 219 +++++++++---
 nucleus/southbound.go                  |  42 +--
 nucleus/store.go                       | 107 ++++++
 nucleus/store_test.go                  | 448 +++++++++++++++++++++++++
 15 files changed, 1138 insertions(+), 263 deletions(-)
 create mode 100644 mocks/PrincipalNetworkDomain.go
 create mode 100644 mocks/Storable.go
 create mode 100644 nucleus/store.go
 create mode 100644 nucleus/store_test.go

diff --git a/mocks/PrincipalNetworkDomain.go b/mocks/PrincipalNetworkDomain.go
new file mode 100644
index 000000000..8796a000b
--- /dev/null
+++ b/mocks/PrincipalNetworkDomain.go
@@ -0,0 +1,207 @@
+// Code generated by mockery v2.6.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	mock "github.com/stretchr/testify/mock"
+
+	uuid "github.com/google/uuid"
+)
+
+// PrincipalNetworkDomain is an autogenerated mock type for the PrincipalNetworkDomain type
+type PrincipalNetworkDomain struct {
+	mock.Mock
+}
+
+// AddDevice provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) AddDevice(_a0 interface{}) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(interface{}) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// AddSbi provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) AddSbi(_a0 interface{}) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(interface{}) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// ContainsDevice provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) ContainsDevice(_a0 uuid.UUID) bool {
+	ret := _m.Called(_a0)
+
+	var r0 bool
+	if rf, ok := ret.Get(0).(func(uuid.UUID) bool); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Get(0).(bool)
+	}
+
+	return r0
+}
+
+// Destroy provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) Destroy() error {
+	ret := _m.Called()
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func() error); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// GetDescription provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) GetDescription() string {
+	ret := _m.Called()
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func() string); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	return r0
+}
+
+// GetName provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) GetName() string {
+	ret := _m.Called()
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func() string); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	return r0
+}
+
+// GetSBIs provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) GetSBIs() interface{} {
+	ret := _m.Called()
+
+	var r0 interface{}
+	if rf, ok := ret.Get(0).(func() interface{}); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(interface{})
+		}
+	}
+
+	return r0
+}
+
+// Id provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) Id() uuid.UUID {
+	ret := _m.Called()
+
+	var r0 uuid.UUID
+	if rf, ok := ret.Get(0).(func() uuid.UUID); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(uuid.UUID)
+		}
+	}
+
+	return r0
+}
+
+// MarshalDevice provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) MarshalDevice(_a0 uuid.UUID) (string, error) {
+	ret := _m.Called(_a0)
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func(uuid.UUID) string); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// RemoveDevice provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) RemoveDevice(_a0 uuid.UUID) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// RemoveSbi provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) RemoveSbi(_a0 uuid.UUID) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Request provides a mock function with given fields: _a0, _a1
+func (_m *PrincipalNetworkDomain) Request(_a0 uuid.UUID, _a1 string) error {
+	ret := _m.Called(_a0, _a1)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(uuid.UUID, string) error); ok {
+		r0 = rf(_a0, _a1)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// RequestAll provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) RequestAll(_a0 string) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/mocks/SouthboundInterface.go b/mocks/SouthboundInterface.go
index ecb98e044..e4e0d4c16 100644
--- a/mocks/SouthboundInterface.go
+++ b/mocks/SouthboundInterface.go
@@ -6,6 +6,8 @@ import (
 	gnmi "github.com/openconfig/gnmi/proto/gnmi"
 	mock "github.com/stretchr/testify/mock"
 
+	uuid "github.com/google/uuid"
+
 	yang "github.com/openconfig/goyang/pkg/yang"
 
 	ytypes "github.com/openconfig/ygot/ytypes"
@@ -16,6 +18,22 @@ type SouthboundInterface struct {
 	mock.Mock
 }
 
+// Id provides a mock function with given fields:
+func (_m *SouthboundInterface) Id() uuid.UUID {
+	ret := _m.Called()
+
+	var r0 uuid.UUID
+	if rf, ok := ret.Get(0).(func() uuid.UUID); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(uuid.UUID)
+		}
+	}
+
+	return r0
+}
+
 // SbiIdentifier provides a mock function with given fields:
 func (_m *SouthboundInterface) SbiIdentifier() string {
 	ret := _m.Called()
diff --git a/mocks/Storable.go b/mocks/Storable.go
new file mode 100644
index 000000000..1d2dc7d99
--- /dev/null
+++ b/mocks/Storable.go
@@ -0,0 +1,30 @@
+// Code generated by mockery v2.6.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	mock "github.com/stretchr/testify/mock"
+
+	uuid "github.com/google/uuid"
+)
+
+// Storable is an autogenerated mock type for the Storable type
+type Storable struct {
+	mock.Mock
+}
+
+// Id provides a mock function with given fields:
+func (_m *Storable) Id() uuid.UUID {
+	ret := _m.Called()
+
+	var r0 uuid.UUID
+	if rf, ok := ret.Get(0).(func() uuid.UUID); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(uuid.UUID)
+		}
+	}
+
+	return r0
+}
diff --git a/nucleus/cli-handling.go b/nucleus/cli-handling.go
index a3cd14a7a..6ad6b730b 100644
--- a/nucleus/cli-handling.go
+++ b/nucleus/cli-handling.go
@@ -142,21 +142,45 @@ func getCLIGoing(core *Core) {
 // the core
 func (s *server) CreatePND(ctx context.Context, in *pb.CreatePNDRequest) (*pb.CreatePNDReply, error) {
 	log.Info("Received: Create a PND with the name", in.GetName())
-	sbi := s.core.southboundInterfaces[in.GetSbi()]
+	sbi, err := s.core.sbic.get(uuid.New())
+	if err != nil {
+		return nil, err
+	}
 	id := uuid.New()
-	s.core.principalNetworkDomains[id] = NewPND(in.GetName(), in.GetDescription(), sbi)
+	pnd, err := NewPNDwithId(in.GetName(), in.GetDescription(), id, sbi.(SouthboundInterface))
+	if err != nil {
+		log.Error(err)
+		return &pb.CreatePNDReply{Message: err.Error()}, err
+	}
+	if err := s.core.pndc.add(pnd); err != nil {
+		return nil, err
+	}
 
 	return &pb.CreatePNDReply{Message: "Created new PND: " + id.String()}, nil
 }
 
+// deprecated
+// Subject to change, using discontinued full device access
 // GetAllPNDs is a request to get all currently registered PNDs and returns a slim
 // variant of PNDs and their respective devices
 func (s *server) GetAllPNDs(ctx context.Context, in *emptypb.Empty) (*pb.AllPNDsReply, error) {
 	log.Info("Received: Get all PNDs")
 	var pnds []*pb.PND
-	for uuidPND, pnd := range s.core.principalNetworkDomains {
+	for _, uuidPND := range s.core.pndc.UUIDs() {
+		pnd, err := s.core.pndc.get(uuidPND)
+		if err != nil {
+			log.Error(err)
+			continue
+		}
 		var devices []*pb.Device
-		for uuidDevice, device := range pnd.(*pndImplementation).devices {
+		for uuidDevice, d := range pnd.(*pndImplementation).devices.store {
+			device, ok := d.(*Device)
+			if !ok {
+				log.Error(&ErrInvalidTypeAssertion{
+					v: d,
+					t: "Device",
+				})
+			}
 			tmpDevice := pb.Device{
 				Uuid:     uuidDevice.String(),
 				Address:  device.Config.Address,
@@ -164,11 +188,16 @@ func (s *server) GetAllPNDs(ctx context.Context, in *emptypb.Empty) (*pb.AllPNDs
 				Password: device.Config.Password}
 			devices = append(devices, &tmpDevice)
 		}
+		sbi, err := s.core.sbic.get(pnd.GetSBIs().(*sbiStore).UUIDs()[0])
+		if err != nil {
+			log.Error(err)
+			continue
+		}
 		tmpPND := pb.PND{
 			Uuid:        uuidPND.String(),
 			Name:        pnd.GetName(),
 			Description: pnd.GetDescription(),
-			Sbi:         pnd.GetSBIs()["default"].SbiIdentifier(),
+			Sbi:         sbi.SbiIdentifier(),
 			Devices:     devices,
 		}
 		pnds = append(pnds, &tmpPND)
@@ -179,7 +208,12 @@ func (s *server) GetAllPNDs(ctx context.Context, in *emptypb.Empty) (*pb.AllPNDs
 // GetAllSBINames returns all registered SBIs from core.
 func (s *server) GetAllSBINames(ctx context.Context, in *emptypb.Empty) (*pb.AllSBINamesReply, error) {
 	var sbiNames []string
-	for _, s := range s.core.southboundInterfaces {
+	for _, uuidDevice := range s.core.sbic.UUIDs() {
+		s, err := s.core.sbic.get(uuidDevice)
+		if err != nil {
+			log.Error(err)
+			continue
+		}
 		sbiNames = append(sbiNames, s.SbiIdentifier())
 	}
 	return &pb.AllSBINamesReply{SbiNames: sbiNames}, nil
@@ -193,12 +227,18 @@ func (s *server) AddDevice(ctx context.Context, in *pb.AddDeviceRequest) (*pb.Ad
 	if err != nil {
 		return &pb.AddDeviceReply{Message: err.Error()}, err
 	}
-	pnd, exists := s.core.principalNetworkDomains[uuidPND]
-	if exists != true {
-		log.Info(err)
+	pnd, err := s.core.pndc.get(uuidPND)
+	if err != nil {
+		log.Error(err)
+		return &pb.AddDeviceReply{Message: err.Error()}, err
+	}
+	// TODO: Add notion of default SBI to PND or solve differently
+	uuidSbi := pnd.GetSBIs().(*sbiStore).UUIDs()[0]
+	sbi, err := s.core.sbic.get(uuidSbi)
+	if err != nil {
+		log.Error(err)
 		return &pb.AddDeviceReply{Message: err.Error()}, err
 	}
-	sbi := s.core.principalNetworkDomains[uuidPND].GetSBIs()["default"]
 
 	//TODO: could the transport and the related config be created in device?
 	transport := &Gnmi{SetNode: sbi.SetNode()}
@@ -215,14 +255,14 @@ func (s *server) AddDevice(ctx context.Context, in *pb.AddDeviceRequest) (*pb.Ad
 
 	err = pnd.AddDevice(newDevice)
 	if err != nil {
-		log.Info(err)
+		log.Error(err)
 		return &pb.AddDeviceReply{Message: err.Error()}, err
 	}
 
 	return &pb.AddDeviceReply{Message: "Added new Device: " + newDevice.Config.Uuid.String()}, err
 }
 
-// HandleDeviceGetRequest handles a GET request via pnd.Request()
+// HandleDeviceGetRequest handles a GET request via newPnd.Request()
 func (s *server) HandleDeviceGetRequest(ctx context.Context, in *pb.DeviceGetRequest) (*pb.DeviceGetReply, error) {
 	log.Info("Received: HandleDeviceGetRequest")
 	uuidPND, err := uuid.Parse(in.GetUuidPND())
@@ -235,8 +275,8 @@ func (s *server) HandleDeviceGetRequest(ctx context.Context, in *pb.DeviceGetReq
 		log.Info(err)
 		return &pb.DeviceGetReply{Message: err.Error()}, err
 	}
-	pnd, exists := s.core.principalNetworkDomains[uuidPND]
-	if exists != true {
+	pnd, err := s.core.pndc.get(uuidPND)
+	if err != nil {
 		err := errors.New("Couldnt find PND: UUID is wrong")
 		log.Info(err)
 		return &pb.DeviceGetReply{Message: err.Error()}, err
diff --git a/nucleus/clientConfig.go b/nucleus/clientConfig.go
index 297e7a7fe..c88b951fc 100644
--- a/nucleus/clientConfig.go
+++ b/nucleus/clientConfig.go
@@ -1,6 +1,6 @@
 package nucleus
 
-// ClientConfig contains SBI ciena
+// ClientConfig contains SBI client
 // configuration parameters
 // Deprecated in favor of spf viper
 type ClientConfig struct {
diff --git a/nucleus/controller.go b/nucleus/controller.go
index 21ef57b7b..6c237be25 100644
--- a/nucleus/controller.go
+++ b/nucleus/controller.go
@@ -4,33 +4,28 @@ import (
 	"os"
 
 	"code.fbi.h-da.de/cocsn/gosdn/database"
-	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/viper"
 )
 
 // Core is the representation of the controllers core
 type Core struct {
-	// deprecated, use sbiStore instead
-	southboundInterfaces map[string]SouthboundInterface
-	// deprecated, use pndStore instead
-	principalNetworkDomains map[uuid.UUID]PrincipalNetworkDomain
 	// deprecated
 	database database.Database
 
-	pndStore  pndStorage
-	sbiStore  sbiStorage
+	pndc      pndStore
+	sbic      sbiStore
 	IsRunning chan bool
 }
 
 //Initialize does start-up housekeeping like reading controller config files
 func (c *Core) Initialize(IsRunningChannel chan bool) error {
-	c.sbiStore = sbiStorage{}
-	c.pndStore = pndStorage{}
-
-	// TODO: Just for compatability remove once deprecated code is cleaned up
-	c.southboundInterfaces = c.sbiStore
-	c.principalNetworkDomains = c.pndStore
+	c.sbic = sbiStore{
+		store{},
+	}
+	c.pndc = pndStore{
+		store{},
+	}
 
 	// Set config defaults
 	viper.SetDefault("socket", "localhost:55055")
@@ -61,10 +56,10 @@ func (c *Core) AttachDatabase() {
 
 // CreateSouthboundInterfaces initializes the controller with its supported SBIs
 func (c *Core) CreateSouthboundInterfaces() error {
-	if err := c.sbiStore.add(&AristaOC{}); err != nil {
+	if err := c.sbic.add(&AristaOC{}); err != nil {
 		return err
 	}
-	if err := c.sbiStore.add(&OpenConfig{}); err != nil {
+	if err := c.sbic.add(&OpenConfig{}); err != nil {
 		return err
 	}
 	return nil
diff --git a/nucleus/controller_test.go b/nucleus/controller_test.go
index dd2a0175a..0fa4ffa90 100644
--- a/nucleus/controller_test.go
+++ b/nucleus/controller_test.go
@@ -1,86 +1 @@
 package nucleus
-
-import (
-	"code.fbi.h-da.de/cocsn/gosdn/database"
-	"github.com/google/uuid"
-	"testing"
-)
-
-func TestCore_CreateSouthboundInterfaces(t *testing.T) {
-	type fields struct {
-		southboundInterfaces    map[string]SouthboundInterface
-		principalNetworkDomains map[uuid.UUID]PrincipalNetworkDomain
-		database                database.Database
-		IsRunning               chan bool
-	}
-	tests := []struct {
-		name   string
-		fields fields
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			_ = &Core{
-				southboundInterfaces:    tt.fields.southboundInterfaces,
-				principalNetworkDomains: tt.fields.principalNetworkDomains,
-				database:                tt.fields.database,
-				IsRunning:               tt.fields.IsRunning,
-			}
-		})
-	}
-}
-
-func TestCore_Initialize(t *testing.T) {
-	type fields struct {
-		southboundInterfaces    map[string]SouthboundInterface
-		principalNetworkDomains map[uuid.UUID]PrincipalNetworkDomain
-		database                database.Database
-		IsRunning               chan bool
-	}
-	type args struct {
-		IsRunningChannel chan bool
-	}
-	tests := []struct {
-		name   string
-		fields fields
-		args   args
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			_ = &Core{
-				southboundInterfaces:    tt.fields.southboundInterfaces,
-				principalNetworkDomains: tt.fields.principalNetworkDomains,
-				database:                tt.fields.database,
-				IsRunning:               tt.fields.IsRunning,
-			}
-		})
-	}
-}
-
-func TestCore_Shutdown(t *testing.T) {
-	type fields struct {
-		southboundInterfaces    map[string]SouthboundInterface
-		principalNetworkDomains map[uuid.UUID]PrincipalNetworkDomain
-		database                database.Database
-		IsRunning               chan bool
-	}
-	tests := []struct {
-		name   string
-		fields fields
-	}{
-		// TODO: Add test cases.
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			_ = &Core{
-				southboundInterfaces:    tt.fields.southboundInterfaces,
-				principalNetworkDomains: tt.fields.principalNetworkDomains,
-				database:                tt.fields.database,
-				IsRunning:               tt.fields.IsRunning,
-			}
-		})
-	}
-}
diff --git a/nucleus/device.go b/nucleus/device.go
index dcad6b388..24007fd30 100644
--- a/nucleus/device.go
+++ b/nucleus/device.go
@@ -35,6 +35,10 @@ func NewDevice(sbi SouthboundInterface, addr, username, password string,
 	}
 }
 
+func (d *Device) Id() uuid.UUID {
+	return d.Config.Uuid
+}
+
 type DeviceConfig struct {
 	Uuid     uuid.UUID
 	Address  string
diff --git a/nucleus/errors.go b/nucleus/errors.go
index 67ebde4ed..ce23a6d6f 100644
--- a/nucleus/errors.go
+++ b/nucleus/errors.go
@@ -24,3 +24,12 @@ type ErrAlreadyExists struct {
 func (e *ErrAlreadyExists) Error() string {
 	return fmt.Sprintf("%v already exists", e.item)
 }
+
+type ErrInvalidTypeAssertion struct {
+	v interface{}
+	t interface{}
+}
+
+func (e ErrInvalidTypeAssertion) Error() string {
+	return fmt.Sprintf("%v does not implement %v", e.v, e.t)
+}
diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go
index ba05a018a..b36a4a542 100644
--- a/nucleus/gnmi_transport_test.go
+++ b/nucleus/gnmi_transport_test.go
@@ -19,9 +19,11 @@ import (
 )
 
 // TestMain bootstraps all tests. Humongous beast
+// TODO: Move somewhere more sensible
 func TestMain(m *testing.M) {
 	testSetupGnmi()
 	testSetupPnd()
+	testSetupStore()
 	os.Exit(m.Run())
 }
 
diff --git a/nucleus/principalNetworkDomain.go b/nucleus/principalNetworkDomain.go
index 2ed330115..7b26ada3e 100644
--- a/nucleus/principalNetworkDomain.go
+++ b/nucleus/principalNetworkDomain.go
@@ -11,9 +11,9 @@ import (
 // interface for PND implementations
 type PrincipalNetworkDomain interface {
 	Destroy() error
-	AddSbi(SouthboundInterface) error
-	RemoveSbi(string) error
-	AddDevice(*Device) error
+	AddSbi(interface{}) error
+	RemoveSbi(uuid.UUID) error
+	AddDevice(interface{}) error
 	RemoveDevice(uuid.UUID) error
 	Request(uuid.UUID, string) error
 	RequestAll(string) error
@@ -21,28 +21,48 @@ type PrincipalNetworkDomain interface {
 	GetDescription() string
 	MarshalDevice(uuid.UUID) (string, error)
 	ContainsDevice(uuid.UUID) bool
-	GetSBIs() map[string]SouthboundInterface
+	GetSBIs() interface{}
+	Id() uuid.UUID
 }
 
 type pndImplementation struct {
 	name        string
 	description string
-	sbi         map[string]SouthboundInterface
-	devices     map[uuid.UUID]*Device
+	sbic        sbiStore
+	devices     deviceStore
 	id          uuid.UUID
 }
 
 // NewPND creates a Principle Network Domain
-func NewPND(name, description string, sbi SouthboundInterface) PrincipalNetworkDomain {
-	sbic := make(map[string]SouthboundInterface)
-	sbic["default"] = sbi
-	devices := make(map[uuid.UUID]*Device)
-	return &pndImplementation{
+func NewPND(name, description string, sbi SouthboundInterface) (PrincipalNetworkDomain, error) {
+	pnd := &pndImplementation{
 		name:        name,
 		description: description,
-		sbi:         sbic,
-		devices:     devices,
+		sbic:        sbiStore{store{}},
+		devices:     deviceStore{store{}},
 	}
+	if err := pnd.sbic.add(sbi); err != nil {
+		return nil, &ErrAlreadyExists{item: sbi}
+	}
+	return pnd, nil
+}
+
+func NewPNDwithId(name, description string, id uuid.UUID, sbi SouthboundInterface) (PrincipalNetworkDomain, error) {
+	pnd := &pndImplementation{
+		name:        name,
+		description: description,
+		sbic:        sbiStore{store{}},
+		devices:     deviceStore{store{}},
+		id:          id,
+	}
+	if err := pnd.sbic.add(sbi); err != nil {
+		return nil, &ErrAlreadyExists{item: sbi}
+	}
+	return pnd, nil
+}
+
+func (pnd *pndImplementation) Id() uuid.UUID {
+	return pnd.id
 }
 
 // GetName returns the name of the PND
@@ -51,9 +71,8 @@ func (pnd *pndImplementation) GetName() string {
 }
 
 // ContainsDevice checks if the given device uuid is registered for this PND
-func (pnd *pndImplementation) ContainsDevice(uuid uuid.UUID) bool {
-	_, exists := pnd.devices[uuid]
-	return exists
+func (pnd *pndImplementation) ContainsDevice(id uuid.UUID) bool {
+	return pnd.devices.exists(id)
 }
 
 // GetDescription returns the current description of the PND
@@ -62,8 +81,8 @@ func (pnd *pndImplementation) GetDescription() string {
 }
 
 // GetSBIs returns the registered SBIs
-func (pnd *pndImplementation) GetSBIs() map[string]SouthboundInterface {
-	return pnd.sbi
+func (pnd *pndImplementation) GetSBIs() interface{} {
+	return &pnd.sbic
 }
 
 // Destroy destroys the PND
@@ -72,21 +91,35 @@ func (pnd *pndImplementation) Destroy() error {
 }
 
 // AddSbi adds a SBI to the PND which will be supported
-func (pnd *pndImplementation) AddSbi(sbi SouthboundInterface) error {
-	return pnd.addSbi(sbi)
+func (pnd *pndImplementation) AddSbi(sbi interface{}) error {
+	s, ok := sbi.(SouthboundInterface)
+	if !ok {
+		return &ErrInvalidTypeAssertion{
+			v: sbi,
+			t: "Device",
+		}
+	}
+	return pnd.addSbi(s)
 }
 
 // AddSbi removes a SBI from the PND
 // TODO: this should to recursivly through
 // devices and remove the devices using
 // this SBI
-func (pnd *pndImplementation) RemoveSbi(sbiIdentifier string) error {
-	return pnd.removeSbi(sbiIdentifier)
+func (pnd *pndImplementation) RemoveSbi(id uuid.UUID) error {
+	return pnd.removeSbi(id)
 }
 
 //AddDevice adds a new device to the PND
-func (pnd *pndImplementation) AddDevice(device *Device) error {
-	return pnd.addDevice(device)
+func (pnd *pndImplementation) AddDevice(device interface{}) error {
+	d, ok := device.(*Device)
+	if !ok {
+		return &ErrInvalidTypeAssertion{
+			v: device,
+			t: "Device",
+		}
+	}
+	return pnd.addDevice(d)
 }
 
 // RemoveDevice removes a device from the PND
@@ -101,31 +134,23 @@ func destroy() error {
 }
 
 func (pnd *pndImplementation) addSbi(sbi SouthboundInterface) error {
-	pnd.sbi[sbi.SbiIdentifier()] = sbi
-	return nil
+	return pnd.sbic.add(sbi)
 }
 
-func (pnd *pndImplementation) removeSbi(sbiIdentifier string) error {
-	delete(pnd.sbi, sbiIdentifier)
-	return nil
+func (pnd *pndImplementation) removeSbi(id uuid.UUID) error {
+	return pnd.sbic.delete(id)
 }
 
 func (pnd *pndImplementation) addDevice(device *Device) error {
-	pnd.devices[device.Config.Uuid] = device
-	return nil
+	return pnd.devices.add(device)
 }
 
-func (pnd *pndImplementation) getDevice(uuid uuid.UUID) (*Device, error) {
-	d, ok := pnd.devices[uuid]
-	if !ok {
-		return nil, &ErrNotFound{id: uuid}
-	}
-	return d, nil
+func (pnd *pndImplementation) getDevice(id uuid.UUID) (*Device, error) {
+	return pnd.devices.get(id)
 }
 
-func (pnd *pndImplementation) removeDevice(uuid uuid.UUID) error {
-	delete(pnd.devices, uuid)
-	return nil
+func (pnd *pndImplementation) removeDevice(id uuid.UUID) error {
+	return pnd.devices.delete(id)
 }
 
 func (pnd *pndImplementation) MarshalDevice(uuid uuid.UUID) (string, error) {
@@ -161,38 +186,10 @@ func (pnd *pndImplementation) Request(uuid uuid.UUID, path string) error {
 
 //RequestAll sends a request for all registered devices
 func (pnd *pndImplementation) RequestAll(path string) error {
-	for k := range pnd.devices {
+	for _, k := range pnd.devices.UUIDs() {
 		if err := pnd.Request(k, path); err != nil {
 			return err
 		}
 	}
 	return nil
 }
-
-type pndStorage map[uuid.UUID]PrincipalNetworkDomain
-
-func (p pndStorage) exists(id uuid.UUID) bool {
-	_, ok := p[id]
-	return ok
-}
-
-func (p pndStorage) add(pnd PrincipalNetworkDomain) error {
-	// TODO: Implement duplicate detection. Changes PrincipalNetworkDomain API
-	p[uuid.New()] = pnd
-	return nil
-}
-
-func (p pndStorage) Sbi(id uuid.UUID) (PrincipalNetworkDomain, error) {
-	if !p.exists(id) {
-		return nil, &ErrNotFound{id: id}
-	}
-	return p[id], nil
-}
-
-func (p pndStorage) delete(id uuid.UUID) error {
-	if !p.exists(id) {
-		return &ErrNotFound{id: id}
-	}
-	delete(p, id)
-	return nil
-}
diff --git a/nucleus/principalNetworkDomain_test.go b/nucleus/principalNetworkDomain_test.go
index 18dbf2dde..5d77a64dd 100644
--- a/nucleus/principalNetworkDomain_test.go
+++ b/nucleus/principalNetworkDomain_test.go
@@ -22,6 +22,16 @@ func testSetupPnd() {
 	if err != nil {
 		log.Fatal(err)
 	}
+
+	defaultSbiId, err = uuid.Parse("b70c8425-68c7-4d4b-bb5e-5586572bd64b")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defaultPndId, err = uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad")
+	if err != nil {
+		log.Fatal(err)
+	}
 }
 
 func mockDevice() Device {
@@ -38,49 +48,114 @@ func mockDevice() Device {
 	}
 }
 
-func freshPnd() pndImplementation {
+func newPnd() pndImplementation {
 	return pndImplementation{
 		name:        "default",
 		description: "default test pnd",
-		sbi:         map[string]SouthboundInterface{"default": &OpenConfig{}},
-		devices:     map[uuid.UUID]*Device{},
+		sbic:        sbiStore{store{}},
+		devices:     deviceStore{store{}},
+	}
+}
+
+func newPndWithId() pndImplementation {
+	return pndImplementation{
+		name:        "default",
+		description: "default test pnd",
+		sbic:        sbiStore{store{}},
+		devices:     deviceStore{store{}},
+		id:          defaultPndId,
 	}
 }
 
 var did uuid.UUID
 var mdid uuid.UUID
+var defaultSbiId uuid.UUID
+var defaultPndId uuid.UUID
 
 func TestNewPND(t *testing.T) {
-	pnd := freshPnd()
+	pnd := newPnd()
+	if err := pnd.addSbi(&OpenConfig{id: defaultSbiId}); err != nil {
+		t.Error(err)
+	}
 	type args struct {
 		name        string
 		description string
 		sbi         SouthboundInterface
 	}
 	tests := []struct {
-		name string
-		args args
-		want PrincipalNetworkDomain
+		name    string
+		args    args
+		want    PrincipalNetworkDomain
+		wantErr bool
 	}{
 		{
 			name: "default",
 			args: args{
 				name:        "default",
 				description: "default test pnd",
-				sbi:         &OpenConfig{},
+				sbi:         &OpenConfig{id: defaultSbiId},
 			},
-			want: &pnd,
+			want:    &pnd,
+			wantErr: false,
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := NewPND(tt.args.name, tt.args.description, tt.args.sbi); !reflect.DeepEqual(got, tt.want) {
+			got, err := NewPND(tt.args.name, tt.args.description, tt.args.sbi)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewPND() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("NewPND() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
+func TestNewPNDwithId(t *testing.T) {
+	pnd := newPndWithId()
+	if err := pnd.addSbi(&OpenConfig{id: defaultSbiId}); err != nil {
+		t.Error(err)
+	}
+	type args struct {
+		name        string
+		description string
+		id          uuid.UUID
+		sbi         SouthboundInterface
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    PrincipalNetworkDomain
+		wantErr bool
+	}{
+		{
+			name: "default",
+			args: args{
+				name:        "default",
+				description: "default test pnd",
+				id:          defaultPndId,
+				sbi:         &OpenConfig{id: defaultSbiId},
+			},
+			want:    &pnd,
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := NewPNDwithId(tt.args.name, tt.args.description, tt.args.id, tt.args.sbi)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewPNDwithId() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewPNDwithId() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
 func Test_destroy(t *testing.T) {
 	tests := []struct {
 		name    string
@@ -118,16 +193,18 @@ func Test_pndImplementation_AddDevice(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
+			pnd := newPnd()
 			if err := pnd.AddDevice(tt.args.device); (err != nil) != tt.wantErr {
 				t.Errorf("AddDevice() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			_, ok := pnd.devices[did]
+			_, ok := pnd.devices.store[did]
 			if !ok {
 				t.Errorf("AddDevice() Device %v not in device store %v",
 					tt.args.device, pnd.devices)
 			}
-			delete(pnd.devices, did)
+			if err := pnd.devices.delete(did); err != nil {
+				t.Error(err)
+			}
 		})
 	}
 }
@@ -141,20 +218,30 @@ func Test_pndImplementation_AddSbi(t *testing.T) {
 		args    args
 		wantErr bool
 	}{
-		{name: "default", args: args{sbi: &OpenConfig{}}, wantErr: false},
+		{
+			name: "default",
+			args: args{
+				sbi: &OpenConfig{
+					id: defaultSbiId,
+				},
+			},
+			wantErr: false,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
+			pnd := newPnd()
 			if err := pnd.AddSbi(tt.args.sbi); (err != nil) != tt.wantErr {
 				t.Errorf("AddSbi() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			_, ok := pnd.GetSBIs()[tt.args.sbi.SbiIdentifier()]
+			_, ok := pnd.sbic.store[defaultSbiId]
 			if !ok {
 				t.Errorf("AddSbi() SBI %v not in device store %v",
 					tt.args.sbi, pnd.GetSBIs())
 			}
-			delete(pnd.sbi, tt.args.sbi.SbiIdentifier())
+			if err := pnd.sbic.delete(defaultSbiId); err != nil {
+				t.Error(err)
+			}
 		})
 	}
 }
@@ -177,15 +264,25 @@ func Test_pndImplementation_ContainsDevice(t *testing.T) {
 			uuid:   uuid.New(),
 			device: &Device{Config: DeviceConfig{Uuid: did}},
 		}, want: false},
+		{name: "fails empty", args: args{
+			uuid:   uuid.New(),
+			device: &Device{Config: DeviceConfig{Uuid: did}},
+		}, want: false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
-			pnd.devices[did] = tt.args.device
+			pnd := newPnd()
+			if tt.name != "fails empty" {
+				if err := pnd.devices.add(tt.args.device); err != nil {
+					t.Error(err)
+				}
+			}
 			if got := pnd.ContainsDevice(tt.args.uuid); got != tt.want {
 				t.Errorf("ContainsDevice() = %v, want %v", got, tt.want)
 			}
-			delete(pnd.devices, did)
+			if err := pnd.devices.delete(did); err != nil && tt.name != "fails empty" {
+				t.Error(err)
+			}
 		})
 	}
 }
@@ -194,8 +291,8 @@ func Test_pndImplementation_Destroy(t *testing.T) {
 	type fields struct {
 		name        string
 		description string
-		sbi         map[string]SouthboundInterface
-		devices     map[uuid.UUID]*Device
+		sbi         sbiStore
+		devices     deviceStore
 	}
 	tests := []struct {
 		name    string
@@ -209,7 +306,7 @@ func Test_pndImplementation_Destroy(t *testing.T) {
 			pnd := &pndImplementation{
 				name:        tt.fields.name,
 				description: tt.fields.description,
-				sbi:         tt.fields.sbi,
+				sbic:        tt.fields.sbi,
 				devices:     tt.fields.devices,
 			}
 			if err := pnd.Destroy(); (err != nil) != tt.wantErr {
@@ -228,7 +325,7 @@ func Test_pndImplementation_GetDescription(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
+			pnd := newPnd()
 			if got := pnd.GetDescription(); got != tt.want {
 				t.Errorf("GetDescription() = %v, want %v", got, tt.want)
 			}
@@ -245,7 +342,7 @@ func Test_pndImplementation_GetName(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
+			pnd := newPnd()
 			if got := pnd.GetName(); got != tt.want {
 				t.Errorf("GetName() = %v, want %v", got, tt.want)
 			}
@@ -254,12 +351,12 @@ func Test_pndImplementation_GetName(t *testing.T) {
 }
 
 func Test_pndImplementation_GetSBIs(t *testing.T) {
-	pnd := freshPnd()
+	pnd := newPnd()
 	tests := []struct {
 		name string
-		want map[string]SouthboundInterface
+		want *sbiStore
 	}{
-		{name: "default", want: pnd.sbi},
+		{name: "default", want: &pnd.sbic},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -284,7 +381,7 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
+			pnd := newPnd()
 			d := &Device{
 				GoStruct: &openconfig.Device{},
 				SBI:      nil,
@@ -296,7 +393,9 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) {
 				},
 				Transport: nil,
 			}
-			_ = pnd.addDevice(d)
+			if err := pnd.addDevice(d); err != nil {
+				t.Error(err)
+			}
 			got, err := pnd.MarshalDevice(tt.args.uuid)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("MarshalDevice() error = %v, wantErr %v", err, tt.wantErr)
@@ -305,7 +404,9 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) {
 			if got != tt.want {
 				t.Errorf("MarshalDevice() got = %v, want %v", got, tt.want)
 			}
-			delete(pnd.devices, did)
+			if err := pnd.devices.delete(did); err != nil {
+				t.Error(err)
+			}
 		})
 	}
 }
@@ -320,18 +421,23 @@ func Test_pndImplementation_RemoveDevice(t *testing.T) {
 		wantErr bool
 	}{
 		{name: "default", args: args{uuid: did}, wantErr: false},
+		{name: "fails", args: args{uuid: uuid.New()}, wantErr: true},
+		{name: "fails empty", args: args{uuid: did}, wantErr: true},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
-			d := &Device{Config: DeviceConfig{Uuid: did}}
-			_ = pnd.addDevice(d)
+			pnd := newPnd()
+			if tt.name != "fails empty" {
+				d := &Device{Config: DeviceConfig{Uuid: did}}
+				if err := pnd.addDevice(d); err != nil {
+					t.Error(err)
+				}
+			}
 			if err := pnd.RemoveDevice(tt.args.uuid); (err != nil) != tt.wantErr {
 				t.Errorf("RemoveDevice() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			_, ok := pnd.devices[did]
-			if ok {
-				t.Errorf("RemoveDevice() device %v still in device store %v", d, pnd.devices)
+			if pnd.devices.exists(did) && tt.name == "default" {
+				t.Errorf("RemoveDevice() device still in device store %v", pnd.devices)
 			}
 		})
 	}
@@ -339,25 +445,36 @@ func Test_pndImplementation_RemoveDevice(t *testing.T) {
 
 func Test_pndImplementation_RemoveSbi(t *testing.T) {
 	type args struct {
-		sbiIdentifier string
+		id uuid.UUID
 	}
 	tests := []struct {
 		name    string
 		args    args
 		wantErr bool
 	}{
-		{name: "default", args: args{sbiIdentifier: "openconfig"}, wantErr: false},
+		{name: "default", args: args{id: defaultSbiId}, wantErr: false},
+		{name: "fails", args: args{id: uuid.New()}, wantErr: true},
+		{name: "fails empty", args: args{id: defaultSbiId}, wantErr: true},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			pnd := freshPnd()
-			_ = pnd.addSbi(&OpenConfig{})
-			if err := pnd.RemoveSbi(tt.args.sbiIdentifier); (err != nil) != tt.wantErr {
+			pnd := &pndImplementation{
+				name:        "test-remove-sbi",
+				description: "test-remove-sbi",
+				sbic:        sbiStore{store{}},
+				devices:     deviceStore{store{}},
+				id:          defaultPndId,
+			}
+			if tt.name != "fails empty" {
+				if err := pnd.addSbi(&OpenConfig{id: defaultSbiId}); err != nil {
+					t.Error(err)
+				}
+			}
+			if err := pnd.RemoveSbi(tt.args.id); (err != nil) != tt.wantErr {
 				t.Errorf("RemoveSbi() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			sbi, ok := pnd.sbi[tt.args.sbiIdentifier]
-			if ok {
-				t.Errorf("RemoveDevice() SBI %v still in SBI store %v", sbi, pnd.sbi)
+			if pnd.sbic.exists(tt.args.id) {
+				t.Errorf("RemoveDevice() SBI still in SBI store %v", pnd.sbic)
 			}
 		})
 	}
@@ -396,7 +513,7 @@ func Test_pndImplementation_Request(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			deviceWithMockTransport := mockDevice()
-			pnd := freshPnd()
+			pnd := newPnd()
 			tr := deviceWithMockTransport.Transport.(*mocks.Transport)
 			tr.On("Get", mockContext, mock.Anything).Return(mock.Anything, tt.args.rErr)
 			tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(tt.args.rErr)
@@ -404,7 +521,9 @@ func Test_pndImplementation_Request(t *testing.T) {
 			if err := pnd.Request(tt.args.uuid, tt.args.path); (err != nil) != tt.wantErr {
 				t.Errorf("Request() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			delete(pnd.devices, mdid)
+			if err := pnd.devices.delete(mdid); err != nil {
+				t.Error(err)
+			}
 		})
 	}
 }
@@ -442,7 +561,7 @@ func Test_pndImplementation_RequestAll(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			deviceWithMockTransport := mockDevice()
-			pnd := freshPnd()
+			pnd := newPnd()
 			tr := deviceWithMockTransport.Transport.(*mocks.Transport)
 			tr.On("Get", mockContext, mock.Anything).Return(mock.Anything, tt.args.rErr)
 			tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(tt.args.rErr)
@@ -450,7 +569,9 @@ func Test_pndImplementation_RequestAll(t *testing.T) {
 			if err := pnd.RequestAll(tt.args.path); (err != nil) != tt.wantErr {
 				t.Errorf("RequestAll() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			delete(pnd.devices, mdid)
+			if err := pnd.devices.delete(mdid); err != nil {
+				t.Error(err)
+			}
 		})
 	}
 }
diff --git a/nucleus/southbound.go b/nucleus/southbound.go
index 7f22d24c8..6d5db29f1 100644
--- a/nucleus/southbound.go
+++ b/nucleus/southbound.go
@@ -4,6 +4,7 @@ import (
 	"code.fbi.h-da.de/cocsn/yang-models/generated/arista"
 	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
 	log "github.com/golang/glog"
+	"github.com/google/uuid"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/goyang/pkg/yang"
 	"github.com/openconfig/ygot/ytypes"
@@ -20,6 +21,7 @@ type SouthboundInterface interface {
 	// Needed for type assertion.
 	SetNode() func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error
 	Schema() *ytypes.Schema
+	Id() uuid.UUID
 }
 
 type Tapi struct {
@@ -33,6 +35,7 @@ type OpenConfig struct {
 	transport Transport
 
 	schema *ytypes.Schema
+	id     uuid.UUID
 }
 
 // SbiIdentifier returns the string representation of
@@ -62,6 +65,10 @@ func (oc *OpenConfig) SetNode() func(schema *yang.Entry, root interface{}, path
 	}
 }
 
+func (oc *OpenConfig) Id() uuid.UUID {
+	return oc.id
+}
+
 // deprecated
 // Use for prototyping only.
 // Use OpenConfig instead
@@ -70,6 +77,11 @@ type AristaOC struct {
 	transport Transport
 
 	schema *ytypes.Schema
+	id     uuid.UUID
+}
+
+func (oc *AristaOC) Id() uuid.UUID {
+	return oc.id
 }
 
 func (oc *AristaOC) SbiIdentifier() string {
@@ -95,33 +107,3 @@ func (oc *AristaOC) SetNode() func(schema *yang.Entry, root interface{}, path *g
 		return nil
 	}
 }
-
-type sbiStorage map[string]SouthboundInterface
-
-func (s sbiStorage) exists(name string) bool {
-	_, ok := s[name]
-	return ok
-}
-
-func (s sbiStorage) add(sbi SouthboundInterface) error {
-	if s.exists(sbi.SbiIdentifier()) {
-		return &ErrAlreadyExists{item: sbi}
-	}
-	s[sbi.SbiIdentifier()] = sbi
-	return nil
-}
-
-func (s sbiStorage) Sbi(name string) (SouthboundInterface, error) {
-	if !s.exists(name) {
-		return nil, &ErrNotFound{id: name}
-	}
-	return s[name], nil
-}
-
-func (s sbiStorage) delete(name string) error {
-	if !s.exists(name) {
-		return &ErrNotFound{id: name}
-	}
-	delete(s, name)
-	return nil
-}
diff --git a/nucleus/store.go b/nucleus/store.go
new file mode 100644
index 000000000..5923873d2
--- /dev/null
+++ b/nucleus/store.go
@@ -0,0 +1,107 @@
+package nucleus
+
+import (
+	"github.com/google/uuid"
+)
+
+type Storable interface {
+	Id() uuid.UUID
+}
+
+type store map[uuid.UUID]Storable
+
+func (s store) exists(id uuid.UUID) bool {
+	_, ok := s[id]
+	return ok
+}
+
+func (s store) add(item Storable) error {
+	if s.exists(item.Id()) {
+		return &ErrAlreadyExists{item: item}
+	}
+	s[item.Id()] = item
+	return nil
+}
+
+func (s store) get(id uuid.UUID) (Storable, error) {
+	if !s.exists(id) {
+		return nil, &ErrNotFound{id: id}
+	}
+	return s[id], nil
+}
+
+func (s store) delete(id uuid.UUID) error {
+	if !s.exists(id) {
+		return &ErrNotFound{id: id}
+	}
+	delete(s, id)
+	return nil
+}
+
+func (s store) UUIDs() []uuid.UUID {
+	keys := make([]uuid.UUID, len(s))
+
+	i := 0
+	for k := range s {
+		keys[i] = k
+		i++
+	}
+	return keys
+}
+
+type sbiStore struct {
+	store
+}
+
+func (s sbiStore) get(id uuid.UUID) (SouthboundInterface, error) {
+	item, err := s.store.get(id)
+	if err != nil {
+		return nil, err
+	}
+	sbi, ok := item.(SouthboundInterface)
+	if !ok {
+		return nil, &ErrInvalidTypeAssertion{
+			v: sbi,
+			t: "SouthboundInterface",
+		}
+	}
+	return sbi, nil
+}
+
+type pndStore struct {
+	store
+}
+
+func (s pndStore) get(id uuid.UUID) (PrincipalNetworkDomain, error) {
+	item, err := s.store.get(id)
+	if err != nil {
+		return nil, err
+	}
+	pnd, ok := item.(PrincipalNetworkDomain)
+	if !ok {
+		return nil, &ErrInvalidTypeAssertion{
+			v: pnd,
+			t: "PrincipalNetworkDomain",
+		}
+	}
+	return pnd, nil
+}
+
+type deviceStore struct {
+	store
+}
+
+func (s deviceStore) get(id uuid.UUID) (*Device, error) {
+	item, err := s.store.get(id)
+	if err != nil {
+		return nil, err
+	}
+	device, ok := item.(*Device)
+	if !ok {
+		return nil, &ErrInvalidTypeAssertion{
+			v: device,
+			t: "Device",
+		}
+	}
+	return device, nil
+}
diff --git a/nucleus/store_test.go b/nucleus/store_test.go
new file mode 100644
index 000000000..8a393efd6
--- /dev/null
+++ b/nucleus/store_test.go
@@ -0,0 +1,448 @@
+package nucleus
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/mocks"
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+	"reflect"
+	"testing"
+)
+
+func testSetupStore() {
+	var err error
+	iid, err = uuid.Parse("8495a8ac-a1e8-418e-b787-10f5878b2690")
+	altIid, err = uuid.Parse("edc5de93-2d15-4586-b2a7-fb1bc770986b")
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+var iid uuid.UUID
+var altIid uuid.UUID
+
+func Test_store_add(t *testing.T) {
+	type args struct {
+		item Storable
+	}
+	tests := []struct {
+		name    string
+		s       store
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "default",
+			s:    store{},
+			args: args{
+				item: &mocks.Storable{},
+			},
+		},
+		{
+			name: "already exists",
+			s: store{
+				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       store
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "default",
+			s: store{
+				iid: &mocks.Storable{},
+			},
+			args:    args{id: iid},
+			wantErr: false,
+		},
+		{
+			name:    "not found empty",
+			s:       store{},
+			args:    args{id: iid},
+			wantErr: true,
+		},
+		{
+			name: "not found",
+			s: store{
+				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 store %v", item, tt.s)
+				}
+			}
+		})
+	}
+}
+
+func Test_store_exists(t *testing.T) {
+	type args struct {
+		id uuid.UUID
+	}
+	tests := []struct {
+		name string
+		s    store
+		args args
+		want bool
+	}{
+		{
+			name: "default",
+			s: store{
+				iid: &mocks.Storable{},
+			},
+			args: args{id: iid},
+			want: true,
+		},
+		{
+			name: "not found empty",
+			s:    store{},
+			args: args{id: iid},
+			want: false,
+		},
+		{
+			name: "not found",
+			s: store{
+				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       store
+		args    args
+		want    Storable
+		wantErr bool
+	}{
+		{
+			name: "exists",
+			s: store{
+				iid: &mocks.Storable{},
+			},
+			args:    args{id: iid},
+			want:    &mocks.Storable{},
+			wantErr: false,
+		},
+		{
+			name: "not found",
+			s: store{
+				iid: &mocks.Storable{},
+			},
+			args:    args{id: altIid},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "not found empty",
+			s:       store{},
+			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    store
+		want []uuid.UUID
+	}{
+		{
+			name: "default",
+			s: store{
+				iid:    &mocks.Storable{},
+				altIid: &mocks.Storable{},
+			},
+			want: []uuid.UUID{iid, altIid},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.s.UUIDs(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("UUIDs() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_sbiStore_get(t *testing.T) {
+	type fields struct {
+		store store
+	}
+	type args struct {
+		id uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    SouthboundInterface
+		wantErr bool
+	}{
+		{
+			name: "exists",
+			fields: fields{
+				store: store{
+					defaultSbiId: &OpenConfig{id: defaultSbiId},
+				},
+			},
+			args:    args{id: defaultSbiId},
+			want:    &OpenConfig{id: defaultSbiId},
+			wantErr: false,
+		},
+		{
+			name: "fails",
+			fields: fields{
+				store: store{
+					defaultSbiId: &OpenConfig{id: defaultSbiId},
+				},
+			},
+			args:    args{id: iid},
+			wantErr: true,
+		},
+		{
+			name: "fails empty",
+			fields: fields{
+				store: store{},
+			},
+			args:    args{id: defaultSbiId},
+			wantErr: true,
+		},
+		{
+			name: "fails wrong type",
+			fields: fields{
+				store: store{
+					did: &Device{
+						Config: DeviceConfig{
+							Uuid: did,
+						},
+					},
+				},
+			},
+			args:    args{id: did},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := sbiStore{
+				store: tt.fields.store,
+			}
+			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 {
+		store store
+	}
+	type args struct {
+		id uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    PrincipalNetworkDomain
+		wantErr bool
+	}{
+		{
+			name: "exists",
+			fields: fields{
+				store: store{
+					defaultPndId: &pndImplementation{id: defaultPndId},
+				},
+			},
+			args:    args{id: defaultPndId},
+			want:    &pndImplementation{id: defaultPndId},
+			wantErr: false,
+		},
+		{
+			name: "fails",
+			fields: fields{
+				store: store{
+					defaultPndId: &pndImplementation{id: defaultPndId},
+				},
+			},
+			args:    args{id: iid},
+			wantErr: true,
+		},
+		{
+			name: "fails empty",
+			fields: fields{
+				store: store{},
+			},
+			args:    args{id: defaultPndId},
+			wantErr: true,
+		},
+		{
+			name: "fails wrong type",
+			fields: fields{
+				store: store{
+					did: &Device{
+						Config: DeviceConfig{
+							Uuid: did,
+						},
+					},
+				},
+			},
+			args:    args{id: defaultPndId},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := pndStore{
+				store: tt.fields.store,
+			}
+			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 {
+		store store
+	}
+	type args struct {
+		id uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    *Device
+		wantErr bool
+	}{
+		{
+			name: "exists",
+			fields: fields{
+				store: store{
+					defaultPndId: &Device{Config: DeviceConfig{Uuid: did}}}},
+			args: args{id: defaultPndId},
+			want: &Device{
+				Config: DeviceConfig{
+					Uuid: did,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "fails",
+			fields: fields{
+				store: store{
+					defaultPndId: &Device{Config: DeviceConfig{Uuid: did}}}},
+			args:    args{id: iid},
+			wantErr: true,
+		},
+		{
+			name: "fails empty",
+			fields: fields{
+				store: store{},
+			},
+			args:    args{id: defaultPndId},
+			wantErr: true,
+		},
+		{
+			name: "fails wrong type",
+			fields: fields{
+				store: store{
+					defaultPndId: &pndImplementation{id: defaultPndId}}},
+			args:    args{id: defaultPndId},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := deviceStore{
+				store: tt.fields.store,
+			}
+			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)
+			}
+		})
+	}
+}
-- 
GitLab