From e576199b52eb0c0dbbee96c427850af41dcec133 Mon Sep 17 00:00:00 2001
From: Andre Sterba <andre.sterba@stud.h-da.de>
Date: Wed, 6 Apr 2022 13:05:26 +0000
Subject: [PATCH] Refactor store interfaces and provide database as example
 implementation

See merge request danet/gosdn!267
---
 cli/cmd/deviceList.go                         |   5 +-
 cli/cmd/init.go                               |   1 +
 controller/api/initialise_test.go             |  21 +-
 controller/config/config.go                   |  18 +
 controller/config/config_test.go              |  27 +-
 controller/config/config_test.toml            |   5 +
 controller/controller.go                      |  77 ++--
 controller/interfaces/device/device.go        |   9 +
 controller/interfaces/device/deviceStore.go   |  14 +
 controller/interfaces/networkdomain/pnd.go    |   8 +-
 .../interfaces/networkdomain/pndStore.go      |  18 +
 controller/interfaces/southbound/sbi.go       |   1 +
 controller/interfaces/southbound/sbiStore.go  |  13 +
 controller/mocks/Change.go                    |   4 +-
 controller/mocks/Device.go                    |   2 +-
 controller/mocks/NetworkDomain.go             |  80 ++--
 controller/mocks/Plugin.go                    |   2 +-
 controller/mocks/SouthboundInterface.go       |  17 +-
 controller/mocks/Storable.go                  |   2 +-
 controller/mocks/Store.go                     |  95 -----
 controller/mocks/Transport.go                 |   2 +-
 controller/northbound/server/core.go          |  23 +-
 controller/northbound/server/csbi.go          |   3 +-
 controller/northbound/server/nbi.go           |   6 +-
 controller/northbound/server/pnd.go           |  79 ++--
 controller/northbound/server/pnd_test.go      |  49 +--
 controller/northbound/server/sbi.go           |   5 +-
 .../nucleus/database/mongo-connection.go      |  43 ++
 controller/nucleus/device.go                  |  47 +++
 controller/nucleus/deviceStore.go             | 248 +++++++++++
 controller/nucleus/errors/errors.go           |  40 ++
 controller/nucleus/genericStore.go            |  86 ++++
 controller/nucleus/initialise_test.go         |   8 +-
 controller/nucleus/memoryPndStore.go          |  83 ++++
 controller/nucleus/pndStore.go                | 198 +++++++++
 controller/nucleus/principalNetworkDomain.go  | 260 ++++++------
 .../nucleus/principalNetworkDomain_test.go    | 387 ++++--------------
 controller/nucleus/sbiStore.go                | 188 +++++++++
 controller/nucleus/southbound.go              |  22 +
 ...tore_test.go => change_store_test.go.wasd} |   0
 controller/store/deviceStore.go               | 228 -----------
 controller/store/device_store_test.go         | 242 -----------
 ...ore_test.go => generic_store_test.go.wasd} |   0
 controller/store/pndStore.go                  | 172 --------
 controller/store/pnd_store_test.go            |  82 ----
 controller/store/sbiStore.go                  | 169 --------
 controller/store/sbi_store_test.go            | 310 --------------
 controller/store/storageMode.go               |  37 ++
 controller/store/utils.go                     |   6 +
 .../integration/nucleusIntegration_test.go    |   2 +-
 controller/test/plugin/faulty/gostructs.go    |   4 +
 controller/test/plugin/gostructs.go           |   4 +
 docker-compose.yml                            |  18 +
 go.mod                                        |   9 +
 go.sum                                        |  19 +
 gosdn.clab.yaml                               |  18 +
 scripts/test-add-device.sh                    |   4 +
 scripts/test-add-pnd.sh                       |   4 +
 58 files changed, 1565 insertions(+), 1959 deletions(-)
 create mode 100644 controller/config/config_test.toml
 create mode 100644 controller/interfaces/device/deviceStore.go
 create mode 100644 controller/interfaces/networkdomain/pndStore.go
 create mode 100644 controller/interfaces/southbound/sbiStore.go
 delete mode 100644 controller/mocks/Store.go
 create mode 100644 controller/nucleus/database/mongo-connection.go
 create mode 100644 controller/nucleus/deviceStore.go
 create mode 100644 controller/nucleus/genericStore.go
 create mode 100644 controller/nucleus/memoryPndStore.go
 create mode 100644 controller/nucleus/pndStore.go
 create mode 100644 controller/nucleus/sbiStore.go
 rename controller/store/{change_store_test.go => change_store_test.go.wasd} (100%)
 delete mode 100644 controller/store/deviceStore.go
 delete mode 100644 controller/store/device_store_test.go
 rename controller/store/{generic_store_test.go => generic_store_test.go.wasd} (100%)
 delete mode 100644 controller/store/pndStore.go
 delete mode 100644 controller/store/pnd_store_test.go
 delete mode 100644 controller/store/sbiStore.go
 delete mode 100644 controller/store/sbi_store_test.go
 create mode 100644 controller/store/storageMode.go
 create mode 100644 docker-compose.yml
 create mode 100755 scripts/test-add-device.sh
 create mode 100755 scripts/test-add-pnd.sh

diff --git a/cli/cmd/deviceList.go b/cli/cmd/deviceList.go
index 9e73e82eb..3ad626279 100644
--- a/cli/cmd/deviceList.go
+++ b/cli/cmd/deviceList.go
@@ -46,11 +46,12 @@ var deviceListCmd = &cobra.Command{
 	Long:    "List all orchestrated network devices within the current PND.",
 
 	RunE: func(cmd *cobra.Command, args []string) error {
-		resp, err := pndAdapter.GetDevices()
+		respONDs, err := pndAdapter.GetDevices()
 		if err != nil {
 			return err
 		}
-		for i, dev := range resp {
+
+		for i, dev := range respONDs {
 			log.Infof("OND %v: name: %v, uuid: %v", i+1, dev.Name, dev.Id)
 			sid, err := uuid.Parse(dev.GetSbi().GetId())
 			if err != nil {
diff --git a/cli/cmd/init.go b/cli/cmd/init.go
index 833af65bd..b23c67c46 100644
--- a/cli/cmd/init.go
+++ b/cli/cmd/init.go
@@ -58,6 +58,7 @@ The --controller flag is required to change the controller address`,
 		if err != nil {
 			return err
 		}
+
 		sid := resp.Sbi[0].GetId()
 		viper.Set("CLI_SBI", sid)
 		log.Infof("SBI: %v", sid)
diff --git a/controller/api/initialise_test.go b/controller/api/initialise_test.go
index 499cb033e..004fc7af9 100644
--- a/controller/api/initialise_test.go
+++ b/controller/api/initialise_test.go
@@ -12,11 +12,13 @@ import (
 	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
 	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 	"code.fbi.h-da.de/danet/gosdn/controller/config"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
 	nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/server"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/util/proto"
-	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"code.fbi.h-da.de/danet/yang-models/generated/openconfig"
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
@@ -40,8 +42,8 @@ const changeID = "0992d600-f7d4-4906-9559-409b04d59a5f"
 const sbiID = "f6fd4b35-f039-4111-9156-5e4501bb8a5a"
 const ondID = "7e0ed8cc-ebf5-46fa-9794-741494914883"
 
-var pndStore *store.PndStore
-var sbiStore *store.SbiStore
+var pndStore networkdomain.PndStore
+var sbiStore southbound.SbiStore
 var lis *bufconn.Listener
 var pndUUID uuid.UUID
 var sbiUUID uuid.UUID
@@ -74,8 +76,8 @@ func bootstrapUnitTest() {
 		log.Fatal(err)
 	}
 
-	pndStore = store.NewPndStore()
-	sbiStore = store.NewSbiStore(pndUUID)
+	pndStore = nucleus.NewPndStore()
+	sbiStore = nucleus.NewSbiStore(pndUUID)
 
 	mockChange := &mocks.Change{}
 	mockChange.On("Age").Return(time.Hour)
@@ -95,8 +97,13 @@ func bootstrapUnitTest() {
 	}, nil)
 	mockPnd.On("Commit", mock.Anything).Return(nil)
 	mockPnd.On("Confirm", mock.Anything).Return(nil)
-	mockPnd.On("Devices").Return([]uuid.UUID{deviceUUID})
-	mockPnd.On("GetSBIs").Return(sbiStore)
+	mockPnd.On("Devices").Return([]device.Device{
+		&nucleus.CommonDevice{
+			UUID:     deviceUUID,
+			GoStruct: &openconfig.Device{},
+		},
+	})
+	mockPnd.On("GetSBIs").Return([]mocks.SouthboundInterface{})
 	mockPnd.On("ChangeOND", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(uuid.Nil, nil)
 
 	if err := pndStore.Add(&mockPnd); err != nil {
diff --git a/controller/config/config.go b/controller/config/config.go
index 294ff53b5..0defa09c3 100644
--- a/controller/config/config.go
+++ b/controller/config/config.go
@@ -16,6 +16,7 @@ const (
 	baseSouthBoundTypeKey           = "baseSouthBoundType"
 	baseSouthBoundUUIDKey           = "baseSouthBoundUUID"
 	changeTimeoutKey                = "GOSDN_CHANGE_TIMEOUT"
+	databaseConnectionKey           = "databaseConnection"
 )
 
 // BasePndUUID is an uuid for the base PND
@@ -33,6 +34,9 @@ var ChangeTimeout time.Duration
 // LogLevel ist the default log level
 var LogLevel logrus.Level
 
+// DatabaseConnection holds the credentials and address of the used database
+var DatabaseConnection string
+
 // Init gets called on module import
 func Init() {
 	err := InitializeConfig()
@@ -75,9 +79,17 @@ func InitializeConfig() error {
 
 	setLogLevel()
 
+	DatabaseConnection = getStringFromViper(databaseConnectionKey)
+
 	return nil
 }
 
+// UseDatabase enables other modules to decide if they should use
+// a database as backend.
+func UseDatabase() bool {
+	return len(DatabaseConnection) != 0
+}
+
 func getUUIDFromViper(viperKey string) (uuid.UUID, error) {
 	UUIDAsString := viper.GetString(viperKey)
 	if UUIDAsString == "" {
@@ -98,6 +110,12 @@ func getUUIDFromViper(viperKey string) (uuid.UUID, error) {
 	return parsedUUID, nil
 }
 
+func getStringFromViper(viperKey string) string {
+	stringFromViper := viper.GetString(viperKey)
+
+	return stringFromViper
+}
+
 func setChangeTimeout() error {
 	e := os.Getenv(changeTimeoutKey)
 	if e != "" {
diff --git a/controller/config/config_test.go b/controller/config/config_test.go
index 351667e57..a36dcc068 100644
--- a/controller/config/config_test.go
+++ b/controller/config/config_test.go
@@ -15,11 +15,11 @@ func TestInit(t *testing.T) {
 	viper.Set("baseSouthBoundUUID", "bf8160d4-4659-4a1b-98fd-f409a04111eb")
 	viper.Set("basePNDUUID", "bf8160d4-4659-4a1b-98fd-f409a04111ec")
 	viper.Set("GOSDN_CHANGE_TIMEOUT", "10m")
+	viper.Set("databaseConnection", "test@test:test")
 }
 
 func TestUseExistingConfig(t *testing.T) {
 	TestInit(t)
-
 	err := InitializeConfig()
 	if err != nil {
 		t.Error(err)
@@ -59,4 +59,29 @@ func TestUseExistingConfig(t *testing.T) {
 				logrus.InfoLevel, LogLevel)
 		}
 	}
+
+	if DatabaseConnection != "test@test:test" {
+		t.Fatalf("DatabaseConnection is not test@test:test. got=%s",
+			DatabaseConnection)
+	}
+
+	if UseDatabase() != true {
+		t.Fatalf("DatabaseConnection is not set, but should not be used. got=%t",
+			UseDatabase())
+	}
+}
+
+func TestUseDatabase(t *testing.T) {
+	viper.Set("databaseConnection", "")
+
+	err := InitializeConfig()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if UseDatabase() != false {
+		t.Fatalf("DatabaseConnection is not set and should not be used. got=%t",
+			UseDatabase())
+	}
 }
diff --git a/controller/config/config_test.toml b/controller/config/config_test.toml
new file mode 100644
index 000000000..fa96c7490
--- /dev/null
+++ b/controller/config/config_test.toml
@@ -0,0 +1,5 @@
+basepnduuid = "bf8160d4-4659-4a1b-98fd-f409a04111ec"
+basesouthboundtype = 1
+basesouthbounduuid = "bf8160d4-4659-4a1b-98fd-f409a04111eb"
+databaseconnection = "test@test:test"
+gosdn_change_timeout = "10m"
diff --git a/controller/controller.go b/controller/controller.go
index 907bc084b..0de056392 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -22,6 +22,8 @@ import (
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
 	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/config"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/northbound/server"
 	nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/server"
@@ -36,7 +38,7 @@ var coreOnce sync.Once
 
 // Core is the representation of the controller's core
 type Core struct {
-	pndc       *store.PndStore
+	pndc       networkdomain.PndStore
 	httpServer *http.Server
 	grpcServer *grpc.Server
 	nbi        *nbi.NorthboundInterface
@@ -47,18 +49,21 @@ type Core struct {
 
 var c *Core
 
-func init() {
+// initialize does start-up housekeeping like reading controller config files
+func initialize() error {
+	err := config.InitializeConfig()
+	if err != nil {
+		return err
+	}
+
 	c = &Core{
-		pndc:     store.NewPndStore(),
+		pndc:     nucleus.NewPndStore(),
 		stopChan: make(chan os.Signal, 1),
 	}
 
 	// Setting up signal capturing
 	signal.Notify(c.stopChan, os.Interrupt, syscall.SIGTERM)
-}
 
-// initialize does start-up housekeeping like reading controller config files
-func initialize() error {
 	if err := startGrpc(); err != nil {
 		return err
 	}
@@ -67,22 +72,12 @@ func initialize() error {
 	startHttpServer()
 	coreLock.Unlock()
 
-	err := config.InitializeConfig()
-	if err != nil {
-		return err
-	}
-
-	err = restorePrincipalNetworkDomains()
+	err = config.InitializeConfig()
 	if err != nil {
 		return err
 	}
 
-	sbi, err := createSouthboundInterfaces()
-	if err != nil {
-		return err
-	}
-
-	err = createPrincipalNetworkDomain(sbi)
+	err = createPrincipalNetworkDomain()
 	if err != nil {
 		return err
 	}
@@ -135,52 +130,28 @@ func createSouthboundInterfaces() (southbound.SouthboundInterface, error) {
 }
 
 // createPrincipalNetworkDomain initializes the controller with an initial PND
-func createPrincipalNetworkDomain(s southbound.SouthboundInterface) error {
-	if !c.pndc.Exists(config.BasePndUUID) {
-		pnd, err := nucleus.NewPND("base", "gosdn base pnd", config.BasePndUUID, s, c.csbiClient, callback)
-		if err != nil {
-			return err
-		}
-		err = c.pndc.Add(pnd)
-		if err != nil {
-			return err
-		}
-		return nil
-	}
-
-	return nil
-}
-
-// restorePrincipalNetworkDomains restores previously stored PNDs
-func restorePrincipalNetworkDomains() error {
-	pndsFromStore, err := c.pndc.Load()
-	if err != nil {
-		return err
-	}
-
-	sbi, err := createSouthboundInterfaces()
+func createPrincipalNetworkDomain() error {
+	basePnd, err := c.pndc.Get(store.Query{ID: config.BasePndUUID})
 	if err != nil {
-		return err
+		log.Info(err)
 	}
 
-	for _, pndFromStore := range pndsFromStore {
-		log.Debugf("Restoring PND: %s\n", pndFromStore.Name)
-		newPnd, err := nucleus.NewPND(
-			pndFromStore.Name,
-			pndFromStore.Description,
-			pndFromStore.ID,
-			sbi,
+	if basePnd == nil {
+		pnd, err := nucleus.NewPND(
+			"base",
+			"gosdn base pnd",
+			config.BasePndUUID,
 			c.csbiClient,
 			callback,
 		)
 		if err != nil {
 			return err
 		}
-
-		err = c.pndc.Add(newPnd)
+		err = c.pndc.Add(pnd)
 		if err != nil {
 			return err
 		}
+		return nil
 	}
 
 	return nil
@@ -215,7 +186,7 @@ func shutdown() error {
 	return stopHttpServer()
 }
 
-func callback(id uuid.UUID, ch chan store.DeviceDetails) {
+func callback(id uuid.UUID, ch chan device.Details) {
 	if ch != nil {
 		c.pndc.AddPendingChannel(id, ch)
 		log.Infof("pending channel %v added", id)
diff --git a/controller/interfaces/device/device.go b/controller/interfaces/device/device.go
index 3e8c56d4f..318c0a79f 100644
--- a/controller/interfaces/device/device.go
+++ b/controller/interfaces/device/device.go
@@ -6,6 +6,8 @@ import (
 	"github.com/google/uuid"
 	"github.com/openconfig/ygot/ygot"
 	"google.golang.org/protobuf/proto"
+
+	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 )
 
 // Device represents an Orchestrated Network Device (OND) which is managed by
@@ -18,3 +20,10 @@ type Device interface {
 	SBI() southbound.SouthboundInterface
 	ProcessResponse(proto.Message) error
 }
+
+// Details contains details of a device used by the cSBI mechanism
+type Details struct {
+	ID              string
+	Address         string
+	TransportOption *tpb.TransportOption
+}
diff --git a/controller/interfaces/device/deviceStore.go b/controller/interfaces/device/deviceStore.go
new file mode 100644
index 000000000..10a056771
--- /dev/null
+++ b/controller/interfaces/device/deviceStore.go
@@ -0,0 +1,14 @@
+package device
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+)
+
+// Store describes an interface for device store implementations.
+type Store interface {
+	Add(d Device) error
+	Update(d Device) error
+	Delete(Device) error
+	Get(store.Query) (Device, error)
+	GetAll() ([]Device, error)
+}
diff --git a/controller/interfaces/networkdomain/pnd.go b/controller/interfaces/networkdomain/pnd.go
index a0d06fe1b..2c86f6260 100644
--- a/controller/interfaces/networkdomain/pnd.go
+++ b/controller/interfaces/networkdomain/pnd.go
@@ -6,7 +6,6 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/change"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
 	"github.com/google/uuid"
 	"google.golang.org/protobuf/proto"
 )
@@ -18,18 +17,17 @@ type NetworkDomain interface {
 	AddSbi(s southbound.SouthboundInterface) error
 	RemoveSbi(uuid.UUID) error
 	AddDevice(name string, opts *tpb.TransportOption, sid uuid.UUID) error
-	AddDeviceFromStore(name string, deviceUUID uuid.UUID, opt *tpb.TransportOption, sid uuid.UUID) error
 	GetDevice(identifier string) (device.Device, error)
 	RemoveDevice(uuid.UUID) error
-	Devices() []uuid.UUID
+	Devices() []device.Device
 	ChangeOND(uuid uuid.UUID, operation ppb.ApiOperation, path string, value ...string) (uuid.UUID, error)
 	Request(uuid.UUID, string) (proto.Message, error)
 	RequestAll(string) error
 	GetName() string
 	GetDescription() string
 	MarshalDevice(string) (string, error)
-	ContainsDevice(uuid.UUID) bool
-	GetSBIs() store.Store
+	GetSBIs() ([]southbound.SouthboundInterface, error)
+	GetSBI(uuid.UUID) (southbound.SouthboundInterface, error)
 	ID() uuid.UUID
 	PendingChanges() []uuid.UUID
 	CommittedChanges() []uuid.UUID
diff --git a/controller/interfaces/networkdomain/pndStore.go b/controller/interfaces/networkdomain/pndStore.go
new file mode 100644
index 000000000..cb0c1b482
--- /dev/null
+++ b/controller/interfaces/networkdomain/pndStore.go
@@ -0,0 +1,18 @@
+package networkdomain
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+)
+
+// PndStore describes an interface for pnd store implementations.
+type PndStore interface {
+	Add(NetworkDomain) error
+	Delete(NetworkDomain) error
+	Get(store.Query) (NetworkDomain, error)
+	GetAll() ([]NetworkDomain, error)
+	PendingChannels(id uuid.UUID, parseErrors ...error) (chan device.Details, error)
+	AddPendingChannel(id uuid.UUID, ch chan device.Details)
+	RemovePendingChannel(id uuid.UUID)
+}
diff --git a/controller/interfaces/southbound/sbi.go b/controller/interfaces/southbound/sbi.go
index 1e78b1fd0..485a54ced 100644
--- a/controller/interfaces/southbound/sbi.go
+++ b/controller/interfaces/southbound/sbi.go
@@ -23,4 +23,5 @@ type SouthboundInterface interface { // nolint
 	SetID(id uuid.UUID)
 	Type() spb.Type
 	Unmarshal([]byte, *gpb.Path, ygot.ValidatedGoStruct, ...ytypes.UnmarshalOpt) error
+	Name() string
 }
diff --git a/controller/interfaces/southbound/sbiStore.go b/controller/interfaces/southbound/sbiStore.go
new file mode 100644
index 000000000..bf69ec54b
--- /dev/null
+++ b/controller/interfaces/southbound/sbiStore.go
@@ -0,0 +1,13 @@
+package southbound
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+)
+
+// SbiStore describes an interface for sbi store implementations.
+type SbiStore interface {
+	Add(SouthboundInterface) error
+	Delete(SouthboundInterface) error
+	Get(store.Query) (SouthboundInterface, error)
+	GetAll() ([]SouthboundInterface, error)
+}
diff --git a/controller/mocks/Change.go b/controller/mocks/Change.go
index b5fdea020..9c3e9b9f2 100644
--- a/controller/mocks/Change.go
+++ b/controller/mocks/Change.go
@@ -3,11 +3,11 @@
 package mocks
 
 import (
+	time "time"
+
 	pnd "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
 	mock "github.com/stretchr/testify/mock"
 
-	time "time"
-
 	uuid "github.com/google/uuid"
 )
 
diff --git a/controller/mocks/Device.go b/controller/mocks/Device.go
index 9abe6357d..5004309d1 100644
--- a/controller/mocks/Device.go
+++ b/controller/mocks/Device.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.9.4. DO NOT EDIT.
+// Code generated by mockery v2.10.0. DO NOT EDIT.
 
 package mocks
 
diff --git a/controller/mocks/NetworkDomain.go b/controller/mocks/NetworkDomain.go
index dd3c6ac69..607db2834 100644
--- a/controller/mocks/NetworkDomain.go
+++ b/controller/mocks/NetworkDomain.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.9.4. DO NOT EDIT.
+// Code generated by mockery v2.10.0. DO NOT EDIT.
 
 package mocks
 
@@ -14,8 +14,6 @@ import (
 
 	southbound "code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 
-	store "code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-
 	transport "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 
 	uuid "github.com/google/uuid"
@@ -40,20 +38,6 @@ func (_m *NetworkDomain) AddDevice(name string, opts *transport.TransportOption,
 	return r0
 }
 
-// AddDeviceFromStore provides a mock function with given fields: name, deviceUUID, opt, sid
-func (_m *NetworkDomain) AddDeviceFromStore(name string, deviceUUID uuid.UUID, opt *transport.TransportOption, sid uuid.UUID) error {
-	ret := _m.Called(name, deviceUUID, opt, sid)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(string, uuid.UUID, *transport.TransportOption, uuid.UUID) error); ok {
-		r0 = rf(name, deviceUUID, opt, sid)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
 // AddSbi provides a mock function with given fields: s
 func (_m *NetworkDomain) AddSbi(s southbound.SouthboundInterface) error {
 	ret := _m.Called(s)
@@ -142,20 +126,6 @@ func (_m *NetworkDomain) Confirm(_a0 uuid.UUID) error {
 	return r0
 }
 
-// ContainsDevice provides a mock function with given fields: _a0
-func (_m *NetworkDomain) 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 *NetworkDomain) Destroy() error {
 	ret := _m.Called()
@@ -171,15 +141,15 @@ func (_m *NetworkDomain) Destroy() error {
 }
 
 // Devices provides a mock function with given fields:
-func (_m *NetworkDomain) Devices() []uuid.UUID {
+func (_m *NetworkDomain) Devices() []device.Device {
 	ret := _m.Called()
 
-	var r0 []uuid.UUID
-	if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
+	var r0 []device.Device
+	if rf, ok := ret.Get(0).(func() []device.Device); ok {
 		r0 = rf()
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]uuid.UUID)
+			r0 = ret.Get(0).([]device.Device)
 		}
 	}
 
@@ -260,20 +230,50 @@ func (_m *NetworkDomain) GetName() string {
 	return r0
 }
 
+// GetSBI provides a mock function with given fields: _a0
+func (_m *NetworkDomain) GetSBI(_a0 uuid.UUID) (southbound.SouthboundInterface, error) {
+	ret := _m.Called(_a0)
+
+	var r0 southbound.SouthboundInterface
+	if rf, ok := ret.Get(0).(func(uuid.UUID) southbound.SouthboundInterface); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(southbound.SouthboundInterface)
+		}
+	}
+
+	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
+}
+
 // GetSBIs provides a mock function with given fields:
-func (_m *NetworkDomain) GetSBIs() store.Store {
+func (_m *NetworkDomain) GetSBIs() ([]southbound.SouthboundInterface, error) {
 	ret := _m.Called()
 
-	var r0 store.Store
-	if rf, ok := ret.Get(0).(func() store.Store); ok {
+	var r0 []southbound.SouthboundInterface
+	if rf, ok := ret.Get(0).(func() []southbound.SouthboundInterface); ok {
 		r0 = rf()
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(store.Store)
+			r0 = ret.Get(0).([]southbound.SouthboundInterface)
 		}
 	}
 
-	return r0
+	var r1 error
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
 }
 
 // ID provides a mock function with given fields:
diff --git a/controller/mocks/Plugin.go b/controller/mocks/Plugin.go
index b8dae9bfc..f69729e0c 100644
--- a/controller/mocks/Plugin.go
+++ b/controller/mocks/Plugin.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.9.4. DO NOT EDIT.
+// Code generated by mockery v2.10.0. DO NOT EDIT.
 
 package mocks
 
diff --git a/controller/mocks/SouthboundInterface.go b/controller/mocks/SouthboundInterface.go
index 727083c29..abf903d86 100644
--- a/controller/mocks/SouthboundInterface.go
+++ b/controller/mocks/SouthboundInterface.go
@@ -1,11 +1,10 @@
-// Code generated by mockery v2.9.4. DO NOT EDIT.
+// Code generated by mockery v2.10.0. DO NOT EDIT.
 
 package mocks
 
 import (
 	gosdnsouthbound "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
 	gnmi "github.com/openconfig/gnmi/proto/gnmi"
-
 	mock "github.com/stretchr/testify/mock"
 
 	uuid "github.com/google/uuid"
@@ -38,6 +37,20 @@ func (_m *SouthboundInterface) ID() uuid.UUID {
 	return r0
 }
 
+// Name provides a mock function with given fields:
+func (_m *SouthboundInterface) Name() 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
+}
+
 // Schema provides a mock function with given fields:
 func (_m *SouthboundInterface) Schema() *ytypes.Schema {
 	ret := _m.Called()
diff --git a/controller/mocks/Storable.go b/controller/mocks/Storable.go
index e00e47c4b..2f37e9dc9 100644
--- a/controller/mocks/Storable.go
+++ b/controller/mocks/Storable.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.9.4. DO NOT EDIT.
+// Code generated by mockery v2.10.0. DO NOT EDIT.
 
 package mocks
 
diff --git a/controller/mocks/Store.go b/controller/mocks/Store.go
deleted file mode 100644
index b56cf00cd..000000000
--- a/controller/mocks/Store.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Code generated by mockery v2.9.4. DO NOT EDIT.
-
-package mocks
-
-import (
-	store "code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-	uuid "github.com/google/uuid"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// Store is an autogenerated mock type for the Store type
-type Store struct {
-	mock.Mock
-}
-
-// Add provides a mock function with given fields: item
-func (_m *Store) Add(item store.Storable) error {
-	ret := _m.Called(item)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(store.Storable) error); ok {
-		r0 = rf(item)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// Delete provides a mock function with given fields: id
-func (_m *Store) Delete(id uuid.UUID) error {
-	ret := _m.Called(id)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
-		r0 = rf(id)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// Exists provides a mock function with given fields: id
-func (_m *Store) Exists(id uuid.UUID) bool {
-	ret := _m.Called(id)
-
-	var r0 bool
-	if rf, ok := ret.Get(0).(func(uuid.UUID) bool); ok {
-		r0 = rf(id)
-	} else {
-		r0 = ret.Get(0).(bool)
-	}
-
-	return r0
-}
-
-// Get provides a mock function with given fields: id
-func (_m *Store) Get(id uuid.UUID) (store.Storable, error) {
-	ret := _m.Called(id)
-
-	var r0 store.Storable
-	if rf, ok := ret.Get(0).(func(uuid.UUID) store.Storable); ok {
-		r0 = rf(id)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(store.Storable)
-		}
-	}
-
-	var r1 error
-	if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
-		r1 = rf(id)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
-// UUIDs provides a mock function with given fields:
-func (_m *Store) UUIDs() []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/controller/mocks/Transport.go b/controller/mocks/Transport.go
index da59c7bd6..b2c7aa34f 100644
--- a/controller/mocks/Transport.go
+++ b/controller/mocks/Transport.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.9.4. DO NOT EDIT.
+// Code generated by mockery v2.10.0. DO NOT EDIT.
 
 package mocks
 
diff --git a/controller/northbound/server/core.go b/controller/northbound/server/core.go
index ef7c61732..d002ded24 100644
--- a/controller/northbound/server/core.go
+++ b/controller/northbound/server/core.go
@@ -8,6 +8,7 @@ import (
 	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
 	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 	"github.com/prometheus/client_golang/prometheus"
 )
@@ -28,7 +29,7 @@ func (s core) GetPnd(ctx context.Context, request *pb.GetPndRequest) (*pb.GetPnd
 
 	pnds := make([]*ppb.PrincipalNetworkDomain, len(pndList))
 	for i, id := range pndList {
-		pnd, err := pndc.GetPND(id)
+		pnd, err := pndc.Get(store.Query{ID: id})
 		if err != nil {
 			return nil, err
 		}
@@ -50,15 +51,13 @@ func (s core) GetPndList(ctx context.Context, request *pb.GetPndListRequest) (*p
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	pndList := pndc.UUIDs()
+	pndList, err := pndc.GetAll()
+	if err != nil {
+		return nil, err
+	}
 
 	pnds := make([]*ppb.PrincipalNetworkDomain, len(pndList))
-	for i, id := range pndList {
-		pnd, err := pndc.GetPND(id)
-		if err != nil {
-			return nil, err
-		}
-
+	for i, pnd := range pndList {
 		pnds[i] = &ppb.PrincipalNetworkDomain{
 			Id:          pnd.ID().String(),
 			Name:        pnd.GetName(),
@@ -76,7 +75,7 @@ func (s core) CreatePndList(ctx context.Context, request *pb.CreatePndListReques
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 	for _, r := range request.Pnd {
-		pnd, err := nucleus.NewPND(r.Name, r.Description, uuid.New(), nil, nil, nil)
+		pnd, err := nucleus.NewPND(r.Name, r.Description, uuid.New(), nil, nil)
 		if err != nil {
 			return nil, handleRPCError(labels, err)
 		}
@@ -100,7 +99,11 @@ func (s core) DeletePnd(ctx context.Context, request *pb.DeletePndRequest) (*pb.
 		return nil, handleRPCError(labels, err)
 	}
 
-	err = pndc.Delete(pndID)
+	pnd, err := pndc.Get(store.Query{ID: pndID})
+	if err != nil {
+		return nil, handleRPCError(labels, err)
+	}
+	err = pndc.Delete(pnd)
 	if err != nil {
 		return &pb.DeletePndResponse{
 			Timestamp: time.Now().UnixNano(),
diff --git a/controller/northbound/server/csbi.go b/controller/northbound/server/csbi.go
index f2585a637..acfb4d670 100644
--- a/controller/northbound/server/csbi.go
+++ b/controller/northbound/server/csbi.go
@@ -10,6 +10,7 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"google.golang.org/grpc/codes"
@@ -39,7 +40,7 @@ func (s csbi) Hello(ctx context.Context, syn *cpb.Syn) (*cpb.Ack, error) {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
 	}
-	ch <- store.DeviceDetails{
+	ch <- device.Details{
 		ID:      syn.Id,
 		Address: net.JoinHostPort(csbiAddress, syn.Address),
 	}
diff --git a/controller/northbound/server/nbi.go b/controller/northbound/server/nbi.go
index ac5d02da1..38230e7f0 100644
--- a/controller/northbound/server/nbi.go
+++ b/controller/northbound/server/nbi.go
@@ -1,15 +1,15 @@
 package server
 
 import (
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
 	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
-	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/prometheus/client_golang/prometheus"
 	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 )
 
-var pndc *store.PndStore
+var pndc networkdomain.PndStore
 
 // NorthboundInterface is the representation of the
 // gRPC services used provided.
@@ -22,7 +22,7 @@ type NorthboundInterface struct {
 }
 
 // NewNBI receives a PndStore and returns a new gRPC *NorthboundInterface
-func NewNBI(pnds *store.PndStore) *NorthboundInterface {
+func NewNBI(pnds networkdomain.PndStore) *NorthboundInterface {
 	pndc = pnds
 	return &NorthboundInterface{
 		Pnd:  &pndServer{},
diff --git a/controller/northbound/server/pnd.go b/controller/northbound/server/pnd.go
index df50fd617..4949a2292 100644
--- a/controller/northbound/server/pnd.go
+++ b/controller/northbound/server/pnd.go
@@ -2,15 +2,18 @@ package server
 
 import (
 	"context"
+	"fmt"
 	"strings"
 	"time"
 
 	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
 	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
 	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 	"github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/ygot/ygot"
@@ -34,7 +37,7 @@ func (p pndServer) GetOnd(ctx context.Context, request *ppb.GetOndRequest) (*ppb
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -64,7 +67,7 @@ func (p pndServer) GetOndList(ctx context.Context, request *ppb.GetOndListReques
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -86,7 +89,7 @@ func (p pndServer) GetOndList(ctx context.Context, request *ppb.GetOndListReques
 }
 
 func fillOnds(pnd networkdomain.NetworkDomain, all bool, did ...string) ([]*ppb.OrchestratedNetworkingDevice, error) {
-	var ondList []uuid.UUID
+	var ondList []device.Device
 	var onds []*ppb.OrchestratedNetworkingDevice
 
 	// all indicates if a client wants all devices or only a single one
@@ -94,9 +97,10 @@ func fillOnds(pnd networkdomain.NetworkDomain, all bool, did ...string) ([]*ppb.
 	case true:
 		ondList = pnd.Devices()
 		onds = make([]*ppb.OrchestratedNetworkingDevice, len(ondList))
-		for _, id := range ondList {
-			did = append(did, id.String())
+		for _, dev := range ondList {
+			did = append(did, dev.ID().String())
 		}
+
 	default:
 		if len(did) == 0 {
 			err := &errors.ErrInvalidParameters{
@@ -244,7 +248,7 @@ func (p pndServer) GetSbi(ctx context.Context, request *ppb.GetSbiRequest) (*ppb
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -274,7 +278,7 @@ func (p pndServer) GetSbiList(ctx context.Context, request *ppb.GetSbiListReques
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -296,39 +300,24 @@ func (p pndServer) GetSbiList(ctx context.Context, request *ppb.GetSbiListReques
 }
 
 func fillSbis(pnd networkdomain.NetworkDomain, all bool, sid ...string) ([]*spb.SouthboundInterface, error) {
-	var sbiList []uuid.UUID
-
-	sbiStore := pnd.GetSBIs()
-	switch all {
-	case true:
-		sbiList = sbiStore.UUIDs()
-	default:
-		var err error
-		if len(sid) == 0 {
-			return nil, &errors.ErrInvalidParameters{
-				Func:  fillSbis,
-				Param: "length of 'sid' cannot be '0' when 'all' is set to 'false'",
-			}
-		}
-		sbiList, err = stringToUUID(sid)
-		if err != nil {
-			log.Error(err)
-			return nil, status.Errorf(codes.Aborted, "%v", err)
-		}
+	sbis, err := pnd.GetSBIs()
+	if err != nil {
+		return nil, err
 	}
-	sbis := make([]*spb.SouthboundInterface, len(sbiList))
-	for i, id := range sbiList {
-		_, 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(),
-		}
+	fmt.Printf("SBIS: %+v\n", sbis)
+
+	sbisToReturn := []*spb.SouthboundInterface{}
+
+	for _, sbi := range sbis {
+		sbisToReturn = append(sbisToReturn, &spb.SouthboundInterface{
+			Id: sbi.ID().String(),
+		})
 	}
-	return sbis, nil
+
+	fmt.Printf("SBIS: %+v\n", sbisToReturn)
+
+	return sbisToReturn, nil
 }
 
 func stringToUUID(sid []string) ([]uuid.UUID, error) {
@@ -353,7 +342,7 @@ func (p pndServer) GetPath(ctx context.Context, request *ppb.GetPathRequest) (*p
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -399,7 +388,7 @@ func (p pndServer) GetChange(ctx context.Context, request *ppb.GetChangeRequest)
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -429,7 +418,7 @@ func (p pndServer) GetChangeList(ctx context.Context, request *ppb.GetChangeList
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -498,7 +487,7 @@ func (p pndServer) SetOndList(ctx context.Context, request *ppb.SetOndListReques
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		return nil, handleRPCError(labels, err)
 	}
@@ -534,7 +523,7 @@ func (p pndServer) SetChangeList(ctx context.Context, request *ppb.SetChangeList
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		return nil, handleRPCError(labels, err)
 	}
@@ -582,7 +571,7 @@ func (p pndServer) SetPathList(ctx context.Context, request *ppb.SetPathListRequ
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		return nil, handleRPCError(labels, err)
 	}
@@ -620,7 +609,7 @@ func (p pndServer) SetSbiList(ctx context.Context, request *ppb.SetSbiListReques
 		return nil, handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		return nil, handleRPCError(labels, err)
 	}
@@ -672,7 +661,7 @@ func (p pndServer) DeleteOnd(ctx context.Context, request *ppb.DeleteOndRequest)
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
 	}
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		log.Error(err)
 		return nil, status.Errorf(codes.Aborted, "%v", err)
diff --git a/controller/northbound/server/pnd_test.go b/controller/northbound/server/pnd_test.go
index 243ebc23e..bd2c75388 100644
--- a/controller/northbound/server/pnd_test.go
+++ b/controller/northbound/server/pnd_test.go
@@ -10,12 +10,11 @@ import (
 	pb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/core"
 	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
 	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
-	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
-	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"code.fbi.h-da.de/danet/yang-models/generated/openconfig"
 	"github.com/golang/protobuf/proto"
 	"github.com/google/go-cmp/cmp"
@@ -45,22 +44,13 @@ var committedChangeUUID uuid.UUID
 var deviceUUID uuid.UUID
 var mockPnd *mocks.NetworkDomain
 var mockDevice device.Device
-var sbiStore *store.SbiStore
+var sbiStore southbound.SbiStore
 
-func callback(id uuid.UUID, ch chan store.DeviceDetails) {
+func callback(id uuid.UUID, ch chan device.Details) {
 	// Need for pnd creation, but not needed for this test case.
 }
 
-func removeExistingStores() {
-	os.RemoveAll("stores/")
-}
-
 func getMockPND() networkdomain.NetworkDomain {
-	sbi, err := nucleus.NewSBI(spb.Type(1), sbiUUID)
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	conn, err := grpc.Dial("orchestrator", grpc.WithTransportCredentials(insecure.NewCredentials()))
 	if err != nil {
 		log.Fatal(err)
@@ -72,30 +62,14 @@ func getMockPND() networkdomain.NetworkDomain {
 		"test",
 		"test",
 		pndUUID,
-		sbi,
 		csbiClient,
 		callback,
 	)
 
-	newPnd.AddDeviceFromStore(
-		"test",
-		deviceUUID,
-		&transport.TransportOption{
-			Address:  "test",
-			Username: "test",
-			Password: "test",
-			TransportOption: &transport.TransportOption_GnmiTransportOption{
-				GnmiTransportOption: &transport.GnmiTransportOption{},
-			},
-			Type: spb.Type_TYPE_OPENCONFIG,
-		}, sbiUUID)
-
 	return newPnd
 }
 
 func TestMain(m *testing.M) {
-	removeExistingStores()
-
 	log.SetReportCaller(true)
 	var err error
 	pndUUID, err = uuid.Parse(pndID)
@@ -142,7 +116,7 @@ func TestMain(m *testing.M) {
 	mockDevice.(*nucleus.CommonDevice).SetSBI(sbi)
 	mockDevice.(*nucleus.CommonDevice).SetTransport(&mocks.Transport{})
 	mockDevice.(*nucleus.CommonDevice).SetName(hostname)
-	sbiStore = store.NewSbiStore(pndUUID)
+	sbiStore = nucleus.NewSbiStore(pndUUID)
 	if err := sbiStore.Add(mockDevice.SBI()); err != nil {
 		log.Fatal(err)
 	}
@@ -156,6 +130,7 @@ func TestMain(m *testing.M) {
 	mockPnd.On("GetName").Return("test")
 	mockPnd.On("GetDescription").Return("test")
 	mockPnd.On("GetSBIs").Return(sbiStore)
+	mockPnd.On("GetSBI").Return(mockDevice.SBI(), nil)
 	mockPnd.On("Devices").Return([]uuid.UUID{deviceUUID})
 	mockPnd.On("PendingChanges").Return([]uuid.UUID{pendingChangeUUID})
 	mockPnd.On("CommittedChanges").Return([]uuid.UUID{committedChangeUUID})
@@ -167,13 +142,7 @@ func TestMain(m *testing.M) {
 	mockPnd.On("ChangeOND", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(uuid.Nil, nil)
 	mockPnd.On("Request", mock.Anything, mock.Anything).Return(nil, nil)
 
-	//newPnd := getMockPND()
-
-	pndc = store.NewPndStore()
-	//if err := pndc.Add(newPnd); err != nil {
-	//	log.Fatal(err)
-	//}
-
+	pndc = nucleus.NewPndStore()
 	if err := pndc.Add(mockPnd); err != nil {
 		log.Fatal(err)
 	}
@@ -185,8 +154,6 @@ func TestMain(m *testing.M) {
 // As of now this is not possible as we can't use the mock pnd, as it can't be serialized because of
 // cyclic use of mock in it.
 func Test_pnd_Get(t *testing.T) {
-	removeExistingStores()
-
 	type args struct {
 		ctx     context.Context
 		request *pb.GetPndRequest
@@ -236,8 +203,6 @@ func Test_pnd_Get(t *testing.T) {
 }
 
 func Test_pnd_GetPath(t *testing.T) {
-	removeExistingStores()
-
 	opts := cmp.Options{
 		cmpopts.SortSlices(
 			func(x, y *gnmi.Update) bool {
@@ -355,8 +320,6 @@ func Test_pnd_GetPath(t *testing.T) {
 }
 
 func Test_pnd_Set(t *testing.T) {
-	removeExistingStores()
-
 	// type args struct {
 	// 	ctx     context.Context
 	// 	request *ppb.SetRequest
diff --git a/controller/northbound/server/sbi.go b/controller/northbound/server/sbi.go
index faf88e4be..6c6102806 100644
--- a/controller/northbound/server/sbi.go
+++ b/controller/northbound/server/sbi.go
@@ -9,6 +9,7 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 	"github.com/prometheus/client_golang/prometheus"
+
 	log "github.com/sirupsen/logrus"
 )
 
@@ -40,12 +41,12 @@ func (s sbiServer) GetSchema(request *spb.GetSchemaRequest, stream spb.SbiServic
 		return handleRPCError(labels, err)
 	}
 
-	pnd, err := pndc.GetPND(pid)
+	pnd, err := pndc.Get(store.Query{ID: pid})
 	if err != nil {
 		return handleRPCError(labels, err)
 	}
 
-	sbi, err := pnd.GetSBIs().(*store.SbiStore).GetSBI(sid)
+	sbi, err := pnd.GetSBI(sid)
 	if err != nil {
 		return handleRPCError(labels, err)
 	}
diff --git a/controller/nucleus/database/mongo-connection.go b/controller/nucleus/database/mongo-connection.go
new file mode 100644
index 000000000..1db8205ae
--- /dev/null
+++ b/controller/nucleus/database/mongo-connection.go
@@ -0,0 +1,43 @@
+package database
+
+import (
+	"context"
+	"log"
+	"time"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/config"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
+)
+
+const (
+	// Timeout operations after N seconds
+	connectTimeout = 5
+	// DatabaseName is the name of the mongoDB database used.
+	DatabaseName = "gosdn"
+	//mongoConnection = "mongodb://root:example@localhost"
+)
+
+// GetMongoConnection Retrieves a client to the MongoDB
+func GetMongoConnection() (*mongo.Client, context.Context, context.CancelFunc) {
+	mongoConnection := config.DatabaseConnection
+	client, err := mongo.NewClient(options.Client().ApplyURI(mongoConnection))
+	if err != nil {
+		log.Printf("Failed to create client: %v", err)
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), connectTimeout*time.Second)
+
+	err = client.Connect(ctx)
+	if err != nil {
+		log.Printf("Failed to connect to cluster: %v", err)
+	}
+
+	// Force a connection to verify our connection string
+	err = client.Ping(ctx, nil)
+	if err != nil {
+		log.Printf("Failed to connect to database: %v\n", err)
+	}
+
+	return client, ctx, cancel
+}
diff --git a/controller/nucleus/device.go b/controller/nucleus/device.go
index 434daa478..6db1d6f6e 100644
--- a/controller/nucleus/device.go
+++ b/controller/nucleus/device.go
@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/pkg/namesgenerator"
 	"github.com/google/uuid"
 	"github.com/openconfig/ygot/ygot"
+	"go.mongodb.org/mongo-driver/bson"
 	"google.golang.org/protobuf/proto"
 )
 
@@ -212,3 +213,49 @@ func (d *CommonDevice) MarshalJSON() ([]byte, error) {
 		SBI:                 sbiUUID,
 	})
 }
+
+// MarshalBSON implements the MarshalBSON interface to store a device as BSON
+func (d *CommonDevice) MarshalBSON() ([]byte, error) {
+	var transportType string
+	var transportAddress string
+	var transportUsername string
+	var transportPassword string
+	var transportOptionType spb.Type
+
+	// Handling of these cases is necessary as we use partial devices for testing.
+	// eg. in most tests no transport or sbi is defined.
+	// The marshaller will crash if we want to access a nil field.
+	if d.transport == nil || d.transportOptions == nil {
+		transportType = "testing"
+		transportAddress = "testing"
+		transportUsername = "testing"
+		transportPassword = "testing"
+		transportOptionType = spb.Type_TYPE_OPENCONFIG
+	} else {
+		transportType = d.transport.Type()
+		transportAddress = d.transportOptions.Address
+		transportUsername = d.transportOptions.Username
+		transportPassword = d.transportOptions.Password
+		transportOptionType = d.transportOptions.Type
+	}
+
+	return bson.Marshal(&struct {
+		DeviceID            string   `bson:"_id,omitempty"`
+		Name                string   `bson:"name,omitempty"`
+		TransportType       string   `bson:"transport_type,omitempty"`
+		TransportAddress    string   `bson:"transport_address,omitempty"`
+		TransportUsername   string   `bson:"transport_username,omitempty"`
+		TransportPassword   string   `bson:"transport_password,omitempty"`
+		TransportOptionType spb.Type `bson:"transport_option"`
+		SBI                 string   `bson:"sbi,omitempty"`
+	}{
+		DeviceID:            d.ID().String(),
+		Name:                d.Name(),
+		TransportType:       transportType,
+		TransportAddress:    transportAddress,
+		TransportUsername:   transportUsername,
+		TransportPassword:   transportPassword,
+		TransportOptionType: transportOptionType,
+		SBI:                 d.sbi.ID().String(),
+	})
+}
diff --git a/controller/nucleus/deviceStore.go b/controller/nucleus/deviceStore.go
new file mode 100644
index 000000000..b6b7c492a
--- /dev/null
+++ b/controller/nucleus/deviceStore.go
@@ -0,0 +1,248 @@
+package nucleus
+
+import (
+	"fmt"
+
+	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
+	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/database"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo/options"
+
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+)
+
+const (
+	deviceStoreName = "device"
+)
+
+// DeviceStore is used to store Devices
+type DeviceStore struct {
+	storeName string
+	sbiStore  southbound.SbiStore
+}
+
+// LoadedDevice represents a Orchestrated Networking Device that was loaeded
+// by using the Load() method of the DeviceStore.
+type LoadedDevice struct {
+	// DeviceID represents the UUID of the LoadedDevice.
+	DeviceID string `json:"id" bson:"_id,omitempty"`
+	// Name represents the name of the LoadedDevice.
+	Name string `json:"name,omitempty"`
+	// TransportType represent the type of the transport in use of the LoadedDevice.
+	TransportType string `json:"transport_type,omitempty"`
+	// TransportAddress represents the address from which the device can be reached via the transport method.
+	TransportAddress string `json:"transport_address,omitempty"`
+	// TransportUsername is used for authentication via the transport method in use.
+	TransportUsername string `json:"transport_username,omitempty"`
+	// TransportPassword is used for authentication via the transport method in use.
+	TransportPassword   string `json:"transport_password,omitempty"`
+	TransportOptionCsbi bool   `json:"transport_option_csbi,omitempty"`
+	// SBI indicates the southbound interface, which is used by this device as UUID.
+	SBI string `json:"sbi,omitempty"`
+}
+
+// ID returns the ID of the LoadedDevice as UUID.
+func (ld LoadedDevice) ID() uuid.UUID {
+	return uuid.MustParse(ld.DeviceID)
+}
+
+// NewDeviceStore returns a DeviceStore
+func NewDeviceStore(pndUUID uuid.UUID, sbiStore southbound.SbiStore) device.Store {
+	storeMode := store.GetStoreMode()
+	log.Debugf("StoreMode: %s", storeMode)
+
+	switch storeMode {
+	case store.Filesystem:
+		store := NewGenericStore[device.Device]()
+
+		return &store
+	case store.Database:
+		return &DeviceStore{
+			storeName: fmt.Sprintf("device-store-%s.json", pndUUID.String()),
+			sbiStore:  sbiStore,
+		}
+	case store.Memory:
+		store := NewGenericStore[device.Device]()
+
+		return &store
+	default:
+		return nil
+	}
+}
+
+// Get takes a Device's UUID or name and returns the Device.
+func (s *DeviceStore) Get(query store.Query) (device.Device, error) {
+	var loadedDevice LoadedDevice
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: query.ID}})
+	if result == nil {
+		return nil, errors.ErrCouldNotFind{StoreName: pndStoreName}
+	}
+
+	err := result.Decode(&loadedDevice)
+	if err != nil {
+		db := client.Database(database.DatabaseName)
+		collection := db.Collection(s.storeName)
+		result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: query.Name}})
+		if result == nil {
+			return nil, errors.ErrCouldNotFind{StoreName: pndStoreName}
+		}
+
+		err := result.Decode(&loadedDevice)
+		if err != nil {
+			log.Printf("Failed marshalling %v", err)
+			return nil, errors.ErrCouldNotFind{StoreName: pndStoreName}
+		}
+	}
+
+	sbiForDevice, err := s.sbiStore.Get(store.Query{ID: uuid.MustParse(loadedDevice.SBI)})
+	if err != nil {
+		return nil, err
+	}
+
+	d, err := NewDevice(
+		loadedDevice.Name,
+		uuid.MustParse(loadedDevice.DeviceID),
+		&tpb.TransportOption{
+			Address:  loadedDevice.TransportAddress,
+			Username: loadedDevice.TransportUsername,
+			Password: loadedDevice.TransportPassword,
+			TransportOption: &tpb.TransportOption_GnmiTransportOption{
+				GnmiTransportOption: &tpb.GnmiTransportOption{},
+			},
+			Type: spb.Type_TYPE_OPENCONFIG,
+		}, sbiForDevice)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return d, nil
+}
+
+// GetAll returns all stored devices.
+func (s *DeviceStore) GetAll() ([]device.Device, error) {
+	var loadedDevices []LoadedDevice
+	var devices []device.Device
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+
+	cursor, err := collection.Find(ctx, bson.D{})
+	if err != nil {
+		return nil, err
+	}
+	defer cursor.Close(ctx)
+
+	err = cursor.All(ctx, &loadedDevices)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return nil, errors.ErrCouldNotMarshall{StoreName: pndStoreName}
+	}
+
+	for _, device := range loadedDevices {
+		sbiForDevice, err := s.sbiStore.Get(store.Query{ID: uuid.MustParse(device.SBI)})
+		d, err := NewDevice(
+			device.Name,
+			uuid.MustParse(device.DeviceID),
+			&tpb.TransportOption{
+				Address:  device.TransportAddress,
+				Username: device.TransportUsername,
+				Password: device.TransportPassword,
+				TransportOption: &tpb.TransportOption_GnmiTransportOption{
+					GnmiTransportOption: &tpb.GnmiTransportOption{},
+				},
+				Type: spb.Type_TYPE_OPENCONFIG,
+			}, sbiForDevice)
+		if err != nil {
+			return nil, err
+		}
+
+		devices = append(devices, d)
+	}
+
+	return devices, nil
+}
+
+// Add adds a device to the device store.
+func (s *DeviceStore) Add(deviceToAdd device.Device) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	_, err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		InsertOne(ctx, deviceToAdd)
+	if err != nil {
+		log.Printf("Could not create Device: %v", err)
+		return errors.ErrCouldNotCreate{StoreName: pndStoreName}
+	}
+
+	return nil
+}
+
+// Update updates a existing device.
+func (s *DeviceStore) Update(deviceToUpdate device.Device) error {
+	var updatedDevice device.Device
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	update := bson.M{
+		"$set": deviceToUpdate,
+	}
+
+	upsert := false
+	after := options.After
+	opt := options.FindOneAndUpdateOptions{
+		Upsert:         &upsert,
+		ReturnDocument: &after,
+	}
+
+	err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		FindOneAndUpdate(
+			ctx, bson.M{"id": deviceToUpdate.ID}, update, &opt).
+		Decode(&updatedDevice)
+	if err != nil {
+		log.Printf("Could not update Device: %v", err)
+
+		return errors.ErrCouldNotUpdate{StoreName: pndStoreName}
+	}
+
+	return nil
+}
+
+// Delete deletes a device from the device store.
+func (s *DeviceStore) Delete(deviceToDelete device.Device) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	_, err := collection.DeleteOne(ctx, bson.D{primitive.E{Key: deviceToDelete.ID().String()}})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/controller/nucleus/errors/errors.go b/controller/nucleus/errors/errors.go
index 2e75d8a5d..abee04244 100644
--- a/controller/nucleus/errors/errors.go
+++ b/controller/nucleus/errors/errors.go
@@ -155,3 +155,43 @@ type ErrTypeNotSupported struct {
 func (e ErrTypeNotSupported) Error() string {
 	return fmt.Sprintf("type not supported: %v", reflect.TypeOf(e.Type))
 }
+
+// ErrCouldNotMarshall implements Error interface and is called if a
+// database respone can not be parsed.
+type ErrCouldNotMarshall struct {
+	StoreName string
+}
+
+func (e ErrCouldNotMarshall) Error() string {
+	return fmt.Sprintf("could not marshall %s", e.StoreName)
+}
+
+// ErrCouldNotUpdate implements the Error interface and is called if a
+// stored item can not be updated.
+type ErrCouldNotUpdate struct {
+	StoreName string
+}
+
+func (e ErrCouldNotUpdate) Error() string {
+	return fmt.Sprintf("could not update %s", e.StoreName)
+}
+
+// ErrCouldNotFind implements the Error interface and is called if a
+// stored item can not be found.
+type ErrCouldNotFind struct {
+	StoreName string
+}
+
+func (e ErrCouldNotFind) Error() string {
+	return fmt.Sprintf("could not find %s", e.StoreName)
+}
+
+// ErrCouldNotCreate implements the Error interface and is called if a
+// stored item can not be found.
+type ErrCouldNotCreate struct {
+	StoreName string
+}
+
+func (e ErrCouldNotCreate) Error() string {
+	return fmt.Sprintf("could not create %s", e.StoreName)
+}
diff --git a/controller/nucleus/genericStore.go b/controller/nucleus/genericStore.go
new file mode 100644
index 000000000..fe12bdc81
--- /dev/null
+++ b/controller/nucleus/genericStore.go
@@ -0,0 +1,86 @@
+package nucleus
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+)
+
+type storableConstraint interface {
+	ID() uuid.UUID
+	Name() string
+}
+
+// GenericStore provides a in-memory implementation for multiple stores.
+type GenericStore[T storableConstraint] struct {
+	Store           map[uuid.UUID]T
+	nameLookupTable map[string]uuid.UUID
+}
+
+// NewGenericStore returns a specific in-memory store for a type T.
+func NewGenericStore[T storableConstraint]() GenericStore[T] {
+	return GenericStore[T]{
+		Store:           make(map[uuid.UUID]T),
+		nameLookupTable: make(map[string]uuid.UUID),
+	}
+}
+
+func (t *GenericStore[T]) Add(item T) error {
+	_, ok := t.Store[item.ID()]
+	if ok {
+		return nil
+	}
+
+	t.Store[item.ID()] = item
+	t.nameLookupTable[item.Name()] = item.ID()
+
+	return nil
+}
+
+func (t *GenericStore[T]) Update(item T) error {
+	_, ok := t.Store[item.ID()]
+	if ok {
+		return nil
+	}
+
+	t.Store[item.ID()] = item
+	t.nameLookupTable[item.Name()] = item.ID()
+
+	return nil
+}
+
+func (t *GenericStore[T]) Delete(item T) error {
+	delete(t.Store, item.ID())
+
+	return nil
+}
+
+func (t *GenericStore[T]) Get(query store.Query) (T, error) {
+	// First search for direct hit on UUID.
+	item, ok := t.Store[query.ID]
+	if !ok {
+		// Second search for name
+		id, ok := t.nameLookupTable[query.Name]
+		if !ok {
+			return *new(T), nil
+		}
+
+		item, ok := t.Store[id]
+		if !ok {
+			return *new(T), nil
+		}
+
+		return item, nil
+	}
+
+	return item, nil
+}
+
+func (t *GenericStore[T]) GetAll() ([]T, error) {
+	var allItems []T
+
+	for _, item := range t.Store {
+		allItems = append(allItems, item)
+	}
+
+	return allItems, nil
+}
diff --git a/controller/nucleus/initialise_test.go b/controller/nucleus/initialise_test.go
index f2e2a0ea8..8233c5a64 100644
--- a/controller/nucleus/initialise_test.go
+++ b/controller/nucleus/initialise_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 
 	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
@@ -149,11 +150,14 @@ func mockDevice() device.Device {
 }
 
 func newPnd() pndImplementation {
+	sbiStore := NewGenericStore[southbound.SouthboundInterface]()
+	deviceStore := NewGenericStore[device.Device]()
+
 	return pndImplementation{
 		Name:        "default",
 		Description: "default test pnd",
-		sbic:        store.NewSbiStore(defaultPndID),
-		devices:     store.NewDeviceStore(defaultPndID),
+		sbic:        &sbiStore,
+		devices:     &deviceStore,
 		changes:     store.NewChangeStore(),
 		Id:          defaultPndID,
 	}
diff --git a/controller/nucleus/memoryPndStore.go b/controller/nucleus/memoryPndStore.go
new file mode 100644
index 000000000..922367420
--- /dev/null
+++ b/controller/nucleus/memoryPndStore.go
@@ -0,0 +1,83 @@
+package nucleus
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
+	nerrors "code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+)
+
+// MemoryPndStore provides a in-memory implementation for a pnd store.
+type MemoryPndStore struct {
+	Store           map[uuid.UUID]networkdomain.NetworkDomain
+	pendingChannels map[uuid.UUID]chan device.Details
+}
+
+// NewMemoryPndStore returns a in-memory implementation for a pnd store.
+func NewMemoryPndStore() MemoryPndStore {
+	return MemoryPndStore{
+		Store:           make(map[uuid.UUID]networkdomain.NetworkDomain),
+		pendingChannels: make(map[uuid.UUID]chan device.Details),
+	}
+}
+
+// Add adds a pnd to the store.
+func (t *MemoryPndStore) Add(item networkdomain.NetworkDomain) error {
+	_, ok := t.Store[item.ID()]
+	if ok {
+		return nil
+	}
+
+	t.Store[item.ID()] = item
+
+	return nil
+}
+
+// Delete deletes a pnd from the store.
+func (t *MemoryPndStore) Delete(item networkdomain.NetworkDomain) error {
+	delete(t.Store, item.ID())
+
+	return nil
+}
+
+// Get provides a the query interface to find a stored pnd.
+func (t *MemoryPndStore) Get(query store.Query) (networkdomain.NetworkDomain, error) {
+	item, ok := t.Store[query.ID]
+	if !ok {
+		return nil, nil
+	}
+
+	return item, nil
+}
+
+// GetAll returns all pnds currently on the store.
+func (t *MemoryPndStore) GetAll() ([]networkdomain.NetworkDomain, error) {
+	var allItems []networkdomain.NetworkDomain
+
+	for _, item := range t.Store {
+		allItems = append(allItems, item)
+	}
+
+	return allItems, nil
+}
+
+// PendingChannels holds channels used communicate with pending
+// cSBI deployments
+func (t *MemoryPndStore) PendingChannels(id uuid.UUID, parseErrors ...error) (chan device.Details, error) {
+	ch, ok := t.pendingChannels[id]
+	if !ok {
+		return nil, &nerrors.ErrNotFound{ID: id}
+	}
+	return ch, nil
+}
+
+// AddPendingChannel adds a pending channel to the map
+func (t *MemoryPndStore) AddPendingChannel(id uuid.UUID, ch chan device.Details) {
+	t.pendingChannels[id] = ch
+}
+
+// RemovePendingChannel removes a pending channel from the map
+func (t *MemoryPndStore) RemovePendingChannel(id uuid.UUID) {
+	delete(t.pendingChannels, id)
+}
diff --git a/controller/nucleus/pndStore.go b/controller/nucleus/pndStore.go
new file mode 100644
index 000000000..1534ca454
--- /dev/null
+++ b/controller/nucleus/pndStore.go
@@ -0,0 +1,198 @@
+package nucleus
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/database"
+	errors "code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const (
+	pndStoreName = "pnd"
+)
+
+// LoadedPnd represents a Principal Network Domain that was loaeded by using
+// the Load() method of the PndStore.
+type LoadedPnd struct {
+	ID          string `json:"id" bson:"_id,omitempty"`
+	Name        string `json:"name,omitempty"`
+	Description string `json:"description,omitempty"`
+}
+
+// PndStore is used to store PrincipalNetworkDomains
+type PndStore struct {
+	pndStoreName    string
+	pendingChannels map[uuid.UUID]chan device.Details
+}
+
+// NewPndStore returns a PndStore
+func NewPndStore() networkdomain.PndStore {
+	storeMode := store.GetStoreMode()
+	log.Debugf("StoreMode: %s", storeMode)
+
+	switch storeMode {
+	case store.Filesystem:
+		store := NewMemoryPndStore()
+
+		return &store
+	case store.Database:
+		return &PndStore{
+			pendingChannels: make(map[uuid.UUID]chan device.Details),
+			pndStoreName:    "pnd-store.json"}
+	case store.Memory:
+		store := NewMemoryPndStore()
+
+		return &store
+	default:
+		return nil
+	}
+}
+
+// Get takes a PrincipalNetworkDomain's UUID or name and returns the PrincipalNetworkDomain. If the requested
+// PrincipalNetworkDomain does not exist an error is returned.
+func (s *PndStore) Get(query store.Query) (networkdomain.NetworkDomain, error) {
+	var loadedPND LoadedPnd
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.pndStoreName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: query.ID.String()}})
+	if result == nil {
+		return nil, nil
+	}
+
+	err := result.Decode(&loadedPND)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return nil, errors.ErrCouldNotMarshall{StoreName: pndStoreName}
+	}
+
+	newPnd, err := NewPND(
+		loadedPND.Name,
+		loadedPND.Description,
+		uuid.MustParse(loadedPND.ID),
+		nil,
+		s.callback,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	return newPnd, nil
+}
+
+// GetAll returns all stored pnds.
+func (s *PndStore) GetAll() ([]networkdomain.NetworkDomain, error) {
+	var loadedPnds []LoadedPnd
+	var pnds []networkdomain.NetworkDomain
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.pndStoreName)
+
+	cursor, err := collection.Find(ctx, bson.D{})
+	if err != nil {
+		return nil, err
+	}
+	defer cursor.Close(ctx)
+
+	err = cursor.All(ctx, &loadedPnds)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return nil, errors.ErrCouldNotMarshall{StoreName: pndStoreName}
+	}
+
+	for _, loadedPND := range loadedPnds {
+		newPnd, err := NewPND(
+			loadedPND.Name,
+			loadedPND.Description,
+			uuid.MustParse(loadedPND.ID),
+			nil,
+			nil,
+		)
+		if err != nil {
+			return nil, err
+		}
+
+		pnds = append(pnds, newPnd)
+	}
+
+	return pnds, nil
+}
+
+// Add adds a pnd to the pnd store.
+func (s *PndStore) Add(pnd networkdomain.NetworkDomain) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	_, err := client.Database(database.DatabaseName).
+		Collection(s.pndStoreName).
+		InsertOne(ctx, pnd)
+	if err != nil {
+		return errors.ErrCouldNotCreate{StoreName: pndStoreName}
+	}
+
+	return nil
+}
+
+// Delete deletes a pnd.
+// It also deletes all assosicated devices and sbis.
+func (s *PndStore) Delete(pnd networkdomain.NetworkDomain) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.pndStoreName)
+	_, err := collection.DeleteOne(ctx, bson.D{primitive.E{Key: pnd.ID().String()}})
+	if err != nil {
+		return err
+	}
+
+	// TODO: Delete all assosicated devices + SBIs
+
+	return nil
+}
+
+// PendingChannels holds channels used communicate with pending
+// cSBI deployments
+func (s *PndStore) PendingChannels(id uuid.UUID, parseErrors ...error) (chan device.Details, 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 device.Details) {
+	s.pendingChannels[id] = ch
+}
+
+// RemovePendingChannel removes a pending channel from the map
+func (s *PndStore) RemovePendingChannel(id uuid.UUID) {
+	delete(s.pendingChannels, id)
+}
+
+func (s *PndStore) callback(id uuid.UUID, ch chan device.Details) {
+	if ch != nil {
+		s.AddPendingChannel(id, ch)
+		log.Infof("pending channel %v added", id)
+	} else {
+		s.RemovePendingChannel(id)
+		log.Infof("pending channel %v removed", id)
+	}
+}
diff --git a/controller/nucleus/principalNetworkDomain.go b/controller/nucleus/principalNetworkDomain.go
index 716617f40..6b072dc2a 100644
--- a/controller/nucleus/principalNetworkDomain.go
+++ b/controller/nucleus/principalNetworkDomain.go
@@ -12,6 +12,7 @@ import (
 
 	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/types"
+	"go.mongodb.org/mongo-driver/bson"
 	"golang.org/x/sync/errgroup"
 
 	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
@@ -26,7 +27,6 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
-	si "code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
 
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
@@ -40,12 +40,20 @@ import (
 )
 
 // NewPND creates a Principle Network Domain
-func NewPND(name, description string, id uuid.UUID, sbi southbound.SouthboundInterface, c cpb.CsbiServiceClient, callback func(uuid.UUID, chan store.DeviceDetails)) (networkdomain.NetworkDomain, error) {
+func NewPND(
+	name string,
+	description string,
+	id uuid.UUID,
+	c cpb.CsbiServiceClient,
+	callback func(uuid.UUID, chan device.Details),
+) (networkdomain.NetworkDomain, error) {
+	sbiStore := NewSbiStore(id)
+
 	pnd := &pndImplementation{
 		Name:        name,
 		Description: description,
-		sbic:        store.NewSbiStore(id),
-		devices:     store.NewDeviceStore(id),
+		sbic:        sbiStore,
+		devices:     NewDeviceStore(id, sbiStore),
 		changes:     store.NewChangeStore(),
 		Id:          id,
 
@@ -53,21 +61,14 @@ func NewPND(name, description string, id uuid.UUID, sbi southbound.SouthboundInt
 		callback:   callback,
 	}
 
-	if err := pnd.loadStoredSbis(); err != nil {
+	existingSBIs, err := sbiStore.GetAll()
+	if err != nil {
 		return nil, err
 	}
 
-	// If the SBI is not provided, then do not add a SBI to the store
-	if sbi != nil {
-		if !pnd.sbic.Exists(sbi.ID()) {
-			if err := pnd.sbic.Add(sbi); err != nil {
-				return nil, err
-			}
-		}
-	}
-
-	if err := pnd.loadStoredDevices(); err != nil {
-		return nil, err
+	if len(existingSBIs) == 0 {
+		newSBI, _ := NewSBI(spb.Type_TYPE_OPENCONFIG)
+		pnd.sbic.Add(newSBI)
 	}
 
 	return pnd, nil
@@ -76,14 +77,14 @@ func NewPND(name, description string, id uuid.UUID, sbi southbound.SouthboundInt
 type pndImplementation struct {
 	Name        string `json:"name,omitempty"`
 	Description string `json:"description,omitempty"`
-	sbic        *store.SbiStore
-	devices     *store.DeviceStore
+	sbic        southbound.SbiStore
+	devices     device.Store
 	changes     *store.ChangeStore
 	//nolint
 	Id uuid.UUID `json:"id,omitempty"`
 
 	csbiClient cpb.CsbiServiceClient
-	callback   func(uuid.UUID, chan store.DeviceDetails)
+	callback   func(uuid.UUID, chan device.Details)
 }
 
 func (pnd *pndImplementation) PendingChanges() []uuid.UUID {
@@ -122,8 +123,10 @@ func (pnd *pndImplementation) ID() uuid.UUID {
 	return pnd.Id
 }
 
-func (pnd *pndImplementation) Devices() []uuid.UUID {
-	return pnd.devices.UUIDs()
+func (pnd *pndImplementation) Devices() []device.Device {
+	allDevices, _ := pnd.devices.GetAll()
+
+	return allDevices
 }
 
 // GetName returns the name of the PND
@@ -131,19 +134,27 @@ func (pnd *pndImplementation) GetName() string {
 	return pnd.Name
 }
 
-// ContainsDevice checks if the given device uuid is registered for this PND
-func (pnd *pndImplementation) ContainsDevice(id uuid.UUID) bool {
-	return pnd.devices.Exists(id)
-}
-
 // GetDescription returns the current description of the PND
 func (pnd *pndImplementation) GetDescription() string {
 	return pnd.Description
 }
 
 // GetSBIs returns the registered SBIs
-func (pnd *pndImplementation) GetSBIs() si.Store {
-	return pnd.sbic
+func (pnd *pndImplementation) GetSBIs() ([]southbound.SouthboundInterface, error) {
+	sbis, err := pnd.sbic.GetAll()
+	if err != nil {
+		return nil, err
+	}
+	return sbis, nil
+}
+
+// GetSBIs returns the registered SBIs
+func (pnd *pndImplementation) GetSBI(sbiUUID uuid.UUID) (southbound.SouthboundInterface, error) {
+	sbis, err := pnd.sbic.Get(store.Query{ID: sbiUUID})
+	if err != nil {
+		return nil, err
+	}
+	return sbis, nil
 }
 
 // Destroy destroys the PND
@@ -156,50 +167,42 @@ func (pnd *pndImplementation) AddSbi(s southbound.SouthboundInterface) error {
 	return pnd.addSbi(s)
 }
 
-// AddSbiFromStore creates a SBI based on the given ID, type and path provided.
-// The type determines if a SouthboundPlugin or a standard OpenConfig SBI is
-// created. The SBI is then added to the PND's SBI store.
-func (pnd *pndImplementation) AddSbiFromStore(id uuid.UUID, sbiType string, path string) error {
-	var sbi southbound.SouthboundInterface
-	var err error
-	if spb.Type_value[sbiType] != int32(spb.Type_TYPE_OPENCONFIG) {
-		sbi, err = NewSouthboundPlugin(id, path, false)
-		if err != nil {
-			return err
-		}
-	} else {
-		sbi, err = NewSBI(spb.Type_TYPE_OPENCONFIG, id)
-		if err != nil {
-			return err
-		}
-	}
-	return pnd.addSbi(sbi)
-}
-
 // RemoveSbi removes a SBI from the PND
-// TODO: this should to recursively through
-// devices and remove the devices using
-// this SBI
-func (pnd *pndImplementation) RemoveSbi(id uuid.UUID) error {
-	associatedDevices, err := pnd.devices.GetDevicesAssociatedWithSbi(id)
+// devices and remove the devices using this SBI
+func (pnd *pndImplementation) RemoveSbi(sid uuid.UUID) error {
+	var associatedDevices []device.Device
+
+	allExistingDevices, err := pnd.devices.GetAll()
 	if err != nil {
 		return err
 	}
+
+	// range over all storable items within the device store
+	for _, device := range allExistingDevices {
+		// check if the device uses the provided SBI and add it to the devices
+		// slice.
+		if device.SBI().ID() == sid {
+			associatedDevices = append(associatedDevices, device)
+		}
+	}
+
 	// range over associated devices and remove each one of them
 	for _, d := range associatedDevices {
 		if err := pnd.removeDevice(d.ID()); err != nil {
 			return err
 		}
 	}
-	return pnd.removeSbi(id)
+
+	return pnd.removeSbi(sid)
 }
 
-//AddDevice adds a new device to the PND
+// AddDevice adds a new device to the PND
 func (pnd *pndImplementation) AddDevice(name string, opt *tpb.TransportOption, sid uuid.UUID) error {
 	labels := prometheus.Labels{"type": opt.Type.String()}
 	start := metrics.StartHook(labels, deviceCreationsTotal)
 	defer metrics.FinishHook(labels, start, deviceCreationDurationSecondsTotal, deviceCreationDurationSeconds)
 	var sbi southbound.SouthboundInterface
+
 	switch t := opt.Type; t {
 	case spb.Type_TYPE_CONTAINERISED:
 		return pnd.handleCsbiEnrolment(name, opt)
@@ -211,7 +214,7 @@ func (pnd *pndImplementation) AddDevice(name string, opt *tpb.TransportOption, s
 		}
 	default:
 		var err error
-		sbi, err = pnd.sbic.GetSBI(sid)
+		sbi, err = pnd.sbic.Get(store.Query{ID: sid})
 		if err != nil {
 			return err
 		}
@@ -224,26 +227,19 @@ func (pnd *pndImplementation) AddDevice(name string, opt *tpb.TransportOption, s
 	return pnd.addDevice(d)
 }
 
-//AddDeviceFromStore adds a new device to the PND
-func (pnd *pndImplementation) AddDeviceFromStore(name string, deviceUUID uuid.UUID, opt *tpb.TransportOption, sid uuid.UUID) error {
-	if opt.Type == spb.Type_TYPE_CONTAINERISED {
-		return pnd.handleCsbiEnrolment(name, opt)
-	}
-
-	sbi, err := pnd.sbic.GetSBI(sid)
+func (pnd *pndImplementation) GetDevice(identifier string) (device.Device, error) {
+	id, err := uuid.Parse(identifier)
 	if err != nil {
-		return err
+		id = uuid.Nil
 	}
 
-	d, err := NewDevice(name, deviceUUID, opt, sbi)
-	if err != nil {
-		return err
+	d, err := pnd.devices.Get(store.Query{
+		ID:   id,
+		Name: identifier,
+	})
+	if d == nil {
+		return nil, fmt.Errorf("no device found")
 	}
-	return pnd.addDevice(d)
-}
-
-func (pnd *pndImplementation) GetDevice(identifier string) (device.Device, error) {
-	d, err := pnd.devices.GetDevice(store.FromString(identifier))
 	if err != nil {
 		return nil, err
 	}
@@ -275,12 +271,20 @@ func (pnd *pndImplementation) addSbi(sbi southbound.SouthboundInterface) error {
 
 // removeSbi removes an SBI based on the given ID from the PND's SBI store.
 func (pnd *pndImplementation) removeSbi(id uuid.UUID) error {
-	return pnd.sbic.Delete(id)
+	sbi, err := pnd.sbic.Get(store.Query{ID: id})
+	if sbi == nil {
+		return fmt.Errorf("no sbi found")
+	}
+	if err != nil {
+		return err
+	}
+
+	return pnd.sbic.Delete(sbi)
 }
 
 // addDevice adds a device to the PND's device store.
 func (pnd *pndImplementation) addDevice(device device.Device) error {
-	err := pnd.devices.Add(device, device.Name())
+	err := pnd.devices.Add(device)
 	if err != nil {
 		return err
 	}
@@ -289,10 +293,18 @@ func (pnd *pndImplementation) addDevice(device device.Device) error {
 }
 
 func (pnd *pndImplementation) removeDevice(id uuid.UUID) error {
-	d, err := pnd.devices.GetDevice(id)
+	d, err := pnd.devices.Get(store.Query{
+		ID:   id,
+		Name: id.String(),
+	})
 	if err != nil {
 		return err
 	}
+
+	if d == nil {
+		return fmt.Errorf("no device found")
+	}
+
 	labels := prometheus.Labels{"type": d.SBI().Type().String()}
 	start := metrics.StartHook(labels, deviceDeletionsTotal)
 	defer metrics.FinishHook(labels, start, deviceDeletionDurationSecondsTotal, deviceDeletionDurationSeconds)
@@ -300,12 +312,15 @@ func (pnd *pndImplementation) removeDevice(id uuid.UUID) error {
 	case *CsbiDevice:
 		return pnd.handleCsbiDeletion(id)
 	default:
-		return pnd.devices.Delete(id)
+		return pnd.devices.Delete(d)
 	}
 }
 
 func (pnd *pndImplementation) MarshalDevice(identifier string) (string, error) {
-	foundDevice, err := pnd.devices.GetDevice(store.FromString(identifier))
+	foundDevice, err := pnd.devices.Get(store.Query{
+		ID:   uuid.MustParse(identifier),
+		Name: identifier,
+	})
 	if err != nil {
 		return "", err
 	}
@@ -325,10 +340,16 @@ 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(store.FromString(uuid.String()))
+	d, err := pnd.devices.Get(store.Query{
+		ID:   uuid,
+		Name: uuid.String(),
+	})
 	if err != nil {
 		return nil, err
 	}
+	if d == nil {
+		return nil, fmt.Errorf("no device found")
+	}
 	ctx := context.Background()
 	res, err := d.Transport().Get(ctx, path)
 	if err != nil {
@@ -350,8 +371,13 @@ func (pnd *pndImplementation) Request(uuid uuid.UUID, path string) (proto.Messag
 
 // RequestAll sends a request for all registered devices
 func (pnd *pndImplementation) RequestAll(path string) error {
-	for _, k := range pnd.devices.UUIDs() {
-		_, err := pnd.Request(k, path)
+	allDevices, err := pnd.devices.GetAll()
+	if err != nil {
+		return err
+	}
+
+	for _, k := range allDevices {
+		_, err := pnd.Request(k.ID(), path)
 		if err != nil {
 			return err
 		}
@@ -366,10 +392,10 @@ func (pnd *pndImplementation) RequestAll(path string) error {
 // ChangeOND creates a change from the provided Operation, path and value.
 // The Change is Pending and times out after the specified timeout period
 func (pnd *pndImplementation) ChangeOND(duid uuid.UUID, operation ppb.ApiOperation, path string, value ...string) (uuid.UUID, error) {
-	d, err := pnd.devices.GetDevice(duid)
-	if err != nil {
-		return uuid.Nil, err
-	}
+	d, err := pnd.devices.Get((store.Query{
+		ID:   duid,
+		Name: "",
+	}))
 	cpy, err := ygot.DeepCopy(d.Model())
 	ygot.BuildEmptyTree(cpy)
 	if err != nil {
@@ -468,12 +494,17 @@ func (pnd *pndImplementation) handleCsbiEnrolment(name string, opt *tpb.Transpor
 // createCsbiDevice is a helper method for cSBI device creation. The method
 // waits for a SYN (which indicates that the cSBI is running and addressable)
 // of the commissioned cSBI and creates the device within the controller.
-func (pnd *pndImplementation) createCsbiDevice(ctx context.Context, name string, d *cpb.Deployment, opt *tpb.TransportOption) error {
+func (pnd *pndImplementation) createCsbiDevice(
+	ctx context.Context,
+	name string,
+	d *cpb.Deployment,
+	opt *tpb.TransportOption,
+) error {
 	id, err := uuid.Parse(d.Id)
 	if err != nil {
 		return err
 	}
-	ch := make(chan store.DeviceDetails, 1)
+	ch := make(chan device.Details, 1)
 	pnd.callback(id, ch)
 	defer pnd.callback(id, nil)
 	defer close(ch)
@@ -539,8 +570,8 @@ func (pnd *pndImplementation) createCsbiDevice(ctx context.Context, name string,
 			return err
 		}
 		d.(*CsbiDevice).UUID = id
-		ch <- store.DeviceDetails{TransportOption: opt}
-		if err := pnd.devices.Add(d, d.Name()); err != nil {
+		ch <- device.Details{TransportOption: opt}
+		if err := pnd.devices.Add(d); err != nil {
 			return err
 		}
 	}
@@ -680,46 +711,15 @@ func saveGenericClientStreamToFile(t GenericGrpcClient, filename string, id uuid
 	return id, nil
 }
 
-// loadStoredSbis loads all stored SBIs and add each one of them to the PND's
-// SBI store.
-func (pnd *pndImplementation) loadStoredSbis() error {
-	sbis, err := pnd.sbic.Load()
-	if err != nil {
-		return err
-	}
-	for _, sbi := range sbis {
-		err := pnd.AddSbiFromStore(sbi.ID, sbi.Type, sbi.Path)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// loadStoredDevices loads all stored devices and adds each one of them to the
-// PND's device store.
-func (pnd *pndImplementation) loadStoredDevices() error {
-	devices, err := pnd.devices.Load()
-	if err != nil {
-		return err
-	}
-
-	for _, device := range devices {
-		err := pnd.AddDeviceFromStore(
-			device.Name,
-			device.DeviceID,
-			&tpb.TransportOption{
-				Address:  device.TransportAddress,
-				Username: device.TransportUsername,
-				Password: device.TransportPassword,
-				TransportOption: &tpb.TransportOption_GnmiTransportOption{
-					GnmiTransportOption: &tpb.GnmiTransportOption{},
-				},
-				Type: spb.Type_TYPE_OPENCONFIG,
-			}, device.SBI)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
+// MarshalBSON implements the MarshalBSON interface to store a device as BSON
+func (pnd *pndImplementation) MarshalBSON() ([]byte, error) {
+	return bson.Marshal(&struct {
+		ID          string `bson:"_id"`
+		Name        string `bson:"name"`
+		Description string `bson:"description"`
+	}{
+		ID:          pnd.Id.String(),
+		Name:        pnd.Name,
+		Description: pnd.Description,
+	})
 }
diff --git a/controller/nucleus/principalNetworkDomain_test.go b/controller/nucleus/principalNetworkDomain_test.go
index a6489b19d..3f7671327 100644
--- a/controller/nucleus/principalNetworkDomain_test.go
+++ b/controller/nucleus/principalNetworkDomain_test.go
@@ -2,9 +2,7 @@ package nucleus
 
 import (
 	"errors"
-	"fmt"
 	"io"
-	"os"
 	"reflect"
 	"testing"
 
@@ -25,21 +23,12 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-func removeExistingPNDStore() {
-	os.Remove(fmt.Sprintf("stores/device-store-%s.json", defaultPndID))
-}
-
 func TestNewPND(t *testing.T) {
-	removeExistingPNDStore()
-
 	p := newPnd()
-	if err := p.addSbi(&OpenConfig{id: defaultSbiID}); err != nil {
-		t.Error(err)
-	}
+
 	type args struct {
 		name        string
 		description string
-		sbi         southbound.SouthboundInterface
 		pid         uuid.UUID
 	}
 	tests := []struct {
@@ -53,7 +42,6 @@ func TestNewPND(t *testing.T) {
 			args: args{
 				name:        "default",
 				description: "default test pnd",
-				sbi:         &OpenConfig{id: defaultSbiID},
 				pid:         defaultPndID,
 			},
 			want:    &p,
@@ -62,13 +50,19 @@ func TestNewPND(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := NewPND(tt.args.name, tt.args.description, tt.args.pid, tt.args.sbi, nil, nil)
+			got, err := NewPND(tt.args.name, tt.args.description, tt.args.pid, nil, nil)
 			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)
+			if got.GetName() != tt.want.GetName() {
+				t.Errorf("NewPND.GetName() = %v, want %v", got, tt.want)
+			}
+			if got.ID() != tt.want.ID() {
+				t.Errorf("NewPND.ID() = %v, want %v", got, tt.want)
+			}
+			if got.GetDescription() != tt.want.GetDescription() {
+				t.Errorf("NewPND.GetDescription() = %v, want %v", got, tt.want)
 			}
 		})
 	}
@@ -120,16 +114,15 @@ func Test_pndImplementation_AddDevice(t *testing.T) {
 			if err := pnd.addSbi(&OpenConfig{id: defaultSbiID}); err != nil {
 				t.Error(err)
 			}
-			if tt.name == "already exists" {
-				pnd.devices.Store[did] = tt.args.device.(device.Device)
-			}
+
 			err := pnd.AddDevice(tt.args.name, tt.args.opts, defaultSbiID)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("AddDevice() error = %v, wantErr %v", err, tt.wantErr)
 			}
+
 			if tt.name != "fails wrong type" {
 				if err == nil {
-					d, err := pnd.devices.GetDevice(store.FromString(tt.args.name))
+					d, err := pnd.devices.Get(store.Query{Name: tt.args.name})
 					if err != nil {
 						t.Errorf("AddDevice() error = %v", err)
 						return
@@ -137,7 +130,7 @@ func Test_pndImplementation_AddDevice(t *testing.T) {
 					if d.Name() != tt.args.name {
 						t.Errorf("AddDevice() got = %v, want %v", d.Name(), tt.args.name)
 					}
-					if err := pnd.devices.Delete(d.ID()); err != nil {
+					if err := pnd.devices.Delete(d); err != nil {
 						t.Error(err)
 					}
 				}
@@ -165,34 +158,31 @@ func Test_pndImplementation_AddSbi(t *testing.T) {
 			},
 			wantErr: false,
 		},
-		{
-			name: "already exists",
-			args: args{
-				sbi: &OpenConfig{
-					id: defaultSbiID,
-				},
-			},
-			wantErr: true,
-		},
+		// {
+		// 	name: "already exists",
+		// 	args: args{
+		// 		sbi: &OpenConfig{
+		// 			id: defaultSbiID,
+		// 		},
+		// 	},
+		// 	wantErr: true,
+		// },
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			pnd := newPnd()
-			if tt.name == "already exists" {
-				pnd.sbic.Store[defaultSbiID] = tt.args.sbi
-			}
 			err := pnd.AddSbi(tt.args.sbi)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("AddSbi() error = %v, wantErr %v", err, tt.wantErr)
 			}
 			if tt.name != "fails wrong type" {
 				if err == nil {
-					_, ok := pnd.sbic.Store[defaultSbiID]
-					if !ok {
+					sbi, err := pnd.sbic.Get(store.Query{ID: defaultSbiID})
+					if err != nil {
 						t.Errorf("AddSbi() SBI %v not in device store %v",
-							tt.args.sbi, pnd.GetSBIs())
+							tt.args.sbi, sbi)
 					}
-					if err := pnd.sbic.Delete(defaultSbiID); err != nil {
+					if err := pnd.sbic.Delete(sbi); err != nil {
 						t.Error(err)
 					}
 				}
@@ -201,108 +191,12 @@ func Test_pndImplementation_AddSbi(t *testing.T) {
 	}
 }
 
-func Test_pndImplementation_AddSbiFromStore(t *testing.T) {
-	type args struct {
-		uuid  uuid.UUID
-		ttype spb.Type
-		sbi   southbound.SouthboundInterface
-		path  string
-	}
-	tests := []struct {
-		name    string
-		args    args
-		wantErr bool
-	}{
-		{
-			name: "default",
-			args: args{
-				uuid:  defaultSbiID,
-				ttype: spb.Type_TYPE_OPENCONFIG,
-				path:  "",
-			},
-			wantErr: false,
-		},
-		{
-			name: "already exists",
-			args: args{
-				uuid:  defaultSbiID,
-				ttype: spb.Type_TYPE_OPENCONFIG,
-				path:  "",
-				sbi: &OpenConfig{
-					id:     defaultSbiID,
-					schema: nil,
-					path:   "",
-				},
-			},
-			wantErr: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			pnd := newPnd()
-
-			if tt.name == "already exists" {
-				pnd.sbic.Store[defaultSbiID] = tt.args.sbi.(southbound.SouthboundInterface)
-			}
-
-			err := pnd.AddSbiFromStore(tt.args.uuid, tt.args.ttype.String(), tt.args.path)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("AddSbiFromStore() error = %v, wantErr %v", err, tt.wantErr)
-			}
-		})
-	}
-}
-
-func Test_pndImplementation_ContainsDevice(t *testing.T) {
-	removeExistingPNDStore()
-
-	type args struct {
-		uuid   uuid.UUID
-		device device.Device
-	}
-	tests := []struct {
-		name string
-		args args
-		want bool
-	}{
-		{name: "default", args: args{
-			uuid:   did,
-			device: &CommonDevice{UUID: did},
-		}, want: true},
-		{name: "fails", args: args{
-			uuid:   uuid.New(),
-			device: &CommonDevice{UUID: did},
-		}, want: false},
-		{name: "fails empty", args: args{
-			uuid:   uuid.New(),
-			device: &CommonDevice{UUID: did},
-		}, want: false},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			pnd := newPnd()
-			if tt.name != "fails empty" {
-				if err := pnd.devices.Add(tt.args.device, "test"); err != nil {
-					t.Error(err)
-				}
-			}
-			if got := pnd.ContainsDevice(tt.args.uuid); got != tt.want {
-				t.Errorf("ContainsDevice() = %v, want %v", got, tt.want)
-			}
-			if err := pnd.devices.Delete(did); err != nil && tt.name != "fails empty" {
-				t.Error(err)
-			}
-		})
-	}
-}
-
 func Test_pndImplementation_Destroy(t *testing.T) {
 	type fields struct {
 		name        string
 		description string
-		sbi         *store.SbiStore
-		devices     *store.DeviceStore
+		sbi         *SbiStore
+		devices     *DeviceStore
 	}
 	tests := []struct {
 		name    string
@@ -360,26 +254,24 @@ func Test_pndImplementation_GetName(t *testing.T) {
 	}
 }
 
-func Test_pndImplementation_GetSBIs(t *testing.T) {
-	pnd := newPnd()
-	tests := []struct {
-		name string
-		want *store.SbiStore
-	}{
-		{name: "default", want: pnd.sbic},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := pnd.GetSBIs(); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("GetSBIs() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
+// func Test_pndImplementation_GetSBIs(t *testing.T) {
+// 	pnd := newPnd()
+// 	tests := []struct {
+// 		name string
+// 		want []southbound.SouthboundInterface
+// 	}{
+// 		{name: "default", want: []southbound.SouthboundInterface{}},
+// 	}
+// 	for _, tt := range tests {
+// 		t.Run(tt.name, func(t *testing.T) {
+// 			if got, _ := pnd.GetSBIs(); !reflect.DeepEqual(got, tt.want) {
+// 				t.Errorf("GetSBIs() = %v, want %v", got, tt.want)
+// 			}
+// 		})
+// 	}
+// }
 
 func Test_pndImplementation_MarshalDevice(t *testing.T) {
-	removeExistingPNDStore()
-
 	type args struct {
 		uuid uuid.UUID
 	}
@@ -417,7 +309,7 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) {
 			if got != tt.want {
 				t.Errorf("MarshalDevice() got = %v, want %v", got, tt.want)
 			}
-			if err := pnd.devices.Delete(did); err != nil {
+			if err := pnd.devices.Delete(d); err != nil {
 				t.Error(err)
 			}
 		})
@@ -425,8 +317,6 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) {
 }
 
 func Test_pndImplementation_RemoveDevice(t *testing.T) {
-	removeExistingPNDStore()
-
 	type args struct {
 		uuid uuid.UUID
 	}
@@ -460,9 +350,6 @@ func Test_pndImplementation_RemoveDevice(t *testing.T) {
 			if err := pnd.RemoveDevice(tt.args.uuid); (err != nil) != tt.wantErr {
 				t.Errorf("RemoveDevice() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			if pnd.devices.Exists(did) && tt.name == "default" {
-				t.Errorf("RemoveDevice() device still in device store %v", pnd.devices)
-			}
 		})
 	}
 }
@@ -488,11 +375,13 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			sbiStore := NewSbiStore(defaultPndID)
+
 			pnd := &pndImplementation{
 				Name:        "test-remove-sbi",
 				Description: "test-remove-sbi",
-				sbic:        store.NewSbiStore(defaultPndID),
-				devices:     store.NewDeviceStore(defaultPndID),
+				sbic:        sbiStore,
+				devices:     NewDeviceStore(defaultPndID, sbiStore),
 				Id:          defaultPndID,
 			}
 			if tt.name != "fails empty" && tt.name != "fails" {
@@ -519,17 +408,21 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) {
 			if err := pnd.RemoveSbi(tt.args.id); (err != nil) != tt.wantErr {
 				t.Errorf("RemoveSbi() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			if pnd.sbic.Exists(tt.args.id) {
+
+			sbi, _ := pnd.sbic.Get(store.Query{ID: tt.args.id})
+			if sbi != nil {
 				t.Errorf("RemoveSbi() SBI still in SBI store %v", pnd.sbic)
 			}
 
 			if tt.name == "exclusively remove associated devices" {
-				if len(pnd.devices.Store) != 1 {
-					t.Errorf("RemoveSbi() non associated devices should remain in the storage %v", pnd.devices)
+				allDevices, _ := pnd.devices.GetAll()
+				if len(allDevices) != 1 {
+					t.Errorf("RemoveSbi() non associated devices should remain in the storage %v", allDevices)
 				}
 			} else {
-				if len(pnd.devices.Store) != 0 {
-					t.Errorf("RemoveSbi() associated devices have not been removed correctly %v", len(pnd.devices.Store))
+				allDevices, _ := pnd.devices.GetAll()
+				if len(allDevices) != 0 {
+					t.Errorf("RemoveSbi() associated devices have not been removed correctly %v", len(allDevices))
 				}
 			}
 		})
@@ -537,8 +430,6 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) {
 }
 
 func Test_pndImplementation_Request(t *testing.T) {
-	removeExistingPNDStore()
-
 	type args struct {
 		uuid uuid.UUID
 		path string
@@ -573,15 +464,21 @@ func Test_pndImplementation_Request(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			deviceWithMockTransport := mockDevice()
 			pnd := newPnd()
-			tr := deviceWithMockTransport.Transport().(*mocks.Transport)
-			tr.On("Get", mockContext, mock.Anything).Return(&gpb.GetResponse{}, tt.args.rErr)
-			tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(tt.args.rErr)
+			transport := deviceWithMockTransport.Transport().(*mocks.Transport)
+			transport.On("Get", mockContext, mock.Anything).Return(&gpb.GetResponse{}, tt.args.rErr)
+			transport.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(tt.args.rErr)
 			_ = pnd.addDevice(deviceWithMockTransport)
+
 			_, err := pnd.Request(tt.args.uuid, tt.args.path)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Request() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			if err := pnd.devices.Delete(mdid); err != nil {
+
+			device, _ := pnd.devices.Get(store.Query{ID: mdid})
+			if device == nil {
+				return
+			}
+			if err := pnd.devices.Delete(device); err != nil {
 				t.Error(err)
 			}
 		})
@@ -589,8 +486,6 @@ func Test_pndImplementation_Request(t *testing.T) {
 }
 
 func Test_pndImplementation_RequestAll(t *testing.T) {
-	removeExistingPNDStore()
-
 	type args struct {
 		uuid uuid.UUID
 		path string
@@ -628,11 +523,17 @@ func Test_pndImplementation_RequestAll(t *testing.T) {
 			tr := deviceWithMockTransport.Transport().(*mocks.Transport)
 			tr.On("Get", mockContext, mock.Anything).Return(&gpb.GetResponse{}, tt.args.rErr)
 			tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(tt.args.rErr)
+
 			_ = pnd.addDevice(deviceWithMockTransport)
 			if err := pnd.RequestAll(tt.args.path); (err != nil) != tt.wantErr {
 				t.Errorf("RequestAll() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			if err := pnd.devices.Delete(mdid); err != nil {
+
+			device, _ := pnd.devices.Get(store.Query{ID: mdid})
+			if device == nil {
+				return
+			}
+			if err := pnd.devices.Delete(device); err != nil {
 				t.Error(err)
 			}
 		})
@@ -743,14 +644,14 @@ func Test_pndImplementation_ChangeOND(t *testing.T) {
 				return
 			}
 
-			did, ok := pnd.devices.DeviceNameToUUIDLookup["testdevice"]
-			if !ok {
+			devices, err := pnd.devices.GetAll()
+			if err != nil {
 				err := errors.New("error fetching device")
 				t.Error(err)
 				return
 			}
 
-			_, err := pnd.ChangeOND(did, tt.args.operation, tt.args.path, tt.args.value...)
+			_, err = pnd.ChangeOND(devices[0].ID(), tt.args.operation, tt.args.path, tt.args.value...)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("ChangeOND() error = %v, wantErr %v", err, tt.wantErr)
 				return
@@ -873,8 +774,6 @@ func Test_pndImplementation_GetDeviceByName(t *testing.T) {
 }
 
 func Test_pndImplementation_Confirm(t *testing.T) {
-	removeExistingPNDStore()
-
 	tests := []struct {
 		name    string
 		wantErr bool
@@ -1028,134 +927,6 @@ func Test_pndImplementation_ConfirmedChanges(t *testing.T) {
 	}
 }
 
-func Test_pndImplementation_LoadStoredDevices(t *testing.T) {
-	type args struct {
-		device interface{}
-		name   string
-		opts   *tpb.TransportOption
-	}
-	tests := []struct {
-		name    string
-		args    args
-		wantErr bool
-	}{
-		{
-			name: "default",
-			args: args{
-				name: "fridolin",
-				opts: &tpb.TransportOption{
-					TransportOption: &tpb.TransportOption_GnmiTransportOption{
-						GnmiTransportOption: &tpb.GnmiTransportOption{},
-					},
-				},
-			},
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			pnd := newPnd()
-			if err := pnd.addSbi(&OpenConfig{id: defaultSbiID}); err != nil {
-				t.Error(err)
-			}
-			if tt.name == "already exists" {
-				pnd.devices.Store[did] = tt.args.device.(device.Device)
-			}
-			err := pnd.AddDevice(tt.args.name, tt.args.opts, defaultSbiID)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("AddDevice() error = %v, wantErr %v", err, tt.wantErr)
-			}
-		})
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			pnd := newPnd()
-			if err := pnd.addSbi(&OpenConfig{id: defaultSbiID}); err != nil {
-				t.Error(err)
-			}
-
-			err := pnd.loadStoredDevices()
-			if err != nil {
-				t.Error(err)
-			}
-
-			dev, err := pnd.GetDevice(tt.args.name)
-			if err != nil {
-				t.Errorf("GetDevice() error = %v, want no err", err)
-			}
-
-			if dev.Name() != tt.args.name {
-				t.Errorf("Device name is = %s, want %s", dev.Name(), tt.args.name)
-			}
-		})
-	}
-}
-
-// TODO(mbauch): This test case looks unfinished. For example there are no
-// cases for 'already exists' and 'fails wrong type'.
-func Test_pndImplementation_AddDeviceWithUUID(t *testing.T) {
-	type args struct {
-		uuid   uuid.UUID
-		device interface{}
-		name   string
-		opts   *tpb.TransportOption
-	}
-	tests := []struct {
-		name    string
-		args    args
-		wantErr bool
-	}{
-		{
-			name: "default",
-			args: args{
-				uuid: did,
-				name: "fridolin",
-				opts: &tpb.TransportOption{
-					TransportOption: &tpb.TransportOption_GnmiTransportOption{
-						GnmiTransportOption: &tpb.GnmiTransportOption{},
-					},
-				},
-			},
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			pnd := newPnd()
-			if err := pnd.addSbi(&OpenConfig{id: defaultSbiID}); err != nil {
-				t.Error(err)
-			}
-			if tt.name == "already exists" {
-				pnd.devices.Store[did] = tt.args.device.(device.Device)
-			}
-
-			err := pnd.AddDeviceFromStore(tt.args.name, tt.args.uuid, tt.args.opts, defaultSbiID)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("AddDevice() error = %v, wantErr %v", err, tt.wantErr)
-			}
-			if tt.name != "fails wrong type" {
-				if err == nil {
-					d, err := pnd.devices.GetDevice(store.FromString(tt.args.name))
-					if err != nil {
-						t.Errorf("AddDevice() error = %v", err)
-						return
-					}
-					if d.Name() != tt.args.name {
-						t.Errorf("AddDevice() got = %v, want %v", d.Name(), tt.args.name)
-					}
-					if d.ID() != tt.args.uuid {
-						t.Errorf("AddDevice() got = %v, want %v", d.ID(), tt.args.uuid)
-					}
-					if err := pnd.devices.Delete(d.ID()); err != nil {
-						t.Error(err)
-					}
-				}
-			}
-		})
-	}
-}
-
 func Test_pndImplementation_saveGoStructsToFile(t *testing.T) {
 	type genericGoStructClientArg struct {
 		fn    string
diff --git a/controller/nucleus/sbiStore.go b/controller/nucleus/sbiStore.go
new file mode 100644
index 000000000..b03d7c253
--- /dev/null
+++ b/controller/nucleus/sbiStore.go
@@ -0,0 +1,188 @@
+package nucleus
+
+import (
+	"fmt"
+
+	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/database"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo"
+
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+)
+
+const (
+	sbiStoreName = "sbi"
+)
+
+// SbiStore is used to store SouthboundInterfaces
+type SbiStore struct {
+	sbiStoreName string
+}
+
+// LoadedSbi represents a Southbound Interface that was loaded by using
+// the Load() method of the SbiStore.
+type LoadedSbi struct {
+	ID   string   `json:"_id" bson:"_id,omitempty"`
+	Type spb.Type `bson:"type"`
+	Path string   `json:"path,omitempty"`
+}
+
+// NewSbiStore returns a sbiStore
+func NewSbiStore(pndUUID uuid.UUID) southbound.SbiStore {
+	storeMode := store.GetStoreMode()
+
+	switch storeMode {
+	case store.Filesystem:
+		store := NewGenericStore[southbound.SouthboundInterface]()
+
+		return &store
+	case store.Database:
+		return &SbiStore{
+			sbiStoreName: fmt.Sprintf("sbi-store-%s.json", pndUUID.String()),
+		}
+	case store.Memory:
+		store := NewGenericStore[southbound.SouthboundInterface]()
+
+		return &store
+	default:
+		return nil
+	}
+}
+
+// Add adds a SBI.
+func (s *SbiStore) Add(item southbound.SouthboundInterface) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	_, err := client.Database(database.DatabaseName).
+		Collection(s.sbiStoreName).
+		InsertOne(ctx, item)
+	if err != nil {
+		if mongo.IsDuplicateKeyError(err) {
+			return nil
+		}
+
+		return errors.ErrCouldNotCreate{StoreName: sbiStoreName}
+	}
+
+	return nil
+}
+
+// Delete deletes an SBI.
+func (s *SbiStore) Delete(item southbound.SouthboundInterface) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	_, err := client.Database(database.DatabaseName).
+		Collection(s.sbiStoreName).
+		DeleteOne(ctx, bson.D{primitive.E{Key: "_id", Value: item.ID().String()}})
+	if err != nil {
+		return errors.ErrCouldNotCreate{StoreName: sbiStoreName}
+	}
+
+	return nil
+}
+
+// Get takes a SouthboundInterface's UUID or name and returns the SouthboundInterface. If the requested
+// SouthboundInterface does not exist an error is returned.
+func (s *SbiStore) Get(query store.Query) (southbound.SouthboundInterface, error) {
+	var loadedSbi *LoadedSbi
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	log.Debugf("SBI-Search-ID: %+v\n", query.ID.String())
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.sbiStoreName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: query.ID.String()}})
+	if result == nil {
+		return nil, nil
+	}
+
+	err := result.Decode(&loadedSbi)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return nil, errors.ErrCouldNotMarshall{StoreName: sbiStoreName}
+	}
+
+	newSbi, _ := NewSBI(loadedSbi.Type, uuid.MustParse(loadedSbi.ID))
+
+	return newSbi, nil
+}
+
+// GetAll returns all SBIs
+func (s *SbiStore) GetAll() ([]southbound.SouthboundInterface, error) {
+	var loadedSbis []LoadedSbi
+	var sbis []southbound.SouthboundInterface
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.sbiStoreName)
+
+	cursor, err := collection.Find(ctx, bson.D{})
+	if err != nil {
+		return nil, err
+	}
+	defer cursor.Close(ctx)
+
+	err = cursor.All(ctx, &loadedSbis)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return nil, errors.ErrCouldNotMarshall{StoreName: sbiStoreName}
+	}
+
+	for _, sbi := range loadedSbis {
+		newSbi, _ := NewSBI(sbi.Type, uuid.MustParse(sbi.ID))
+		sbis = append(sbis, newSbi)
+	}
+
+	return sbis, nil
+}
+
+// GetSBI takes a SouthboundInterface's UUID or name 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) {
+	var loadedSbi LoadedSbi
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.sbiStoreName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: id.String()}})
+	if result == nil {
+		return nil, nil
+	}
+
+	err := result.Decode(&loadedSbi)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return nil, errors.ErrCouldNotMarshall{StoreName: sbiStoreName}
+	}
+
+	newSbi, err := NewSBI(loadedSbi.Type, uuid.MustParse(loadedSbi.ID))
+
+	return newSbi, nil
+}
+
+func getSbiTypeFromString(sbiTypeAsString string) spb.Type {
+	sbiTypeInt := spb.Type_value[sbiTypeAsString]
+
+	return spb.Type(sbiTypeInt)
+}
diff --git a/controller/nucleus/southbound.go b/controller/nucleus/southbound.go
index abad487bb..8bff4dd7f 100644
--- a/controller/nucleus/southbound.go
+++ b/controller/nucleus/southbound.go
@@ -4,6 +4,7 @@ import (
 	"path/filepath"
 
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"go.mongodb.org/mongo-driver/bson"
 
 	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
@@ -85,6 +86,11 @@ func (oc *OpenConfig) SbiIdentifier() string {
 	return "openconfig"
 }
 
+// Name returns the name of a sbi
+func (oc *OpenConfig) Name() string {
+	return oc.SbiIdentifier()
+}
+
 // Schema returns a ygot generated openconfig Schema as ytypes.Schema
 func (oc *OpenConfig) Schema() *ytypes.Schema {
 	schema, err := openconfig.Schema()
@@ -179,6 +185,11 @@ type SouthboundPlugin struct {
 	manifest   *plugin.Manifest
 }
 
+// Name returns the name of a sbi
+func (p *SouthboundPlugin) Name() string {
+	return "plugin"
+}
+
 // SetNode injects SBI specific model representation to the transport.
 // Needed for type assertion.
 func (p *SouthboundPlugin) SetNode(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error {
@@ -282,3 +293,14 @@ func (p *SouthboundPlugin) Update() error {
 	}
 	return nil
 }
+
+// MarshalBSON implements the MarshalBSON interface to store a device as BSON
+func (oc *OpenConfig) MarshalBSON() ([]byte, error) {
+	return bson.Marshal(&struct {
+		ID   string   `bson:"_id"`
+		Type spb.Type `bson:"type"`
+	}{
+		ID:   oc.id.String(),
+		Type: oc.Type(),
+	})
+}
diff --git a/controller/store/change_store_test.go b/controller/store/change_store_test.go.wasd
similarity index 100%
rename from controller/store/change_store_test.go
rename to controller/store/change_store_test.go.wasd
diff --git a/controller/store/deviceStore.go b/controller/store/deviceStore.go
deleted file mode 100644
index ed210c39c..000000000
--- a/controller/store/deviceStore.go
+++ /dev/null
@@ -1,228 +0,0 @@
-package store
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"reflect"
-
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
-
-	"github.com/google/uuid"
-	log "github.com/sirupsen/logrus"
-)
-
-// DeviceStore is used to store Devices
-type DeviceStore struct {
-	deviceStoreName        string
-	DeviceNameToUUIDLookup map[string]uuid.UUID
-	*genericStore
-}
-
-// NewDeviceStore returns a DeviceStore
-func NewDeviceStore(pndUUID uuid.UUID) *DeviceStore {
-	return &DeviceStore{
-		genericStore:           newGenericStore(),
-		DeviceNameToUUIDLookup: make(map[string]uuid.UUID),
-		deviceStoreName:        fmt.Sprintf("device-store-%s.json", pndUUID.String()),
-	}
-}
-
-// 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 _, parseErr := range parseErrors {
-		if parseErr != nil {
-			switch e := parseErr.(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{ID: foundID}
-				}
-				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:  (*device.Device)(nil),
-		}
-	}
-	log.WithFields(log.Fields{
-		"uuid": foundID,
-		"name": d.Name(),
-	}).Debug("device was accessed")
-
-	return d, nil
-}
-
-// GetDevicesAssociatedWithSbi ranges over devices within the device store and
-// checks if they are associated with the provided SBI. Returns a slice of
-// device.Device with all associated devices.
-func (s *DeviceStore) GetDevicesAssociatedWithSbi(sid uuid.UUID) ([]device.Device, error) {
-	var devices []device.Device
-	// range over all storable items within the device store
-	for _, item := range s.Store {
-		d, ok := item.(device.Device)
-		if !ok {
-			return nil, &errors.ErrInvalidTypeAssertion{
-				Value: d,
-				Type:  (*device.Device)(nil),
-			}
-		}
-		// check if the device uses the provided SBI and add it to the devices
-		// slice.
-		if d.SBI().ID() == sid {
-			devices = append(devices, d)
-		}
-	}
-
-	return devices, 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")
-
-	err := s.persist(item, name)
-	if err != nil {
-		return err
-	}
-
-	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
-}
-
-// persist is a helper method that persists the given store.Storable within
-// the storage file.
-func (s *DeviceStore) persist(item store.Storable, name string) error {
-	ensureFilesystemStorePathExists(s.deviceStoreName)
-
-	_, ok := item.(device.Device)
-	if !ok {
-		return fmt.Errorf("item is no Device, got=%T", item)
-	}
-
-	var devicesToPersist []device.Device
-
-	for _, value := range s.genericStore.Store {
-		dev, ok := value.(device.Device)
-		if !ok {
-			return fmt.Errorf("item is no Device, got=%T", item)
-		}
-		devicesToPersist = append(devicesToPersist, dev)
-	}
-
-	storeDataAsJSON, err := json.MarshalIndent(devicesToPersist, "", " ")
-	if err != nil {
-		return err
-	}
-
-	err = ioutil.WriteFile(getCompletePathToFileStore(s.deviceStoreName), storeDataAsJSON, 0644)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// LoadedDevice represents a Orchestrated Networking Device that was loaeded
-// by using the Load() method of the DeviceStore.
-type LoadedDevice struct {
-	// DeviceID represents the UUID of the LoadedDevice.
-	DeviceID uuid.UUID `json:"id,omitempty"`
-	// Name represents the name of the LoadedDevice.
-	Name string `json:"name,omitempty"`
-	// TransportType represent the type of the transport in use of the LoadedDevice.
-	TransportType string `json:"transport_type,omitempty"`
-	// TransportAddress represents the address from which the device can be reached via the transport method.
-	TransportAddress string `json:"transport_address,omitempty"`
-	// TransportUsername is used for authentication via the transport method in use.
-	TransportUsername string `json:"transport_username,omitempty"`
-	// TransportPassword is used for authentication via the transport method in use.
-	TransportPassword   string `json:"transport_password,omitempty"`
-	TransportOptionCsbi bool   `json:"transport_option_csbi,omitempty"`
-	// SBI indicates the southbound interface, which is used by this device as UUID.
-	SBI uuid.UUID `json:"sbi,omitempty"`
-}
-
-// ID returns the ID of the LoadedDevice as UUID.
-func (ld LoadedDevice) ID() uuid.UUID {
-	return ld.DeviceID
-}
-
-// Load unmarshals the contents of the storage file associated with a DeviceStore
-// and returns it as []LoadedDevice.
-func (s *DeviceStore) Load() ([]LoadedDevice, error) {
-	var loadedDevices []LoadedDevice
-
-	err := ensureFilesystemStorePathExists(s.deviceStoreName)
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedDevices, err
-	}
-
-	dat, err := ioutil.ReadFile(getCompletePathToFileStore(s.deviceStoreName))
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedDevices, err
-	}
-
-	err = json.Unmarshal(dat, &loadedDevices)
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedDevices, err
-	}
-
-	return loadedDevices, nil
-}
diff --git a/controller/store/device_store_test.go b/controller/store/device_store_test.go
deleted file mode 100644
index 8d6285420..000000000
--- a/controller/store/device_store_test.go
+++ /dev/null
@@ -1,242 +0,0 @@
-package store
-
-import (
-	"reflect"
-	"sync"
-	"testing"
-
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
-	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-cmp/cmp/cmpopts"
-	"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)
-			}
-		})
-	}
-}
-
-func Test_deviceStore_GetDevicesAsssociatedWithSbi(t *testing.T) {
-	mockSBI := &mocks.SouthboundInterface{}
-	mockSBI.On("ID").Return(defaultSbiID)
-
-	createDeviceMock := func(name string, sid uuid.UUID) *mocks.Device {
-		dm := &mocks.Device{}
-		dm.On("ID").Return(uuid.New())
-		dm.On("Name").Return("did")
-		dm.On("SBI").Return(mockSBI)
-		return dm
-	}
-
-	associatedDevices := []device.Device{
-		createDeviceMock("mockDevice1", defaultSbiID),
-		createDeviceMock("mockDevice2", defaultSbiID),
-		createDeviceMock("mockDevice3", defaultSbiID),
-	}
-
-	nonAssociatedDevice := createDeviceMock("mockDevice4", uuid.New())
-
-	pndMock := &mocks.NetworkDomain{}
-	pndMock.On("ID").Return(did)
-
-	// options to apply to cmp.Equal
-	opts := []cmp.Option{
-		// create custom comparer that simply checks if the device ID's are the
-		// same.
-		cmp.Comparer(func(x, y device.Device) bool {
-			return x.ID() == y.ID()
-		}),
-		// compare option to treat slices of length zero as equal
-		cmpopts.EquateEmpty(),
-		// sort the slices based on the ID
-		cmpopts.SortSlices(func(x, y device.Device) bool {
-			return x.ID().ID() < y.ID().ID()
-		}),
-	}
-
-	type fields struct {
-		genericStore *genericStore
-	}
-	type args struct {
-		sid uuid.UUID
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		want    []device.Device
-		wantErr bool
-	}{
-		{
-			name: "return devices associated with SBI",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						associatedDevices[0].ID(): associatedDevices[0],
-						associatedDevices[1].ID(): associatedDevices[1],
-						associatedDevices[2].ID(): associatedDevices[2],
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			args: args{
-				sid: defaultSbiID,
-			},
-			want:    associatedDevices,
-			wantErr: false,
-		},
-		{
-			name: "non associated devices should not be part of the returned slice",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						associatedDevices[0].ID(): associatedDevices[0],
-						associatedDevices[1].ID(): associatedDevices[1],
-						associatedDevices[2].ID(): associatedDevices[2],
-						nonAssociatedDevice.ID():  nonAssociatedDevice,
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			args: args{
-				sid: defaultSbiID,
-			},
-			want:    append(associatedDevices, nonAssociatedDevice),
-			wantErr: false,
-		},
-		{
-			name: "empty",
-			fields: fields{
-				&genericStore{
-					Store:     map[uuid.UUID]store.Storable{},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want:    []device.Device{},
-			wantErr: false,
-		},
-		{
-			name: "no device associated",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						nonAssociatedDevice.ID(): nonAssociatedDevice,
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want:    []device.Device{},
-			wantErr: false,
-		},
-		{
-			name: "fails wrong type",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultPndID: pndMock,
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			args:    args{sid: defaultSbiID},
-			want:    nil,
-			wantErr: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			s := DeviceStore{genericStore: tt.fields.genericStore}
-
-			got, err := s.GetDevicesAssociatedWithSbi(tt.args.sid)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("GetDevicesAssociatedWithSbi() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !cmp.Equal(got, tt.want, opts...) {
-				t.Errorf("GetDevicesAssociatedWithSbi() got = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
diff --git a/controller/store/generic_store_test.go b/controller/store/generic_store_test.go.wasd
similarity index 100%
rename from controller/store/generic_store_test.go
rename to controller/store/generic_store_test.go.wasd
diff --git a/controller/store/pndStore.go b/controller/store/pndStore.go
deleted file mode 100644
index 8f38f8dcf..000000000
--- a/controller/store/pndStore.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package store
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"reflect"
-
-	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-	"code.fbi.h-da.de/danet/gosdn/controller/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 {
-	pndStoreName    string
-	pendingChannels map[uuid.UUID]chan DeviceDetails
-	*genericStore
-}
-
-// NewPndStore returns a PndStore
-func NewPndStore() *PndStore {
-	return &PndStore{
-		genericStore:    newGenericStore(),
-		pendingChannels: make(map[uuid.UUID]chan DeviceDetails),
-		pndStoreName:    "pnd-store.json"}
-}
-
-// 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:  (*networkdomain.NetworkDomain)(nil),
-		}
-	}
-	log.WithFields(log.Fields{
-		"uuid": id,
-	}).Debug("principal network domain was accessed")
-	return pnd, nil
-}
-
-// Add adds a device to the device store.
-// It also adds the name of the device to the lookup table.
-func (s *PndStore) Add(item store.Storable) error {
-	if s.Exists(item.ID()) {
-		return &errors.ErrAlreadyExists{Item: item}
-	}
-
-	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")
-
-	err := s.persist(item)
-	if err != nil {
-		return err
-	}
-
-	return 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)
-}
-
-// persist is a helper method that persists the given store.Storable within
-// the storage file.
-func (s *PndStore) persist(item store.Storable) error {
-	ensureFilesystemStorePathExists(s.pndStoreName)
-
-	_, ok := item.(networkdomain.NetworkDomain)
-	if !ok {
-		return fmt.Errorf("item is no NetworkDomain, got=%T", item)
-	}
-
-	var networkDomainsToPersist []LoadedPnd
-
-	for _, value := range s.genericStore.Store {
-		networkDomain, ok := value.(networkdomain.NetworkDomain)
-		if !ok {
-			return fmt.Errorf("item is no NetworkdDomain, got=%T", item)
-		}
-		networkDomainsToPersist = append(networkDomainsToPersist, LoadedPnd{
-			Name:        networkDomain.GetName(),
-			Description: networkDomain.GetDescription(),
-			ID:          networkDomain.ID(),
-		})
-	}
-
-	storeDataAsJSON, err := json.MarshalIndent(networkDomainsToPersist, "", " ")
-	if err != nil {
-		return err
-	}
-
-	err = ioutil.WriteFile(getCompletePathToFileStore(s.pndStoreName), storeDataAsJSON, 0644)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// LoadedPnd represents a Principal Network Domain that was loaeded by using
-// the Load() method of the PndStore.
-type LoadedPnd struct {
-	ID          uuid.UUID `json:"id,omitempty"`
-	Name        string    `json:"name,omitempty"`
-	Description string    `json:"description,omitempty"`
-}
-
-// Load unmarshals the contents of the storage file associated with a PndStore
-// and returns it as []LoadedPnd.
-func (s *PndStore) Load() ([]LoadedPnd, error) {
-	var loadedNetworkDomains []LoadedPnd
-
-	err := ensureFilesystemStorePathExists(s.pndStoreName)
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedNetworkDomains, err
-	}
-
-	dat, err := ioutil.ReadFile(getCompletePathToFileStore(s.pndStoreName))
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedNetworkDomains, err
-	}
-
-	err = json.Unmarshal(dat, &loadedNetworkDomains)
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedNetworkDomains, err
-	}
-
-	return loadedNetworkDomains, nil
-}
diff --git a/controller/store/pnd_store_test.go b/controller/store/pnd_store_test.go
deleted file mode 100644
index 5ed14847a..000000000
--- a/controller/store/pnd_store_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package store
-
-import (
-	"reflect"
-	"sync"
-	"testing"
-
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-	"code.fbi.h-da.de/danet/gosdn/controller/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/controller/store/sbiStore.go b/controller/store/sbiStore.go
deleted file mode 100644
index ebc160d2f..000000000
--- a/controller/store/sbiStore.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package store
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"reflect"
-
-	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
-
-	"github.com/google/uuid"
-	log "github.com/sirupsen/logrus"
-)
-
-// SbiStore is used to store SouthboundInterfaces
-type SbiStore struct {
-	sbiStoreName string
-	*genericStore
-}
-
-// NewSbiStore returns a SbiStore
-func NewSbiStore(pndUUID uuid.UUID) *SbiStore {
-	return &SbiStore{
-		genericStore: newGenericStore(),
-		sbiStoreName: fmt.Sprintf("sbi-store-%s.json", pndUUID.String()),
-	}
-}
-
-// 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:  (*southbound.SouthboundInterface)(nil),
-		}
-	}
-	log.WithFields(log.Fields{
-		"uuid": id,
-	}).Debug("southbound interface was accessed")
-	return sbi, nil
-}
-
-// Add adds a Southbound Interface to the southbound store.
-func (s *SbiStore) Add(item store.Storable) error {
-	if s.Exists(item.ID()) {
-		return &errors.ErrAlreadyExists{Item: item}
-	}
-
-	// check if item is a SouthboundInterface
-	_, ok := item.(southbound.SouthboundInterface)
-	if !ok {
-		return &errors.ErrInvalidTypeAssertion{
-			Value: item,
-			Type:  (*southbound.SouthboundInterface)(nil),
-		}
-	}
-
-	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")
-
-	err := s.persist()
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// persist is a helper method that persists the given store.Storable within
-// the storage file.
-func (s *SbiStore) persist() error {
-	err := ensureFilesystemStorePathExists(s.sbiStoreName)
-	if err != nil {
-		return err
-	}
-
-	var southboundInterfacesToPersist []LoadedSbi
-
-	for _, value := range s.genericStore.Store {
-		southboundInterface, ok := value.(southbound.SouthboundInterface)
-		if !ok {
-			return &errors.ErrInvalidTypeAssertion{
-				Value: value,
-				Type:  (*southbound.SouthboundInterface)(nil),
-			}
-		}
-		if southboundInterface.Type() == spb.Type_TYPE_CONTAINERISED || southboundInterface.Type() == spb.Type_TYPE_PLUGIN {
-			southboundPlugin, ok := southboundInterface.(plugin.Plugin)
-			if !ok {
-				return &errors.ErrInvalidTypeAssertion{
-					Value: southboundInterface,
-					Type:  (*plugin.Plugin)(nil),
-				}
-			}
-			southboundInterfacesToPersist = append(southboundInterfacesToPersist, LoadedSbi{
-				ID:   southboundPlugin.ID(),
-				Type: southboundInterface.Type().String(),
-				Path: southboundPlugin.Path(),
-			})
-		} else {
-			southboundInterfacesToPersist = append(southboundInterfacesToPersist, LoadedSbi{
-				ID:   southboundInterface.ID(),
-				Type: southboundInterface.Type().String(),
-			})
-		}
-	}
-
-	storeDataAsJSON, err := json.MarshalIndent(southboundInterfacesToPersist, "", " ")
-	if err != nil {
-		return err
-	}
-
-	err = ioutil.WriteFile(getCompletePathToFileStore(s.sbiStoreName), storeDataAsJSON, 0644)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// LoadedSbi represents a Southbound Interface that was loaeded by using
-// the Load() method of the SbiStore.
-type LoadedSbi struct {
-	ID   uuid.UUID `json:"id,omitempty"`
-	Type string    `json:"type,omitempty"`
-	Path string    `json:"path,omitempty"`
-}
-
-// Load unmarshals the contents of the storage file associated with a SbiStore
-// and returns it as []LoadedSbi.
-func (s *SbiStore) Load() ([]LoadedSbi, error) {
-	var loadedSouthboundInterfaces []LoadedSbi
-
-	err := ensureFilesystemStorePathExists(s.sbiStoreName)
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedSouthboundInterfaces, err
-	}
-
-	dat, err := ioutil.ReadFile(getCompletePathToFileStore(s.sbiStoreName))
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedSouthboundInterfaces, err
-	}
-
-	err = json.Unmarshal(dat, &loadedSouthboundInterfaces)
-	if err != nil {
-		log.Debug(fmt.Printf("Err: %+v\n", err))
-		return loadedSouthboundInterfaces, err
-	}
-
-	return loadedSouthboundInterfaces, nil
-}
diff --git a/controller/store/sbi_store_test.go b/controller/store/sbi_store_test.go
deleted file mode 100644
index fe602f7ee..000000000
--- a/controller/store/sbi_store_test.go
+++ /dev/null
@@ -1,310 +0,0 @@
-package store
-
-import (
-	"encoding/json"
-	"io/ioutil"
-	"reflect"
-	"sync"
-	"testing"
-
-	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
-	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/store"
-	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
-
-	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-cmp/cmp/cmpopts"
-	"github.com/google/uuid"
-)
-
-func newStorableSbi(id uuid.UUID, t spb.Type) *mocks.SouthboundInterface {
-	s := &mocks.SouthboundInterface{}
-	s.On("ID").Return(id)
-	s.On("Type").Return(t)
-	return s
-}
-
-func Test_sbi_store_GetSBI(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.GetSBI(tt.args.id)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("GetSBI() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("GetSBI() got = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test_sbi_store_Add(t *testing.T) {
-	faultySbi := &mocks.NetworkDomain{}
-	faultySbi.On("ID").Return(defaultPndID)
-
-	type fields struct {
-		genericStore *genericStore
-	}
-	type args struct {
-		storable store.Storable
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		wantErr bool
-	}{
-		{
-			name: "default",
-			fields: fields{
-				&genericStore{
-					Store:     map[uuid.UUID]store.Storable{},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			args:    args{storable: newStorableSbi(defaultSbiID, spb.Type_TYPE_OPENCONFIG)},
-			wantErr: false,
-		},
-		{
-			name: "does not implement SouthboundInterface",
-			fields: fields{
-				&genericStore{
-					Store:     map[uuid.UUID]store.Storable{},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			args:    args{storable: faultySbi},
-			wantErr: true,
-		},
-		{
-			name: "already exists",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultSbiID: newStorableSbi(defaultSbiID, spb.Type_TYPE_OPENCONFIG),
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			args:    args{storable: newStorableSbi(defaultSbiID, spb.Type_TYPE_OPENCONFIG)},
-			wantErr: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			s := SbiStore{
-				sbiStoreName: "sbi-store_test.json",
-				genericStore: tt.fields.genericStore,
-			}
-
-			err := s.Add(tt.args.storable)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Add() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-		})
-	}
-}
-
-func Test_sbi_store_persist(t *testing.T) {
-	faultySbi := &mocks.NetworkDomain{}
-	faultySbi.On("ID").Return(defaultPndID)
-
-	csbi := &mocks.Csbi{}
-	csbi.On("ID").Return(defaultSbiID)
-	csbi.On("Type").Return(spb.Type_TYPE_CONTAINERISED)
-	csbi.On("Path").Return("a/test/path/")
-
-	plugin := &mocks.Csbi{}
-	plugin.On("ID").Return(defaultSbiID)
-	plugin.On("Type").Return(spb.Type_TYPE_PLUGIN)
-	plugin.On("Path").Return("a/test/path/")
-
-	// options to apply to cmp.Equal
-	opts := []cmp.Option{
-		// compare option to treat slices of length zero as equal
-		cmpopts.EquateEmpty(),
-		// sort the slices based on the ID
-		cmpopts.SortSlices(func(x, y LoadedSbi) bool {
-			return x.ID.ID() < y.ID.ID()
-		}),
-	}
-
-	type fields struct {
-		genericStore *genericStore
-	}
-	tests := []struct {
-		name    string
-		fields  fields
-		want    []LoadedSbi
-		wantErr bool
-	}{
-		{
-			name: "default",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultSbiID: newStorableSbi(defaultSbiID, spb.Type_TYPE_OPENCONFIG),
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want:    []LoadedSbi{{ID: defaultSbiID, Type: spb.Type_TYPE_OPENCONFIG.String()}},
-			wantErr: false,
-		},
-		{
-			name: "multiple items in store",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultPndID: newStorableSbi(defaultPndID, spb.Type_TYPE_OPENCONFIG),
-						defaultSbiID: newStorableSbi(defaultSbiID, spb.Type_TYPE_OPENCONFIG),
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want: []LoadedSbi{
-				{ID: defaultSbiID, Type: spb.Type_TYPE_OPENCONFIG.String()},
-				{ID: defaultPndID, Type: spb.Type_TYPE_OPENCONFIG.String()},
-			},
-			wantErr: false,
-		},
-		{
-			name: "storable is not of type SouthboundInterface",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultPndID: faultySbi,
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name: "storable is of type csbi",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultSbiID: csbi,
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want:    []LoadedSbi{{ID: defaultSbiID, Type: spb.Type_TYPE_CONTAINERISED.String(), Path: csbi.Path()}},
-			wantErr: false,
-		},
-		{
-			name: "storable is of type plugin",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultSbiID: plugin,
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want:    []LoadedSbi{{ID: defaultSbiID, Type: spb.Type_TYPE_PLUGIN.String(), Path: csbi.Path()}},
-			wantErr: false,
-		},
-		{
-			name: "type of spb.CONTAINERISED but does not implement Csbi",
-			fields: fields{
-				&genericStore{
-					Store: map[uuid.UUID]store.Storable{
-						defaultSbiID: newStorableSbi(defaultSbiID, spb.Type_TYPE_CONTAINERISED),
-					},
-					storeLock: sync.RWMutex{},
-				},
-			},
-			want:    nil,
-			wantErr: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			s := SbiStore{
-				sbiStoreName: "sbi-store_test.json",
-				genericStore: tt.fields.genericStore}
-
-			err := s.persist()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("persist() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-
-			if err == nil {
-				var got []LoadedSbi
-				dat, err := ioutil.ReadFile(getCompletePathToFileStore(s.sbiStoreName))
-				if err != nil {
-					t.Errorf("persist() error = %v", err)
-				}
-
-				err = json.Unmarshal(dat, &got)
-				if err != nil {
-					t.Errorf("persist() error = %v", err)
-				}
-
-				if !cmp.Equal(got, tt.want, opts...) {
-					t.Errorf("persist() got = %v, want %v", got, tt.want)
-				}
-			}
-		})
-	}
-}
diff --git a/controller/store/storageMode.go b/controller/store/storageMode.go
new file mode 100644
index 000000000..513fb9693
--- /dev/null
+++ b/controller/store/storageMode.go
@@ -0,0 +1,37 @@
+package store
+
+import "code.fbi.h-da.de/danet/gosdn/controller/config"
+
+// StorageMode decides which backend all stores should use.
+type StorageMode int64
+
+const (
+	// Memory is the default storage mode
+	Memory StorageMode = iota
+	// Filesystem is a persistent storage mode
+	Filesystem
+	// Database is a persistent storage mode provided by a database
+	Database
+)
+
+// GetStoreMode returns which store should be used.
+func GetStoreMode() StorageMode {
+	if config.UseDatabase() {
+		return Database
+	}
+
+	return Memory
+}
+
+func (s StorageMode) String() string {
+	switch s {
+	case Memory:
+		return "Memory"
+	case Filesystem:
+		return "Filesystem"
+	case Database:
+		return "Database"
+	}
+
+	return "unknown"
+}
diff --git a/controller/store/utils.go b/controller/store/utils.go
index de8033786..a3e52bf0a 100644
--- a/controller/store/utils.go
+++ b/controller/store/utils.go
@@ -13,6 +13,12 @@ const (
 	pathToStores string = "stores"
 )
 
+// Query is used to query objects from stores.
+type Query struct {
+	ID   uuid.UUID
+	Name string
+}
+
 // 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)
diff --git a/controller/test/integration/nucleusIntegration_test.go b/controller/test/integration/nucleusIntegration_test.go
index 402c2c94e..c7d44567d 100644
--- a/controller/test/integration/nucleusIntegration_test.go
+++ b/controller/test/integration/nucleusIntegration_test.go
@@ -172,7 +172,7 @@ func TestGnmi_SetValidIntegration(t *testing.T) {
 			GnmiTransportOption: &tpb.GnmiTransportOption{},
 		},
 	}
-	pnd, err := nucleus.NewPND("test", "test", uuid.New(), sbi, nil, nil)
+	pnd, err := nucleus.NewPND("test", "test", uuid.New(), nil, nil)
 	if err != nil {
 		t.Error(err)
 		return
diff --git a/controller/test/plugin/faulty/gostructs.go b/controller/test/plugin/faulty/gostructs.go
index f5c7a7af8..7f7870ba4 100644
--- a/controller/test/plugin/faulty/gostructs.go
+++ b/controller/test/plugin/faulty/gostructs.go
@@ -50,3 +50,7 @@ func (fp *FaultyPlugin) SchemaTreeGzip() []byte {
 func (fp *FaultyPlugin) Type() spb.Type {
 	return spb.Type_TYPE_PLUGIN
 }
+
+func (fp *FaultyPlugin) Name() string {
+	return "csbi"
+}
diff --git a/controller/test/plugin/gostructs.go b/controller/test/plugin/gostructs.go
index cef53187d..524508c97 100644
--- a/controller/test/plugin/gostructs.go
+++ b/controller/test/plugin/gostructs.go
@@ -52,3 +52,7 @@ func (csbi *Csbi) SchemaTreeGzip() []byte {
 func (csbi *Csbi) Type() spb.Type {
 	return spb.Type_TYPE_PLUGIN
 }
+
+func (csbi *Csbi) Name() string {
+	return "csbi"
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..1dedf6ff2
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+version: '3'
+
+services:
+  mongo:
+    image: mongo:5
+    ports:
+      - 127.0.0.1:27017:27017
+    environment:
+      MONGO_INITDB_ROOT_USERNAME: root
+      MONGO_INITDB_ROOT_PASSWORD: example
+
+  mongo-express:
+    image: mongo-express:0.54.0
+    ports:
+        - 127.0.0.1:8081:8081
+    environment:
+      ME_CONFIG_MONGODB_ADMINUSERNAME: root
+      ME_CONFIG_MONGODB_ADMINPASSWORD: example
diff --git a/go.mod b/go.mod
index e402f0ac5..ee4533b62 100644
--- a/go.mod
+++ b/go.mod
@@ -29,6 +29,7 @@ require (
 	github.com/c-bata/go-prompt v0.2.6
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/spf13/pflag v1.0.5
+	go.mongodb.org/mongo-driver v1.8.4
 	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa
 )
 
@@ -44,13 +45,16 @@ require (
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
+	github.com/go-stack/stack v1.8.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang-jwt/jwt v3.2.2+incompatible
 	github.com/golang/glog v1.0.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/snappy v0.0.3 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
+	github.com/klauspost/compress v1.13.6 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/magiconair/properties v1.8.5 // indirect
 	github.com/mattn/go-colorable v0.1.7 // indirect
@@ -75,7 +79,12 @@ require (
 	github.com/spf13/cast v1.4.1 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/subosito/gotenv v1.2.0 // indirect
+	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
+	github.com/xdg-go/scram v1.0.2 // indirect
+	github.com/xdg-go/stringprep v1.0.2 // indirect
+	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 	go.opencensus.io v0.23.0 // indirect
+	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
 	golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
 	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
 	golang.org/x/text v0.3.7 // indirect
diff --git a/go.sum b/go.sum
index 231fbb8f0..f59c16085 100644
--- a/go.sum
+++ b/go.sum
@@ -438,6 +438,7 @@ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
@@ -500,6 +501,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -667,6 +669,8 @@ github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
 github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.11.9/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -769,6 +773,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
@@ -1047,6 +1052,7 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG
 github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
 github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
 github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -1070,6 +1076,12 @@ github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1
 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
 github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
 github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
 github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
 github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -1080,6 +1092,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
 github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1101,6 +1115,8 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
 go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
 go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
 go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
+go.mongodb.org/mongo-driver v1.8.4 h1:NruvZPPL0PBcRJKmbswoWSrmHeUvzdxA3GCPfD/NEOA=
+go.mongodb.org/mongo-driver v1.8.4/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
 go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -1164,9 +1180,11 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1443,6 +1461,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
diff --git a/gosdn.clab.yaml b/gosdn.clab.yaml
index 0a86d8183..6b51ed28e 100644
--- a/gosdn.clab.yaml
+++ b/gosdn.clab.yaml
@@ -71,6 +71,24 @@ topology:
         - ./csbi/prometheus:/etc/prometheus
       cmd: --web.enable-lifecycle  --config.file=/etc/prometheus/prometheus.yml
       mgmt_ipv4: 172.100.0.9
+    mongodb:
+      kind: linux
+      image: mongo:5
+      ports:
+        - 27017:27017
+      env:
+        MONGO_INITDB_ROOT_USERNAME: root
+        MONGO_INITDB_ROOT_PASSWORD: example
+      mgmt_ipv4: 172.100.0.13
+    mongodb-express:
+      kind: linux
+      image: mongo-express:0.54.0
+      ports:
+        - 8081:8081
+      env:
+        MONGO_INITDB_ROOT_USERNAME: root
+        MONGO_INITDB_ROOT_PASSWORD: example
+      mgmt_ipv4: 172.100.0.14
 
   links:
     - endpoints: ["ceos0:eth1","ceos1a:eth1"]
diff --git a/scripts/test-add-device.sh b/scripts/test-add-device.sh
new file mode 100755
index 000000000..615f16213
--- /dev/null
+++ b/scripts/test-add-device.sh
@@ -0,0 +1,4 @@
+./artifacts/gosdnc init --controller 127.0.0.1:55055
+./artifacts/gosdnc device create -a 172.20.20.2:6030 -u admin -p admin --name='test-ceos-1'
+./artifacts/gosdnc device list
+
diff --git a/scripts/test-add-pnd.sh b/scripts/test-add-pnd.sh
new file mode 100755
index 000000000..75704be2e
--- /dev/null
+++ b/scripts/test-add-pnd.sh
@@ -0,0 +1,4 @@
+./artifacts/gosdnc init --controller 127.0.0.1:55055
+./artifacts/gosdnc pnd create --name test-pnd-1 test
+./artifacts/gosdnc device list
+
-- 
GitLab