Skip to content
Snippets Groups Projects
Commit 50722ba9 authored by Manuel Kieweg's avatar Manuel Kieweg
Browse files

Merge branch 'istaester/autogenerate-device-names' into 'develop'

Let user set a name for a device or autogenerate it

See merge request cocsn/gosdn!150
parents a94be2bb 8e2fe42b
Branches
Tags
9 merge requests!246Develop,!245Develop into Master,!244Master into develop2 into master,!219Draft: Testing,!214Test pipelines,!195DO NOT MERGE 2,!194DO NOT MERGE! just for testing,!150Let user set a name for a device or autogenerate it,!138Develop
Pipeline #72526 failed
...@@ -18,20 +18,20 @@ import ( ...@@ -18,20 +18,20 @@ import (
// Subscribe starts a gNMI subscriber requersting the specified paths on the target and // 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. // 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{} sbi := &nucleus.OpenConfig{}
tOpts := &nucleus.GnmiTransportOptions{ tOpts := &nucleus.GnmiTransportOptions{
Config: gnmi.Config{ Config: gnmi.Config{
Addr: a, Addr: address,
Username: u, Username: username,
Password: p, Password: password,
Encoding: gpb.Encoding_JSON_IETF, Encoding: gpb.Encoding_JSON_IETF,
}, },
SetNode: sbi.SetNode(), SetNode: sbi.SetNode(),
RespChan: make(chan *gpb.SubscribeResponse), RespChan: make(chan *gpb.SubscribeResponse),
} }
device, err := nucleus.NewDevice(sbi, tOpts) device, err := nucleus.NewDevice(sbi, tOpts, deviceName)
if err != nil { if err != nil {
return err return err
} }
...@@ -46,7 +46,7 @@ func Subscribe(a, u, p string, sample, heartbeat int64, args ...string) error { ...@@ -46,7 +46,7 @@ func Subscribe(a, u, p string, sample, heartbeat int64, args ...string) error {
HeartbeatInterval: uint64(heartbeat * time.Second.Nanoseconds()), HeartbeatInterval: uint64(heartbeat * time.Second.Nanoseconds()),
Paths: gnmi.SplitPaths(args), Paths: gnmi.SplitPaths(args),
Origin: "", Origin: "",
Target: a, Target: address,
} }
done := make(chan os.Signal, 1) done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGILL, syscall.SIGTERM) signal.Notify(done, syscall.SIGILL, syscall.SIGTERM)
...@@ -59,5 +59,6 @@ func Subscribe(a, u, p string, sample, heartbeat int64, args ...string) error { ...@@ -59,5 +59,6 @@ func Subscribe(a, u, p string, sample, heartbeat int64, args ...string) error {
fmt.Println("awaiting signal") fmt.Println("awaiting signal")
<-done <-done
fmt.Println("exiting") fmt.Println("exiting")
return nil return nil
} }
...@@ -36,12 +36,14 @@ import ( ...@@ -36,12 +36,14 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var deviceName string
// addDeviceCmd represents the addDevice command // addDeviceCmd represents the addDevice command
var addDeviceCmd = &cobra.Command{ var addDeviceCmd = &cobra.Command{
Use: "add-device", Use: "add-device",
Short: "adds a device to the controller", 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 Device address and user credentials need to be provided
if they diverge from the default credentials.`, if they diverge from the default credentials.`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
...@@ -53,10 +55,13 @@ if they diverge from the default credentials.`, ...@@ -53,10 +55,13 @@ if they diverge from the default credentials.`,
"username="+username, "username="+username,
"sbi="+cliSbi, "sbi="+cliSbi,
"pnd="+cliPnd, "pnd="+cliPnd,
"name="+deviceName,
) )
}, },
} }
func init() { func init() {
cliCmd.AddCommand(addDeviceCmd) cliCmd.AddCommand(addDeviceCmd)
addDeviceCmd.PersistentFlags().StringVarP(&deviceName, "device-name", "n", "", "Human readable device name.")
} }
...@@ -42,13 +42,13 @@ var getDeviceCmd = &cobra.Command{ ...@@ -42,13 +42,13 @@ var getDeviceCmd = &cobra.Command{
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Short: "gets device information from the controller", Short: "gets device information from the controller",
Long: `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 { RunE: func(cmd *cobra.Command, args []string) error {
return cli.HTTPGet( return cli.HTTPGet(
apiEndpoint, apiEndpoint,
"getDevice", "getDevice",
"uuid="+args[0], "identifier="+args[0],
"sbi="+cliSbi, "sbi="+cliSbi,
"pnd="+cliPnd, "pnd="+cliPnd,
) )
......
...@@ -41,7 +41,7 @@ var initCmd = &cobra.Command{ ...@@ -41,7 +41,7 @@ var initCmd = &cobra.Command{
Use: "init", Use: "init",
Short: "initialise SBI and PND", Short: "initialise SBI and PND",
Long: `Initialise SBI and PND and saves values to config file. Long: `Initialise SBI and PND and saves values to config file.
Same as invoking "gosdn cli" without any arguments`, Same as invoking "gosdn cli" without any arguments`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return cli.HTTPGet(apiEndpoint, "init") return cli.HTTPGet(apiEndpoint, "init")
......
...@@ -42,7 +42,7 @@ var requestCmd = &cobra.Command{ ...@@ -42,7 +42,7 @@ var requestCmd = &cobra.Command{
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Short: "requests a path from a specified device on the controller", Short: "requests a path from a specified device on the controller",
Long: `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.`, The request path is passed as positional argument.`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return cli.HTTPGet( return cli.HTTPGet(
......
...@@ -43,7 +43,7 @@ var requestAllCmd = &cobra.Command{ ...@@ -43,7 +43,7 @@ var requestAllCmd = &cobra.Command{
Short: "requests specified path from all devices on the controller", Short: "requests specified path from all devices on the controller",
Long: `Requests a path from all devices on the controller known by Long: `Requests a path from all devices on the controller known by
the controller. the controller.
The request path is passed as positional argument.`, The request path is passed as positional argument.`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return cli.HTTPGet( return cli.HTTPGet(
......
...@@ -47,7 +47,7 @@ var subscribeCmd = &cobra.Command{ ...@@ -47,7 +47,7 @@ var subscribeCmd = &cobra.Command{
Only 'stream' mode with 'sample' operation supported.`, Only 'stream' mode with 'sample' operation supported.`,
RunE: func(cmd *cobra.Command, args []string) error { 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...)
}, },
} }
......
...@@ -5,6 +5,7 @@ go 1.14 ...@@ -5,6 +5,7 @@ go 1.14
require ( require (
code.fbi.h-da.de/cocsn/yang-models v0.0.7 code.fbi.h-da.de/cocsn/yang-models v0.0.7
github.com/aristanetworks/goarista v0.0.0-20201120222254-94a892eb0c6a 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/golang/protobuf v1.5.0
github.com/google/gnxi v0.0.0-20201221102247-c26672548161 github.com/google/gnxi v0.0.0-20201221102247-c26672548161
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
......
package gosdn package gosdn
import ( 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/forks/goarista/gnmi"
"code.fbi.h-da.de/cocsn/gosdn/nucleus" "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/errors"
"code.fbi.h-da.de/cocsn/gosdn/nucleus/types" "code.fbi.h-da.de/cocsn/gosdn/nucleus/types"
"context"
"fmt"
"github.com/google/uuid" "github.com/google/uuid"
gpb "github.com/openconfig/gnmi/proto/gnmi" gpb "github.com/openconfig/gnmi/proto/gnmi"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io"
"net/http"
"net/url"
"time"
) )
var apiOpmap = map[string]types.Operation{ var apiOpmap = map[string]types.Operation{
...@@ -119,15 +122,26 @@ func httpApi(writer http.ResponseWriter, request *http.Request) { ...@@ -119,15 +122,26 @@ func httpApi(writer http.ResponseWriter, request *http.Request) {
SetNode: httpSbi.SetNode(), SetNode: httpSbi.SetNode(),
Unmarshal: httpSbi.(*nucleus.OpenConfig).Unmarshal(), Unmarshal: httpSbi.(*nucleus.OpenConfig).Unmarshal(),
RespChan: make(chan *gpb.SubscribeResponse), RespChan: make(chan *gpb.SubscribeResponse),
}) },
query.Get("name"),
)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
log.Error(err)
return
}
err = httpPnd.AddDevice(d) err = httpPnd.AddDevice(d)
if err != nil { if err != nil {
writer.WriteHeader(http.StatusInternalServerError) writer.WriteHeader(http.StatusInternalServerError)
log.Error(err) log.Error(err)
return return
} }
writer.WriteHeader(http.StatusCreated) writer.WriteHeader(http.StatusCreated)
fmt.Fprintf(writer, "device added\n") fmt.Fprintf(writer, "device added\n")
fmt.Fprintf(writer, "Name: %s\n", d.Name)
fmt.Fprintf(writer, "UUID: %v\n", d.UUID) fmt.Fprintf(writer, "UUID: %v\n", d.UUID)
case "request": case "request":
err = httpPnd.Request(id, query.Get("path")) err = httpPnd.Request(id, query.Get("path"))
...@@ -156,7 +170,9 @@ func httpApi(writer http.ResponseWriter, request *http.Request) { ...@@ -156,7 +170,9 @@ func httpApi(writer http.ResponseWriter, request *http.Request) {
} }
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
case "getDevice": case "getDevice":
device, err := httpPnd.MarshalDevice(id) deviceIdentifier := query.Get("identifier")
device, err := httpPnd.MarshalDevice(deviceIdentifier)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case *errors.ErrNotFound: case *errors.ErrNotFound:
...@@ -165,10 +181,13 @@ func httpApi(writer http.ResponseWriter, request *http.Request) { ...@@ -165,10 +181,13 @@ func httpApi(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusInternalServerError) writer.WriteHeader(http.StatusInternalServerError)
} }
log.Error(err) log.Error(err)
return return
} }
writer.Header().Set("Content-Type", "application/json") writer.Header().Set("Content-Type", "application/json")
fmt.Fprintf(writer, "%v", device) fmt.Fprintf(writer, "%v", device)
case "getIDs": case "getIDs":
pnds := c.pndc.UUIDs() pnds := c.pndc.UUIDs()
......
...@@ -35,6 +35,7 @@ func testSetupHTTP() { ...@@ -35,6 +35,7 @@ func testSetupHTTP() {
} }
args = "&uuid=" + mdid.String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String() args = "&uuid=" + mdid.String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String()
argsNotFound = "&uuid=" + uuid.New().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 { if err := c.sbic.Add(sbi); err != nil {
log.Fatal(err) log.Fatal(err)
} }
...@@ -119,7 +120,7 @@ func Test_httpApi(t *testing.T) { ...@@ -119,7 +120,7 @@ func Test_httpApi(t *testing.T) {
}, },
{ {
name: "get-device not found", name: "get-device not found",
request: apiEndpoint + "/api?q=getDevice" + argsNotFound, request: apiEndpoint + "/api?q=getDevice" + argsNotFoundGetDevice,
want: &http.Response{StatusCode: http.StatusNotFound}, want: &http.Response{StatusCode: http.StatusNotFound},
wantErr: false, wantErr: false,
}, },
......
...@@ -30,6 +30,7 @@ var httpTestDevice nucleus.Device ...@@ -30,6 +30,7 @@ var httpTestDevice nucleus.Device
var args string var args string
var argsNotFound string var argsNotFound string
var argsNotFoundGetDevice string
var mockContext = mock.MatchedBy(func(ctx context.Context) bool { return true }) var mockContext = mock.MatchedBy(func(ctx context.Context) bool { return true })
......
...@@ -2,6 +2,7 @@ package nucleus ...@@ -2,6 +2,7 @@ package nucleus
import ( import (
"code.fbi.h-da.de/cocsn/gosdn/nucleus/errors" "code.fbi.h-da.de/cocsn/gosdn/nucleus/errors"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/openconfig/ygot/ygot" "github.com/openconfig/ygot/ygot"
) )
...@@ -20,20 +21,28 @@ type Device struct { ...@@ -20,20 +21,28 @@ type Device struct {
// Transport is the device's Transport implementation // Transport is the device's Transport implementation
Transport Transport Transport Transport
// Name is the device's human readable name
Name string
} }
// NewDevice creates a Device // 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 transport Transport
var err error var err error
switch o := opts.(type) {
if name == "" {
name = namesgenerator.GetRandomName(0)
}
switch opts := opts.(type) {
case *GnmiTransportOptions: case *GnmiTransportOptions:
transport, err = NewGnmiTransport(o) transport, err = NewGnmiTransport(opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
default: default:
return nil, &errors.ErrInvalidTransportOptions{Opt: o} return nil, &errors.ErrInvalidTransportOptions{Opt: opts}
} }
return &Device{ return &Device{
...@@ -41,6 +50,7 @@ func NewDevice(sbi SouthboundInterface, opts TransportOptions) (*Device, error) ...@@ -41,6 +50,7 @@ func NewDevice(sbi SouthboundInterface, opts TransportOptions) (*Device, error)
GoStruct: sbi.Schema().Root, GoStruct: sbi.Schema().Root,
SBI: sbi, SBI: sbi,
Transport: transport, Transport: transport,
Name: name,
}, nil }, nil
} }
......
...@@ -16,6 +16,7 @@ func TestDevice_Id(t *testing.T) { ...@@ -16,6 +16,7 @@ func TestDevice_Id(t *testing.T) {
SBI SouthboundInterface SBI SouthboundInterface
Transport Transport Transport Transport
UUID uuid.UUID UUID uuid.UUID
Name string
} }
tests := []struct { tests := []struct {
name string name string
...@@ -37,6 +38,7 @@ func TestDevice_Id(t *testing.T) { ...@@ -37,6 +38,7 @@ func TestDevice_Id(t *testing.T) {
SBI: tt.fields.SBI, SBI: tt.fields.SBI,
Transport: tt.fields.Transport, Transport: tt.fields.Transport,
UUID: tt.fields.UUID, UUID: tt.fields.UUID,
Name: tt.fields.Name,
} }
if got := d.ID(); !reflect.DeepEqual(got, tt.want) { if got := d.ID(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ID() = %v, want %v", got, tt.want) t.Errorf("ID() = %v, want %v", got, tt.want)
...@@ -50,6 +52,7 @@ func TestNewDevice(t *testing.T) { ...@@ -50,6 +52,7 @@ func TestNewDevice(t *testing.T) {
type args struct { type args struct {
sbi SouthboundInterface sbi SouthboundInterface
opts TransportOptions opts TransportOptions
name string
} }
tests := []struct { tests := []struct {
name string name string
...@@ -68,6 +71,7 @@ func TestNewDevice(t *testing.T) { ...@@ -68,6 +71,7 @@ func TestNewDevice(t *testing.T) {
Password: "test", Password: "test",
}, },
}, },
name: "MyDevice",
}, },
want: &Device{ want: &Device{
GoStruct: &openconfig.Device{}, GoStruct: &openconfig.Device{},
...@@ -82,12 +86,13 @@ func TestNewDevice(t *testing.T) { ...@@ -82,12 +86,13 @@ func TestNewDevice(t *testing.T) {
}, },
}, },
}, },
Name: "MyDevice",
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { if err != nil {
t.Error(err) t.Error(err)
} }
......
...@@ -41,6 +41,15 @@ func (e *ErrAlreadyExists) Error() string { ...@@ -41,6 +41,15 @@ func (e *ErrAlreadyExists) Error() string {
return fmt.Sprintf("%v already exists", e.Item) 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 // ErrInvalidTypeAssertion implements the Error interface and is called if the
// type of a storable item does not correspond to the expected type. // type of a storable item does not correspond to the expected type.
type ErrInvalidTypeAssertion struct { type ErrInvalidTypeAssertion struct {
......
...@@ -129,7 +129,7 @@ func newPnd() pndImplementation { ...@@ -129,7 +129,7 @@ func newPnd() pndImplementation {
name: "default", name: "default",
description: "default test pnd", description: "default test pnd",
sbic: SbiStore{store{}}, sbic: SbiStore{store{}},
devices: DeviceStore{store{}}, devices: NewDeviceStore(),
pendingChanges: ChangeStore{store{}}, pendingChanges: ChangeStore{store{}},
committedChanges: ChangeStore{store{}}, committedChanges: ChangeStore{store{}},
confirmedChanges: ChangeStore{store{}}, confirmedChanges: ChangeStore{store{}},
......
...@@ -23,7 +23,7 @@ type PrincipalNetworkDomain interface { ...@@ -23,7 +23,7 @@ type PrincipalNetworkDomain interface {
AddSbi(interface{}) error AddSbi(interface{}) error
RemoveSbi(uuid.UUID) error RemoveSbi(uuid.UUID) error
AddDevice(interface{}) error AddDevice(interface{}) error
GetDevice(uuid uuid.UUID) (ygot.GoStruct, error) GetDevice(identifier string) (*Device, error)
RemoveDevice(uuid.UUID) error RemoveDevice(uuid.UUID) error
Devices() []uuid.UUID Devices() []uuid.UUID
ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error
...@@ -31,7 +31,7 @@ type PrincipalNetworkDomain interface { ...@@ -31,7 +31,7 @@ type PrincipalNetworkDomain interface {
RequestAll(string) error RequestAll(string) error
GetName() string GetName() string
GetDescription() string GetDescription() string
MarshalDevice(uuid.UUID) (string, error) MarshalDevice(string) (string, error)
ContainsDevice(uuid.UUID) bool ContainsDevice(uuid.UUID) bool
GetSBIs() interface{} GetSBIs() interface{}
ID() uuid.UUID ID() uuid.UUID
...@@ -47,7 +47,7 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr ...@@ -47,7 +47,7 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr
name: name, name: name,
description: description, description: description,
sbic: SbiStore{store{}}, sbic: SbiStore{store{}},
devices: DeviceStore{store{}}, devices: NewDeviceStore(),
pendingChanges: ChangeStore{store{}}, pendingChanges: ChangeStore{store{}},
committedChanges: ChangeStore{store{}}, committedChanges: ChangeStore{store{}},
confirmedChanges: ChangeStore{store{}}, confirmedChanges: ChangeStore{store{}},
...@@ -64,7 +64,7 @@ type pndImplementation struct { ...@@ -64,7 +64,7 @@ type pndImplementation struct {
name string name string
description string description string
sbic SbiStore sbic SbiStore
devices DeviceStore devices *DeviceStore
pendingChanges ChangeStore pendingChanges ChangeStore
committedChanges ChangeStore committedChanges ChangeStore
confirmedChanges ChangeStore confirmedChanges ChangeStore
...@@ -172,7 +172,7 @@ func (pnd *pndImplementation) RemoveSbi(id uuid.UUID) error { ...@@ -172,7 +172,7 @@ func (pnd *pndImplementation) RemoveSbi(id uuid.UUID) error {
return pnd.removeSbi(id) 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 { func (pnd *pndImplementation) AddDevice(device interface{}) error {
d, ok := device.(*Device) d, ok := device.(*Device)
if !ok { if !ok {
...@@ -184,12 +184,20 @@ func (pnd *pndImplementation) AddDevice(device interface{}) error { ...@@ -184,12 +184,20 @@ func (pnd *pndImplementation) AddDevice(device interface{}) error {
return pnd.addDevice(d) return pnd.addDevice(d)
} }
func (pnd *pndImplementation) GetDevice(uuid uuid.UUID) (ygot.GoStruct, error) { func (pnd *pndImplementation) GetDevice(identifier string) (*Device, error) {
d, err := pnd.devices.Get(uuid) d, err := pnd.devices.Get(FromString(identifier))
if err != nil { if err != nil {
return nil, err 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 // RemoveDevice removes a device from the PND
...@@ -212,36 +220,40 @@ func (pnd *pndImplementation) removeSbi(id uuid.UUID) error { ...@@ -212,36 +220,40 @@ func (pnd *pndImplementation) removeSbi(id uuid.UUID) error {
} }
func (pnd *pndImplementation) addDevice(device *Device) 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 nil
return pnd.devices.Get(id)
} }
func (pnd *pndImplementation) removeDevice(id uuid.UUID) error { func (pnd *pndImplementation) removeDevice(id uuid.UUID) error {
return pnd.devices.Delete(id) return pnd.devices.Delete(id)
} }
func (pnd *pndImplementation) MarshalDevice(uuid uuid.UUID) (string, error) { func (pnd *pndImplementation) MarshalDevice(identifier string) (string, error) {
d, err := pnd.getDevice(uuid) foundDevice, err := pnd.devices.Get(FromString(identifier))
if err != nil { if err != nil {
return "", err return "", err
} }
jsonTree, err := json.MarshalIndent(d.GoStruct, "", "\t")
jsonTree, err := json.MarshalIndent(foundDevice.GoStruct, "", "\t")
if err != nil { if err != nil {
return "", err return "", err
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"pnd": pnd.id, "pnd": pnd.id,
"device": uuid, "Identifier": identifier,
"Name": foundDevice.Name,
}).Info("marshalled device") }).Info("marshalled device")
return string(jsonTree), nil return string(jsonTree), nil
} }
// Request sends a get request to a specific device // Request sends a get request to a specific device
func (pnd *pndImplementation) Request(uuid uuid.UUID, path string) error { 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 { if err != nil {
return err return err
} }
...@@ -273,12 +285,15 @@ func (pnd *pndImplementation) RequestAll(path string) error { ...@@ -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 // 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 { 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 { if err != nil {
return err return err
} }
cpy, err := ygot.DeepCopy(d.GoStruct) cpy, err := ygot.DeepCopy(d.GoStruct)
ygot.BuildEmptyTree(cpy) ygot.BuildEmptyTree(cpy)
if err != nil {
return err
}
p, err := ygot.StringToStructuredPath(path) p, err := ygot.StringToStructuredPath(path)
if err != nil { if err != nil {
......
...@@ -224,7 +224,7 @@ func Test_pndImplementation_ContainsDevice(t *testing.T) { ...@@ -224,7 +224,7 @@ func Test_pndImplementation_ContainsDevice(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
pnd := newPnd() pnd := newPnd()
if tt.name != "fails empty" { 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) t.Error(err)
} }
} }
...@@ -243,7 +243,7 @@ func Test_pndImplementation_Destroy(t *testing.T) { ...@@ -243,7 +243,7 @@ func Test_pndImplementation_Destroy(t *testing.T) {
name string name string
description string description string
sbi SbiStore sbi SbiStore
devices DeviceStore devices *DeviceStore
} }
tests := []struct { tests := []struct {
name string name string
...@@ -347,7 +347,7 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) { ...@@ -347,7 +347,7 @@ func Test_pndImplementation_MarshalDevice(t *testing.T) {
if err := pnd.addDevice(d); err != nil { if err := pnd.addDevice(d); err != nil {
t.Error(err) t.Error(err)
} }
got, err := pnd.MarshalDevice(tt.args.uuid) got, err := pnd.MarshalDevice(tt.args.uuid.String())
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("MarshalDevice() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("MarshalDevice() error = %v, wantErr %v", err, tt.wantErr)
return return
...@@ -413,7 +413,7 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) { ...@@ -413,7 +413,7 @@ func Test_pndImplementation_RemoveSbi(t *testing.T) {
name: "test-remove-sbi", name: "test-remove-sbi",
description: "test-remove-sbi", description: "test-remove-sbi",
sbic: SbiStore{store{}}, sbic: SbiStore{store{}},
devices: DeviceStore{store{}}, devices: NewDeviceStore(),
id: defaultPndID, id: defaultPndID,
} }
if tt.name != "fails empty" { if tt.name != "fails empty" {
...@@ -661,7 +661,7 @@ func Test_pndImplementation_ChangeOND(t *testing.T) { ...@@ -661,7 +661,7 @@ func Test_pndImplementation_ChangeOND(t *testing.T) {
func Test_pndImplementation_GetDevice(t *testing.T) { func Test_pndImplementation_GetDevice(t *testing.T) {
pnd := newPnd() pnd := newPnd()
sbi := NewSBI(types.Openconfig) sbi := NewSBI(types.Openconfig)
d, err := NewDevice(sbi, &GnmiTransportOptions{}) d, err := NewDevice(sbi, &GnmiTransportOptions{}, "")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
...@@ -694,14 +694,68 @@ func Test_pndImplementation_GetDevice(t *testing.T) { ...@@ -694,14 +694,68 @@ func Test_pndImplementation_GetDevice(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { if (err != nil) != tt.wantErr {
t.Errorf("GetDevice() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetDevice() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if !reflect.DeepEqual(got, tt.want) { if foundDevice != nil {
t.Errorf("GetDevice() got = %v, want %v", got, tt.want) 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)
}
}
}) })
} }
} }
......
package nucleus package nucleus
import ( import (
"fmt"
"reflect" "reflect"
"sync" "sync"
...@@ -43,7 +44,7 @@ func NewSbiStore() *SbiStore { ...@@ -43,7 +44,7 @@ func NewSbiStore() *SbiStore {
// NewDeviceStore returns a DeviceStore // NewDeviceStore returns a DeviceStore
func NewDeviceStore() *DeviceStore { func NewDeviceStore() *DeviceStore {
return &DeviceStore{store{}} return &DeviceStore{store: store{}, deviceNameToUUIDLookup: make(map[string]uuid.UUID)}
} }
// NewChangeStore returns a ChangeStore // NewChangeStore returns a ChangeStore
...@@ -170,13 +171,33 @@ func (s PndStore) Get(id uuid.UUID) (PrincipalNetworkDomain, error) { ...@@ -170,13 +171,33 @@ func (s PndStore) Get(id uuid.UUID) (PrincipalNetworkDomain, error) {
// DeviceStore is used to store Devices // DeviceStore is used to store Devices
type DeviceStore struct { type DeviceStore struct {
deviceNameToUUIDLookup map[string]uuid.UUID
store store
} }
// Get takes a Device's UUID and returns the Device. If the requested // Get takes a Device's UUID and returns the Device. If the requested
// Device does not exist an error is returned. // Device does not exist an error is returned.
func (s DeviceStore) Get(id uuid.UUID) (*Device, error) { func (s DeviceStore) Get(id uuid.UUID, parseErrors ...error) (*Device, error) {
item, err := s.store.Get(id) 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -189,10 +210,72 @@ func (s DeviceStore) Get(id uuid.UUID) (*Device, error) { ...@@ -189,10 +210,72 @@ func (s DeviceStore) Get(id uuid.UUID) (*Device, error) {
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"uuid": id, "uuid": id,
"name": device.Name,
}).Debug("device was accessed") }).Debug("device was accessed")
return device, nil 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 // ChangeStore is used to store Changes
type ChangeStore struct { type ChangeStore struct {
store store
......
...@@ -423,9 +423,11 @@ func Test_deviceStore_get(t *testing.T) { ...@@ -423,9 +423,11 @@ func Test_deviceStore_get(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := DeviceStore{ 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 { if (err != nil) != tt.wantErr {
t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("get() error = %v, wantErr %v", err, tt.wantErr)
return return
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment