diff --git a/forks/google/gnmi/model.go b/forks/google/gnmi/model.go deleted file mode 100644 index cf704a545dda6c5f0ca8bc367cfcce93220da010..0000000000000000000000000000000000000000 --- a/forks/google/gnmi/model.go +++ /dev/null @@ -1,79 +0,0 @@ -/* Copyright 2017 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 - - https://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 gnmi - -import ( - "errors" - "fmt" - "reflect" - "sort" - - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/ygot" - "github.com/openconfig/ygot/ytypes" - - pb "github.com/openconfig/gnmi/proto/gnmi" -) - -// JSONUnmarshaler is the signature of the Unmarshal() function in the GoStruct code generated by openconfig ygot library. -type JSONUnmarshaler func([]byte, ygot.GoStruct, ...ytypes.UnmarshalOpt) error - -// GoStructEnumData is the data type to maintain GoStruct enum type. -type GoStructEnumData map[string]map[int64]ygot.EnumDefinition - -// Model contains the model data and GoStruct information for the device to config. -type Model struct { - modelData []*pb.ModelData - structRootType reflect.Type - schemaTreeRoot *yang.Entry - jsonUnmarshaler JSONUnmarshaler - enumData GoStructEnumData -} - -// NewModel returns an instance of Model struct. -func NewModel(m []*pb.ModelData, t reflect.Type, r *yang.Entry, f JSONUnmarshaler, e GoStructEnumData) *Model { - return &Model{ - modelData: m, - structRootType: t, - schemaTreeRoot: r, - jsonUnmarshaler: f, - enumData: e, - } -} - -func (m *Model) newRootValue() interface{} { - return reflect.New(m.structRootType.Elem()).Interface() -} - -// NewConfigStruct creates a ValidatedGoStruct of this model from jsonConfig. If jsonConfig is nil, creates an empty GoStruct. -func (m *Model) NewConfigStruct(jsonConfig []byte) (ygot.ValidatedGoStruct, error) { - rootStruct, ok := m.newRootValue().(ygot.ValidatedGoStruct) - if !ok { - return nil, errors.New("root node is not a ygot.ValidatedGoStruct") - } - - return rootStruct, nil -} - -// SupportedModels returns a list of supported models. -func (m *Model) SupportedModels() []string { - mDesc := make([]string, len(m.modelData)) - for i, m := range m.modelData { - mDesc[i] = fmt.Sprintf("%s %s", m.Name, m.Version) - } - sort.Strings(mDesc) - return mDesc -} diff --git a/forks/google/gnmi/server.go b/forks/google/gnmi/server.go deleted file mode 100644 index 21e54686916bfab859bd4acf18e6412516c7603f..0000000000000000000000000000000000000000 --- a/forks/google/gnmi/server.go +++ /dev/null @@ -1,602 +0,0 @@ -/* Copyright 2017 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 - - https://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 gnmi implements a gnmi server to mock a device with YANG models. -package gnmi - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "fmt" - "io/ioutil" - "reflect" - "strconv" - "sync" - "time" - - "golang.org/x/net/context" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/golang/protobuf/proto" - "github.com/openconfig/gnmi/value" - "github.com/openconfig/ygot/util" - "github.com/openconfig/ygot/ygot" - "github.com/openconfig/ygot/ytypes" - log "github.com/sirupsen/logrus" - - dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" - pb "github.com/openconfig/gnmi/proto/gnmi" -) - -// ConfigCallback is the signature of the function to apply a validated config to the physical device. -type ConfigCallback func(ygot.ValidatedGoStruct) error - -var ( - pbRootPath = &pb.Path{} - supportedEncodings = []pb.Encoding{pb.Encoding_PROTO, pb.Encoding_JSON_IETF, pb.Encoding_JSON} -) - -// Server struct maintains the data structure for device config and implements the interface of gnmi server. -// It supports Capabilities, Get, and Set APIs. -// Typical usage: -// g := grpc.NewServer() -// s, err := Server.NewServer(model, config, callback) -// pb.NewServer(g, s) -// reflection.Register(g) -// listen, err := net.Listen("tcp", ":8080") -// g.Serve(listen) -// -// For a real device, apply the config changes to the hardware in the callback function. -// Arguments: -// newConfig: new root config to be applied on the device. - -type Server struct { - model *Model - callback ConfigCallback - - config ygot.ValidatedGoStruct - mu sync.RWMutex // mu is the RW lock to protect the access to config -} - -// NewServer creates an instance of Server with given json config. -func NewServer(model *Model, config []byte, callback ConfigCallback) (*Server, error) { - rootStruct, err := model.NewConfigStruct(config) - if err != nil { - return nil, err - } - s := &Server{ - model: model, - config: rootStruct, - callback: callback, - } - if config != nil && s.callback != nil { - if err := s.callback(rootStruct); err != nil { - return nil, err - } - } - return s, nil -} - -// checkEncodingAndModel checks whether encoding and models are supported by the server. Return error if anything is unsupported. -func (s *Server) checkEncodingAndModel(encoding pb.Encoding, models []*pb.ModelData) error { - hasSupportedEncoding := false - for _, supportedEncoding := range supportedEncodings { - if encoding == supportedEncoding { - hasSupportedEncoding = true - break - } - } - if !hasSupportedEncoding { - return fmt.Errorf("unsupported encoding: %s", pb.Encoding_name[int32(encoding)]) - } - for _, m := range models { - isSupported := false - for _, supportedModel := range s.model.modelData { - if reflect.DeepEqual(m, supportedModel) { - isSupported = true - break - } - } - if !isSupported { - return fmt.Errorf("unsupported model: %v", m) - } - } - return nil -} - -// doDelete deletes the path from the json tree if the path exists. If success, -// it calls the callback function to apply the change to the device hardware. -func (s *Server) doDelete(jsonTree map[string]interface{}, prefix, path *pb.Path) (*pb.UpdateResult, error) { - // Update json tree of the device config - var curNode interface{} = jsonTree - pathDeleted := false - fullPath := gnmiFullPath(prefix, path) - schema := s.model.schemaTreeRoot - for i, elem := range fullPath.Elem { // Delete sub-tree or leaf node. - node, ok := curNode.(map[string]interface{}) - if !ok { - break - } - - // Delete node - if i == len(fullPath.Elem)-1 { - if elem.GetKey() == nil { - delete(node, elem.Name) - pathDeleted = true - break - } - pathDeleted = deleteKeyedListEntry(node, elem) - break - } - - if curNode, schema = getChildNode(node, schema, elem, false); curNode == nil { - break - } - } - if reflect.DeepEqual(fullPath, pbRootPath) { // Delete root - for k := range jsonTree { - delete(jsonTree, k) - } - } - - // Apply the validated operation to the config tree and device. - if pathDeleted { - newConfig, err := s.toGoStruct(jsonTree) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - if s.callback != nil { - if applyErr := s.callback(newConfig); applyErr != nil { - if rollbackErr := s.callback(s.config); rollbackErr != nil { - return nil, status.Errorf(codes.Internal, "error in rollback the failed operation (%v): %v", applyErr, rollbackErr) - } - return nil, status.Errorf(codes.Aborted, "error in applying operation to device: %v", applyErr) - } - } - } - return &pb.UpdateResult{ - Path: path, - Op: pb.UpdateResult_DELETE, - }, nil -} - -// doReplaceOrUpdate validates the replace or update operation to be applied to -// the device, modifies the json tree of the config struct, then calls the -// callback function to apply the operation to the device hardware. -func (s *Server) doReplaceOrUpdate(jsonTree map[string]interface{}, op pb.UpdateResult_Operation, prefix, path *pb.Path, val *pb.TypedValue) (*pb.UpdateResult, error) { - // Validate the operation. - fullPath := gnmiFullPath(prefix, path) - emptyNode, _, err := ytypes.GetOrCreateNode(s.model.schemaTreeRoot, s.model.newRootValue(), fullPath) - if err != nil { - return nil, status.Errorf(codes.NotFound, "path %v is not found in the config structure: %v", fullPath, err) - } - var nodeVal interface{} - nodeStruct, ok := emptyNode.(ygot.ValidatedGoStruct) - if ok { - if err := s.model.jsonUnmarshaler(val.GetJsonIetfVal(), nodeStruct); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "unmarshaling json data to config struct fails: %v", err) - } - if err := nodeStruct.ΛValidate(); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "config data validation fails: %v", err) - } - var err error - if nodeVal, err = ygot.ConstructIETFJSON(nodeStruct, &ygot.RFC7951JSONConfig{}); err != nil { - msg := fmt.Sprintf("error in constructing IETF JSON tree from config struct: %v", err) - log.Error(msg) - return nil, status.Error(codes.Internal, msg) - } - } else { - var err error - if nodeVal, err = value.ToScalar(val); err != nil { - return nil, status.Errorf(codes.Internal, "cannot convert leaf node to scalar type: %v", err) - } - } - - // Update json tree of the device config. - var curNode interface{} = jsonTree - schema := s.model.schemaTreeRoot - for i, elem := range fullPath.Elem { - switch node := curNode.(type) { - case map[string]interface{}: - // Set node value. - if i == len(fullPath.Elem)-1 { - if elem.GetKey() == nil { - if grpcStatusError := setPathWithoutAttribute(op, node, elem, nodeVal); grpcStatusError != nil { - return nil, grpcStatusError - } - break - } - if grpcStatusError := setPathWithAttribute(op, node, elem, nodeVal); grpcStatusError != nil { - return nil, grpcStatusError - } - break - } - - if curNode, schema = getChildNode(node, schema, elem, true); curNode == nil { - return nil, status.Errorf(codes.NotFound, "path elem not found: %v", elem) - } - case []interface{}: - return nil, status.Errorf(codes.NotFound, "incompatible path elem: %v", elem) - default: - return nil, status.Errorf(codes.Internal, "wrong node type: %T", curNode) - } - } - if reflect.DeepEqual(fullPath, pbRootPath) { // Replace/Update root. - if op == pb.UpdateResult_UPDATE { - return nil, status.Error(codes.Unimplemented, "update the root of config tree is unsupported") - } - nodeValAsTree, ok := nodeVal.(map[string]interface{}) - if !ok { - return nil, status.Errorf(codes.InvalidArgument, "expect a tree to replace the root, got a scalar value: %T", nodeVal) - } - for k := range jsonTree { - delete(jsonTree, k) - } - for k, v := range nodeValAsTree { - jsonTree[k] = v - } - } - newConfig, err := s.toGoStruct(jsonTree) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - // Apply the validated operation to the device. - if s.callback != nil { - if applyErr := s.callback(newConfig); applyErr != nil { - if rollbackErr := s.callback(s.config); rollbackErr != nil { - return nil, status.Errorf(codes.Internal, "error in rollback the failed operation (%v): %v", applyErr, rollbackErr) - } - return nil, status.Errorf(codes.Aborted, "error in applying operation to device: %v", applyErr) - } - } - return &pb.UpdateResult{ - Path: path, - Op: op, - }, nil -} - -func (s *Server) toGoStruct(jsonTree map[string]interface{}) (ygot.ValidatedGoStruct, error) { - jsonDump, err := json.Marshal(jsonTree) - if err != nil { - return nil, fmt.Errorf("error in marshaling IETF JSON tree to bytes: %v", err) - } - goStruct, err := s.model.NewConfigStruct(jsonDump) - if err != nil { - return nil, fmt.Errorf("error in creating config struct from IETF JSON data: %v", err) - } - return goStruct, nil -} - -// 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{}).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) - } - defer r.Close() - b, err := ioutil.ReadAll(r) - if err != nil { - return nil, fmt.Errorf("error in reading gzip data: %v", err) - } - desc := &dpb.FileDescriptorProto{} - if err := proto.Unmarshal(b, desc); err != nil { - return nil, fmt.Errorf("error in unmarshaling proto: %v", err) - } - ver, err := proto.GetExtension(desc.Options, pb.E_GnmiService) - if err != nil { - return nil, fmt.Errorf("error in getting version from proto extension: %v", err) - } - return ver.(*string), nil -} - -// deleteKeyedListEntry deletes the keyed list entry from node that matches the -// path elem. If the entry is the only one in keyed list, deletes the entire -// list. If the entry is found and deleted, the function returns true. If it is -// not found, the function returns false. -func deleteKeyedListEntry(node map[string]interface{}, elem *pb.PathElem) bool { - curNode, ok := node[elem.Name] - if !ok { - return false - } - - keyedList, ok := curNode.([]interface{}) - if !ok { - return false - } - for i, n := range keyedList { - m, ok := n.(map[string]interface{}) - if !ok { - log.Errorf("expect map[string]interface{} for a keyed list entry, got %T", n) - return false - } - keyMatching := true - for k, v := range elem.Key { - attrVal, ok := m[k] - if !ok { - return false - } - if v != fmt.Sprintf("%v", attrVal) { - keyMatching = false - break - } - } - if keyMatching { - listLen := len(keyedList) - if listLen == 1 { - delete(node, elem.Name) - return true - } - keyedList[i] = keyedList[listLen-1] - node[elem.Name] = keyedList[0 : listLen-1] - return true - } - } - return false -} - -// 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.GetElem() != nil { - fullPath.Elem = append(prefix.GetElem(), path.GetElem()...) - } - return fullPath -} - -// isNIl checks if an interface is nil or its value is nil. -func isNil(i interface{}) bool { - if i == nil { - return true - } - switch kind := reflect.ValueOf(i).Kind(); kind { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return reflect.ValueOf(i).IsNil() - default: - return false - } -} - -// setPathWithAttribute replaces or updates a child node of curNode in the IETF -// JSON config tree, where the child node is indexed by pathElem with attribute. -// The function returns grpc status error if unsuccessful. -func setPathWithAttribute(op pb.UpdateResult_Operation, curNode map[string]interface{}, pathElem *pb.PathElem, nodeVal interface{}) error { - nodeValAsTree, ok := nodeVal.(map[string]interface{}) - if !ok { - return status.Errorf(codes.InvalidArgument, "expect nodeVal is a json node of map[string]interface{}, received %T", nodeVal) - } - m := getKeyedListEntry(curNode, pathElem, true) - if m == nil { - return status.Errorf(codes.NotFound, "path elem not found: %v", pathElem) - } - if op == pb.UpdateResult_REPLACE { - for k := range m { - delete(m, k) - } - } - for attrKey, attrVal := range pathElem.GetKey() { - m[attrKey] = attrVal - if asNum, err := strconv.ParseFloat(attrVal, 64); err == nil { - m[attrKey] = asNum - } - for k, v := range nodeValAsTree { - if k == attrKey && fmt.Sprintf("%v", v) != attrVal { - return status.Errorf(codes.InvalidArgument, "invalid config data: %v is a path attribute", k) - } - } - } - for k, v := range nodeValAsTree { - m[k] = v - } - return nil -} - -// setPathWithoutAttribute replaces or updates a child node of curNode in the -// IETF config tree, where the child node is indexed by pathElem without -// attribute. The function returns grpc status error if unsuccessful. -func setPathWithoutAttribute(op pb.UpdateResult_Operation, curNode map[string]interface{}, pathElem *pb.PathElem, nodeVal interface{}) error { - target, hasElem := curNode[pathElem.Name] - nodeValAsTree, nodeValIsTree := nodeVal.(map[string]interface{}) - if op == pb.UpdateResult_REPLACE || !hasElem || !nodeValIsTree { - curNode[pathElem.Name] = nodeVal - return nil - } - targetAsTree, ok := target.(map[string]interface{}) - if !ok { - return status.Errorf(codes.Internal, "error in setting path: expect map[string]interface{} to update, got %T", target) - } - for k, v := range nodeValAsTree { - targetAsTree[k] = v - } - return nil -} - -// Capabilities returns supported encodings and supported models. -func (s *Server) Capabilities(ctx context.Context, req *pb.CapabilityRequest) (*pb.CapabilityResponse, error) { - ver, err := getGNMIServiceVersion() - if err != nil { - return nil, status.Errorf(codes.Internal, "error in getting gnmi service version: %v", err) - } - return &pb.CapabilityResponse{ - SupportedModels: s.model.modelData, - SupportedEncodings: supportedEncodings, - GNMIVersion: *ver, - }, nil -} - -// Get implements the Get RPC in gNMI spec. -func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) { - if req.GetType() != pb.GetRequest_ALL { - return nil, status.Errorf(codes.Unimplemented, "unsupported request type: %s", pb.GetRequest_DataType_name[int32(req.GetType())]) - } - if err := s.checkEncodingAndModel(req.GetEncoding(), req.GetUseModels()); err != nil { - return nil, status.Error(codes.Unimplemented, err.Error()) - } - - prefix := req.GetPrefix() - paths := req.GetPath() - notifications := make([]*pb.Notification, 0) - - s.mu.RLock() - defer s.mu.RUnlock() - - for _, path := range paths { - // Get schema node for path from config struct. - fullPath := path - if prefix != nil { - fullPath = gnmiFullPath(prefix, path) - } - if fullPath.GetElem() == nil && fullPath.GetElement() != nil { - return nil, status.Error(codes.Unimplemented, "deprecated path element type is unsupported") - } - opts := []ytypes.GetNodeOpt{&ytypes.GetHandleWildcards{}, &ytypes.GetPartialKeyMatch{}} - nodes, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config, fullPath, opts...) - if len(nodes) == 0 || err != nil || util.IsValueNil(nodes[0].Data) { - return nil, status.Errorf(codes.NotFound, "path %v not found: %v", fullPath, err) - } - for _, n := range nodes { - node := n.Data - ts := time.Now().UnixNano() - - nodeStruct, ok := node.(ygot.GoStruct) - // Return leaf node. - if !ok { - var val *pb.TypedValue - switch kind := reflect.ValueOf(node).Kind(); kind { - case reflect.Ptr, reflect.Interface: - var err error - val, err = value.FromScalar(reflect.ValueOf(node).Elem().Interface()) - if err != nil { - msg := fmt.Sprintf("leaf node %v does not contain a scalar type value: %v", path, err) - log.Error(msg) - return nil, status.Error(codes.Internal, msg) - } - case reflect.Int64: - enumMap, ok := s.model.enumData[reflect.TypeOf(node).Name()] - if !ok { - return nil, status.Error(codes.Internal, "not a GoStruct enumeration type") - } - val = &pb.TypedValue{ - Value: &pb.TypedValue_StringVal{ - StringVal: enumMap[reflect.ValueOf(node).Int()].Name, - }, - } - default: - return nil, status.Errorf(codes.Internal, "unexpected kind of leaf node type: %v %v", node, kind) - } - - update := &pb.Update{Path: path, Val: val} - notification := &pb.Notification{ - Timestamp: ts, - Prefix: prefix, - Update: []*pb.Update{update}, - } - notifications = append(notifications, notification) - continue - } - - if req.GetUseModels() != nil { - return nil, status.Errorf(codes.Unimplemented, "filtering Get using use_models is unsupported, got: %v", req.GetUseModels()) - } - - nots, err := ygot.TogNMINotifications(nodeStruct, ts, ygot.GNMINotificationsConfig{ - UsePathElem: false, - StringSlicePrefix: []string{"interfaces", "interface"}, - }) - - if err != nil { - return nil, err - } - - notifications = append(notifications, nots...) - - } - } - - return &pb.GetResponse{Notification: notifications}, nil -} - -// Set implements the Set RPC in gNMI spec. -func (s *Server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, error) { - s.mu.Lock() - defer s.mu.Unlock() - - jsonTree, err := ygot.ConstructIETFJSON(s.config, &ygot.RFC7951JSONConfig{}) - if err != nil { - msg := fmt.Sprintf("error in constructing IETF JSON tree from config struct: %v", err) - log.Error(msg) - return nil, status.Error(codes.Internal, msg) - } - - prefix := req.GetPrefix() - var results []*pb.UpdateResult - - for _, path := range req.GetDelete() { - res, grpcStatusError := s.doDelete(jsonTree, prefix, path) - if grpcStatusError != nil { - return nil, grpcStatusError - } - results = append(results, res) - } - for _, upd := range req.GetReplace() { - res, grpcStatusError := s.doReplaceOrUpdate(jsonTree, pb.UpdateResult_REPLACE, prefix, upd.GetPath(), upd.GetVal()) - if grpcStatusError != nil { - return nil, grpcStatusError - } - results = append(results, res) - } - for _, upd := range req.GetUpdate() { - res, grpcStatusError := s.doReplaceOrUpdate(jsonTree, pb.UpdateResult_UPDATE, prefix, upd.GetPath(), upd.GetVal()) - if grpcStatusError != nil { - return nil, grpcStatusError - } - results = append(results, res) - } - - jsonDump, err := json.Marshal(jsonTree) - if err != nil { - msg := fmt.Sprintf("error in marshaling IETF JSON tree to bytes: %v", err) - log.Error(msg) - return nil, status.Error(codes.Internal, msg) - } - rootStruct, err := s.model.NewConfigStruct(jsonDump) - if err != nil { - msg := fmt.Sprintf("error in creating config struct from IETF JSON data: %v", err) - log.Error(msg) - return nil, status.Error(codes.Internal, msg) - } - s.config = rootStruct - return &pb.SetResponse{ - Prefix: req.GetPrefix(), - Response: results, - }, nil -} - -// Subscribe method is not implemented. -func (s *Server) Subscribe(stream pb.GNMI_SubscribeServer) error { - return status.Error(codes.Unimplemented, "Subscribe is not implemented.") -} - -// InternalUpdate is an experimental feature to let the server update its -// internal states. Use it with your own risk. -func (s *Server) InternalUpdate(fp func(config ygot.ValidatedGoStruct) error) error { - s.mu.Lock() - defer s.mu.Unlock() - return fp(s.config) -} diff --git a/forks/google/gnmi/server_test.go b/forks/google/gnmi/server_test.go deleted file mode 100644 index 68ffea696df150f69e722256389f472a875de107..0000000000000000000000000000000000000000 --- a/forks/google/gnmi/server_test.go +++ /dev/null @@ -1,1161 +0,0 @@ -/* Copyright 2017 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 - - https://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 gnmi - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/golang/protobuf/proto" - "github.com/openconfig/gnmi/value" - "github.com/openconfig/ygot/ygot" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - pb "github.com/openconfig/gnmi/proto/gnmi" - - "github.com/google/gnxi/gnmi/modeldata" - "github.com/google/gnxi/gnmi/modeldata/gostruct" -) - -var ( - // model is the model for test config server. - model = &Model{ - modelData: modeldata.ModelData, - structRootType: reflect.TypeOf((*gostruct.Device)(nil)), - schemaTreeRoot: gostruct.SchemaTree["Device"], - jsonUnmarshaler: gostruct.Unmarshal, - enumData: gostruct.ΛEnum, - } -) - -func TestCapabilities(t *testing.T) { - s, err := NewServer(model, nil, nil) - if err != nil { - t.Fatalf("error in creating server: %v", err) - } - resp, err := s.Capabilities(nil, &pb.CapabilityRequest{}) - if err != nil { - t.Fatalf("got error %v, want nil", err) - } - if !reflect.DeepEqual(resp.GetSupportedModels(), model.modelData) { - t.Errorf("got supported models %v\nare not the same as\nmodel supported by the server %v", resp.GetSupportedModels(), model.modelData) - } - if !reflect.DeepEqual(resp.GetSupportedEncodings(), supportedEncodings) { - t.Errorf("got supported encodings %v\nare not the same as\nencodings supported by the server %v", resp.GetSupportedEncodings(), supportedEncodings) - } -} - -func TestGet(t *testing.T) { - jsonConfigRoot := `{ - "openconfig-system:system": { - "openconfig-openflow:openflow": { - "agent": { - "config": { - "failure-mode": "SECURE", - "max-backoff": 10 - } - } - } - }, - "openconfig-platform:components": { - "component": [ - { - "config": { - "name": "swpri1-1-1" - }, - "name": "swpri1-1-1" - } - ] - } - }` - - s, err := NewServer(model, []byte(jsonConfigRoot), nil) - if err != nil { - t.Fatalf("error in creating server: %v", err) - } - - tds := []struct { - desc string - textPbPath string - modelData []*pb.ModelData - wantRetCode codes.Code - wantRespVal interface{} - }{{ - desc: "get valid but non-existing node", - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - `, - wantRetCode: codes.NotFound, - }, { - desc: "root node", - wantRetCode: codes.OK, - wantRespVal: jsonConfigRoot, - }, { - desc: "get non-enum type", - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "agent" > - elem: <name: "config" > - elem: <name: "max-backoff" > - `, - wantRetCode: codes.OK, - wantRespVal: uint64(10), - }, { - desc: "get enum type", - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "agent" > - elem: <name: "config" > - elem: <name: "failure-mode" > - `, - wantRetCode: codes.OK, - wantRespVal: "SECURE", - }, { - desc: "root child node", - textPbPath: `elem: <name: "components" >`, - wantRetCode: codes.OK, - wantRespVal: `{ - "openconfig-platform:component": [{ - "config": { - "name": "swpri1-1-1" - }, - "name": "swpri1-1-1" - }]}`, - }, { - desc: "node with attribute", - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - >`, - wantRetCode: codes.OK, - wantRespVal: `{ - "openconfig-platform:config": {"name": "swpri1-1-1"}, - "openconfig-platform:name": "swpri1-1-1" - }`, - }, { - desc: "node with attribute in its parent", - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - > - elem: <name: "config" >`, - wantRetCode: codes.OK, - wantRespVal: `{"openconfig-platform:name": "swpri1-1-1"}`, - }, { - desc: "ref leaf node", - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - > - elem: <name: "name" >`, - wantRetCode: codes.OK, - wantRespVal: "swpri1-1-1", - }, { - desc: "regular leaf node", - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - > - elem: <name: "config" > - elem: <name: "name" >`, - wantRetCode: codes.OK, - wantRespVal: "swpri1-1-1", - }, { - desc: "non-existing node: wrong path name", - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "foo" value: "swpri1-1-1" > - > - elem: <name: "bar" >`, - wantRetCode: codes.NotFound, - }, { - desc: "non-existing node: wrong path attribute", - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "foo" value: "swpri2-2-2" > - > - elem: <name: "name" >`, - wantRetCode: codes.NotFound, - }, { - desc: "use of model data not supported", - modelData: []*pb.ModelData{{}}, - wantRetCode: codes.Unimplemented, - }} - - for _, td := range tds { - t.Run(td.desc, func(t *testing.T) { - runTestGet(t, s, td.textPbPath, td.wantRetCode, td.wantRespVal, td.modelData) - }) - } -} - -// runTestGet requests a path from the server by Get grpc call, and compares if -// the return code and response value are expected. -func runTestGet(t *testing.T, s *Server, textPbPath string, wantRetCode codes.Code, wantRespVal interface{}, useModels []*pb.ModelData) { - // Send request - var pbPath pb.Path - if err := proto.UnmarshalText(textPbPath, &pbPath); err != nil { - t.Fatalf("error in unmarshaling path: %v", err) - } - req := &pb.GetRequest{ - Path: []*pb.Path{&pbPath}, - Encoding: pb.Encoding_JSON_IETF, - UseModels: useModels, - } - resp, err := s.Get(nil, req) - - // Check return code - gotRetStatus, ok := status.FromError(err) - if !ok { - t.Fatal("got a non-grpc error from grpc call") - } - if gotRetStatus.Code() != wantRetCode { - t.Fatalf("got return code %v, want %v", gotRetStatus.Code(), wantRetCode) - } - - // Check response value - var gotVal interface{} - if resp != nil { - notifs := resp.GetNotification() - if len(notifs) != 1 { - t.Fatalf("got %d notifications, want 1", len(notifs)) - } - updates := notifs[0].GetUpdate() - if len(updates) != 1 { - t.Fatalf("got %d updates in the notification, want 1", len(updates)) - } - val := updates[0].GetVal() - if val.GetJsonIetfVal() == nil { - gotVal, err = value.ToScalar(val) - if err != nil { - t.Errorf("got: %v, want a scalar value", gotVal) - } - } else { - // Unmarshal json data to gotVal container for comparison - if err := json.Unmarshal(val.GetJsonIetfVal(), &gotVal); err != nil { - t.Fatalf("error in unmarshaling IETF JSON data to json container: %v", err) - } - var wantJSONStruct interface{} - if err := json.Unmarshal([]byte(wantRespVal.(string)), &wantJSONStruct); err != nil { - t.Fatalf("error in unmarshaling IETF JSON data to json container: %v", err) - } - wantRespVal = wantJSONStruct - } - } - - if !reflect.DeepEqual(gotVal, wantRespVal) { - t.Errorf("got: %v (%T),\nwant %v (%T)", gotVal, gotVal, wantRespVal, wantRespVal) - } -} - -type gnmiSetTestCase struct { - desc string // description of test case. - initConfig string // config before the operation. - op pb.UpdateResult_Operation // operation type. - textPbPath string // text format of gnmi Path proto. - val *pb.TypedValue // value for UPDATE/REPLACE operations. always nil for DELETE. - wantRetCode codes.Code // grpc return code. - wantConfig string // config after the operation. -} - -func TestDelete(t *testing.T) { - tests := []gnmiSetTestCase{{ - desc: "delete leaf node", - initConfig: `{ - "system": { - "config": { - "hostname": "switch_a", - "login-banner": "Hello!" - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "config" > - elem: <name: "login-banner" > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "config": { - "hostname": "switch_a" - } - } - }`, - }, { - desc: "delete sub-tree", - initConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - }, - "config": { - "hostname": "switch_a" - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "config": { - "hostname": "switch_a" - } - } - }`, - }, { - desc: "delete a sub-tree with only one leaf node", - initConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - }, - "config": { - "hostname": "switch_a" - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - elem: <name: "config" > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "config": { - "hostname": "switch_a" - } - } - }`, - }, { - desc: "delete a leaf node whose parent has only this child", - initConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - }, - "config": { - "hostname": "switch_a" - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - elem: <name: "config" > - elem: <name: "timezone-name" > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "config": { - "hostname": "switch_a" - } - } - }`, - }, { - desc: "delete root", - initConfig: `{ - "system": { - "config": { - "hostname": "switch_a" - } - } - }`, - op: pb.UpdateResult_DELETE, - wantRetCode: codes.OK, - wantConfig: `{}`, - }, { - desc: "delete non-existing node", - initConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - elem: <name: "config" > - elem: <name: "foo-bar" > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - }, { - desc: "delete node with non-existing precedent path", - initConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - elem: <name: "foo-bar" > - elem: <name: "timezone-name" > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - }, { - desc: "delete node with non-existing attribute in precedent path", - initConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - elem: < - name: "config" - key: <key: "name" value: "foo" > - > - elem: <name: "timezone-name" >`, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - }, { - desc: "delete node with non-existing attribute", - initConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - elem: <name: "config" > - elem: < - name: "timezone-name" - key: <key: "name" value: "foo" > - > - elem: <name: "timezone-name" >`, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - } - } - }`, - }, { - desc: "delete leaf node with attribute in its precedent path", - initConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - }, - "state": { - "name": "swpri1-1-1", - "mfg-name": "foo bar inc." - } - } - ] - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - > - elem: <name: "state" > - elem: <name: "mfg-name" >`, - wantRetCode: codes.OK, - wantConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - }, - "state": { - "name": "swpri1-1-1" - } - } - ] - } - }`, - }, { - desc: "delete sub-tree with attribute in its precedent path", - initConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - }, - "state": { - "name": "swpri1-1-1", - "mfg-name": "foo bar inc." - } - } - ] - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - > - elem: <name: "state" >`, - wantRetCode: codes.OK, - wantConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - } - } - ] - } - }`, - }, { - desc: "delete path node with attribute", - initConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - } - }, - { - "name": "swpri1-1-2", - "config": { - "name": "swpri1-1-2" - } - } - ] - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - >`, - wantRetCode: codes.OK, - wantConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-2", - "config": { - "name": "swpri1-1-2" - } - } - ] - } - }`, - }, { - desc: "delete path node with int type attribute", - initConfig: `{ - "system": { - "openflow": { - "controllers": { - "controller": [ - { - "config": { - "name": "main" - }, - "connections": { - "connection": [ - { - "aux-id": 0, - "config": { - "address": "192.0.2.10", - "aux-id": 0 - } - } - ] - }, - "name": "main" - } - ] - } - } - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "controllers" > - elem: < - name: "controller" - key: <key: "name" value: "main" > - > - elem: <name: "connections" > - elem: < - name: "connection" - key: <key: "aux-id" value: "0" > - > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "openflow": { - "controllers": { - "controller": [ - { - "config": { - "name": "main" - }, - "name": "main" - } - ] - } - } - } - }`, - }, { - desc: "delete leaf node with non-existing attribute value", - initConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - } - } - ] - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "foo" > - >`, - wantRetCode: codes.OK, - wantConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - } - } - ] - } - }`, - }, { - desc: "delete leaf node with non-existing attribute value in precedent path", - initConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - }, - "state": { - "name": "swpri1-1-1", - "mfg-name": "foo bar inc." - } - } - ] - } - }`, - op: pb.UpdateResult_DELETE, - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "foo" > - > - elem: <name: "state" > - elem: <name: "mfg-name" > - `, - wantRetCode: codes.OK, - wantConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - }, - "state": { - "name": "swpri1-1-1", - "mfg-name": "foo bar inc." - } - } - ] - } - }`, - }} - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - runTestSet(t, model, tc) - }) - } -} - -func TestReplace(t *testing.T) { - systemConfig := `{ - "system": { - "clock": { - "config": { - "timezone-name": "Europe/Stockholm" - } - }, - "config": { - "hostname": "switch_a", - "login-banner": "Hello!" - } - } - }` - - tests := []gnmiSetTestCase{{ - desc: "replace root", - initConfig: `{}`, - op: pb.UpdateResult_REPLACE, - val: &pb.TypedValue{ - Value: &pb.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte(systemConfig), - }}, - wantRetCode: codes.OK, - wantConfig: systemConfig, - }, { - desc: "replace a subtree", - initConfig: `{}`, - op: pb.UpdateResult_REPLACE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "clock" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte(`{"config": {"timezone-name": "US/New York"}}`), - }, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "clock": { - "config": { - "timezone-name": "US/New York" - } - } - } - }`, - }, { - desc: "replace a keyed list subtree", - initConfig: `{}`, - op: pb.UpdateResult_REPLACE, - textPbPath: ` - elem: <name: "components" > - elem: < - name: "component" - key: <key: "name" value: "swpri1-1-1" > - >`, - val: &pb.TypedValue{ - Value: &pb.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte(`{"config": {"name": "swpri1-1-1"}}`), - }, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "components": { - "component": [ - { - "name": "swpri1-1-1", - "config": { - "name": "swpri1-1-1" - } - } - ] - } - }`, - }, { - desc: "replace node with int type attribute in its precedent path", - initConfig: `{ - "system": { - "openflow": { - "controllers": { - "controller": [ - { - "config": { - "name": "main" - }, - "name": "main" - } - ] - } - } - } - }`, - op: pb.UpdateResult_REPLACE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "controllers" > - elem: < - name: "controller" - key: <key: "name" value: "main" > - > - elem: <name: "connections" > - elem: < - name: "connection" - key: <key: "aux-id" value: "0" > - > - elem: <name: "config" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte(`{"address": "192.0.2.10", "aux-id": 0}`), - }, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "openflow": { - "controllers": { - "controller": [ - { - "config": { - "name": "main" - }, - "connections": { - "connection": [ - { - "aux-id": 0, - "config": { - "address": "192.0.2.10", - "aux-id": 0 - } - } - ] - }, - "name": "main" - } - ] - } - } - } - }`, - }, { - desc: "replace a leaf node of int type", - initConfig: `{}`, - op: pb.UpdateResult_REPLACE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "agent" > - elem: <name: "config" > - elem: <name: "backoff-interval" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_IntVal{IntVal: 5}, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "openflow": { - "agent": { - "config": { - "backoff-interval": 5 - } - } - } - } - }`, - }, { - desc: "replace a leaf node of string type", - initConfig: `{}`, - op: pb.UpdateResult_REPLACE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "agent" > - elem: <name: "config" > - elem: <name: "datapath-id" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_StringVal{StringVal: "00:16:3e:00:00:00:00:00"}, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "openflow": { - "agent": { - "config": { - "datapath-id": "00:16:3e:00:00:00:00:00" - } - } - } - } - }`, - }, { - desc: "replace a leaf node of enum type", - initConfig: `{}`, - op: pb.UpdateResult_REPLACE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "agent" > - elem: <name: "config" > - elem: <name: "failure-mode" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_StringVal{StringVal: "SECURE"}, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "openflow": { - "agent": { - "config": { - "failure-mode": "SECURE" - } - } - } - } - }`, - }, { - desc: "replace an non-existing leaf node", - initConfig: `{}`, - op: pb.UpdateResult_REPLACE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "openflow" > - elem: <name: "agent" > - elem: <name: "config" > - elem: <name: "foo-bar" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_StringVal{StringVal: "SECURE"}, - }, - wantRetCode: codes.NotFound, - wantConfig: `{}`, - }} - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - runTestSet(t, model, tc) - }) - } -} - -func TestUpdate(t *testing.T) { - tests := []gnmiSetTestCase{{ - desc: "update leaf node", - initConfig: `{ - "system": { - "config": { - "hostname": "switch_a" - } - } - }`, - op: pb.UpdateResult_UPDATE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "config" > - elem: <name: "domain-name" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_StringVal{StringVal: "foo.bar.com"}, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "config": { - "domain-name": "foo.bar.com", - "hostname": "switch_a" - } - } - }`, - }, { - desc: "update subtree", - initConfig: `{ - "system": { - "config": { - "hostname": "switch_a" - } - } - }`, - op: pb.UpdateResult_UPDATE, - textPbPath: ` - elem: <name: "system" > - elem: <name: "config" > - `, - val: &pb.TypedValue{ - Value: &pb.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte(`{"domain-name": "foo.bar.com", "hostname": "switch_a"}`), - }, - }, - wantRetCode: codes.OK, - wantConfig: `{ - "system": { - "config": { - "domain-name": "foo.bar.com", - "hostname": "switch_a" - } - } - }`, - }} - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - runTestSet(t, model, tc) - }) - } -} - -func runTestSet(t *testing.T, m *Model, tc gnmiSetTestCase) { - // Create a new server with empty config - s, err := NewServer(m, []byte(tc.initConfig), nil) - if err != nil { - t.Fatalf("error in creating config server: %v", err) - } - - // Send request - var pbPath pb.Path - if err := proto.UnmarshalText(tc.textPbPath, &pbPath); err != nil { - t.Fatalf("error in unmarshaling path: %v", err) - } - var req *pb.SetRequest - switch tc.op { - case pb.UpdateResult_DELETE: - req = &pb.SetRequest{Delete: []*pb.Path{&pbPath}} - case pb.UpdateResult_REPLACE: - req = &pb.SetRequest{Replace: []*pb.Update{{Path: &pbPath, Val: tc.val}}} - case pb.UpdateResult_UPDATE: - req = &pb.SetRequest{Update: []*pb.Update{{Path: &pbPath, Val: tc.val}}} - default: - t.Fatalf("invalid op type: %v", tc.op) - } - _, err = s.Set(nil, req) - - // Check return code - gotRetStatus, ok := status.FromError(err) - if !ok { - t.Fatal("got a non-grpc error from grpc call") - } - if gotRetStatus.Code() != tc.wantRetCode { - t.Fatalf("got return code %v, want %v\nerror message: %v", gotRetStatus.Code(), tc.wantRetCode, err) - } - - // Check server config - wantConfigStruct, err := m.NewConfigStruct([]byte(tc.wantConfig)) - if err != nil { - t.Fatalf("wantConfig data cannot be loaded as a config struct: %v", err) - } - wantConfigJSON, err := ygot.ConstructIETFJSON(wantConfigStruct, &ygot.RFC7951JSONConfig{}) - if err != nil { - t.Fatalf("error in constructing IETF JSON tree from wanted config: %v", err) - } - gotConfigJSON, err := ygot.ConstructIETFJSON(s.config, &ygot.RFC7951JSONConfig{}) - if err != nil { - t.Fatalf("error in constructing IETF JSON tree from server config: %v", err) - } - if !reflect.DeepEqual(gotConfigJSON, wantConfigJSON) { - t.Fatalf("got server config %v\nwant: %v", gotConfigJSON, wantConfigJSON) - } -} diff --git a/forks/google/gnmi/util.go b/forks/google/gnmi/util.go deleted file mode 100644 index 73d17b49f7cab79a20236681c773b50844060af4..0000000000000000000000000000000000000000 --- a/forks/google/gnmi/util.go +++ /dev/null @@ -1,102 +0,0 @@ -package gnmi - -import ( - "fmt" - "strconv" - - "github.com/openconfig/goyang/pkg/yang" - log "github.com/sirupsen/logrus" - - pb "github.com/openconfig/gnmi/proto/gnmi" -) - -// getChildNode gets a node's child with corresponding schema specified by path -// element. If not found and createIfNotExist is set as true, an empty node is -// created and returned. -func getChildNode(node map[string]interface{}, schema *yang.Entry, elem *pb.PathElem, createIfNotExist bool) (interface{}, *yang.Entry) { - var nextSchema *yang.Entry - var ok bool - - if nextSchema, ok = schema.Dir[elem.Name]; !ok { - return nil, nil - } - - var nextNode interface{} - if elem.GetKey() == nil { - if nextNode, ok = node[elem.Name]; !ok { - if createIfNotExist { - node[elem.Name] = make(map[string]interface{}) - nextNode = node[elem.Name] - } - } - return nextNode, nextSchema - } - - nextNode = getKeyedListEntry(node, elem, createIfNotExist) - return nextNode, nextSchema -} - -// getKeyedListEntry finds the keyed list entry in node by the name and key of -// path elem. If entry is not found and createIfNotExist is true, an empty entry -// will be created (the list will be created if necessary). -func getKeyedListEntry(node map[string]interface{}, elem *pb.PathElem, createIfNotExist bool) map[string]interface{} { - curNode, ok := node[elem.Name] - if !ok { - if !createIfNotExist { - return nil - } - - // Create a keyed list as node child and initialize an entry. - m := make(map[string]interface{}) - for k, v := range elem.Key { - m[k] = v - if vAsNum, err := strconv.ParseFloat(v, 64); err == nil { - m[k] = vAsNum - } - } - node[elem.Name] = []interface{}{m} - return m - } - - // Search entry in keyed list. - keyedList, ok := curNode.([]interface{}) - if !ok { - return nil - } - for _, n := range keyedList { - m, ok := n.(map[string]interface{}) - if !ok { - log.Errorf("wrong keyed list entry type: %T", n) - return nil - } - keyMatching := true - // must be exactly match - for k, v := range elem.Key { - attrVal, ok := m[k] - if !ok { - return nil - } - if v != fmt.Sprintf("%v", attrVal) { - keyMatching = false - break - } - } - if keyMatching { - return m - } - } - if !createIfNotExist { - return nil - } - - // Create an entry in keyed list. - m := make(map[string]interface{}) - for k, v := range elem.Key { - m[k] = v - if vAsNum, err := strconv.ParseFloat(v, 64); err == nil { - m[k] = vAsNum - } - } - node[elem.Name] = append(keyedList, m) - return m -}