From 8e2fe42b442e42e60ed34d1be62bf5457f75d941 Mon Sep 17 00:00:00 2001 From: Andre Sterba <andre.sterba@stud.h-da.de> Date: Fri, 28 May 2021 17:57:08 +0000 Subject: [PATCH] Let user set a name for a device or autogenerate it --- cli/subscribe.go | 13 ++-- cmd/addDevice.go | 9 ++- cmd/getDevice.go | 6 +- cmd/init.go | 2 +- cmd/request.go | 2 +- cmd/requestAll.go | 2 +- cmd/subscribe.go | 2 +- go.mod | 1 + go.sum | 1 + http.go | 35 +++++++--- http_test.go | 3 +- initialise_test.go | 1 + nucleus/device.go | 18 ++++-- nucleus/device_test.go | 7 +- nucleus/errors/errors.go | 9 +++ nucleus/initialise_test.go | 2 +- nucleus/principalNetworkDomain.go | 53 +++++++++------ nucleus/principalNetworkDomain_test.go | 70 +++++++++++++++++--- nucleus/store.go | 89 +++++++++++++++++++++++++- nucleus/store_test.go | 6 +- 20 files changed, 269 insertions(+), 62 deletions(-) diff --git a/cli/subscribe.go b/cli/subscribe.go index 284a74d89..031ab1042 100644 --- a/cli/subscribe.go +++ b/cli/subscribe.go @@ -18,20 +18,20 @@ import ( // Subscribe starts a gNMI subscriber requersting the specified paths on the target and // logs the response to stdout. Only 'stream' mode with 'sample' operation supported. -func Subscribe(a, u, p string, sample, heartbeat int64, args ...string) error { +func Subscribe(address, username, password, deviceName string, sample, heartbeat int64, args ...string) error { sbi := &nucleus.OpenConfig{} tOpts := &nucleus.GnmiTransportOptions{ Config: gnmi.Config{ - Addr: a, - Username: u, - Password: p, + Addr: address, + Username: username, + Password: password, Encoding: gpb.Encoding_JSON_IETF, }, SetNode: sbi.SetNode(), RespChan: make(chan *gpb.SubscribeResponse), } - device, err := nucleus.NewDevice(sbi, tOpts) + device, err := nucleus.NewDevice(sbi, tOpts, deviceName) if err != nil { return err } @@ -46,7 +46,7 @@ func Subscribe(a, u, p string, sample, heartbeat int64, args ...string) error { HeartbeatInterval: uint64(heartbeat * time.Second.Nanoseconds()), Paths: gnmi.SplitPaths(args), Origin: "", - Target: a, + Target: address, } done := make(chan os.Signal, 1) signal.Notify(done, syscall.SIGILL, syscall.SIGTERM) @@ -59,5 +59,6 @@ func Subscribe(a, u, p string, sample, heartbeat int64, args ...string) error { fmt.Println("awaiting signal") <-done fmt.Println("exiting") + return nil } diff --git a/cmd/addDevice.go b/cmd/addDevice.go index 95180504c..d3d65e7ed 100644 --- a/cmd/addDevice.go +++ b/cmd/addDevice.go @@ -36,12 +36,14 @@ import ( "github.com/spf13/cobra" ) +var deviceName string + // addDeviceCmd represents the addDevice command var addDeviceCmd = &cobra.Command{ Use: "add-device", Short: "adds a device to the controller", - Long: `Adds a device to the controller. - + Long: `Adds a device to the controller. + Device address and user credentials need to be provided if they diverge from the default credentials.`, RunE: func(cmd *cobra.Command, args []string) error { @@ -53,10 +55,13 @@ if they diverge from the default credentials.`, "username="+username, "sbi="+cliSbi, "pnd="+cliPnd, + "name="+deviceName, ) }, } func init() { cliCmd.AddCommand(addDeviceCmd) + + addDeviceCmd.PersistentFlags().StringVarP(&deviceName, "device-name", "n", "", "Human readable device name.") } diff --git a/cmd/getDevice.go b/cmd/getDevice.go index 2daf93407..ac3836288 100644 --- a/cmd/getDevice.go +++ b/cmd/getDevice.go @@ -42,13 +42,13 @@ var getDeviceCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Short: "gets device information from the controller", Long: `Gets device information from the controller. - -Device UUID needs to be specified as positional argument.`, + +Device UUID or name needs to be specified as positional argument.`, RunE: func(cmd *cobra.Command, args []string) error { return cli.HTTPGet( apiEndpoint, "getDevice", - "uuid="+args[0], + "identifier="+args[0], "sbi="+cliSbi, "pnd="+cliPnd, ) diff --git a/cmd/init.go b/cmd/init.go index 6a4b3e9ba..f39f09287 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -41,7 +41,7 @@ var initCmd = &cobra.Command{ Use: "init", Short: "initialise SBI and PND", Long: `Initialise SBI and PND and saves values to config file. - + Same as invoking "gosdn cli" without any arguments`, RunE: func(cmd *cobra.Command, args []string) error { return cli.HTTPGet(apiEndpoint, "init") diff --git a/cmd/request.go b/cmd/request.go index 54dd12bec..3bf82b6c6 100644 --- a/cmd/request.go +++ b/cmd/request.go @@ -42,7 +42,7 @@ var requestCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Short: "requests a path from a specified device on the controller", Long: `Requests a path from a specified device on the controller. - + The request path is passed as positional argument.`, RunE: func(cmd *cobra.Command, args []string) error { return cli.HTTPGet( diff --git a/cmd/requestAll.go b/cmd/requestAll.go index 11234826e..67fe1da1c 100644 --- a/cmd/requestAll.go +++ b/cmd/requestAll.go @@ -43,7 +43,7 @@ var requestAllCmd = &cobra.Command{ Short: "requests specified path from all devices on the controller", Long: `Requests a path from all devices on the controller known by the controller. - + The request path is passed as positional argument.`, RunE: func(cmd *cobra.Command, args []string) error { return cli.HTTPGet( diff --git a/cmd/subscribe.go b/cmd/subscribe.go index a641b7fb4..63e7a9ac5 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -47,7 +47,7 @@ var subscribeCmd = &cobra.Command{ Only 'stream' mode with 'sample' operation supported.`, RunE: func(cmd *cobra.Command, args []string) error { - return cli.Subscribe(address, username, password, sampleInterval, heartbeatInterval, args...) + return cli.Subscribe(address, username, password, "", sampleInterval, heartbeatInterval, args...) }, } diff --git a/go.mod b/go.mod index 8e06b5944..a8879d2d3 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( code.fbi.h-da.de/cocsn/yang-models v0.0.7 github.com/aristanetworks/goarista v0.0.0-20201120222254-94a892eb0c6a + github.com/docker/docker v1.13.1 github.com/golang/protobuf v1.5.0 github.com/google/gnxi v0.0.0-20201221102247-c26672548161 github.com/google/uuid v1.1.2 diff --git a/go.sum b/go.sum index 611ef2822..c92b0a484 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= diff --git a/http.go b/http.go index 92ce1890a..cc44a1059 100644 --- a/http.go +++ b/http.go @@ -1,19 +1,22 @@ package gosdn import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "time" + "code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi" "code.fbi.h-da.de/cocsn/gosdn/nucleus" "code.fbi.h-da.de/cocsn/gosdn/nucleus/errors" "code.fbi.h-da.de/cocsn/gosdn/nucleus/types" - "context" - "fmt" + "github.com/google/uuid" + gpb "github.com/openconfig/gnmi/proto/gnmi" log "github.com/sirupsen/logrus" - "io" - "net/http" - "net/url" - "time" ) var apiOpmap = map[string]types.Operation{ @@ -119,15 +122,26 @@ func httpApi(writer http.ResponseWriter, request *http.Request) { SetNode: httpSbi.SetNode(), Unmarshal: httpSbi.(*nucleus.OpenConfig).Unmarshal(), RespChan: make(chan *gpb.SubscribeResponse), - }) + }, + query.Get("name"), + ) + if err != nil { + writer.WriteHeader(http.StatusInternalServerError) + log.Error(err) + + return + } + err = httpPnd.AddDevice(d) if err != nil { writer.WriteHeader(http.StatusInternalServerError) log.Error(err) + return } writer.WriteHeader(http.StatusCreated) fmt.Fprintf(writer, "device added\n") + fmt.Fprintf(writer, "Name: %s\n", d.Name) fmt.Fprintf(writer, "UUID: %v\n", d.UUID) case "request": err = httpPnd.Request(id, query.Get("path")) @@ -156,7 +170,9 @@ func httpApi(writer http.ResponseWriter, request *http.Request) { } writer.WriteHeader(http.StatusOK) case "getDevice": - device, err := httpPnd.MarshalDevice(id) + deviceIdentifier := query.Get("identifier") + + device, err := httpPnd.MarshalDevice(deviceIdentifier) if err != nil { switch err.(type) { case *errors.ErrNotFound: @@ -165,10 +181,13 @@ func httpApi(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusInternalServerError) } log.Error(err) + return } + writer.Header().Set("Content-Type", "application/json") fmt.Fprintf(writer, "%v", device) + case "getIDs": pnds := c.pndc.UUIDs() diff --git a/http_test.go b/http_test.go index 47a7a3e12..8b80c8b4e 100644 --- a/http_test.go +++ b/http_test.go @@ -35,6 +35,7 @@ func testSetupHTTP() { } args = "&uuid=" + mdid.String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String() argsNotFound = "&uuid=" + uuid.New().String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String() + argsNotFoundGetDevice = "&identifier=" + uuid.New().String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String() if err := c.sbic.Add(sbi); err != nil { log.Fatal(err) } @@ -119,7 +120,7 @@ func Test_httpApi(t *testing.T) { }, { name: "get-device not found", - request: apiEndpoint + "/api?q=getDevice" + argsNotFound, + request: apiEndpoint + "/api?q=getDevice" + argsNotFoundGetDevice, want: &http.Response{StatusCode: http.StatusNotFound}, wantErr: false, }, diff --git a/initialise_test.go b/initialise_test.go index 5fbb298a7..74effe95e 100644 --- a/initialise_test.go +++ b/initialise_test.go @@ -30,6 +30,7 @@ var httpTestDevice nucleus.Device var args string var argsNotFound string +var argsNotFoundGetDevice string var mockContext = mock.MatchedBy(func(ctx context.Context) bool { return true }) diff --git a/nucleus/device.go b/nucleus/device.go index 0c03706fc..ccfa738f6 100644 --- a/nucleus/device.go +++ b/nucleus/device.go @@ -2,6 +2,7 @@ package nucleus import ( "code.fbi.h-da.de/cocsn/gosdn/nucleus/errors" + "github.com/docker/docker/pkg/namesgenerator" "github.com/google/uuid" "github.com/openconfig/ygot/ygot" ) @@ -20,20 +21,28 @@ type Device struct { // Transport is the device's Transport implementation Transport Transport + + // Name is the device's human readable name + Name string } // NewDevice creates a Device -func NewDevice(sbi SouthboundInterface, opts TransportOptions) (*Device, error) { +func NewDevice(sbi SouthboundInterface, opts TransportOptions, name string) (*Device, error) { var transport Transport var err error - switch o := opts.(type) { + + if name == "" { + name = namesgenerator.GetRandomName(0) + } + + switch opts := opts.(type) { case *GnmiTransportOptions: - transport, err = NewGnmiTransport(o) + transport, err = NewGnmiTransport(opts) if err != nil { return nil, err } default: - return nil, &errors.ErrInvalidTransportOptions{Opt: o} + return nil, &errors.ErrInvalidTransportOptions{Opt: opts} } return &Device{ @@ -41,6 +50,7 @@ func NewDevice(sbi SouthboundInterface, opts TransportOptions) (*Device, error) GoStruct: sbi.Schema().Root, SBI: sbi, Transport: transport, + Name: name, }, nil } diff --git a/nucleus/device_test.go b/nucleus/device_test.go index 829c60c5d..1b4a6fd0c 100644 --- a/nucleus/device_test.go +++ b/nucleus/device_test.go @@ -16,6 +16,7 @@ func TestDevice_Id(t *testing.T) { SBI SouthboundInterface Transport Transport UUID uuid.UUID + Name string } tests := []struct { name string @@ -37,6 +38,7 @@ func TestDevice_Id(t *testing.T) { SBI: tt.fields.SBI, Transport: tt.fields.Transport, UUID: tt.fields.UUID, + Name: tt.fields.Name, } if got := d.ID(); !reflect.DeepEqual(got, tt.want) { t.Errorf("ID() = %v, want %v", got, tt.want) @@ -50,6 +52,7 @@ func TestNewDevice(t *testing.T) { type args struct { sbi SouthboundInterface opts TransportOptions + name string } tests := []struct { name string @@ -68,6 +71,7 @@ func TestNewDevice(t *testing.T) { Password: "test", }, }, + name: "MyDevice", }, want: &Device{ GoStruct: &openconfig.Device{}, @@ -82,12 +86,13 @@ func TestNewDevice(t *testing.T) { }, }, }, + Name: "MyDevice", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewDevice(tt.args.sbi, tt.args.opts) + got, err := NewDevice(tt.args.sbi, tt.args.opts, tt.args.name) if err != nil { t.Error(err) } diff --git a/nucleus/errors/errors.go b/nucleus/errors/errors.go index 3aad20211..4cc6ca1c6 100644 --- a/nucleus/errors/errors.go +++ b/nucleus/errors/errors.go @@ -41,6 +41,15 @@ func (e *ErrAlreadyExists) Error() string { return fmt.Sprintf("%v already exists", e.Item) } +// ErrInvalidUUID implements the Error interface and is called if a UUID is not valid. +type ErrInvalidUUID struct { + DeviceName string +} + +func (e *ErrInvalidUUID) Error() string { + return fmt.Sprintf("UUID not valid") +} + // ErrInvalidTypeAssertion implements the Error interface and is called if the // type of a storable item does not correspond to the expected type. type ErrInvalidTypeAssertion struct { diff --git a/nucleus/initialise_test.go b/nucleus/initialise_test.go index 85b938a0d..dc60751e1 100644 --- a/nucleus/initialise_test.go +++ b/nucleus/initialise_test.go @@ -129,7 +129,7 @@ func newPnd() pndImplementation { name: "default", description: "default test pnd", sbic: SbiStore{store{}}, - devices: DeviceStore{store{}}, + devices: NewDeviceStore(), pendingChanges: ChangeStore{store{}}, committedChanges: ChangeStore{store{}}, confirmedChanges: ChangeStore{store{}}, diff --git a/nucleus/principalNetworkDomain.go b/nucleus/principalNetworkDomain.go index e3d535d81..4c1611729 100644 --- a/nucleus/principalNetworkDomain.go +++ b/nucleus/principalNetworkDomain.go @@ -23,7 +23,7 @@ type PrincipalNetworkDomain interface { AddSbi(interface{}) error RemoveSbi(uuid.UUID) error AddDevice(interface{}) error - GetDevice(uuid uuid.UUID) (ygot.GoStruct, error) + GetDevice(identifier string) (*Device, error) RemoveDevice(uuid.UUID) error Devices() []uuid.UUID ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error @@ -31,7 +31,7 @@ type PrincipalNetworkDomain interface { RequestAll(string) error GetName() string GetDescription() string - MarshalDevice(uuid.UUID) (string, error) + MarshalDevice(string) (string, error) ContainsDevice(uuid.UUID) bool GetSBIs() interface{} ID() uuid.UUID @@ -47,7 +47,7 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr name: name, description: description, sbic: SbiStore{store{}}, - devices: DeviceStore{store{}}, + devices: NewDeviceStore(), pendingChanges: ChangeStore{store{}}, committedChanges: ChangeStore{store{}}, confirmedChanges: ChangeStore{store{}}, @@ -64,7 +64,7 @@ type pndImplementation struct { name string description string sbic SbiStore - devices DeviceStore + devices *DeviceStore pendingChanges ChangeStore committedChanges ChangeStore confirmedChanges ChangeStore @@ -172,7 +172,7 @@ func (pnd *pndImplementation) RemoveSbi(id uuid.UUID) error { return pnd.removeSbi(id) } -//AddDevice adds a new device to the PND +// AddDevice adds a new device to the PND func (pnd *pndImplementation) AddDevice(device interface{}) error { d, ok := device.(*Device) if !ok { @@ -184,12 +184,20 @@ func (pnd *pndImplementation) AddDevice(device interface{}) error { return pnd.addDevice(d) } -func (pnd *pndImplementation) GetDevice(uuid uuid.UUID) (ygot.GoStruct, error) { - d, err := pnd.devices.Get(uuid) +func (pnd *pndImplementation) GetDevice(identifier string) (*Device, error) { + d, err := pnd.devices.Get(FromString(identifier)) if err != nil { return nil, err } - return ygot.DeepCopy(d.GoStruct) + + copiedGoStruct, err := ygot.DeepCopy(d.GoStruct) + if err != nil { + return nil, err + } + + copiedDevice := &Device{Name: d.Name, UUID: d.UUID, GoStruct: copiedGoStruct} + + return copiedDevice, nil } // RemoveDevice removes a device from the PND @@ -212,36 +220,40 @@ func (pnd *pndImplementation) removeSbi(id uuid.UUID) error { } func (pnd *pndImplementation) addDevice(device *Device) error { - return pnd.devices.Add(device) -} + err := pnd.devices.Add(device, device.Name) + if err != nil { + return err + } -func (pnd *pndImplementation) getDevice(id uuid.UUID) (*Device, error) { - return pnd.devices.Get(id) + return nil } func (pnd *pndImplementation) removeDevice(id uuid.UUID) error { return pnd.devices.Delete(id) } -func (pnd *pndImplementation) MarshalDevice(uuid uuid.UUID) (string, error) { - d, err := pnd.getDevice(uuid) +func (pnd *pndImplementation) MarshalDevice(identifier string) (string, error) { + foundDevice, err := pnd.devices.Get(FromString(identifier)) if err != nil { return "", err } - jsonTree, err := json.MarshalIndent(d.GoStruct, "", "\t") + + jsonTree, err := json.MarshalIndent(foundDevice.GoStruct, "", "\t") if err != nil { return "", err } log.WithFields(log.Fields{ - "pnd": pnd.id, - "device": uuid, + "pnd": pnd.id, + "Identifier": identifier, + "Name": foundDevice.Name, }).Info("marshalled device") + return string(jsonTree), nil } // Request sends a get request to a specific device func (pnd *pndImplementation) Request(uuid uuid.UUID, path string) error { - d, err := pnd.getDevice(uuid) + d, err := pnd.devices.Get(FromString(uuid.String())) if err != nil { return err } @@ -273,12 +285,15 @@ func (pnd *pndImplementation) RequestAll(path string) error { // ChangeOND creates a change from the provided Operation, path and value. The Change is pending and func (pnd *pndImplementation) ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error { - d, err := pnd.getDevice(uuid) + d, err := pnd.devices.Get(FromString(uuid.String())) if err != nil { return err } cpy, err := ygot.DeepCopy(d.GoStruct) ygot.BuildEmptyTree(cpy) + if err != nil { + return err + } p, err := ygot.StringToStructuredPath(path) if err != nil { diff --git a/nucleus/principalNetworkDomain_test.go b/nucleus/principalNetworkDomain_test.go index 17d5c0a30..88341b5b6 100644 --- a/nucleus/principalNetworkDomain_test.go +++ b/nucleus/principalNetworkDomain_test.go @@ -224,7 +224,7 @@ func Test_pndImplementation_ContainsDevice(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pnd := newPnd() if tt.name != "fails empty" { - if err := pnd.devices.Add(tt.args.device); err != nil { + if err := pnd.devices.Add(tt.args.device, "test"); err != nil { t.Error(err) } } @@ -243,7 +243,7 @@ func Test_pndImplementation_Destroy(t *testing.T) { name string description string sbi SbiStore - devices DeviceStore + devices *DeviceStore } tests := []struct { name string @@ -347,7 +347,7 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) { if err := pnd.addDevice(d); err != nil { t.Error(err) } - got, err := pnd.MarshalDevice(tt.args.uuid) + got, err := pnd.MarshalDevice(tt.args.uuid.String()) if (err != nil) != tt.wantErr { t.Errorf("MarshalDevice() error = %v, wantErr %v", err, tt.wantErr) return @@ -413,7 +413,7 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) { name: "test-remove-sbi", description: "test-remove-sbi", sbic: SbiStore{store{}}, - devices: DeviceStore{store{}}, + devices: NewDeviceStore(), id: defaultPndID, } if tt.name != "fails empty" { @@ -661,7 +661,7 @@ func Test_pndImplementation_ChangeOND(t *testing.T) { func Test_pndImplementation_GetDevice(t *testing.T) { pnd := newPnd() sbi := NewSBI(types.Openconfig) - d, err := NewDevice(sbi, &GnmiTransportOptions{}) + d, err := NewDevice(sbi, &GnmiTransportOptions{}, "") if err != nil { t.Error(err) return @@ -694,14 +694,68 @@ func Test_pndImplementation_GetDevice(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := pnd.GetDevice(tt.args.uuid) + foundDevice, err := pnd.GetDevice(tt.args.uuid.String()) if (err != nil) != tt.wantErr { t.Errorf("GetDevice() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetDevice() got = %v, want %v", got, tt.want) + if foundDevice != nil { + if !reflect.DeepEqual(foundDevice.GoStruct, tt.want) { + t.Errorf("GetDevice() got = %v, want %v", foundDevice.GoStruct, tt.want) + } } + + }) + } +} + +func Test_pndImplementation_GetDeviceByName(t *testing.T) { + p := newPnd() + sbi := NewSBI(types.Openconfig) + d, err := NewDevice(sbi, &GnmiTransportOptions{}, "my-device") + if err != nil { + t.Error(err) + return + } + if err = p.addDevice(d); err != nil { + t.Error(err) + return + } + type args struct { + name string + } + tests := []struct { + name string + args args + want ygot.GoStruct + wantErr bool + }{ + { + name: "default", + args: args{name: d.Name}, + want: sbi.Schema().Root, + wantErr: false, + }, + { + name: "device not found", + args: args{name: "test-device"}, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + foundDevice, err := p.GetDevice(tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("GetDeviceByName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if foundDevice != nil { + if !reflect.DeepEqual(foundDevice.GoStruct, tt.want) { + t.Errorf("GetDeviceByName() got = %v, want %v", foundDevice.GoStruct, tt.want) + } + } + }) } } diff --git a/nucleus/store.go b/nucleus/store.go index 291ed361c..639762d43 100644 --- a/nucleus/store.go +++ b/nucleus/store.go @@ -1,6 +1,7 @@ package nucleus import ( + "fmt" "reflect" "sync" @@ -43,7 +44,7 @@ func NewSbiStore() *SbiStore { // NewDeviceStore returns a DeviceStore func NewDeviceStore() *DeviceStore { - return &DeviceStore{store{}} + return &DeviceStore{store: store{}, deviceNameToUUIDLookup: make(map[string]uuid.UUID)} } // NewChangeStore returns a ChangeStore @@ -170,13 +171,33 @@ func (s PndStore) Get(id uuid.UUID) (PrincipalNetworkDomain, error) { // DeviceStore is used to store Devices type DeviceStore struct { + deviceNameToUUIDLookup map[string]uuid.UUID store } // Get takes a Device's UUID and returns the Device. If the requested // Device does not exist an error is returned. -func (s DeviceStore) Get(id uuid.UUID) (*Device, error) { - item, err := s.store.Get(id) +func (s DeviceStore) Get(id uuid.UUID, parseErrors ...error) (*Device, error) { + var foundID uuid.UUID + + foundID = id + + for _, parseErrs := range parseErrors { + if parseErrs != nil { + switch e := parseErrs.(type) { + case *errors.ErrInvalidUUID: + myID, ok := s.deviceNameToUUIDLookup[e.DeviceName] + if !ok { + log.Debug(fmt.Sprintf("no device named %s found", foundID)) + return nil, &errors.ErrNotFound{} + } + + foundID = myID + } + } + } + + item, err := s.store.Get(foundID) if err != nil { return nil, err } @@ -189,10 +210,72 @@ func (s DeviceStore) Get(id uuid.UUID) (*Device, error) { } log.WithFields(log.Fields{ "uuid": id, + "name": device.Name, }).Debug("device was accessed") + return device, nil } +// FromString is a helper to check if a provided string as a valid UUID or a name. +func FromString(id string) (uuid.UUID, error) { + idAsUUID, err := uuid.Parse(id) + + // id is no UUID therefore it could be a device name. + // The name will be returned within the error. + if err != nil { + log.WithFields(log.Fields{ + "identifier": id, + }).Debug(err) + return uuid.Nil, &errors.ErrInvalidUUID{DeviceName: id} + } + + return idAsUUID, nil +} + +// Add adds a device to the device store. +// It also adds the name of the device to the lookup table. +func (s DeviceStore) Add(item Storable, name string) error { + if s.Exists(item.ID()) { + return &errors.ErrAlreadyExists{Item: item} + } + + s.deviceNameToUUIDLookup[name] = item.ID() + + storeLock.Lock() + s.store[item.ID()] = item + storeLock.Unlock() + + log.WithFields(log.Fields{ + "type": reflect.TypeOf(item), + "uuid": item.ID(), + }).Debug("storable was added") + + return nil +} + +// Delete deletes a device from the device store. +func (s DeviceStore) Delete(id uuid.UUID) error { + if !s.Exists(id) { + return &errors.ErrNotFound{ID: id} + } + + storeLock.Lock() + delete(s.store, id) + storeLock.Unlock() + + for key, value := range s.deviceNameToUUIDLookup { + if value == id { + delete(s.deviceNameToUUIDLookup, key) + } + } + + log.WithFields(log.Fields{ + "uuid": id, + }).Debug("storable was deleted") + + return nil +} + // ChangeStore is used to store Changes type ChangeStore struct { store diff --git a/nucleus/store_test.go b/nucleus/store_test.go index f2db1ed61..18a13fd4c 100644 --- a/nucleus/store_test.go +++ b/nucleus/store_test.go @@ -423,9 +423,11 @@ func Test_deviceStore_get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := DeviceStore{ - store: tt.fields.store, + store: tt.fields.store, + deviceNameToUUIDLookup: make(map[string]uuid.UUID), } - got, err := s.Get(tt.args.id) + + got, err := s.Get(FromString(tt.args.id.String())) if (err != nil) != tt.wantErr { t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) return -- GitLab