Skip to content
Snippets Groups Projects
gnmi_transport.go 8.42 KiB
Newer Older
  • Learn to ignore specific revisions
  • package nucleus
    
    import (
    	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
    	"context"
    	gpb "github.com/openconfig/gnmi/proto/gnmi"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"github.com/openconfig/gnmi/proto/gnmi_ext"
    	"github.com/openconfig/goyang/pkg/yang"
    	"github.com/openconfig/ygot/ytypes"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	log "github.com/sirupsen/logrus"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"reflect"
    
    Malte Bauch's avatar
    Malte Bauch committed
    // CtxKeyType is a custom type to be used as key in a context.WithValue() or
    // context.Value() call. For more information see:
    // https://www.calhoun.io/pitfalls-of-context-values-and-how-to-avoid-or-mitigate-them/
    
    type CtxKeyType string
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    const (
    
    Malte Bauch's avatar
    Malte Bauch committed
    	// CtxKeyOpts context key for gnmi.SubscribeOptions
    
    	CtxKeyOpts CtxKeyType = "opts"
    
    Malte Bauch's avatar
    Malte Bauch committed
    	// CtxKeyConfig is a context key for gnmi.Config
    
    	CtxKeyConfig = "config"
    
    // Gnmi implements the Transport interface and provides an SBI with the
    // possibility to access a gNMI endpoint.
    
    type Gnmi struct {
    
    	SetNode   func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error
    	RespChan  chan *gpb.SubscribeResponse
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	Unmarshal func([]byte, []string, interface{}, ...ytypes.UnmarshalOpt) error
    
    	Options   *GnmiTransportOptions
    	client    gpb.GNMIClient
    
    // NewGnmiTransport takes a struct of GnmiTransportOptions and returns a Gnmi
    // transport based on the values of it.
    
    func NewGnmiTransport(opts *GnmiTransportOptions) (*Gnmi, error) {
    
    	c, err := gnmi.Dial(&opts.Config)
    
    	if err != nil {
    		return nil, err
    	}
    
    	log.WithFields(log.Fields{
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		"target":   opts.Addr,
    		"tls":      opts.TLS,
    
    		"encoding": opts.Encoding,
    	}).Info("building new gNMI transport")
    
    	return &Gnmi{
    
    		SetNode:  opts.SetNode,
    		RespChan: opts.RespChan,
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		Options:  opts,
    
    	}, nil
    
    //SetOptions sets Gnmi Options
    
    func (g *Gnmi) SetOptions(to TransportOptions) {
    
    	g.Options = to.(*GnmiTransportOptions)
    
    //GetOptions returns the Gnmi options
    
    func (g *Gnmi) GetOptions() interface{} {
    
    Malte Bauch's avatar
    Malte Bauch committed
    // Get takes a slice of gnmi paths, splits them and calls get for each one of them.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (g *Gnmi) Get(ctx context.Context, params ...string) (interface{}, error) {
    
    	if g.client == nil {
    		return nil, &ErrNilClient{}
    	}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	paths := gnmi.SplitPaths(params)
    	return g.get(ctx, paths, "")
    }
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    
    // Set takes a slice of params. This slice must contain at least one operation.
    // It can contain an additional arbitrary amount of operations and extensions.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (g *Gnmi) Set(ctx context.Context, args ...interface{}) (interface{}, error) {
    
    	if g.client == nil {
    		return nil, &ErrNilClient{}
    	}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	if len(args) == 0 {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		return nil, &ErrInvalidParameters{
    			f: "gnmi.Set()",
    			r: "no parameters provided",
    		}
    	}
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	// Loop over args and create ops and exts
    	// Invalid args cause unhealable error
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	ops := make([]*gnmi.Operation, 0)
    	exts := make([]*gnmi_ext.Extension, 0)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	for _, p := range args {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		switch p.(type) {
    		case *gnmi.Operation:
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			op := p.(*gnmi.Operation)
    			if op.Target == "" {
    				op.Target = g.Options.Addr
    			}
    			ops = append(ops, op)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		case *gnmi_ext.Extension:
    			exts = append(exts, p.(*gnmi_ext.Extension))
    		default:
    			return nil, &ErrInvalidParameters{
    				f: "gnmi.Set()",
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    				r: "args contain invalid type",
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			}
    		}
    	}
    	if len(ops) == 0 {
    		return nil, &ErrInvalidParameters{
    			f: "gnmi.Set()",
    			r: "no operations provided",
    		}
    	}
    	return g.set(ctx, ops, exts...)
    
    Malte Bauch's avatar
    Malte Bauch committed
    //Subscribe subscribes to a gNMI target
    
    func (g *Gnmi) Subscribe(ctx context.Context, params ...string) error {
    
    	if g.client == nil {
    		return &ErrNilClient{}
    	}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	return g.subscribe(ctx)
    }
    
    // Type returns the gNMI transport type
    
    func (g *Gnmi) Type() string {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	return "gnmi"
    }
    
    
    // ProcessResponse takes a gNMI response and serializes the contents to the root struct.
    func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Schema) error {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	models := s.SchemaTree
    	r := resp.(*gpb.GetResponse)
    	rn := r.Notification
    	for _, msg := range rn {
    
    		for _, update := range msg.Update {
    			path := update.Path
    			fullPath := path
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			val, ok := update.Val.Value.(*gpb.TypedValue_JsonIetfVal)
    			if ok {
    				opts := []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}}
    				if err := g.Unmarshal(val.JsonIetfVal, extraxtPathElements(fullPath), root, opts...); err != nil {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    					return err
    				}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    				return nil
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			// TODO(mk): Evaluate hardcoded model key
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			schema := models["Device"]
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			opts := []ytypes.SetNodeOpt{&ytypes.InitMissingElements{}, &ytypes.TolerateJSONInconsistencies{}}
    
    			if err := g.SetNode(schema, root, update.Path, update.Val, opts...); err != nil {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func extraxtPathElements(path *gpb.Path) []string {
    	elems := make([]string, len(path.Elem))
    	for i, e := range path.Elem {
    		elems[i] = strings.Title(e.Name)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	return elems
    
    // Capabilities calls GNMI capabilities
    func (g *Gnmi) Capabilities(ctx context.Context) (interface{}, error) {
    
    	log.WithFields(log.Fields{
    		"target": g.Options.Addr,
    	}).Info("sending gNMI capabilities request")
    
    	ctx = gnmi.NewContext(ctx, &g.Options.Config)
    
    	ctx = context.WithValue(ctx, CtxKeyConfig, &g.Options.Config) //nolint
    
    	resp, err := g.client.Capabilities(ctx, &gpb.CapabilityRequest{})
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // get calls GNMI get
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (g *Gnmi) get(ctx context.Context, paths [][]string, origin string) (interface{}, error) {
    
    	ctx = gnmi.NewContext(ctx, &g.Options.Config)
    
    	ctx = context.WithValue(ctx, CtxKeyConfig, &g.Options.Config) //nolint
    
    	req, err := gnmi.NewGetRequest(ctx, paths, origin)
    
    	return g.getWithRequest(ctx, req)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // getWithRequest takes a fully formed GetRequest, performs the Get,
    // and returns any response.
    
    func (g *Gnmi) getWithRequest(ctx context.Context, req *gpb.GetRequest) (interface{}, error) {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	if req == nil {
    		return nil, &ErrNil{}
    	}
    
    	log.WithFields(log.Fields{
    		"target": g.Options.Addr,
    		"path":   req.Path,
    	}).Info("sending gNMI get request")
    
    
    	resp, err := g.client.Get(ctx, req)
    
    	if err != nil {
    		return nil, err
    	}
    	return resp, nil
    }
    
    // Set calls GNMI set
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (g *Gnmi) set(ctx context.Context, setOps []*gnmi.Operation,
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	exts ...*gnmi_ext.Extension) (*gpb.SetResponse, error) {
    
    	ctx = gnmi.NewContext(ctx, &g.Options.Config)
    
    	targets := make([]string, len(setOps))
    	paths := make([][]string, len(setOps))
    	values := make([]string, len(setOps))
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	for i, v := range setOps {
    
    		targets[i] = v.Target
    		paths[i] = v.Path
    		values[i] = v.Val
    	}
    	log.WithFields(log.Fields{
    		"targets": targets,
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		"paths":   paths,
    		"values":  values,
    
    	}).Info("sending gNMI set request")
    
    	return gnmi.Set(ctx, g.client, setOps, exts...)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (g *Gnmi) subscribe(ctx context.Context) error {
    
    	ctx = gnmi.NewContext(ctx, &g.Options.Config)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	opts, ok := ctx.Value(CtxKeyOpts).(*gnmi.SubscribeOptions)
    	if !ok{
    		return &ErrInvalidTypeAssertion{
    			v: reflect.TypeOf(ctx.Value(CtxKeyOpts)),
    			t: reflect.TypeOf(&gnmi.SubscribeOptions{}),
    		}
    	}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	go func() {
    
    		log.WithFields(log.Fields{
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			"address":  opts.Target,
    			"paths":    opts.Paths,
    			"mode":     opts.Mode,
    
    			"interval": opts.SampleInterval,
    		}).Info("subscribed to gNMI target")
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			if resp != nil {
    				if err := gnmi.LogSubscribeResponse(resp); err != nil {
    					log.Fatal(err)
    				}
    
    	return gnmi.SubscribeErr(ctx, g.client, opts, g.RespChan)
    
    }
    
    // Close calls GNMI close
    func (g *Gnmi) Close() error {
    	return nil
    }
    
    // GnmiTransportOptions implements the TransportOptions interface.
    // GnmiTransportOptions contains all needed information to setup a Gnmi
    
    Malte Bauch's avatar
    Malte Bauch committed
    // transport and therefore inherits gnmi.Config.
    
    type GnmiTransportOptions struct {
    
    Malte Bauch's avatar
    Malte Bauch committed
    	// all needed gnmi transport parameters
    
    	gnmi.Config
    
    	SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path,
    
    Malte Bauch's avatar
    Malte Bauch committed
    		val interface{}, opts ...ytypes.SetNodeOpt) error
    
    	Unmarshal func([]byte, []string, interface{}, ...ytypes.UnmarshalOpt) error
    	RespChan  chan *gpb.SubscribeResponse
    
    // GetAddress returns the address used by the transport to connect to a
    // gRPC endpoint.
    
    func (gto *GnmiTransportOptions) GetAddress() string {
    
    	return gto.Config.Addr
    
    
    // GetUsername returns the username used by the transport to connect to a
    // gRPC endpoint.
    
    func (gto *GnmiTransportOptions) GetUsername() string {
    
    	return gto.Config.Username
    
    
    // GetPassword returns the password used by the transport to connect to a
    // gRPC endpoint.
    
    func (gto *GnmiTransportOptions) GetPassword() string {
    
    	return gto.Config.Password
    
    // IsTransportOption is needed to fulfill the requirements of the
    // TransportOptions interface. It does not need any further implementation.
    
    func (gto *GnmiTransportOptions) IsTransportOption() {}