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