diff --git a/cmd/gnmi-target/oc-target.go b/cmd/gnmi-target/oc-target.go deleted file mode 100644 index a6971ee0afe3e681b1c91433c140bf9fca0c70da..0000000000000000000000000000000000000000 --- 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 556310961aad37a3599c9c4c3cbf69090cf8b2cf..4239ea9faaa0bb556e3f122cba9441b0e9b352e5 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 7d098f7361fde2d58cf418693dd9764d8303760b..21902da124c9ee07229b37293db009056f0c21e1 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 8d08079dc5426d6c51814a57e84c4f295c921ad6..d4a9958baf56c494705949b3deedb61075ae5e65 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 b46fd00dedf9b7c5ff3352192217752c203a4ce6..3af5d9b50e74aa79c77a87f116920349374fbdc9 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 9683b7af7c755c01d000a62d0a3d2f811d8d8c18..b3921329db044ec15235d12f7a57d76331291b4b 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 24af7305c9a272af5d4cced6441d1994fb88ddd0..04fd14215216703ae7259fc4a5c8619c30dab5cd 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) } }) }