From 450cf188c0558791dab12433488ffae1cc3e302e Mon Sep 17 00:00:00 2001
From: Manuel Kieweg <mail@manuelkieweg.de>
Date: Mon, 8 Feb 2021 17:33:13 +0000
Subject: [PATCH] Gnmi proto target mvp.

---
 cmd/gnmi-target/oc-target.go   | 191 ---------------------------------
 cmd/gnmi-target/target.go      |  11 +-
 cmd/gnmi/gnmi.go               |  11 +-
 forks/goarista/gnmi/client.go  |   5 +-
 forks/google/gnmi/server.go    |   7 +-
 nucleus/gnmi_transport.go      |  38 ++++---
 nucleus/gnmi_transport_test.go |   6 +-
 7 files changed, 41 insertions(+), 228 deletions(-)
 delete mode 100644 cmd/gnmi-target/oc-target.go

diff --git a/cmd/gnmi-target/oc-target.go b/cmd/gnmi-target/oc-target.go
deleted file mode 100644
index a6971ee0a..000000000
--- a/cmd/gnmi-target/oc-target.go
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
-Copyright 2018 Google Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package target has utility functions for working with target configuration
-// proto messages in target.proto.
-package main
-
-import (
-	"errors"
-	"fmt"
-	"sync"
-
-	"github.com/golang/protobuf/proto"
-
-	gpb "github.com/openconfig/gnmi/proto/gnmi"
-	pb "github.com/openconfig/gnmi/proto/target"
-)
-
-// Update describes a single target configuration.
-type Update struct {
-	Name    string
-	Request *gpb.SubscribeRequest
-	Target  *pb.Target
-}
-
-// Handler defines callbacks to be synchronously invoked in response to
-// configuration changes.
-type Handler struct {
-	// Add handles addition of a new target.
-	Add func(Update)
-	// Update handles target modification, including subscription request changes.
-	Update func(Update)
-	// Delete handles a target being removed.
-	Delete func(name string)
-}
-
-// Config handles configuration file changes and contains configuration state.
-type Config struct {
-	h             Handler
-	mu            sync.Mutex
-	configuration *pb.Configuration
-}
-
-// NewConfig creates a new Config that can process configuration changes.
-func NewConfig(h Handler) *Config {
-	return &Config{
-		h: h,
-	}
-}
-
-// NewConfigWithBase creates a new Config that can process configuration
-// changes. An optional configuration is used as the initial state.
-func NewConfigWithBase(h Handler, config *pb.Configuration) (*Config, error) {
-	if config != nil {
-		if err := Validate(config); err != nil {
-			return nil, fmt.Errorf("invalid configuration: %v", err)
-		}
-	}
-	return &Config{
-		configuration: config,
-		h:             h,
-	}, nil
-}
-
-// Current returns a copy of the current configuration.
-func (c *Config) Current() *pb.Configuration {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-	return proto.Clone(c.configuration).(*pb.Configuration)
-}
-
-// Load updates the current configuration and invokes Handler callbacks for
-// detected changes. An error is returned when loading fails, or the new revision
-// is not strictly increasing.
-func (c *Config) Load(config *pb.Configuration) error {
-	if config == nil {
-		return fmt.Errorf("attempted to load nil configuration")
-	}
-	if err := Validate(config); err != nil {
-		return fmt.Errorf("invalid configuration: %v", err)
-	}
-
-	c.mu.Lock()
-	defer c.mu.Unlock()
-	if err := c.checkRevision(config); err != nil {
-		return err
-	}
-	// Diff before setting new state.
-	c.handleDiffs(config)
-	c.configuration = config
-
-	return nil
-}
-
-func (c *Config) checkRevision(cf *pb.Configuration) error {
-	switch {
-	case c.configuration == nil:
-		return nil
-	case cf.Revision <= c.configuration.GetRevision():
-		return fmt.Errorf("revision %v is not strictly greater than %v", cf.Revision, c.configuration.GetRevision())
-	}
-	return nil
-}
-
-// handleDiffs should be called while locking c. It performs a read-only diff on
-// state in c against the new configuration.
-func (c *Config) handleDiffs(config *pb.Configuration) {
-	requestChanged := map[string]bool{}
-	for k, new := range config.Request {
-		if old, ok := c.configuration.GetRequest()[k]; ok {
-			if !proto.Equal(old, new) {
-				requestChanged[k] = true
-			}
-		}
-	}
-
-	// Make a copy of new targets so we can safely modify the map.
-	newTargets := make(map[string]*pb.Target)
-	for k, t := range config.GetTarget() {
-		newTargets[k] = t
-	}
-
-	for k, t := range c.configuration.GetTarget() {
-		nt := newTargets[k]
-		switch {
-		case nt == nil:
-			if c.h.Delete != nil {
-				c.h.Delete(k)
-			}
-		case !requestChanged[t.GetRequest()] && proto.Equal(t, nt):
-			delete(newTargets, k)
-		default:
-			if c.h.Update != nil {
-				r := config.GetRequest()[nt.GetRequest()]
-				c.h.Update(Update{
-					Name:    k,
-					Request: r,
-					Target:  nt,
-				})
-			}
-			delete(newTargets, k)
-		}
-	}
-
-	// Anything left in newTargets must be a new target.
-	for k, t := range newTargets {
-		r := config.GetRequest()[t.GetRequest()]
-		if c.h.Add != nil {
-			c.h.Add(Update{
-				Name:    k,
-				Request: r,
-				Target:  t,
-			})
-		}
-	}
-}
-
-// Validate confirms that the configuration is valid.
-func Validate(config *pb.Configuration) error {
-	for name, target := range config.Target {
-		if name == "" {
-			return errors.New("target with empty name")
-		}
-		if target == nil {
-			return fmt.Errorf("missing target configuration for %q", name)
-		}
-		if len(target.Addresses) == 0 {
-			return fmt.Errorf("target %q missing address", name)
-		}
-		if target.Request == "" {
-			return fmt.Errorf("target %q missing request", name)
-		}
-		if _, ok := config.Request[target.Request]; !ok {
-			return fmt.Errorf("missing request %q for target %q", target.Request, name)
-		}
-	}
-	return nil
-}
diff --git a/cmd/gnmi-target/target.go b/cmd/gnmi-target/target.go
index 556310961..4239ea9fa 100644
--- a/cmd/gnmi-target/target.go
+++ b/cmd/gnmi-target/target.go
@@ -15,15 +15,13 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/reflection"
 	"google.golang.org/grpc/status"
-	"io/ioutil"
 	"net"
 	"os"
 	"reflect"
 )
 
 var (
-	bindAddr   = flag.String("bind_address", ":9339", "Bind to address:port or just :port")
-	configFile = flag.String("config", "", "IETF JSON file for target startup config")
+	bindAddr = flag.String("bind_address", ":9339", "Bind to address:port or just :port")
 )
 
 type server struct {
@@ -94,13 +92,6 @@ func main() {
 	g := grpc.NewServer()
 
 	var configData []byte
-	if *configFile != "" {
-		var err error
-		configData, err = ioutil.ReadFile(*configFile)
-		if err != nil {
-			log.Fatalf("error in reading config file: %v", err)
-		}
-	}
 	s, err := newServer(model, configData)
 	if err != nil {
 		log.Fatalf("error in creating gnmi target: %v", err)
diff --git a/cmd/gnmi/gnmi.go b/cmd/gnmi/gnmi.go
index 7d098f736..21902da12 100644
--- a/cmd/gnmi/gnmi.go
+++ b/cmd/gnmi/gnmi.go
@@ -6,9 +6,14 @@ import (
 	schema "code.fbi.h-da.de/cocsn/yang-models/generated/arista"
 	"context"
 	"github.com/google/uuid"
+	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	log "github.com/sirupsen/logrus"
 )
 
+/*
+  Simple gnmi request program. Use with cauton and leaf paths only.
+*/
+
 func main() {
 	log.SetLevel(log.DebugLevel)
 	sbi := &nucleus.AristaOC{}
@@ -33,15 +38,15 @@ func main() {
 		Addr:     device.Config.Address,
 		Password: device.Config.Password,
 		Username: device.Config.Username,
+		Encoding: gpb.Encoding_PROTO,
 	}
 	ctx := gnmi.NewContext(context.Background(), cfg)
 	ctx = context.WithValue(ctx, "config", cfg)
 
-	p := []string{"/interfaces/interface"}
+	p := []string{"/interfaces/interface[name=en0]/state/name"}
 	errors := 0
 	for _, path := range p {
-		req, err := gnmi.NewGetRequest(gnmi.SplitPaths([]string{path}), "")
-		resp, err := nucleus.GetWithRequest(ctx, req)
+		resp, err := device.Transport.Get(ctx, path)
 		if err != nil {
 			log.Debug(err)
 			errors++
diff --git a/forks/goarista/gnmi/client.go b/forks/goarista/gnmi/client.go
index 8d08079dc..d4a9958ba 100644
--- a/forks/goarista/gnmi/client.go
+++ b/forks/goarista/gnmi/client.go
@@ -64,6 +64,7 @@ type Config struct {
 	Compression string
 	DialOptions []grpc.DialOption
 	Token       string
+	Encoding    pb.Encoding
 }
 
 // SubscribeOptions is the gNMI subscription request options
@@ -250,10 +251,10 @@ func NewContext(ctx context.Context, cfg *Config) context.Context {
 }
 
 // NewGetRequest returns a GetRequest for the given paths
-func NewGetRequest(paths [][]string, origin string) (*pb.GetRequest, error) {
+func NewGetRequest(ctx context.Context, paths [][]string, origin string) (*pb.GetRequest, error) {
 	req := &pb.GetRequest{
 		Path:     make([]*pb.Path, len(paths)),
-		Encoding: pb.Encoding_PROTO,
+		Encoding: ctx.Value("Config").(*Config).Encoding,
 	}
 	for i, p := range paths {
 		gnmiPath, err := ParseGNMIElements(p)
diff --git a/forks/google/gnmi/server.go b/forks/google/gnmi/server.go
index b46fd00de..3af5d9b50 100644
--- a/forks/google/gnmi/server.go
+++ b/forks/google/gnmi/server.go
@@ -285,8 +285,8 @@ func (s *Server) toGoStruct(jsonTree map[string]interface{}) (ygot.ValidatedGoSt
 // getGNMIServiceVersion returns a pointer to the gNMI service version string.
 // The method is non-trivial because of the way it is defined in the proto file.
 func getGNMIServiceVersion() (*string, error) {
-	gzB, _ := (&pb.Update{}).Descriptor()
-	r, err := gzip.NewReader(bytes.NewReader(gzB))
+	gzB := (&pb.Update{}).ProtoReflect().Descriptor()
+	r, err := gzip.NewReader(bytes.NewReader([]byte(gzB.Name())))
 	if err != nil {
 		return nil, fmt.Errorf("error in initializing gzip reader: %v", err)
 	}
@@ -354,9 +354,6 @@ func deleteKeyedListEntry(node map[string]interface{}, elem *pb.PathElem) bool {
 // gnmiFullPath builds the full path from the prefix and path.
 func gnmiFullPath(prefix, path *pb.Path) *pb.Path {
 	fullPath := &pb.Path{Origin: path.Origin}
-	if path.GetElement() != nil {
-		fullPath.Element = append(prefix.GetElement(), path.GetElement()...)
-	}
 	if path.GetElem() != nil {
 		fullPath.Elem = append(prefix.GetElem(), path.GetElem()...)
 	}
diff --git a/nucleus/gnmi_transport.go b/nucleus/gnmi_transport.go
index 9683b7af7..b3921329d 100644
--- a/nucleus/gnmi_transport.go
+++ b/nucleus/gnmi_transport.go
@@ -3,6 +3,7 @@ package nucleus
 import (
 	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
 	"context"
+	"errors"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/gnmi/proto/gnmi_ext"
 	"github.com/openconfig/goyang/pkg/yang"
@@ -26,7 +27,10 @@ func (g *Gnmi) GetConfig() interface{} {
 
 // interface satisfaction for now
 // TODO: Convert to meaningfiul calls
-func (g *Gnmi) Get(ctx context.Context, params ...string) (interface{}, error) { return nil, nil }
+func (g *Gnmi) Get(ctx context.Context, params ...string) (interface{}, error) {
+	paths := gnmi.SplitPaths(params)
+	return g.get(ctx, paths, "")
+}
 func (g *Gnmi) Set(ctx context.Context, params ...string) (interface{}, error) { return nil, nil }
 func (g *Gnmi) Subscribe(ctx context.Context, params ...string) error {
 	return g.subscribe(ctx)
@@ -48,13 +52,17 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch
 			path := update.Path
 			fullPath := path
 			if prefix != nil {
-				fullPath = gnmiFullPath(prefix, path)
+				var err error
+				fullPath, err = gnmiFullPath(prefix, path)
+				if err != nil {
+					return err
+				}
 			}
 			modelKey := extractModelKey(fullPath)
 
 			log.Debug(modelKey)
 
-			schema := models["device"]
+			schema := models["Device"]
 			if err := g.SetNode(schema, root, update.Path, update.Val, opts...); err != nil {
 				log.Error(err)
 			}
@@ -70,25 +78,25 @@ func extractModelKey(path *gpb.Path) string {
 	var b strings.Builder
 	delim := "_"
 	b.WriteString("OpenconfigInterfaces")
-	for i := 0; i < len(path.Element)-1; i++ {
-		pathElement := path.Element[i]
+	for i := 0; i < len(path.Elem)-1; i++ {
+		pathElement := path.Elem[i]
 		b.WriteString(delim)
-		b.WriteString(strings.Title(pathElement))
+		b.WriteString(strings.Title(pathElement.Name))
 	}
 	return b.String()
 }
 
 // gnmiFullPath builds the full path from the prefix and path.
 // Copycat from forks/google/gnmi/server.go
-func gnmiFullPath(prefix, path *gpb.Path) *gpb.Path {
+func gnmiFullPath(prefix, path *gpb.Path) (*gpb.Path, error) {
 	fullPath := &gpb.Path{Origin: path.Origin}
 	if path.GetElement() != nil {
-		fullPath.Element = append(prefix.GetElement(), path.GetElement()...)
+		return nil, errors.New("deprecated path element type is unsupported")
 	}
 	if path.GetElem() != nil {
 		fullPath.Elem = append(prefix.GetElem(), path.GetElem()...)
 	}
-	return fullPath
+	return fullPath, nil
 }
 
 // Capabilities calls GNMI capabilities
@@ -110,16 +118,18 @@ func (g *Gnmi) get(ctx context.Context, paths [][]string, origin string) (interf
 	if err != nil {
 		return nil, err
 	}
-	return GetWithRequest(ctx, req)
+	return getWithRequest(ctx, req)
 }
 
-// GetWithRequest takes a fully formed GetRequest, performs the Get,
-// and displays any response.
-func GetWithRequest(ctx context.Context, req *gpb.GetRequest) (interface{}, error) {
-	client, err := gnmi.Dial(ctx.Value("config").(*gnmi.Config))
+// getWithRequest takes a fully formed GetRequest, performs the Get,
+// and returns any response.
+func getWithRequest(ctx context.Context, req *gpb.GetRequest) (interface{}, error) {
+	cfg := ctx.Value("config").(*gnmi.Config)
+	client, err := gnmi.Dial(cfg)
 	if err != nil {
 		return nil, err
 	}
+	req.Encoding = cfg.Encoding
 	resp, err := client.Get(ctx, req)
 	if err != nil {
 		return nil, err
diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go
index 24af7305c..04fd14215 100644
--- a/nucleus/gnmi_transport_test.go
+++ b/nucleus/gnmi_transport_test.go
@@ -24,13 +24,13 @@ func TestGetWithRequest(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := GetWithRequest(tt.args.ctx, tt.args.req)
+			got, err := getWithRequest(tt.args.ctx, tt.args.req)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("GetWithRequest() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("getWithRequest() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("GetWithRequest() got = %v, want %v", got, tt.want)
+				t.Errorf("getWithRequest() got = %v, want %v", got, tt.want)
 			}
 		})
 	}
-- 
GitLab