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" "errors" "fmt" "github.com/gogo/protobuf/proto" "github.com/google/uuid" gpb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/goyang/pkg/yang" yutil "github.com/openconfig/ygot/util" "github.com/openconfig/ygot/ygot" "github.com/openconfig/ygot/ytypes" log "github.com/sirupsen/logrus" "os" "os/signal" "reflect" "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) transport.(*nucleus.Gnmi).Unmarshal = southbound.Unmarshal() if err != nil { return err } return nil } func Set(params ...string) error { lenArgs := len(params) - 3 if err := setup(params[lenArgs:]...); err != nil { return err } 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...) 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 } log.Info("wrote root resource to file") default: device := &openconfig.Device{} 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 } if err := transport.ProcessResponse(resp, device, southbound.Schema()); err != nil { return err } log.Infof("response: %v", resp) } return nil } func Subscribe(params ...string) error { o := &gnmi.SubscribeOptions{ 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) ctx := context.WithValue(context.Background(), "opts", o) 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 } // 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 } func (s sbi) Id() uuid.UUID { panic("implement me") }