From b791ebe776a526e1b1db8ada6b87927610a9fa0e Mon Sep 17 00:00:00 2001 From: Manuel Kieweg <mail@manuelkieweg.de> Date: Mon, 8 Mar 2021 14:35:38 +0000 Subject: [PATCH] root and 1st level responses implemented. arbitrary values tricky --- cmd/gnmi/gnmi.go | 2 +- nucleus/errors.go | 8 ++++ nucleus/gnmi_transport.go | 24 ++++++++---- nucleus/gnmi_transport_test.go | 4 +- nucleus/southbound.go | 70 +++++++++++++++++++++++++--------- 5 files changed, 79 insertions(+), 29 deletions(-) diff --git a/cmd/gnmi/gnmi.go b/cmd/gnmi/gnmi.go index da537bf1c..43a1c1152 100644 --- a/cmd/gnmi/gnmi.go +++ b/cmd/gnmi/gnmi.go @@ -46,7 +46,7 @@ func main() { device.Transport = transport - p := []string{"/interfaces"} + p := []string{"/interfaces/interface"} errors := 0 for _, path := range p { err := pnd.RequestAll(path) diff --git a/nucleus/errors.go b/nucleus/errors.go index ce23a6d6f..9a92bf9e7 100644 --- a/nucleus/errors.go +++ b/nucleus/errors.go @@ -33,3 +33,11 @@ type ErrInvalidTypeAssertion struct { func (e ErrInvalidTypeAssertion) Error() string { return fmt.Sprintf("%v does not implement %v", e.v, e.t) } + +type ErrUnsupportedPath struct { + p interface{} +} + +func (e ErrUnsupportedPath) Error() string { + return fmt.Sprintf("path %v is not supported", e.p) +} \ No newline at end of file diff --git a/nucleus/gnmi_transport.go b/nucleus/gnmi_transport.go index 1adf8dd12..13dfa1197 100644 --- a/nucleus/gnmi_transport.go +++ b/nucleus/gnmi_transport.go @@ -29,17 +29,17 @@ func NewGnmiTransport(config *gnmi.Config) (*Gnmi, error) { return nil, err } return &Gnmi{ - config: config, - client: c, + config: config, + client: c, }, nil } type Gnmi struct { - SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error - Unmarshal func([]byte, string, interface{}, ...ytypes.UnmarshalOpt) error - RespChan chan *gpb.SubscribeResponse - config *gnmi.Config - client gpb.GNMIClient + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + Unmarshal func([]byte, []string, interface{}, ...ytypes.UnmarshalOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + client gpb.GNMIClient } func (g *Gnmi) SetConfig(config *gnmi.Config) { @@ -101,7 +101,7 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch val,ok := update.Val.Value.(*gpb.TypedValue_JsonIetfVal) if ok { opts := []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}} - if err := g.Unmarshal(val.JsonIetfVal,fullPath.String(), root, opts...); err != nil { + if err := g.Unmarshal(val.JsonIetfVal, extraxtPathElements(fullPath), root, opts...); err != nil { return err } return nil @@ -115,6 +115,14 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch return nil } +func extraxtPathElements(path *gpb.Path) []string { + elems := make([]string, len(path.Elem)) + for i,e := range path.Elem { + elems[i] = strings.Title(e.Name) + } + return elems +} + // extractModelKey extracts the model's key from the full path. Highly model specific for now // TODO: Remove hard coded model prefix // TODO: Figure out why path.Elem() is empty but path.Elememt() is deprecated diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go index 3f9fdfa56..3a88dd0a5 100644 --- a/nucleus/gnmi_transport_test.go +++ b/nucleus/gnmi_transport_test.go @@ -563,8 +563,8 @@ func Test_extractModelKey(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := extractModelKey(tt.args.path); got != tt.want { - t.Errorf("extractModelKey() = %v, want %v", got, tt.want) + if got := extraxtPathElements(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 81aeb8afa..150497df7 100644 --- a/nucleus/southbound.go +++ b/nucleus/southbound.go @@ -6,6 +6,7 @@ import ( "github.com/google/uuid" gpb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/util" "github.com/openconfig/ygot/ygot" "github.com/openconfig/ygot/ytypes" log "github.com/sirupsen/logrus" @@ -71,31 +72,64 @@ func (oc *OpenConfig) SetNode() func(schema *yang.Entry, root interface{}, path // Unmarshal injects OpenConfig specific model // representation to the transport. // Needed for type assertion. -func (oc *OpenConfig) Unmarshal() func([]byte, string, interface{}, ...ytypes.UnmarshalOpt) error { - return func(bytes []byte, path string, goStruct interface{}, opt ...ytypes.UnmarshalOpt) error { +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{} - s := reflect.ValueOf(&d).Elem() - h := s.FieldByName("Interfaces") - configStruct := reflect.New(h.Type()) - - // Pointer of field needs to be initialized. - // Very convoluted russian doll trick - // https://stackoverflow.com/a/57469950/4378176 - // https://golang.org/src/encoding/json/decode.go?s#L474 - // TODO: Prettify (mk) - p2 := configStruct.Elem() - configStruct.Elem().Set(reflect.New(p2.Type().Elem())) - c := configStruct.Elem().Interface().(ygot.GoStruct) - - if err := oc.schema.Unmarshal(bytes, c, opt...); err != nil { - return err + c, field = iter(&d, fields) + if err := openconfig.Unmarshal(bytes, c, opt...); err != nil { + log.Error(err) } - reflect.ValueOf(goStruct.(*openconfig.Device)).Elem().FieldByName("Interfaces").Set(reflect.ValueOf(c)) + 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 + var c ygot.GoStruct + var configStruct reflect.Value + f := fields[0] + s := reflect.ValueOf(a).Elem() + h := s.FieldByName(f) + configStruct = reflect.New(h.Type()) + + // Pointer of field needs to be initialized. + // Very convoluted russian doll trick + // https://stackoverflow.com/a/57469950/4378176 + // https://golang.org/src/encoding/json/decode.go?s#L474 + // TODO(mk): Prettify + p2 := configStruct.Elem() + // If we have KeyHelperGoStruct we need make and modify map instead of plain struct + if p2.Kind() == reflect.Map { + p2.Set(reflect.MakeMap(p2.Type())) + configStruct.Elem().Set(p2) + if err := util.InsertIntoMapStructField(a, f, "", p2); err != nil { + panic(err) + } + } else { + configStruct.Elem().Set(reflect.New(p2.Type().Elem())) + b = configStruct.Elem().Interface().(ygot.GoStruct) + } + if len(fields) > 1 { + c, _ = iter(b, fields[1:]) + } else { + return b, f + } + reflect.ValueOf(b).Elem().FieldByName(f).Set(reflect.ValueOf(c)) + return b, f +} + func (oc *OpenConfig) Id() uuid.UUID { return oc.id } -- GitLab