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

Tests for change.go now include error channel

parent e0249d0f
No related branches found
No related tags found
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,!147Commit-Confirm Mechanic for PND,!138Develop
...@@ -27,6 +27,7 @@ integration-test: ...@@ -27,6 +27,7 @@ integration-test:
allow_failure: true allow_failure: true
variables: variables:
GOSDN_LOG: "nolog" GOSDN_LOG: "nolog"
GOSDN_CHANGE_TIMEOUT: "100ms"
rules: rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH
- if: $CI_NIGHTLY - if: $CI_NIGHTLY
......
...@@ -64,27 +64,48 @@ func (_m *PrincipalNetworkDomain) ChangeOND(_a0 uuid.UUID, operation interface{} ...@@ -64,27 +64,48 @@ func (_m *PrincipalNetworkDomain) ChangeOND(_a0 uuid.UUID, operation interface{}
return r0 return r0
} }
// Committed provides a mock function with given fields: _a0 // Commit provides a mock function with given fields: _a0
func (_m *PrincipalNetworkDomain) Committed(_a0 uuid.UUID) (interface{}, error) { func (_m *PrincipalNetworkDomain) Commit(_a0 uuid.UUID) error {
ret := _m.Called(_a0) ret := _m.Called(_a0)
var r0 interface{} var r0 error
if rf, ok := ret.Get(0).(func(uuid.UUID) interface{}); ok { if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
r0 = rf(_a0) r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Committed provides a mock function with given fields:
func (_m *PrincipalNetworkDomain) Committed() []uuid.UUID {
ret := _m.Called()
var r0 []uuid.UUID
if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
r0 = rf()
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{}) r0 = ret.Get(0).([]uuid.UUID)
} }
} }
var r1 error return r0
if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok { }
r1 = rf(_a0)
// Confirm provides a mock function with given fields: _a0
func (_m *PrincipalNetworkDomain) Confirm(_a0 uuid.UUID) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
r0 = rf(_a0)
} else { } else {
r1 = ret.Error(1) r0 = ret.Error(0)
} }
return r0, r1 return r0
} }
// ContainsDevice provides a mock function with given fields: _a0 // ContainsDevice provides a mock function with given fields: _a0
...@@ -115,6 +136,22 @@ func (_m *PrincipalNetworkDomain) Destroy() error { ...@@ -115,6 +136,22 @@ func (_m *PrincipalNetworkDomain) Destroy() error {
return r0 return r0
} }
// Devices provides a mock function with given fields:
func (_m *PrincipalNetworkDomain) Devices() []uuid.UUID {
ret := _m.Called()
var r0 []uuid.UUID
if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]uuid.UUID)
}
}
return r0
}
// GetDescription provides a mock function with given fields: // GetDescription provides a mock function with given fields:
func (_m *PrincipalNetworkDomain) GetDescription() string { func (_m *PrincipalNetworkDomain) GetDescription() string {
ret := _m.Called() ret := _m.Called()
...@@ -198,38 +235,6 @@ func (_m *PrincipalNetworkDomain) ID() uuid.UUID { ...@@ -198,38 +235,6 @@ func (_m *PrincipalNetworkDomain) ID() uuid.UUID {
return r0 return r0
} }
// ListCommitted provides a mock function with given fields:
func (_m *PrincipalNetworkDomain) ListCommitted() []uuid.UUID {
ret := _m.Called()
var r0 []uuid.UUID
if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]uuid.UUID)
}
}
return r0
}
// ListPending provides a mock function with given fields:
func (_m *PrincipalNetworkDomain) ListPending() []uuid.UUID {
ret := _m.Called()
var r0 []uuid.UUID
if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]uuid.UUID)
}
}
return r0
}
// MarshalDevice provides a mock function with given fields: _a0 // MarshalDevice provides a mock function with given fields: _a0
func (_m *PrincipalNetworkDomain) MarshalDevice(_a0 uuid.UUID) (string, error) { func (_m *PrincipalNetworkDomain) MarshalDevice(_a0 uuid.UUID) (string, error) {
ret := _m.Called(_a0) ret := _m.Called(_a0)
...@@ -251,27 +256,20 @@ func (_m *PrincipalNetworkDomain) MarshalDevice(_a0 uuid.UUID) (string, error) { ...@@ -251,27 +256,20 @@ func (_m *PrincipalNetworkDomain) MarshalDevice(_a0 uuid.UUID) (string, error) {
return r0, r1 return r0, r1
} }
// Pending provides a mock function with given fields: _a0 // Pending provides a mock function with given fields:
func (_m *PrincipalNetworkDomain) Pending(_a0 uuid.UUID) (interface{}, error) { func (_m *PrincipalNetworkDomain) Pending() []uuid.UUID {
ret := _m.Called(_a0) ret := _m.Called()
var r0 interface{} var r0 []uuid.UUID
if rf, ok := ret.Get(0).(func(uuid.UUID) interface{}); ok { if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
r0 = rf(_a0) r0 = rf()
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{}) r0 = ret.Get(0).([]uuid.UUID)
} }
} }
var r1 error return r0
if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
} }
// RemoveDevice provides a mock function with given fields: _a0 // RemoveDevice provides a mock function with given fields: _a0
......
...@@ -2,7 +2,6 @@ package nucleus ...@@ -2,7 +2,6 @@ package nucleus
import ( import (
"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/pnd"
"context" "context"
"fmt" "fmt"
"github.com/google/uuid" "github.com/google/uuid"
...@@ -179,7 +178,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) { ...@@ -179,7 +178,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) {
handleServerError(writer, err) handleServerError(writer, err)
return return
} }
writeIDs(writer, "Devices", p.(*pndImplementation).devices.UUIDs()) writeIDs(writer, "Devices", p.Devices())
} }
case "init": case "init":
writeIDs(writer, "PNDs", c.pndc.UUIDs()) writeIDs(writer, "PNDs", c.pndc.UUIDs())
...@@ -197,10 +196,10 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) { ...@@ -197,10 +196,10 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) {
} }
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
case "change-list": case "change-list":
changes := pnd.ListCommitted() changes := pnd.Committed()
writeIDs(writer, "Tentative changes", changes) writeIDs(writer, "Tentative changes", changes)
case "change-list-pending": case "change-list-pending":
changes := pnd.ListPending() changes := pnd.Pending()
writeIDs(writer, "Pending changes", changes) writeIDs(writer, "Pending changes", changes)
case "change-commit": case "change-commit":
cuid, err := uuid.Parse(query.Get("cuid")) cuid, err := uuid.Parse(query.Get("cuid"))
...@@ -208,13 +207,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) { ...@@ -208,13 +207,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) {
handleServerError(writer, err) handleServerError(writer, err)
return return
} }
change, err := pnd.Pending(cuid) if err := pnd.Commit(cuid); err != nil {
if err != nil {
handleServerError(writer, err)
return
}
err = change.(*Change).Commit()
if err != nil {
handleServerError(writer, err) handleServerError(writer, err)
return return
} }
...@@ -225,13 +218,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) { ...@@ -225,13 +218,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) {
handleServerError(writer, err) handleServerError(writer, err)
return return
} }
change, err := pnd.Committed(cuid) if err := pnd.Confirm(cuid); err != nil {
if err != nil {
handleServerError(writer, err)
return
}
err = change.(*Change).Confirm()
if err != nil {
handleServerError(writer, err) handleServerError(writer, err)
return return
} }
......
...@@ -150,19 +150,21 @@ func Test_httpApi(t *testing.T) { ...@@ -150,19 +150,21 @@ func Test_httpApi(t *testing.T) {
}, },
{ {
name: "change commit", name: "change commit",
request: apiEndpoint + "/api?q=change-commit" + args + "&cuid=" + uuid.New().String(), request: apiEndpoint + "/api?q=change-commit" + args + "&cuid=" + cuid.String(),
want: &http.Response{StatusCode: http.StatusOK}, // TODO: Mock Change for testing
want: &http.Response{StatusCode: http.StatusInternalServerError},
wantErr: false, wantErr: false,
}, },
{ {
name: "change confirm", name: "change confirm",
request: apiEndpoint + "/api?q=change-confirm" + args + "&cuid=" + uuid.New().String(), request: apiEndpoint + "/api?q=change-confirm" + args + "&cuid=" + cuid.String(),
want: &http.Response{StatusCode: http.StatusOK}, // TODO: Mock Change for testing
want: &http.Response{StatusCode: http.StatusInternalServerError},
wantErr: false, wantErr: false,
}, },
{ {
name: "bad request", name: "bad request",
request: apiEndpoint + "/api?q=bad-request", request: apiEndpoint + "/api?q=bad-request" + args,
want: &http.Response{StatusCode: http.StatusBadRequest}, want: &http.Response{StatusCode: http.StatusBadRequest},
wantErr: false, wantErr: false,
}, },
......
...@@ -25,6 +25,7 @@ var defaultPndID uuid.UUID ...@@ -25,6 +25,7 @@ var defaultPndID uuid.UUID
var ocUUID uuid.UUID var ocUUID uuid.UUID
var iid uuid.UUID var iid uuid.UUID
var altIid uuid.UUID var altIid uuid.UUID
var cuid uuid.UUID
var sbi SouthboundInterface var sbi SouthboundInterface
var pnd PrincipalNetworkDomain var pnd PrincipalNetworkDomain
...@@ -114,6 +115,7 @@ func readTestUUIDs() { ...@@ -114,6 +115,7 @@ func readTestUUIDs() {
ocUUID, err = uuid.Parse("5e252b70-38f2-4c99-a0bf-1b16af4d7e67") ocUUID, err = uuid.Parse("5e252b70-38f2-4c99-a0bf-1b16af4d7e67")
iid, err = uuid.Parse("8495a8ac-a1e8-418e-b787-10f5878b2690") iid, err = uuid.Parse("8495a8ac-a1e8-418e-b787-10f5878b2690")
altIid, err = uuid.Parse("edc5de93-2d15-4586-b2a7-fb1bc770986b") altIid, err = uuid.Parse("edc5de93-2d15-4586-b2a7-fb1bc770986b")
cuid, err = uuid.Parse("3e8219b0-e926-400d-8660-217f2a25a7c6")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
...@@ -139,5 +141,6 @@ func newPnd() pndImplementation { ...@@ -139,5 +141,6 @@ func newPnd() pndImplementation {
committedChanges: changeStore{store{}}, committedChanges: changeStore{store{}},
confirmedChanges: changeStore{store{}}, confirmedChanges: changeStore{store{}},
id: defaultPndID, id: defaultPndID,
errChans: make(map[uuid.UUID]chan error),
} }
} }
...@@ -14,23 +14,23 @@ import ( ...@@ -14,23 +14,23 @@ import (
var changeTimeout time.Duration var changeTimeout time.Duration
func init() { func init() {
timeout, err := time.ParseDuration(os.Getenv("GOSDN_CHANGE_TIMEOUT")) var err error
if err != nil { e := os.Getenv("GOSDN_CHANGE_TIMEOUT")
log.Fatal(err) if e != "" {
} changeTimeout, err = time.ParseDuration(e)
if timeout != time.Duration(0) { if err != nil {
changeTimeout = timeout log.Fatal(err)
}
} else { } else {
var err error
changeTimeout, err = time.ParseDuration("10m") changeTimeout, err = time.ParseDuration("10m")
if err != nil { if err != nil {
log.Fatal() log.Fatal(err)
} }
} }
log.Debugf("change timeout set to %v", changeTimeout) log.Debugf("change timeout set to %v", changeTimeout)
} }
func NewChange(device uuid.UUID, currentState ygot.GoStruct, change ygot.GoStruct, callback func(ygot.GoStruct, ygot.GoStruct) error) *Change { func NewChange(device uuid.UUID, currentState ygot.GoStruct, change ygot.GoStruct, callback func(ygot.GoStruct, ygot.GoStruct) error, errChan chan error) *Change {
return &Change{ return &Change{
cuid: uuid.New(), cuid: uuid.New(),
duid: device, duid: device,
...@@ -40,6 +40,8 @@ func NewChange(device uuid.UUID, currentState ygot.GoStruct, change ygot.GoStruc ...@@ -40,6 +40,8 @@ func NewChange(device uuid.UUID, currentState ygot.GoStruct, change ygot.GoStruc
committed: false, committed: false,
confirmed: false, confirmed: false,
callback: callback, callback: callback,
errChan: errChan,
Done: make(chan int),
} }
} }
...@@ -55,9 +57,13 @@ type Change struct { ...@@ -55,9 +57,13 @@ type Change struct {
intendedState ygot.GoStruct intendedState ygot.GoStruct
committed bool committed bool
confirmed bool confirmed bool
inconsistent bool
callback func(ygot.GoStruct, ygot.GoStruct) error callback func(ygot.GoStruct, ygot.GoStruct) error
lock sync.RWMutex lock sync.RWMutex
cancelFunc context.CancelFunc cancelFunc context.CancelFunc
errChan chan error
// TODO: Move nucleus.pndImplementation and Change to same package and unexport
Done chan int
} }
func (c *Change) ID() uuid.UUID { func (c *Change) ID() uuid.UUID {
...@@ -65,10 +71,10 @@ func (c *Change) ID() uuid.UUID { ...@@ -65,10 +71,10 @@ func (c *Change) ID() uuid.UUID {
} }
func (c *Change) Commit() error { func (c *Change) Commit() error {
c.committed = true
if err := c.callback(c.intendedState, c.previousState); err != nil { if err := c.callback(c.intendedState, c.previousState); err != nil {
return err return err
} }
c.committed = true
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"change uuid": c.cuid, "change uuid": c.cuid,
"device uuid": c.duid, "device uuid": c.duid,
...@@ -87,14 +93,7 @@ func (c *Change) rollbackHandler(ctx context.Context) { ...@@ -87,14 +93,7 @@ func (c *Change) rollbackHandler(ctx context.Context) {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
if !c.confirmed { if !c.confirmed {
err := c.callback(c.previousState, c.intendedState) c.errChan <- c.callback(c.previousState, c.intendedState)
if err != nil {
log.WithFields(log.Fields{
"change uuid": c.cuid,
"device uuid": c.duid,
"error": err,
}).Error("rollback error")
}
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"change uuid": c.cuid, "change uuid": c.cuid,
"device uuid": c.duid, "device uuid": c.duid,
...@@ -114,6 +113,9 @@ func (c *Change) Confirm() error { ...@@ -114,6 +113,9 @@ func (c *Change) Confirm() error {
defer c.lock.Unlock() defer c.lock.Unlock()
c.confirmed = true c.confirmed = true
c.cancelFunc() c.cancelFunc()
close(c.errChan)
c.Done <- 0
close(c.Done)
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"change uuid": c.cuid, "change uuid": c.cuid,
"device uuid": c.duid, "device uuid": c.duid,
......
...@@ -2,10 +2,10 @@ package pnd ...@@ -2,10 +2,10 @@ package pnd
import ( import (
"context" "context"
"errors"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/openconfig/ygot/exampleoc" "github.com/openconfig/ygot/exampleoc"
"github.com/openconfig/ygot/ygot" "github.com/openconfig/ygot/ygot"
"os"
"reflect" "reflect"
"sync" "sync"
"testing" "testing"
...@@ -53,11 +53,7 @@ func TestChange_CommitRollback(t *testing.T) { ...@@ -53,11 +53,7 @@ func TestChange_CommitRollback(t *testing.T) {
if err := c.Commit(); (err != nil) != wantErr { if err := c.Commit(); (err != nil) != wantErr {
t.Errorf("Commit() error = %v, wantErr %v", err, wantErr) t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
} }
timeout, err := time.ParseDuration(os.Getenv("GOSDN_CHANGE_TIMEOUT")) time.Sleep(changeTimeout)
if err != nil {
t.Error(err)
}
time.Sleep(timeout)
}() }()
got := <-callback got := <-callback
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
...@@ -66,6 +62,66 @@ func TestChange_CommitRollback(t *testing.T) { ...@@ -66,6 +62,66 @@ func TestChange_CommitRollback(t *testing.T) {
close(callback) close(callback)
} }
func TestChange_CommitRollbackError(t *testing.T) {
wantErr := false
want := errors.New("this is an expected error")
c := &Change{
cuid: changeUUID,
duid: did,
timestamp: time.Now(),
previousState: rollbackDevice,
intendedState: commitDevice,
callback: func(first ygot.GoStruct, second ygot.GoStruct) error {
hostname := *first.(*exampleoc.Device).System.Hostname
t.Logf("hostname: %v", hostname)
switch hostname {
case rollback:
return errors.New("this is an expected error")
}
return nil
},
lock: sync.RWMutex{},
errChan: make(chan error),
}
go func() {
time.Sleep(time.Millisecond * 10)
if err := c.Commit(); (err != nil) != wantErr {
t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
}
time.Sleep(changeTimeout)
}()
got := <-c.errChan
if !reflect.DeepEqual(got, want) {
t.Errorf("Commit() = %v, want %v", got, want)
}
close(c.errChan)
}
func TestChange_CommitError(t *testing.T) {
wantErr := true
c := &Change{
cuid: changeUUID,
duid: did,
timestamp: time.Now(),
previousState: rollbackDevice,
intendedState: commitDevice,
callback: func(first ygot.GoStruct, second ygot.GoStruct) error {
return errors.New("this is an expected error")
},
lock: sync.RWMutex{},
}
go func() {
time.Sleep(time.Millisecond * 10)
if err := c.Commit(); (err != nil) != wantErr {
t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
}
}()
got := c.committed
if !reflect.DeepEqual(got, false) {
t.Errorf("Commit() = %v, want %v", got, false)
}
}
func TestChange_Commit(t *testing.T) { func TestChange_Commit(t *testing.T) {
wantErr := false wantErr := false
want := commit want := commit
...@@ -83,7 +139,9 @@ func TestChange_Commit(t *testing.T) { ...@@ -83,7 +139,9 @@ func TestChange_Commit(t *testing.T) {
callback <- hostname callback <- hostname
return nil return nil
}, },
lock: sync.RWMutex{}, lock: sync.RWMutex{},
errChan: make(chan error),
Done: make(chan int),
} }
go func() { go func() {
time.Sleep(time.Millisecond * 10) time.Sleep(time.Millisecond * 10)
...@@ -133,8 +191,7 @@ func TestChange_Confirm(t *testing.T) { ...@@ -133,8 +191,7 @@ func TestChange_Confirm(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) {
c := &Change{ c := &Change{
cuid: tt.fields.cuid, committed: tt.fields.committed,
duid: tt.fields.duid,
timestamp: tt.fields.timestamp, timestamp: tt.fields.timestamp,
previousState: &exampleoc.Device{ previousState: &exampleoc.Device{
System: &exampleoc.System{ System: &exampleoc.System{
...@@ -146,10 +203,10 @@ func TestChange_Confirm(t *testing.T) { ...@@ -146,10 +203,10 @@ func TestChange_Confirm(t *testing.T) {
Hostname: &commit, Hostname: &commit,
}, },
}, },
callback: tt.fields.callback,
committed: tt.fields.committed,
cancelFunc: cancel, cancelFunc: cancel,
lock: sync.RWMutex{}, lock: sync.RWMutex{},
errChan: make(chan error),
Done: make(chan int, 1),
} }
if err := c.Confirm(); (err != nil) != tt.wantErr { if err := c.Confirm(); (err != nil) != tt.wantErr {
t.Errorf("Confirm() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("Confirm() error = %v, wantErr %v", err, tt.wantErr)
......
...@@ -20,8 +20,9 @@ type PrincipalNetworkDomain interface { ...@@ -20,8 +20,9 @@ type PrincipalNetworkDomain interface {
RemoveSbi(uuid.UUID) error RemoveSbi(uuid.UUID) error
AddDevice(interface{}) error AddDevice(interface{}) error
GetDevice(uuid uuid.UUID) (ygot.GoStruct, error) GetDevice(uuid uuid.UUID) (ygot.GoStruct, error)
ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error
RemoveDevice(uuid.UUID) error RemoveDevice(uuid.UUID) error
Devices() []uuid.UUID
ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error
Request(uuid.UUID, string) error Request(uuid.UUID, string) error
RequestAll(string) error RequestAll(string) error
GetName() string GetName() string
...@@ -30,21 +31,10 @@ type PrincipalNetworkDomain interface { ...@@ -30,21 +31,10 @@ type PrincipalNetworkDomain interface {
ContainsDevice(uuid.UUID) bool ContainsDevice(uuid.UUID) bool
GetSBIs() interface{} GetSBIs() interface{}
ID() uuid.UUID ID() uuid.UUID
ListPending() []uuid.UUID Pending() []uuid.UUID
Pending(uuid.UUID) (interface{}, error) Committed() []uuid.UUID
ListCommitted() []uuid.UUID Commit(uuid.UUID) error
Committed(uuid.UUID) (interface{}, error) Confirm(uuid.UUID) error
}
type pndImplementation struct {
name string
description string
sbic sbiStore
devices deviceStore
pendingChanges changeStore
committedChanges changeStore
confirmedChanges changeStore
id uuid.UUID
} }
// NewPND creates a Principle Network Domain // NewPND creates a Principle Network Domain
...@@ -58,6 +48,7 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr ...@@ -58,6 +48,7 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr
committedChanges: changeStore{store{}}, committedChanges: changeStore{store{}},
confirmedChanges: changeStore{store{}}, confirmedChanges: changeStore{store{}},
id: id, id: id,
errChans: make(map[uuid.UUID]chan error),
} }
if err := pnd.sbic.add(sbi); err != nil { if err := pnd.sbic.add(sbi); err != nil {
return nil, &ErrAlreadyExists{item: sbi} return nil, &ErrAlreadyExists{item: sbi}
...@@ -65,26 +56,72 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr ...@@ -65,26 +56,72 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr
return pnd, nil return pnd, nil
} }
func (pnd *pndImplementation) ListPending() []uuid.UUID { type pndImplementation struct {
name string
description string
sbic sbiStore
devices deviceStore
pendingChanges changeStore
committedChanges changeStore
confirmedChanges changeStore
id uuid.UUID
errChans map[uuid.UUID]chan error
}
func (pnd *pndImplementation) Pending() []uuid.UUID {
return pnd.pendingChanges.UUIDs() return pnd.pendingChanges.UUIDs()
} }
func (pnd *pndImplementation) ListCommitted() []uuid.UUID { func (pnd *pndImplementation) Committed() []uuid.UUID {
return pnd.committedChanges.UUIDs() return pnd.committedChanges.UUIDs()
} }
func (pnd *pndImplementation)Pending(id uuid.UUID) (interface{}, error) { func (pnd *pndImplementation) Commit(u uuid.UUID) error {
return pnd.pendingChanges.get(id) change, err := pnd.pendingChanges.get(u)
if err != nil {
return err
}
if err := change.Commit(); err != nil {
return err
}
go func() {
for {
select {
case err := <-pnd.errChans[u]:
handleRollbackError(change.ID(), err)
case <-change.Done:
}
}
}()
if err := pnd.committedChanges.add(change); err != nil {
return err
}
return pnd.pendingChanges.delete(u)
} }
func (pnd *pndImplementation)Committed(id uuid.UUID) (interface{}, error) { func (pnd *pndImplementation) Confirm(u uuid.UUID) error {
return pnd.committedChanges.get(id) change, err := pnd.committedChanges.get(u)
if err != nil {
return err
}
if err := change.Confirm(); err != nil {
return err
}
if err := pnd.confirmedChanges.add(change); err != nil {
return err
}
close(pnd.errChans[u])
return pnd.committedChanges.delete(u)
} }
func (pnd *pndImplementation) ID() uuid.UUID { func (pnd *pndImplementation) ID() uuid.UUID {
return pnd.id return pnd.id
} }
func (pnd *pndImplementation) Devices() []uuid.UUID {
return pnd.devices.UUIDs()
}
// GetName returns the name of the PND // GetName returns the name of the PND
func (pnd *pndImplementation) GetName() string { func (pnd *pndImplementation) GetName() string {
return pnd.name return pnd.name
...@@ -243,6 +280,13 @@ func (pnd *pndImplementation) ChangeOND(uuid uuid.UUID, operation interface{}, p ...@@ -243,6 +280,13 @@ func (pnd *pndImplementation) ChangeOND(uuid uuid.UUID, operation interface{}, p
return err return err
} }
if len(value) > 1 {
return &ErrInvalidParameters{
f: pnd.ChangeOND,
r: value,
}
}
switch operation { switch operation {
case TRANSPORT_UPDATE, TRANSPORT_REPLACE: case TRANSPORT_UPDATE, TRANSPORT_REPLACE:
typedValue := gnmi.TypedValue(value[0]) typedValue := gnmi.TypedValue(value[0])
...@@ -262,7 +306,14 @@ func (pnd *pndImplementation) ChangeOND(uuid uuid.UUID, operation interface{}, p ...@@ -262,7 +306,14 @@ func (pnd *pndImplementation) ChangeOND(uuid uuid.UUID, operation interface{}, p
return d.Transport.Set(ctx, state, change) return d.Transport.Set(ctx, state, change)
} }
change := NewChange(uuid, d.GoStruct, cpy, callback) errChan := make(chan error)
change := NewChange(uuid, d.GoStruct, cpy, callback, errChan)
pnd.errChans[change.ID()] = errChan
return pnd.pendingChanges.add(change) return pnd.pendingChanges.add(change)
} }
func handleRollbackError(id uuid.UUID, err error) {
log.Error(err)
// TODO: Notion of invalid state needed.
}
...@@ -524,16 +524,7 @@ func Test_pndImplementation_RequestAll(t *testing.T) { ...@@ -524,16 +524,7 @@ func Test_pndImplementation_RequestAll(t *testing.T) {
} }
func Test_pndImplementation_ChangeOND(t *testing.T) { func Test_pndImplementation_ChangeOND(t *testing.T) {
t.Fail()
type fields struct { type fields struct {
name string
description string
sbic sbiStore
devices deviceStore
pendingChanges changeStore
committedChanges changeStore
confirmedChanges changeStore
id uuid.UUID
} }
type args struct { type args struct {
uuid uuid.UUID uuid uuid.UUID
...@@ -547,23 +538,98 @@ func Test_pndImplementation_ChangeOND(t *testing.T) { ...@@ -547,23 +538,98 @@ func Test_pndImplementation_ChangeOND(t *testing.T) {
args args args args
wantErr bool wantErr bool
}{ }{
// TODO: Add test cases. {
name: "update",
fields: fields{},
args: args{
uuid: mdid,
operation: TRANSPORT_UPDATE,
path: "/system/config/hostname",
value: []string{"ceos3000"},
},
wantErr: false,
},
{
name: "replace",
fields: fields{},
args: args{
uuid: mdid,
operation: TRANSPORT_REPLACE,
path: "/system/config/hostname",
value: []string{"ceos3000"},
},
wantErr: false,
},
{
name: "delete",
fields: fields{},
args: args{
uuid: mdid,
operation: TRANSPORT_DELETE,
path: "/system/config/hostname",
},
wantErr: false,
},
{
name: "delete w/args",
fields: fields{},
args: args{
uuid: mdid,
operation: TRANSPORT_DELETE,
path: "/system/config/hostname",
value: []string{"ceos3000"},
},
wantErr: false,
},
// Negative test cases
{
name: "invalid operation",
fields: fields{},
args: args{
uuid: mdid,
operation: "INVALID",
},
wantErr: true,
},
{
name: "invalid arg count",
fields: fields{},
args: args{
uuid: mdid,
operation: TRANSPORT_UPDATE,
path: "/system/config/hostname",
value: []string{"ceos3000", "ceos3001"},
},
wantErr: true,
},
{
name: "device not found",
fields: fields{},
args: args{
uuid: did,
operation: TRANSPORT_UPDATE,
},
wantErr: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
pnd := &pndImplementation{ p := newPnd()
name: tt.fields.name, d := mockDevice()
description: tt.fields.description, if err := p.AddDevice(&d); err != nil {
sbic: tt.fields.sbic, t.Error(err)
devices: tt.fields.devices, return
pendingChanges: tt.fields.pendingChanges, }
committedChanges: tt.fields.committedChanges, if err := p.ChangeOND(tt.args.uuid, tt.args.operation, tt.args.path, tt.args.value...); (err != nil) != tt.wantErr {
confirmedChanges: tt.fields.confirmedChanges,
id: tt.fields.id,
}
if err := pnd.ChangeOND(tt.args.uuid, tt.args.operation, tt.args.path, tt.args.value...); (err != nil) != tt.wantErr {
t.Errorf("ChangeOND() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ChangeOND() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
if len(p.pendingChanges.store) != 1 {
t.Errorf("ChangeOND() unexpected change count. got %v, want 1", len(p.pendingChanges.store))
}
} }
}) })
} }
} }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment