diff --git a/.gitignore b/.gitignore index 1fc2bce8989614db73e2d0cc8f7552cbbcdc4aea..65899b422cb10044f735e8bc1c7ca91e520a8241 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ csbi/resources/csbi controller/configs/testing-gosdn.toml controller/configs/development-gosdn.toml controller/configs/containerlab-gosdn.toml +**/stores_testing + +config/.gosdnc.toml diff --git a/controller/api/initialise_test.go b/controller/api/initialise_test.go index bdd70085037ef49cedd18f19aa2ba437871e6405..a927efd228f43b7ab048bec326df6cd8b8484e59 100644 --- a/controller/api/initialise_test.go +++ b/controller/api/initialise_test.go @@ -80,8 +80,8 @@ func bootstrapUnitTest() { log.Fatal(err) } - pndStore = nucleus.NewPndStore() - sbiStore = nucleus.NewSbiStore(pndUUID) + pndStore = nucleus.NewMemoryPndStore() + sbiStore = nucleus.NewMemorySbiStore() userService = rbacImpl.NewUserService(rbacImpl.NewMemoryUserStore()) roleService = rbacImpl.NewRoleService(rbacImpl.NewMemoryRoleStore()) diff --git a/controller/config/config.go b/controller/config/config.go index 0defa09c32f6772c0519f27550c41a3911e3814b..ac47b954110a99ea911fc83728ab3281526e2783 100644 --- a/controller/config/config.go +++ b/controller/config/config.go @@ -17,6 +17,7 @@ const ( baseSouthBoundUUIDKey = "baseSouthBoundUUID" changeTimeoutKey = "GOSDN_CHANGE_TIMEOUT" databaseConnectionKey = "databaseConnection" + filesystemPathToStores = "filesystemPathToStores" ) // BasePndUUID is an uuid for the base PND @@ -37,6 +38,9 @@ var LogLevel logrus.Level // DatabaseConnection holds the credentials and address of the used database var DatabaseConnection string +// FilesystemPathToStores determines in which folder the stores should be saved +var FilesystemPathToStores = "stores_testing" + // Init gets called on module import func Init() { err := InitializeConfig() @@ -81,6 +85,11 @@ func InitializeConfig() error { DatabaseConnection = getStringFromViper(databaseConnectionKey) + FilesystemPathToStores = getStringFromViper(filesystemPathToStores) + if FilesystemPathToStores == "" { + FilesystemPathToStores = "stores" + } + return nil } diff --git a/controller/configs/containerlab-gosdn.toml.example b/controller/configs/containerlab-gosdn.toml.example index 0a7a31bfe8b19c0a779c42fd3d26af6737acbb35..2c5e7f28d8a88179317e17d4528afc108a5eed45 100644 --- a/controller/configs/containerlab-gosdn.toml.example +++ b/controller/configs/containerlab-gosdn.toml.example @@ -10,4 +10,5 @@ log-level = "debug" pnduuid = "bf8160d4-4659-4a1b-98fd-f409a04111ec" socket = ":55055" databaseConnection = "mongodb://root:example@clab-gosdn_csbi_arista_base-mongodb:27017" +filesystemPathToStores = "stores" diff --git a/controller/configs/development-gosdn.toml.example b/controller/configs/development-gosdn.toml.example index 60f125602db6e8426f950aaf4daa0c006b2d14c7..6ae6a6a9450b2c2637cbec9366b0b8e8bcdfa9d1 100644 --- a/controller/configs/development-gosdn.toml.example +++ b/controller/configs/development-gosdn.toml.example @@ -10,4 +10,5 @@ log-level = "debug" pnduuid = "bf8160d4-4659-4a1b-98fd-f409a04111ec" socket = ":55055" databaseConnection = "mongodb://root:example@localhost:27017" +filesystemPathToStores = "stores" diff --git a/controller/interfaces/device/device.go b/controller/interfaces/device/device.go index 73ccea34de71b163b5f35f55d47f6990c76176d1..497e7f9aea80e2a763a5ed8a9915ea15aefe03c1 100644 --- a/controller/interfaces/device/device.go +++ b/controller/interfaces/device/device.go @@ -32,8 +32,8 @@ type Details struct { // 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"` + // ID represents the UUID of the LoadedDevice. + ID string `json:"id" bson:"_id"` // Name represents the name of the LoadedDevice. Name string `json:"name,omitempty"` // TransportType represent the type of the transport in use of the LoadedDevice. diff --git a/controller/northbound/server/pnd_test.go b/controller/northbound/server/pnd_test.go index b7fb59e5bbf66829747c7d804f1e55468a2fdb52..797b5d52b3626cfe29d23c5241ebb70d5b7cd7ce 100644 --- a/controller/northbound/server/pnd_test.go +++ b/controller/northbound/server/pnd_test.go @@ -115,7 +115,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) - pndc = nucleus.NewPndStore() + pndc = nucleus.NewMemoryPndStore() if err := pndc.Add(mockPnd); err != nil { log.Fatal(err) } diff --git a/controller/nucleus/device.go b/controller/nucleus/device.go index fa25f32bbe2331f3d1dd4287528ead0a6248e647..db75615eb67619af21b6d927f9ad88af8c08e701 100644 --- a/controller/nucleus/device.go +++ b/controller/nucleus/device.go @@ -229,7 +229,7 @@ func (d *CommonDevice) MarshalJSON() ([]byte, error) { } return json.Marshal(&struct { - DeviceID uuid.UUID `json:"id,omitempty"` + ID uuid.UUID `json:"id,omitempty"` Name string `json:"name,omitempty"` TransportType string `json:"transport_type,omitempty"` TransportAddress string `json:"transport_address,omitempty"` @@ -239,7 +239,7 @@ func (d *CommonDevice) MarshalJSON() ([]byte, error) { SBI uuid.UUID `json:"sbi,omitempty"` Model string `bson:"model,omitempty"` }{ - DeviceID: d.ID(), + ID: d.ID(), Name: d.Name(), TransportType: transportType, TransportAddress: transportAddress, @@ -282,7 +282,7 @@ func (d *CommonDevice) MarshalBSON() ([]byte, error) { } return bson.Marshal(&struct { - DeviceID string `bson:"_id,omitempty"` + ID string `bson:"_id,omitempty"` Name string `bson:"name,omitempty"` TransportType string `bson:"transport_type,omitempty"` TransportAddress string `bson:"transport_address,omitempty"` @@ -292,7 +292,7 @@ func (d *CommonDevice) MarshalBSON() ([]byte, error) { SBI string `bson:"sbi,omitempty"` Model string `bson:"model,omitempty"` }{ - DeviceID: d.ID().String(), + ID: d.ID().String(), Name: d.Name(), TransportType: transportType, TransportAddress: transportAddress, diff --git a/controller/nucleus/deviceFilesystemStore.go b/controller/nucleus/deviceFilesystemStore.go new file mode 100644 index 0000000000000000000000000000000000000000..47d90d55ada59e9f5b7b568fb93efdc3495d7382 --- /dev/null +++ b/controller/nucleus/deviceFilesystemStore.go @@ -0,0 +1,174 @@ +package nucleus + +import ( + "encoding/json" + "io/ioutil" + "sync" + + "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/errors" + "code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem" + "code.fbi.h-da.de/danet/gosdn/controller/store" + "github.com/google/uuid" +) + +// FilesystemDeviceStore is the filesystem implementation of the device store +type FilesystemDeviceStore struct { + sbiStore southbound.Store + pndUUID uuid.UUID + fileMutex sync.Mutex + pathToDeviceFile string +} + +// NewFilesystemDeviceStore returns a filesystem implementation for a pnd store. +func NewFilesystemDeviceStore(pndUUID uuid.UUID) device.Store { + deviceFilenameForUUID := store.GetStoreFilenameForUUID(pndUUID, filesystem.DeviceFilenameSuffix) + + store.EnsureFilesystemStorePathExists(deviceFilenameForUUID) + return &FilesystemDeviceStore{ + pathToDeviceFile: store.GetCompletePathToFileStore(deviceFilenameForUUID), + fileMutex: sync.Mutex{}, + pndUUID: pndUUID, + } +} + +func (s *FilesystemDeviceStore) readAllDevicesFromFile() ([]device.LoadedDevice, error) { + var loadedDevices []device.LoadedDevice + + content, err := ioutil.ReadFile(s.pathToDeviceFile) + if err != nil { + return nil, err + } + + err = json.Unmarshal(content, &loadedDevices) + if err != nil { + return nil, err + } + + return loadedDevices, nil +} + +func (s *FilesystemDeviceStore) writeAllDevicesToFile(devices []device.LoadedDevice) error { + serializedData, err := json.Marshal(devices) + if err != nil { + return err + } + + err = ioutil.WriteFile(s.pathToDeviceFile, serializedData, 0600) + if err != nil { + return err + } + + return nil +} + +// Get takes a Device's UUID or name and returns the Device. +func (s *FilesystemDeviceStore) Get(query store.Query) (device.LoadedDevice, error) { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + var device device.LoadedDevice + + devices, err := s.readAllDevicesFromFile() + if err != nil { + return device, err + } + + for _, device := range devices { + if device.ID == query.ID.String() { + return device, nil + } + } + + return device, &errors.ErrNotFound{ID: query.ID} +} + +// GetAll returns all stored devices. +func (s *FilesystemDeviceStore) GetAll() ([]device.LoadedDevice, error) { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + devices, err := s.readAllDevicesFromFile() + + return devices, err +} + +// Add adds a device to the device store. +func (s *FilesystemDeviceStore) Add(deviceToAdd device.Device) error { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + devices, err := s.readAllDevicesFromFile() + if err != nil { + return err + } + + var loadedDevice device.LoadedDevice + loadedDevice, err = store.TransformObjectToLoadedObject[device.Device, device.LoadedDevice](deviceToAdd) + if err != nil { + return err + } + + devices = append(devices, loadedDevice) + + err = s.writeAllDevicesToFile(devices) + if err != nil { + return err + } + + return nil +} + +// Update updates a existing device. +func (s *FilesystemDeviceStore) Update(deviceToUpdate device.Device) error { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + var loadedDevice device.LoadedDevice + + devices, err := s.readAllDevicesFromFile() + if err != nil { + return err + } + + for i, device := range devices { + if device.ID == deviceToUpdate.ID().String() { + devices[i] = loadedDevice + err = s.writeAllDevicesToFile(devices) + if err != nil { + return err + } + } + } + + return &errors.ErrNotFound{ID: deviceToUpdate.ID().String()} +} + +// Delete deletes a device from the device store. +func (s *FilesystemDeviceStore) Delete(deviceToDelete device.Device) error { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + devices, err := s.readAllDevicesFromFile() + if err != nil { + return err + } + + for i, device := range devices { + if device.ID == deviceToDelete.ID().String() { + //remove item from slice + devices[i] = devices[len(devices)-1] + devices = devices[:len(devices)-1] + + err = s.writeAllDevicesToFile(devices) + if err != nil { + return err + } + + return nil + } + } + + return &errors.ErrNotFound{ID: deviceToDelete.ID} +} diff --git a/controller/nucleus/deviceFilesystemStore_test.go b/controller/nucleus/deviceFilesystemStore_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6534627b1d47b1460b1e9efa7bc53656f5058034 --- /dev/null +++ b/controller/nucleus/deviceFilesystemStore_test.go @@ -0,0 +1,227 @@ +package nucleus + +import ( + "log" + "os" + "path/filepath" + "testing" + + 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/nucleus/filesystem" + "code.fbi.h-da.de/danet/gosdn/controller/store" + "github.com/google/uuid" +) + +func ensureDeviceFilesForTestAreRemoved() { + store.EnsureFilesystemStorePathExists(filesystem.DeviceFilenameSuffix) + wildcartFilename := "*-" + filesystem.DeviceFilenameSuffix + path := store.GetCompletePathToFileStore(wildcartFilename) + + files, err := filepath.Glob(path) + + if err != nil { + log.Println(err) + } + for _, f := range files { + if err := os.Remove(f); err != nil { + log.Println(err) + } + } +} + +func returnBasicTransportOption() tpb.TransportOption { + return tpb.TransportOption{ + Address: "test:///", + Username: "test", + Password: "test", + TransportOption: &tpb.TransportOption_GnmiTransportOption{ + GnmiTransportOption: &tpb.GnmiTransportOption{ + Compression: "", + GrpcDialOptions: nil, + Token: "", + Encoding: 0, + }, + }, + } +} + +func TestAddDevice(t *testing.T) { + ensureDeviceFilesForTestAreRemoved() + defer ensureDeviceFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + deviceID, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + sbiID1, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + + sbi1, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID1) + + deviceStore := NewDeviceStore(pndID) + device, _ := NewDevice("testdevice", deviceID, nil, sbi1) + + err := deviceStore.Add(device) + if err != nil { + t.Error(err) + } +} + +func TestGetAllDevices(t *testing.T) { + ensureDeviceFilesForTestAreRemoved() + defer ensureDeviceFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + deviceStore := NewDeviceStore(pndID) + + sbiID, _ := uuid.Parse("ssssssss-ssss-ssss-ssss-ssssssssssss") + sbi, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID) + + deviceID1, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + deviceID2, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab") + + transportOptions := returnBasicTransportOption() + + device1, err := NewDevice("testname", deviceID1, &transportOptions, sbi) + if err != nil { + t.Error(err) + } + + device2, err := NewDevice("testname2", deviceID2, &transportOptions, sbi) + if err != nil { + t.Error(err) + } + + inputDevices := [2]device.Device{device1, device2} + + for _, device := range inputDevices { + err := deviceStore.Add(device) + if err != nil { + t.Error(err) + } + } + + returnedDevices, err := deviceStore.GetAll() + if err != nil { + t.Error(err) + } + + length := len(returnedDevices) + if length != 2 { + t.Errorf("GetAll() length of array = %v, want %v", length, 2) + } + + for i, device := range returnedDevices { + if device.ID != inputDevices[i].ID().String() { + t.Errorf("GetAll() = %v, want %v", device.ID, inputDevices[i].ID().String()) + } + if device.Name != inputDevices[i].Name() { + t.Errorf("GetAll() = %v, want %v", device.Name, inputDevices[i].Name()) + } + } +} + +func TestGetDevice(t *testing.T) { + ensureDeviceFilesForTestAreRemoved() + defer ensureDeviceFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + deviceStore := NewDeviceStore(pndID) + + sbiID, _ := uuid.Parse("ssssssss-ssss-ssss-ssss-ssssssssssss") + sbi, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID) + + deviceID1, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + deviceID2, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab") + + trop := returnBasicTransportOption() + + device1, err := NewDevice("testname", deviceID1, &trop, sbi) + if err != nil { + t.Error(err) + } + + device2, err := NewDevice("testname2", deviceID2, &trop, sbi) + if err != nil { + t.Error(err) + } + + inputDevices := [2]device.Device{device1, device2} + + for _, device := range inputDevices { + err := deviceStore.Add(device) + if err != nil { + t.Error(err) + } + } + + returnDevice, err := deviceStore.Get(store.Query{ID: deviceID2, Name: "testname2"}) + if err != nil { + t.Error(err) + } + + if returnDevice.ID != inputDevices[1].ID().String() { + t.Errorf("GetAll() = %v, want %v", returnDevice.ID, inputDevices[1].ID().String()) + } + if returnDevice.Name != inputDevices[1].Name() { + t.Errorf("GetAll() = %v, want %v", returnDevice.Name, inputDevices[1].Name()) + } +} + +func TestDeleteDevice(t *testing.T) { + ensureDeviceFilesForTestAreRemoved() + defer ensureDeviceFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + deviceStore := NewDeviceStore(pndID) + + sbiID, _ := uuid.Parse("ssssssss-ssss-ssss-ssss-ssssssssssss") + sbi, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID) + + deviceID1, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + deviceID2, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab") + + trop := returnBasicTransportOption() + + device1, err := NewDevice("testname", deviceID1, &trop, sbi) + if err != nil { + t.Error(err) + } + + device2, err := NewDevice("testname2", deviceID2, &trop, sbi) + if err != nil { + t.Error(err) + } + + inputDevices := [2]device.Device{device1, device2} + + for _, device := range inputDevices { + err := deviceStore.Add(device) + if err != nil { + t.Error(err) + } + } + + err = deviceStore.Delete(device1) + if err != nil { + t.Error(err) + } + + returnDevices, err := deviceStore.GetAll() + if err != nil { + t.Error(err) + } + + length := len(returnDevices) + if length != 1 { + t.Errorf("GetAll() length of array = %v, want %v", length, 2) + } + + for _, device := range returnDevices { + if device.ID != inputDevices[1].ID().String() { + t.Errorf("GetAll() = %v, want %v", device.ID, inputDevices[1].ID().String()) + } + if device.Name != inputDevices[1].Name() { + t.Errorf("GetAll() = %v, want %v", device.Name, inputDevices[1].Name()) + } + } +} diff --git a/controller/nucleus/deviceService.go b/controller/nucleus/deviceService.go index 6679b974e63cbd4c9cc7360271da2152a2399830..30caff6dccec364d4f58b7fc98de7689f3c392cd 100644 --- a/controller/nucleus/deviceService.go +++ b/controller/nucleus/deviceService.go @@ -106,7 +106,7 @@ func (s *DeviceService) createDeviceFromStore(loadedDevice device.LoadedDevice) d, err := NewDevice( loadedDevice.Name, - uuid.MustParse(loadedDevice.DeviceID), + uuid.MustParse(loadedDevice.ID), &tpb.TransportOption{ Address: loadedDevice.TransportAddress, Username: loadedDevice.TransportUsername, diff --git a/controller/nucleus/deviceStore.go b/controller/nucleus/deviceStore.go index 04fa24ba7b03bd53f19292c272d464bf2d3cf3cd..aa886eb11bbc5fdb3c3e26563de22385f01b704d 100644 --- a/controller/nucleus/deviceStore.go +++ b/controller/nucleus/deviceStore.go @@ -4,7 +4,6 @@ import ( "fmt" "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" "github.com/google/uuid" @@ -15,31 +14,19 @@ const ( deviceStoreName = "device" ) -// DeviceStore is used to store Devices -type DeviceStore struct { - storeName string - sbiStore southbound.Store -} - // NewDeviceStore returns a DeviceStore func NewDeviceStore(pndUUID uuid.UUID) device.Store { storeMode := store.GetStoreMode() log.Debugf("StoreMode: %s", storeMode) switch storeMode { - case store.Filesystem: - store := NewMemoryDeviceStore() - - return store case store.Database: return &DatabaseDeviceStore{ storeName: fmt.Sprintf("device-store-%s.json", pndUUID.String()), } - case store.Memory: - store := NewMemoryDeviceStore() - return store default: - return nil + store := NewFilesystemDeviceStore(pndUUID) + return store } } diff --git a/controller/nucleus/filesystem/filesystem-settings.go b/controller/nucleus/filesystem/filesystem-settings.go new file mode 100644 index 0000000000000000000000000000000000000000..8df6e31e7ea541fa4484c7c83c125bfe8925682d --- /dev/null +++ b/controller/nucleus/filesystem/filesystem-settings.go @@ -0,0 +1,10 @@ +package filesystem + +const ( + // PndFilename is the name of the file where the pnds are stored + PndFilename string = "pndStore.json" + // DeviceFilenameSuffix is the suffix of the file where the devices are stored + DeviceFilenameSuffix string = "deviceStore.json" + // SbiFilenameSuffix is the suffix of the file where the sbis are stored + SbiFilenameSuffix string = "sbiStore.json" +) diff --git a/controller/nucleus/memoryDeviceStore.go b/controller/nucleus/memoryDeviceStore.go index 9a04d7a5c1c3fa06648e1eddefe7c2156ce35dcd..a7c775a88257ed6de027eb87a521c49ee417a8c8 100644 --- a/controller/nucleus/memoryDeviceStore.go +++ b/controller/nucleus/memoryDeviceStore.go @@ -35,13 +35,13 @@ func (t *MemoryDeviceStore) Add(item device.Device) error { return err } - _, ok := t.Store[device.DeviceID] + _, ok := t.Store[device.ID] if ok { return nil } - t.Store[device.DeviceID] = device - t.nameLookupTable[item.Name()] = device.DeviceID + t.Store[device.ID] = device + t.nameLookupTable[item.Name()] = device.ID return nil } diff --git a/controller/nucleus/memoryPndStore.go b/controller/nucleus/memoryPndStore.go index 92236742000bbb29594377f62b4197c22087f866..57cb3289d3a327b333491fe8005e980b9c4daa03 100644 --- a/controller/nucleus/memoryPndStore.go +++ b/controller/nucleus/memoryPndStore.go @@ -15,8 +15,8 @@ type MemoryPndStore struct { } // NewMemoryPndStore returns a in-memory implementation for a pnd store. -func NewMemoryPndStore() MemoryPndStore { - return MemoryPndStore{ +func NewMemoryPndStore() networkdomain.PndStore { + return &MemoryPndStore{ Store: make(map[uuid.UUID]networkdomain.NetworkDomain), pendingChannels: make(map[uuid.UUID]chan device.Details), } diff --git a/controller/nucleus/pndFilesystemStore.go b/controller/nucleus/pndFilesystemStore.go new file mode 100644 index 0000000000000000000000000000000000000000..c9c15c055b3f7615c438b8bbd00a7e0126d2ab4b --- /dev/null +++ b/controller/nucleus/pndFilesystemStore.go @@ -0,0 +1,209 @@ +package nucleus + +import ( + "encoding/json" + "io/ioutil" + "sync" + + 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/interfaces/networkdomain" + "code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors" + "code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem" + "code.fbi.h-da.de/danet/gosdn/controller/store" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// FilesystemPndStore provides a filesystem implementation for a pnd store. +type FilesystemPndStore struct { + pendingChannels map[uuid.UUID]chan device.Details + csbiClient cpb.CsbiServiceClient + pathToPndFile string + fileMutex sync.Mutex +} + +// NewFilesystemPndStore returns a filesystem implementation for a pnd store. +func NewFilesystemPndStore() FilesystemPndStore { + store.EnsureFilesystemStorePathExists(filesystem.PndFilename) + return FilesystemPndStore{ + pendingChannels: make(map[uuid.UUID]chan device.Details), + pathToPndFile: store.GetCompletePathToFileStore(filesystem.PndFilename), + fileMutex: sync.Mutex{}, + } +} + +func (t *FilesystemPndStore) readAllPndsFromFile() ([]networkdomain.NetworkDomain, error) { + var loadedPnds []LoadedPnd + + content, err := ioutil.ReadFile(t.pathToPndFile) + if err != nil { + return nil, err + } + + err = json.Unmarshal(content, &loadedPnds) + if err != nil { + return nil, err + } + + pnds := make([]networkdomain.NetworkDomain, len(loadedPnds)) + + csbiClient, err := t.getCsbiClient() + if err != nil { + return nil, err + } + + for i, loadedPND := range loadedPnds { + newPnd, err := NewPND( + loadedPND.Name, + loadedPND.Description, + uuid.MustParse(loadedPND.ID), + csbiClient, + t.callback, + ) + if err != nil { + return nil, err + } + + pnds[i] = newPnd + } + return pnds, nil +} + +func (t *FilesystemPndStore) writeAllPndsToFile(pnds []networkdomain.NetworkDomain) error { + serializedData, err := json.Marshal(pnds) + if err != nil { + return err + } + + err = ioutil.WriteFile(t.pathToPndFile, serializedData, 0600) + if err != nil { + return err + } + + return nil +} + +// Add a pnd to the store. +func (t *FilesystemPndStore) Add(item networkdomain.NetworkDomain) error { + t.fileMutex.Lock() + defer t.fileMutex.Unlock() + + pnds, err := t.readAllPndsFromFile() + if err != nil { + return err + } + + pnds = append(pnds, item) + + err = t.writeAllPndsToFile(pnds) + if err != nil { + return err + } + + return nil +} + +// Delete deletes a pnd from the store. +func (t *FilesystemPndStore) Delete(item networkdomain.NetworkDomain) error { + t.fileMutex.Lock() + defer t.fileMutex.Unlock() + + pnds, err := t.readAllPndsFromFile() + if err != nil { + return err + } + + for i, pnd := range pnds { + if pnd.ID() == item.ID() { + //remove item from slice + pnds[i] = pnds[len(pnds)-1] + pnds = pnds[:len(pnds)-1] + + err = t.writeAllPndsToFile(pnds) + if err != nil { + return err + } + + return nil + } + } + + return &errors.ErrNotFound{ID: item.ID} +} + +// Get provides a the query interface to find a stored pnd. +func (t *FilesystemPndStore) Get(query store.Query) (networkdomain.NetworkDomain, error) { + t.fileMutex.Lock() + defer t.fileMutex.Unlock() + + pnds, err := t.readAllPndsFromFile() + if err != nil { + return nil, err + } + + for _, pnd := range pnds { + if pnd.ID() == query.ID { + return pnd, nil + } + } + + return nil, &errors.ErrNotFound{ID: query.ID} +} + +// GetAll returns all pnds currently on the store. +func (t *FilesystemPndStore) GetAll() ([]networkdomain.NetworkDomain, error) { + t.fileMutex.Lock() + defer t.fileMutex.Unlock() + + pnds, err := t.readAllPndsFromFile() + + return pnds, err +} + +// PendingChannels holds channels used communicate with pending +// cSBI deployments +func (t *FilesystemPndStore) PendingChannels(id uuid.UUID, parseErrors ...error) (chan device.Details, error) { + ch, ok := t.pendingChannels[id] + if !ok { + return nil, &errors.ErrNotFound{ID: id} + } + return ch, nil +} + +// AddPendingChannel adds a pending channel to the map +func (t *FilesystemPndStore) AddPendingChannel(id uuid.UUID, ch chan device.Details) { + t.pendingChannels[id] = ch +} + +// RemovePendingChannel removes a pending channel from the map +func (t *FilesystemPndStore) RemovePendingChannel(id uuid.UUID) { + delete(t.pendingChannels, id) +} + +func (t *FilesystemPndStore) callback(id uuid.UUID, ch chan device.Details) { + if ch != nil { + t.AddPendingChannel(id, ch) + log.Infof("pending channel %v added", id) + } else { + t.RemovePendingChannel(id) + log.Infof("pending channel %v removed", id) + } +} + +func (t *FilesystemPndStore) getCsbiClient() (cpb.CsbiServiceClient, error) { + if t.csbiClient == nil { + orchestrator := viper.GetString("csbi-orchestrator") + conn, err := grpc.Dial(orchestrator, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + + t.csbiClient = cpb.NewCsbiServiceClient(conn) + } + + return t.csbiClient, nil +} diff --git a/controller/nucleus/pndFilesystemStore_test.go b/controller/nucleus/pndFilesystemStore_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3480c6a5392c90c7f0ac59e14906c63bcea722fb --- /dev/null +++ b/controller/nucleus/pndFilesystemStore_test.go @@ -0,0 +1,158 @@ +package nucleus + +import ( + "log" + "os" + "testing" + + "code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain" + "code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem" + "code.fbi.h-da.de/danet/gosdn/controller/store" + "github.com/google/uuid" +) + +func ensurePndFileForTestIsRemoved() { + store.EnsureFilesystemStorePathExists(filesystem.PndFilename) + path := store.GetCompletePathToFileStore(filesystem.PndFilename) + + err := os.Remove(path) + if err != nil { + log.Println(err) + } +} + +func TestAddPnd(t *testing.T) { + ensurePndFileForTestIsRemoved() + defer ensurePndFileForTestIsRemoved() + + pndStore := NewPndStore() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + pnd, _ := NewPND("testpnd", "test", pndID, nil, nil) + + err := pndStore.Add(pnd) + + if err != nil { + t.Error(err) + } +} + +func TestGetAllPnds(t *testing.T) { + ensurePndFileForTestIsRemoved() + defer ensurePndFileForTestIsRemoved() + + pndStore := NewPndStore() + pndID1, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + pndID2, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bab") + pnd1, _ := NewPND("testpnd", "test", pndID1, nil, nil) + pnd2, _ := NewPND("testpnd2", "test", pndID2, nil, nil) + + inputPnds := [2]networkdomain.NetworkDomain{pnd1, pnd2} + + for _, pnd := range inputPnds { + err := pndStore.Add(pnd) + if err != nil { + t.Error(err) + } + } + + returnPnds, err := pndStore.GetAll() + if err != nil { + t.Error(err) + } + + for i, pnd := range returnPnds { + if pnd.GetName() != inputPnds[i].GetName() { + t.Errorf("GetAll() = %v, want %v", pnd.GetName(), inputPnds[i].GetName()) + } + if pnd.GetDescription() != inputPnds[i].GetDescription() { + t.Errorf("GetAll() = %v, want %v", pnd.GetDescription(), inputPnds[i].GetDescription()) + } + if pnd.ID() != inputPnds[i].ID() { + t.Errorf("GetAll() = %v, want %v", pnd.ID(), inputPnds[i].ID()) + } + } +} + +func TestGetPnd(t *testing.T) { + ensurePndFileForTestIsRemoved() + defer ensurePndFileForTestIsRemoved() + + pndStore := NewPndStore() + pndID1, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + pndID2, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bab") + pnd1, _ := NewPND("testpnd", "test", pndID1, nil, nil) + pnd2, _ := NewPND("testpnd2", "test", pndID2, nil, nil) + + inputPnds := [2]networkdomain.NetworkDomain{pnd1, pnd2} + + for _, pnd := range inputPnds { + err := pndStore.Add(pnd) + if err != nil { + t.Error(err) + } + } + + returnPnd, err := pndStore.Get(store.Query{ID: pndID2, Name: ""}) + if err != nil { + t.Error(err) + } + + if returnPnd.GetName() != pnd2.GetName() { + t.Errorf("GetAll() = %v, want %v", pnd2.GetName(), returnPnd.GetName()) + } + if returnPnd.GetDescription() != pnd2.GetDescription() { + t.Errorf("GetAll() = %v, want %v", pnd2.GetDescription(), returnPnd.GetDescription()) + } + if returnPnd.ID() != pnd2.ID() { + t.Errorf("GetAll() = %v, want %v", pnd2.ID(), returnPnd.ID()) + } +} + +func TestDeletePnd(t *testing.T) { + ensurePndFileForTestIsRemoved() + defer ensurePndFileForTestIsRemoved() + + pndStore := NewPndStore() + pndID1, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + pndID2, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bab") + pnd1, _ := NewPND("testpnd", "test", pndID1, nil, nil) + pnd2, _ := NewPND("testpnd2", "test", pndID2, nil, nil) + + inputPnds := [2]networkdomain.NetworkDomain{pnd1, pnd2} + + for _, pnd := range inputPnds { + err := pndStore.Add(pnd) + if err != nil { + t.Error(err) + } + } + + err := pndStore.Delete(pnd1) + if err != nil { + t.Error(err) + } + + returnPnds, err := pndStore.GetAll() + if err != nil { + t.Error(err) + } + + length := len(returnPnds) + if length != 1 { + t.Errorf("GetAll() length of array = %v, want %v", length, 1) + } + + //only check first element in this case + for i, pnd := range returnPnds[1:] { + if pnd.GetName() != inputPnds[i].GetName() { + t.Errorf("GetAll() = %v, want %v", pnd.GetName(), inputPnds[i].GetName()) + } + if pnd.GetDescription() != inputPnds[i].GetDescription() { + t.Errorf("GetAll() = %v, want %v", pnd.GetDescription(), inputPnds[i].GetDescription()) + } + if pnd.ID() != inputPnds[i].ID() { + t.Errorf("GetAll() = %v, want %v", pnd.ID(), inputPnds[i].ID()) + } + } +} diff --git a/controller/nucleus/pndStore.go b/controller/nucleus/pndStore.go index 7b621db0217953aa1688a5ad479bc6ec557a6dcf..876e03742f71b2503aa6fb60bb629df00ee7d5a4 100644 --- a/controller/nucleus/pndStore.go +++ b/controller/nucleus/pndStore.go @@ -32,19 +32,13 @@ func NewPndStore() networkdomain.PndStore { log.Debugf("StoreMode: %s", storeMode) switch storeMode { - case store.Filesystem: - store := NewMemoryPndStore() - - return &store case store.Database: return &DatabasePndStore{ pendingChannels: make(map[uuid.UUID]chan device.Details), pndStoreName: "pnd-store.json"} - case store.Memory: - store := NewMemoryPndStore() - return &store default: - return nil + store := NewFilesystemPndStore() + return &store } } diff --git a/controller/nucleus/principalNetworkDomain_test.go b/controller/nucleus/principalNetworkDomain_test.go index fdd4644c94cc63ea2a7847d138162a20a864365f..26212e9e0b88d804328fd4203ee340dd884b3a97 100644 --- a/controller/nucleus/principalNetworkDomain_test.go +++ b/controller/nucleus/principalNetworkDomain_test.go @@ -365,8 +365,8 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - sbiStore := NewSbiStore(defaultPndID) - deviceStore := NewDeviceStore(defaultPndID) + sbiStore := NewMemorySbiStore() + deviceStore := NewMemoryDeviceStore() sbiService := NewSbiService(sbiStore) deviceService := NewDeviceService(deviceStore, sbiService) @@ -430,8 +430,8 @@ func Test_pndImplementation_RemoveSbiWithAssociatedDevices(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - sbiStore := NewSbiStore(defaultPndID) - deviceStore := NewDeviceStore(defaultPndID) + sbiStore := NewMemorySbiStore() + deviceStore := NewMemoryDeviceStore() sbiService := NewSbiService(sbiStore) deviceService := NewDeviceService(deviceStore, sbiService) diff --git a/controller/nucleus/sbiFilesystemStore.go b/controller/nucleus/sbiFilesystemStore.go new file mode 100644 index 0000000000000000000000000000000000000000..9337d37e646eb0f3e59ca22f5f2f298bd8e16b02 --- /dev/null +++ b/controller/nucleus/sbiFilesystemStore.go @@ -0,0 +1,147 @@ +package nucleus + +import ( + "encoding/json" + "io/ioutil" + "sync" + + "code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound" + "code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors" + "code.fbi.h-da.de/danet/gosdn/controller/nucleus/filesystem" + "code.fbi.h-da.de/danet/gosdn/controller/store" + + "github.com/google/uuid" +) + +// FilesystemSbiStore is used to store SouthboundInterfaces +type FilesystemSbiStore struct { + sbiStoreName string + pndUUID uuid.UUID + fileMutex sync.Mutex + pathToSbiFile string +} + +// NewFilesystemSbiStore returns a filesystem implementation for a pnd store. +func NewFilesystemSbiStore(pndUUID uuid.UUID) southbound.Store { + sbiFilenameForUUID := store.GetStoreFilenameForUUID(pndUUID, filesystem.SbiFilenameSuffix) + + store.EnsureFilesystemStorePathExists(sbiFilenameForUUID) + return &FilesystemSbiStore{ + pathToSbiFile: store.GetCompletePathToFileStore(sbiFilenameForUUID), + fileMutex: sync.Mutex{}, + pndUUID: pndUUID, + } +} + +func (s *FilesystemSbiStore) readAllSbisFromFile() ([]southbound.LoadedSbi, error) { + var loadedSbis []southbound.LoadedSbi + + content, err := ioutil.ReadFile(s.pathToSbiFile) + if err != nil { + return nil, err + } + + err = json.Unmarshal(content, &loadedSbis) + if err != nil { + return nil, err + } + return loadedSbis, nil +} + +func (s *FilesystemSbiStore) writeAllSbisToFile(sbis []southbound.LoadedSbi) error { + serializedData, err := json.Marshal(sbis) + if err != nil { + return err + } + + err = ioutil.WriteFile(s.pathToSbiFile, serializedData, 0600) + if err != nil { + return err + } + return nil +} + +// Add adds a SBI. +func (s *FilesystemSbiStore) Add(sbiToAdd southbound.SouthboundInterface) error { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + sbis, err := s.readAllSbisFromFile() + if err != nil { + return err + } + + var loadedSbi southbound.LoadedSbi + loadedSbi, err = store.TransformObjectToLoadedObject[southbound.SouthboundInterface, southbound.LoadedSbi](sbiToAdd) + if err != nil { + return err + } + + sbis = append(sbis, loadedSbi) + + err = s.writeAllSbisToFile(sbis) + if err != nil { + return err + } + + return nil +} + +// Delete deletes an SBI. +func (s *FilesystemSbiStore) Delete(sbiToDelete southbound.SouthboundInterface) error { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + sbis, err := s.readAllSbisFromFile() + if err != nil { + return err + } + + for i, device := range sbis { + if device.ID == sbiToDelete.ID().String() { + //remove item from slice + sbis[i] = sbis[len(sbis)-1] + sbis = sbis[:len(sbis)-1] + + err = s.writeAllSbisToFile(sbis) + if err != nil { + return err + } + + return nil + } + } + return &errors.ErrNotFound{ID: sbiToDelete.ID} +} + +// 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 *FilesystemSbiStore) Get(query store.Query) (southbound.LoadedSbi, error) { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + var sbi southbound.LoadedSbi + + sbis, err := s.readAllSbisFromFile() + if err != nil { + return sbi, err + } + + for _, device := range sbis { + if device.ID == query.ID.String() { + return device, nil + } + } + + return sbi, &errors.ErrNotFound{ID: query.ID} +} + +// GetAll returns all SBIs +func (s *FilesystemSbiStore) GetAll() ([]southbound.LoadedSbi, error) { + s.fileMutex.Lock() + defer s.fileMutex.Unlock() + + sbis, err := s.readAllSbisFromFile() + + return sbis, err +} diff --git a/controller/nucleus/sbiFilesystemStore_test.go b/controller/nucleus/sbiFilesystemStore_test.go new file mode 100644 index 0000000000000000000000000000000000000000..38ea67d4723ee950e00ba74c05d5b08b286549ae --- /dev/null +++ b/controller/nucleus/sbiFilesystemStore_test.go @@ -0,0 +1,162 @@ +package nucleus + +import ( + "log" + "os" + "path/filepath" + "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/nucleus/filesystem" + "code.fbi.h-da.de/danet/gosdn/controller/store" + "github.com/google/uuid" +) + +func ensureSbiFilesForTestAreRemoved() { + store.EnsureFilesystemStorePathExists(filesystem.SbiFilenameSuffix) + wildcartFilename := "*-" + filesystem.SbiFilenameSuffix + path := store.GetCompletePathToFileStore(wildcartFilename) + + files, err := filepath.Glob(path) + + if err != nil { + log.Println(err) + } + for _, f := range files { + if err := os.Remove(f); err != nil { + log.Println(err) + } + } +} + +func TestAddSbi(t *testing.T) { + ensureSbiFilesForTestAreRemoved() + defer ensureSbiFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + sbiStore := NewSbiStore(pndID) + + sbiID, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + + sbi, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID) + + err := sbiStore.Add(sbi) + if err != nil { + t.Error(err) + } +} + +func TestGetAllSbis(t *testing.T) { + ensureSbiFilesForTestAreRemoved() + defer ensureSbiFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + sbiStore := NewSbiStore(pndID) + + sbiID1, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + sbiID2, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab") + + sbi1, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID1) + sbi2, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID2) + + inputSbis := [2]southbound.SouthboundInterface{sbi1, sbi2} + + for _, sbi := range inputSbis { + err := sbiStore.Add(sbi) + if err != nil { + t.Error(err) + } + } + + returnSbis, err := sbiStore.GetAll() + if err != nil { + t.Error(err) + } + + length := len(returnSbis) + if length != 2 { + t.Errorf("GetAll() length of array = %v, want %v", length, 2) + } + + for i, sbi := range returnSbis { + if sbi.ID != inputSbis[i].ID().String() { + t.Errorf("GetAll() = %v, want %v", sbi.ID, inputSbis[i].ID().String()) + } + } +} + +func TestGetSbi(t *testing.T) { + ensureSbiFilesForTestAreRemoved() + defer ensureSbiFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + + sbiStore := NewSbiStore(pndID) + + sbiID1, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + sbiID2, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab") + + sbi1, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID1) + sbi2, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID2) + + inputSbis := [2]southbound.SouthboundInterface{sbi1, sbi2} + + for _, sbi := range inputSbis { + err := sbiStore.Add(sbi) + if err != nil { + t.Error(err) + } + } + + returnSbi, err := sbiStore.Get(store.Query{ID: sbiID2, Name: ""}) + if err != nil { + t.Error(err) + } + + if returnSbi.ID != sbi2.ID().String() { + t.Errorf("Get() = %v, want %v", returnSbi.ID, sbi2.ID().String()) + } +} + +func TestDeleteAllSbis(t *testing.T) { + ensureSbiFilesForTestAreRemoved() + defer ensureSbiFilesForTestAreRemoved() + + pndID, _ := uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad") + sbiStore := NewSbiStore(pndID) + + sbiID1, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") + sbiID2, _ := uuid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab") + + sbi1, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID1) + sbi2, _ := NewSBI(spb.Type_TYPE_OPENCONFIG, sbiID2) + + inputSbis := [2]southbound.SouthboundInterface{sbi1, sbi2} + + for _, sbi := range inputSbis { + err := sbiStore.Add(sbi) + if err != nil { + t.Error(err) + } + } + + err := sbiStore.Delete(sbi1) + if err != nil { + t.Error(err) + } + + returnSbis, err := sbiStore.GetAll() + if err != nil { + t.Error(err) + } + + length := len(returnSbis) + if length != 1 { + t.Errorf("GetAll() length of array = %v, want %v", length, 2) + } + + if returnSbis[0].ID != inputSbis[1].ID().String() { + t.Errorf("GetAll() = %v, want %v", returnSbis[0].ID, inputSbis[1].ID().String()) + } +} diff --git a/controller/nucleus/sbiStore.go b/controller/nucleus/sbiStore.go index 7038980cfb26fec86fa0c4d348544f3eb25df509..fa992e4c1be4b1e8fbe993db59b8545658db2f7b 100644 --- a/controller/nucleus/sbiStore.go +++ b/controller/nucleus/sbiStore.go @@ -23,19 +23,13 @@ func NewSbiStore(pndUUID uuid.UUID) southbound.Store { storeMode := store.GetStoreMode() switch storeMode { - case store.Filesystem: - store := NewMemorySbiStore() - - return store case store.Database: return &DatabaseSbiStore{ sbiStoreName: fmt.Sprintf("sbi-store-%s.json", pndUUID.String()), } - case store.Memory: - store := NewMemorySbiStore() - return store default: - return nil + store := NewFilesystemSbiStore(pndUUID) + return store } } diff --git a/controller/store/storageMode.go b/controller/store/storageMode.go index 513fb96935f847565719a0a9cea792e1b0fb8dfc..bd295dc345afdbfcf056fe36af5de060300da800 100644 --- a/controller/store/storageMode.go +++ b/controller/store/storageMode.go @@ -20,13 +20,11 @@ func GetStoreMode() StorageMode { return Database } - return Memory + return Filesystem } func (s StorageMode) String() string { switch s { - case Memory: - return "Memory" case Filesystem: return "Filesystem" case Database: diff --git a/controller/store/utils.go b/controller/store/utils.go index b329ffb65376c716bc90f45ccc669b7499397301..769889c7c27d59d201ffed3eb4a0aaf796e989d8 100644 --- a/controller/store/utils.go +++ b/controller/store/utils.go @@ -1,6 +1,11 @@ package store import ( + "encoding/json" + "os" + "path/filepath" + + "code.fbi.h-da.de/danet/gosdn/controller/config" "code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors" "github.com/google/uuid" log "github.com/sirupsen/logrus" @@ -21,3 +26,70 @@ func FromString(id string) (uuid.UUID, error) { return idAsUUID, nil } + +//EnsureFilesystemStorePathExists ensures that the filesystem store path exists +func EnsureFilesystemStorePathExists(storeFileName string) error { + completeStorePath := filepath.Join(config.FilesystemPathToStores, storeFileName) + if _, err := os.Stat(completeStorePath); os.IsNotExist(err) { + err := ensureFileSystemStoreExists(completeStorePath) + if err != nil { + return err + } + } + + return nil +} + +func ensureFileSystemStoreExists(pathToStore string) error { + err := ensureDirExists(pathToStore) + if err != nil { + return err + } + + emptyArray := []byte("[]") + err = os.WriteFile(pathToStore, emptyArray, 0600) + if err != nil { + return err + } + + return nil +} + +func ensureDirExists(fileName string) error { + dirName := filepath.Dir(fileName) + if _, serr := os.Stat(dirName); serr != nil { + merr := os.MkdirAll(dirName, os.ModePerm) + if merr != nil { + return merr + } + } + + return nil +} + +//GetCompletePathToFileStore gets the complete path to a file store +func GetCompletePathToFileStore(storeName string) string { + return filepath.Join(config.FilesystemPathToStores, storeName) +} + +//TransformObjectToLoadedObject transform an object into an loadedObject +func TransformObjectToLoadedObject[T, R any](object T) (R, error) { + var loadedObject R + + serializedData, err := json.Marshal(object) + if err != nil { + return loadedObject, err + } + + err = json.Unmarshal(serializedData, &loadedObject) + if err != nil { + return loadedObject, err + } + + return loadedObject, err +} + +//GetStoreFilenameForUUID returns the full filename for a given pndUUID and suffix +func GetStoreFilenameForUUID(pndUUID uuid.UUID, deviceFilenameSuffix string) string { + return pndUUID.String() + "-" + deviceFilenameSuffix +} diff --git a/scripts/test-add-device.sh b/scripts/test-add-device.sh index 72cb5acfd01275b8b4003bc3309c07cf717e0d65..5268009a17c5c48b410fa39fe6d7f733f9b77148 100755 --- a/scripts/test-add-device.sh +++ b/scripts/test-add-device.sh @@ -1,4 +1,3 @@ ./artifacts/gosdnc init --controller 127.0.0.1:55055 ./artifacts/gosdnc device create -a 172.100.0.11:6030 -u admin -p admin --name='test-ceos-1' --type csbi ./artifacts/gosdnc device list - diff --git a/scripts/test-add-pnd.sh b/scripts/test-add-pnd.sh index 75704be2ec5b3d7ac6e220acbdd506bfb2a6b256..cee5db7f1317de6858600ac5b1d297ca35ad2dbd 100755 --- a/scripts/test-add-pnd.sh +++ b/scripts/test-add-pnd.sh @@ -1,4 +1,3 @@ ./artifacts/gosdnc init --controller 127.0.0.1:55055 ./artifacts/gosdnc pnd create --name test-pnd-1 test -./artifacts/gosdnc device list - +./artifacts/gosdnc list diff --git a/scripts/test-list-pnd.sh b/scripts/test-list-pnd.sh new file mode 100755 index 0000000000000000000000000000000000000000..c96335f56cc2bb7394e8ba408145367e5f3c0560 --- /dev/null +++ b/scripts/test-list-pnd.sh @@ -0,0 +1,2 @@ +./artifacts/gosdnc init --controller 127.0.0.1:55055 +./artifacts/gosdnc list