Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • danet/gosdn
1 result
Select Git revision
Show changes
Commits on Source (4)
Showing with 266 additions and 61 deletions
run:
timeout: 5m
timeout: 10m
issues-exit-code: 1
# directories to be ignored by linters
skip-dirs:
......
......@@ -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
}
......@@ -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.")
}
......@@ -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,
)
......
......@@ -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")
......
......@@ -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(
......
......@@ -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(
......
......@@ -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...)
},
}
......
......@@ -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
......
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()
......
......@@ -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,
},
......
......@@ -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 })
......
......@@ -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
}
......
......@@ -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)
}
......
......@@ -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 {
......
......@@ -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{}},
......
......@@ -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 {
......
......@@ -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)
}
}
})
}
}
......
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
......