Skip to content
Snippets Groups Projects
demo.go 5.93 KiB
Newer Older
  • Learn to ignore specific revisions
  • Manuel Kieweg's avatar
    Manuel Kieweg committed
    package gnmidemo
    
    import (
    	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
    	"code.fbi.h-da.de/cocsn/gosdn/nucleus"
    	"code.fbi.h-da.de/cocsn/gosdn/nucleus/util"
    	"code.fbi.h-da.de/danet/gnmi-demo/generated"
    	"context"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"errors"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"fmt"
    	"github.com/gogo/protobuf/proto"
    	"github.com/google/uuid"
    	gpb "github.com/openconfig/gnmi/proto/gnmi"
    	"github.com/openconfig/goyang/pkg/yang"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	yutil "github.com/openconfig/ygot/util"
    	"github.com/openconfig/ygot/ygot"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"github.com/openconfig/ygot/ytypes"
    	log "github.com/sirupsen/logrus"
    	"os"
    	"os/signal"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"reflect"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"syscall"
    	"time"
    )
    
    var transport nucleus.Transport
    var opts *nucleus.GnmiTransportOptions
    var southbound sbi
    
    func setup(params ...string) error {
    	var err error
    	southbound = sbi{}
    	opts = &nucleus.GnmiTransportOptions{
    		Addr:     params[0],
    		Username: params[1],
    		Password: params[2],
    		SetNode:  southbound.SetNode(),
    		RespChan: make(chan *gpb.SubscribeResponse),
    		Encoding: 0,
    	}
    	transport, err = nucleus.NewGnmiTransport(opts)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	transport.(*nucleus.Gnmi).Unmarshal = southbound.Unmarshal()
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	if err != nil {
    		return err
    	}
    	return nil
    }
    
    func Set(params ...string) error {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	lenArgs := len(params) - 3
    	if err := setup(params[lenArgs:]...); err != nil {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		return err
    	}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	path := gnmi.SplitPath(params[0])
    	req := []interface{}{
    		&gnmi.Operation{
    			Type:   "update",
    			Origin: "",
    			Target: "",
    			Path:   path,
    			Val:    params[1],
    		},
    	}
    	resp, err := transport.Set(context.Background(), req...)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	if err != nil {
    		return err
    	}
    	log.Info(resp)
    	return nil
    }
    
    func Get(params ...string) error {
    	if err := setup(params[1:]...); err != nil {
    		return err
    	}
    	resp, err := transport.Get(context.Background(), params[0])
    	if err != nil {
    		return err
    	}
    	switch params[0] {
    	case "/":
    		if err := util.Write(resp.(proto.Message), "device"); err != nil {
    			return err
    		}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		log.Info("wrote root resource to file")
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	default:
    		device := &openconfig.Device{}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		blank := &gpb.GetResponse{}
    		if err := util.Read("device", blank); err != nil {
    			return err
    		}
    		if err := transport.ProcessResponse(blank, device, southbound.Schema()); err != nil {
    			return err
    		}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		if err := transport.ProcessResponse(resp, device, southbound.Schema()); err != nil {
    			return err
    		}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		log.Infof("response: %v", resp)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	}
    	return nil
    }
    
    func Subscribe(params ...string) error {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	o := &gnmi.SubscribeOptions{
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		UpdatesOnly:       false,
    		Prefix:            "",
    		Mode:              "stream",
    		StreamMode:        "sample",
    		SampleInterval:    uint64(10 * time.Second.Nanoseconds()),
    		SuppressRedundant: false,
    		HeartbeatInterval: uint64(time.Second.Nanoseconds()),
    		Paths:             gnmi.SplitPaths(params[:1]),
    		Origin:            "",
    		Target:            opts.Addr,
    	}
    	done := make(chan os.Signal, 1)
    	signal.Notify(done, syscall.SIGILL, syscall.SIGTERM)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	ctx := context.WithValue(context.Background(), "opts", o)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	go func() {
    		if err := transport.Subscribe(ctx); err != nil {
    			log.Fatal(err)
    		}
    	}()
    	fmt.Println("awaiting signal")
    	<-done
    	fmt.Println("exiting")
    	return nil
    }
    
    type sbi struct {
    	schema *ytypes.Schema
    }
    
    func (s sbi) SbiIdentifier() string {
    	panic("implement me")
    }
    
    func (s sbi) SetNode() func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error {
    	return func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error {
    		if err := ytypes.SetNode(schema, root.(*openconfig.Device), path, val, opts...); err != nil {
    			return err
    		}
    		return nil
    	}
    }
    
    func (s sbi) Schema() *ytypes.Schema {
    	schema, err := openconfig.Schema()
    	s.schema = schema
    	if err != nil {
    		log.Fatal(err)
    	}
    	return schema
    }
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // Unmarshal injects OpenConfig specific model
    // representation to the transport.
    // Needed for type assertion.
    func (s sbi) Unmarshal() func([]byte, []string, interface{}, ...ytypes.UnmarshalOpt) error {
    	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)
    		}
    	}()
    	switch l := len(fields); l {
    	case 0:
    		return openconfig.Unmarshal(bytes, goStruct.(*openconfig.Device), opt...)
    	case 1:
    	default:
    		return errors.New("fehler")
    	}
    	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 {
    		return
    	}
    	reflect.ValueOf(goStruct.(*openconfig.Device)).Elem().FieldByName(field).Set(reflect.ValueOf(c))
    	return nil
    }
    
    // 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]
    	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 := yutil.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
    	}
    	reflect.ValueOf(b).Elem().FieldByName(f).Set(reflect.ValueOf(c))
    	return
    }
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (s sbi) Id() uuid.UUID {
    	panic("implement me")
    }