From a6e918ca0cbf7670cd4e721649fd577c1835ec28 Mon Sep 17 00:00:00 2001 From: Manuel Kieweg <mail@manuelkieweg.de> Date: Mon, 8 Mar 2021 16:30:59 +0000 Subject: [PATCH] tests for unmarshal and adjusted old tests --- cmd/gnmi/gnmi.go | 2 +- nucleus/gnmi_transport.go | 8 +- nucleus/gnmi_transport_test.go | 36 +++--- nucleus/southbound.go | 67 ++++++---- nucleus/southbound_test.go | 227 +++++++++++++++++++++++++++++++++ 5 files changed, 289 insertions(+), 51 deletions(-) diff --git a/cmd/gnmi/gnmi.go b/cmd/gnmi/gnmi.go index 43a1c1152..da537bf1c 100644 --- a/cmd/gnmi/gnmi.go +++ b/cmd/gnmi/gnmi.go @@ -46,7 +46,7 @@ func main() { device.Transport = transport - p := []string{"/interfaces/interface"} + p := []string{"/interfaces"} errors := 0 for _, path := range p { err := pnd.RequestAll(path) diff --git a/nucleus/gnmi_transport.go b/nucleus/gnmi_transport.go index 13dfa1197..8a2c22f5d 100644 --- a/nucleus/gnmi_transport.go +++ b/nucleus/gnmi_transport.go @@ -19,7 +19,7 @@ var tapProto bool func init() { // tapProto taps gpb.getResponse and gpb.Getrequests // to binary file - // CAUTION only set true if you know what you do + // CAUTION only set true if you know what you're doing tapProto = false } @@ -94,10 +94,6 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch return err } } - modelKey := extractModelKey(fullPath) - log.Debug(modelKey) - schema := models[modelKey] - val,ok := update.Val.Value.(*gpb.TypedValue_JsonIetfVal) if ok { opts := []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}} @@ -106,6 +102,8 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch } return nil } + // TODO(mk): Evaluate hardcoded model key + schema := models["Device"] opts := []ytypes.SetNodeOpt{&ytypes.InitMissingElements{}, &ytypes.TolerateJSONInconsistencies{}} if err := g.SetNode(schema, root, update.Path, update.Val, opts...); err != nil { return err diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go index 3a88dd0a5..e54dff7b9 100644 --- a/nucleus/gnmi_transport_test.go +++ b/nucleus/gnmi_transport_test.go @@ -5,7 +5,6 @@ import ( "code.fbi.h-da.de/cocsn/gosdn/mocks" "code.fbi.h-da.de/cocsn/gosdn/nucleus/util" "code.fbi.h-da.de/cocsn/gosdn/test" - "code.fbi.h-da.de/cocsn/yang-models/generated/arista" "code.fbi.h-da.de/cocsn/yang-models/generated/openconfig" "context" log "github.com/golang/glog" @@ -24,6 +23,7 @@ func TestMain(m *testing.M) { testSetupGnmi() testSetupPnd() testSetupStore() + testSetupSbi() os.Exit(m.Run()) } @@ -76,9 +76,11 @@ func TestGnmi_Capabilities(t *testing.T) { capabilityRequest := &gpb.CapabilityRequest{} - ctx := context.Background() transport.client.(*mocks.GNMIClient). - On("Capabilities", ctx, capabilityRequest). + On("NewContext", mockContext, mock.Anything). + Return(mockContext) + transport.client.(*mocks.GNMIClient). + On("Capabilities", mockContext, capabilityRequest). Return(capabilityResponse, nil) type fields struct { @@ -281,37 +283,28 @@ func TestGnmi_ProcessResponse(t *testing.T) { wantErr bool }{ { - name: "Arista Full Node", - fields: fields{Sbi: &Arista{}}, + name: "Interfaces Interface", + fields: fields{Sbi: &OpenConfig{}}, args: args{ path: "../test/resp-full-node", - root: &arista.Device{}, - }, - wantErr: false, - }, - { - name: "Arista Interfaces Wildcard", - fields: fields{Sbi: &Arista{}}, - args: args{ - path: "../test/resp-interfaces-wildcard", - root: &arista.Device{}, + root: &openconfig.Device{}, }, - wantErr: false, + wantErr: true, }, { - name: "OC Full Node", + name: "Interfaces Wildcard", fields: fields{Sbi: &OpenConfig{}}, args: args{ - path: "../test/resp-full-node", + path: "../test/resp-interfaces-wildcard", root: &openconfig.Device{}, }, wantErr: false, }, { - name: "OC Interfaces Wildcard", + name: "Root", fields: fields{Sbi: &OpenConfig{}}, args: args{ - path: "../test/resp-interfaces-wildcard", + path: "../test/resp-full-node-arista-ceos", root: &openconfig.Device{}, }, wantErr: false, @@ -321,6 +314,7 @@ func TestGnmi_ProcessResponse(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := &Gnmi{ SetNode: tt.fields.Sbi.SetNode(), + Unmarshal: tt.fields.Sbi.(*OpenConfig).Unmarshal(), } s := tt.fields.Sbi.Schema() resp := &gpb.GetResponse{} @@ -563,7 +557,7 @@ func Test_extractModelKey(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := extraxtPathElements(tt.args.path); got != tt.want { + if got := extractModelKey(tt.args.path); got != tt.want { t.Errorf("extraxtPathElements() = %v, want %v", got, tt.want) } }) diff --git a/nucleus/southbound.go b/nucleus/southbound.go index 150497df7..d1755dad2 100644 --- a/nucleus/southbound.go +++ b/nucleus/southbound.go @@ -73,33 +73,52 @@ func (oc *OpenConfig) SetNode() func(schema *yang.Entry, root interface{}, path // representation to the transport. // Needed for type assertion. func (oc *OpenConfig) Unmarshal() func([]byte, []string, interface{}, ...ytypes.UnmarshalOpt) error { - return func(bytes []byte, fields []string, goStruct interface{}, opt ...ytypes.UnmarshalOpt) error { - switch l := len(fields); l { - case 0: - return openconfig.Unmarshal(bytes, goStruct.(*openconfig.Device), opt...) - case 1: - default: - return &ErrUnsupportedPath{fields} - } - var c ygot.GoStruct - var field string - - // Load SBI definition - d := openconfig.Device{} - c, field = iter(&d, fields) - if err := openconfig.Unmarshal(bytes, c, opt...); err != nil { - log.Error(err) + return unmarshal +} + +// unmarshal parses a root or 1st level gNMI response to a go struct +// Named return due to how recover works here +func unmarshal(bytes []byte, fields []string, goStruct interface{}, opt ...ytypes.UnmarshalOpt) (err error) { + defer func() { + if r := recover(); r != nil { + err = r.(error) } - reflect.ValueOf(goStruct.(*openconfig.Device)).Elem().FieldByName(field).Set(reflect.ValueOf(c)) - return nil + }() + switch l := len(fields); l { + case 0: + return openconfig.Unmarshal(bytes, goStruct.(*openconfig.Device), opt...) + case 1: + default: + return &ErrUnsupportedPath{fields} + } + var c ygot.GoStruct + var field string + + // Load SBI definition + d := openconfig.Device{} + c, field, err = iter(&d, fields) + if err != nil { + return } + if err := openconfig.Unmarshal(bytes, c, opt...); err != nil { + log.Error(err) + } + reflect.ValueOf(goStruct.(*openconfig.Device)).Elem().FieldByName(field).Set(reflect.ValueOf(c)) + return nil } -func iter(a ygot.GoStruct, fields []string) (ygot.GoStruct, string) { - var b ygot.GoStruct +// iter walks down the provided paths and initializes the ygot.GoStruct. It only works for +// the root level. Watch out for named returns here +// TODO(mk): Fix deeper layers +func iter(a ygot.GoStruct, fields []string) (b ygot.GoStruct, f string, err error) { + defer func() { + if r := recover(); r != nil { + err = r.(error) + } + }() var c ygot.GoStruct var configStruct reflect.Value - f := fields[0] + f = fields[0] s := reflect.ValueOf(a).Elem() h := s.FieldByName(f) configStruct = reflect.New(h.Type()) @@ -122,12 +141,12 @@ func iter(a ygot.GoStruct, fields []string) (ygot.GoStruct, string) { b = configStruct.Elem().Interface().(ygot.GoStruct) } if len(fields) > 1 { - c, _ = iter(b, fields[1:]) + c, _, _ = iter(b, fields[1:]) } else { - return b, f + return } reflect.ValueOf(b).Elem().FieldByName(f).Set(reflect.ValueOf(c)) - return b, f + return } func (oc *OpenConfig) Id() uuid.UUID { diff --git a/nucleus/southbound_test.go b/nucleus/southbound_test.go index 0fa4ffa90..8063b8120 100644 --- a/nucleus/southbound_test.go +++ b/nucleus/southbound_test.go @@ -1 +1,228 @@ package nucleus + +import ( + "code.fbi.h-da.de/cocsn/gosdn/nucleus/util" + "code.fbi.h-da.de/cocsn/yang-models/generated/openconfig" + "github.com/google/uuid" + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" + log "github.com/sirupsen/logrus" + "reflect" + "testing" +) + +func testSetupSbi() { + var err error + aristaUUID, err = uuid.Parse("d3795249-579c-4be7-8818-29f113cb86ee") + if err != nil { + log.Fatal(err) + } + + ocUUID, err = uuid.Parse("5e252b70-38f2-4c99-a0bf-1b16af4d7e67") + if err != nil { + log.Fatal(err) + } +} + +var aristaUUID uuid.UUID +var ocUUID uuid.UUID + +func TestOpenConfig_Id(t *testing.T) { + type fields struct { + transport Transport + schema *ytypes.Schema + id uuid.UUID + } + tests := []struct { + name string + fields fields + want uuid.UUID + }{ + { + name: "default", + fields: fields{ + transport: nil, + schema: nil, + id: ocUUID, + }, + want: ocUUID, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + oc := &OpenConfig{ + transport: tt.fields.transport, + schema: tt.fields.schema, + id: tt.fields.id, + } + if got := oc.Id(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Id() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOpenConfig_SbiIdentifier(t *testing.T) { + type fields struct { + transport Transport + schema *ytypes.Schema + id uuid.UUID + } + tests := []struct { + name string + fields fields + want string + }{ + {name: "default", fields: fields{}, want: "openconfig"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + oc := &OpenConfig{ + transport: tt.fields.transport, + schema: tt.fields.schema, + id: tt.fields.id, + } + if got := oc.SbiIdentifier(); got != tt.want { + t.Errorf("SbiIdentifier() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOpenConfig_Schema(t *testing.T) { + schema, err := openconfig.Schema() + if err != nil { + t.Error(err) + } + type fields struct { + transport Transport + schema *ytypes.Schema + id uuid.UUID + } + tests := []struct { + name string + fields fields + want *ytypes.Schema + }{ + {name: "default", fields: fields{}, want: schema}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + oc := &OpenConfig{ + transport: tt.fields.transport, + schema: tt.fields.schema, + id: tt.fields.id, + } + if got := oc.Schema(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Schema() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_unmarshal(t *testing.T) { + type args struct { + path string + goStruct interface{} + opt []ytypes.UnmarshalOpt + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "fail", + args: args{ + goStruct: &openconfig.Device{}, + path: "../test/resp-interfaces-interface-arista-ceos", + }, + wantErr: true, + }, + { + name: "root w/opts", + args: args{ + path: "../test/resp-full-node-arista-ceos", + goStruct: &openconfig.Device{}, + opt: []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}}, + }, + wantErr: false, + }, + { + name: "root w/o opts", + args: args{ + path: "../test/resp-full-node-arista-ceos", + goStruct: &openconfig.Device{}, + opt: nil, + }, + wantErr: true, + }, + { + name: "interfaces w/opts", + args: args{ + path: "../test/resp-interfaces-arista-ceos", + goStruct: &openconfig.Device{}, + opt: []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}}, + }, + wantErr: false, + }, + { + name: "interfaces w/o opts", + args: args{ + path: "../test/resp-interfaces-arista-ceos", + goStruct: &openconfig.Device{}, + opt: nil, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp := &gpb.GetResponse{} + err := util.Read(tt.args.path, resp) + if err != nil { + t.Error(err) + } + fields := extraxtPathElements(resp.Notification[0].Update[0].Path) + bytes := resp.Notification[0].Update[0].Val.GetJsonIetfVal() + if err := unmarshal(bytes, fields, tt.args.goStruct, tt.args.opt...); (err != nil) != tt.wantErr { + t.Errorf("unmarshal() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.args.goStruct.(*openconfig.Device).Interfaces == nil && tt.args.opt != nil{ + t.Errorf("unmarshal() error: field Interfaces must not be nil") + } + }) + } +} + +func Test_iter(t *testing.T) { + type args struct { + a ygot.GoStruct + fields []string + } + tests := []struct { + name string + args args + wantB ygot.GoStruct + wantField string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotB, gotField, err := iter(tt.args.a, tt.args.fields) + if (err != nil) != tt.wantErr { + t.Errorf("iter() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotB, tt.wantB) { + t.Errorf("iter() gotB = %v, want %v", gotB, tt.wantB) + } + if gotField != tt.wantField { + t.Errorf("iter() gotField = %v, want %v", gotField, tt.wantField) + } + }) + } +} -- GitLab