diff --git a/cli/subscribe.go b/cli/subscribe.go index 284a74d89de56df7c667fd813b4ecd7ef92037f7..031ab104281c6c031ad2dc3c6a403e2a0be86e9a 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 95180504c5828c06dce7cbb713a204e3acd9275b..d3d65e7edd2a2061e9db8cf9b341832ead84561f 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 2daf934073e6f69dc0f8b55971aa747c9eccec13..ac38362886656823f04c0a79c55d3f9d0a15c7b8 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 6a4b3e9bab78477c71efc80a075b3855b1a3265d..f39f09287c4517ed33c027cac6311a0e9be2585b 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 54dd12becaad214a2d19f36d9da6a0a2cf7ac174..3bf82b6c69705fdd1cfb1f7396cd79977ea87de8 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 11234826ed412ed507e9711b7b4af84b379c856e..67fe1da1c40c884da0ac35c6f8d1c38d6260e711 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 a641b7fb4f4abffebd4e6848ae5cf6343644d276..63e7a9ac5c131d862253d34f7ba46e62cd5b2374 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 8e06b5944cab2bd7ffa365c59d78b8295043bc1f..a8879d2d35c76d7fae7ca783814d6ec407cc53b4 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 611ef282280814b75535b07660de63a3df5b77fa..c92b0a484bbfc28c2259f0cca50497156779bb2c 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 92ce1890a2a0d5585d0093f6614fa727d919184c..cc44a1059dd61e717900ef725af22718e95b9b9d 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 47a7a3e12ecc20da64c1f5476ea938d6783c44d7..8b80c8b4e1286b241b5254de5c496ba587e23455 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 5fbb298a7aa6a4cbc30aff8a80695aa1647eb34a..74effe95eed2b368abc0bdf59ef6c527c7ffce7e 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 0c03706fc2c1866784b58d9689b25283f70f5465..ccfa738f62d99f6cb132ee25222726131e573e99 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 829c60c5d53f53611d8dfb72f411145769fca58b..1b4a6fd0c7f5a0174adda177073e7a22cdb1a6d4 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 3aad202118e0bceb2b4e91dd34ce075c1ed454e5..4cc6ca1c61d8cb1d82d125030fabbc8840c74788 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 85b938a0d14aa442c196e020b9f413b8095bb9bb..dc60751e1f3a12d04fab39e4ae4e67d438202476 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 e3d535d81d7dfee3557f6ce2f7d7bdd94ab7f6f1..4c16117293b9b0eb38a48cf49ea39c5c13cdc688 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 17d5c0a30232b69ec82e180fc2fa75706d3d66c5..88341b5b66669e0e184954af6292a90082c1a46f 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 291ed361c39491e49c499a2cefab1e556df88578..639762d43210259758d94970e538a391cd3a84b5 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 f2db1ed61741fb75f4ab4360bc192610573f3697..18a13fd4cc1b08552fe030efb7e02a894fb73028 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