diff --git a/internal/gnmiserver/model.go b/internal/gnmiserver/model.go index 98301308ceac44203aa4419653572c79b3920b74..60de0aeb83325f747358d5ab93d6fcbc269b7325 100644 --- a/internal/gnmiserver/model.go +++ b/internal/gnmiserver/model.go @@ -35,17 +35,19 @@ type GoStructEnumData map[string]map[int64]ygot.EnumDefinition type Model struct { modelData []*pb.ModelData structRootType reflect.Type + schemaTree map[string]*yang.Entry schemaTreeRoot *yang.Entry - jsonUnmarshaler JSONUnmarshaler + jsonUnmarshaler ytypes.UnmarshalFunc 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 { +func NewModel(m []*pb.ModelData, t reflect.Type, st *ytypes.Schema, f ytypes.UnmarshalFunc, e GoStructEnumData) *Model { return &Model{ modelData: m, structRootType: t, - schemaTreeRoot: r, + schemaTree: st.SchemaTree, + schemaTreeRoot: st.RootSchema(), jsonUnmarshaler: f, enumData: e, } diff --git a/internal/gnmiserver/server.go b/internal/gnmiserver/server.go index 423265267e4ae83e160ceeb30ea4fd48dd6a05d1..7c618132fb13ea4276e9388f363d7e187a3d5c3d 100644 --- a/internal/gnmiserver/server.go +++ b/internal/gnmiserver/server.go @@ -23,7 +23,6 @@ import ( "fmt" "io" "reflect" - "strconv" "time" "golang.org/x/net/context" @@ -204,213 +203,6 @@ func (s *Server) checkEncodingAndModel(encoding pb.Encoding, models []*pb.ModelD 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, entry, 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) - } - - if entry.ReadOnly() { - return nil, status.Errorf(codes.Internal, "It is not possible to change the value of a config false field at path: %s", entry.Path()) - } - - jsonIETFValue := val.GetJsonIetfVal() - var nodeVal interface{} - nodeStruct, ok := emptyNode.(ygot.ValidatedGoStruct) - if ok { - if err := s.model.jsonUnmarshaler(jsonIETFValue, 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) - } - } - - // Check if val contains config false fields and return an error if thats - // the case. - if nodeStruct != nil { - nodeStructCopy, err := ygot.DeepCopy(nodeStruct) - if err != nil { - return nil, status.Errorf(codes.Internal, "could not create a deep copy of current nodeStruct: %v", err) - } - - if err := ygot.PruneConfigFalse(entry, nodeStruct); err != nil { - return nil, status.Errorf(codes.Internal, "failed prune config false for nodeStruct: %v", err) - } - - diff, err := ygot.Diff(nodeStructCopy, nodeStruct) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to create a diff for nodeStruct: %v", err) - } - - if diff != nil && len(diff.Delete) != 0 { - paths := make([]string, 0) - for _, path := range diff.Delete { - p, err := ygot.PathToString(path) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to create string from path: %v", err) - } - paths = append(paths, p) - } - return nil, status.Errorf(codes.InvalidArgument, "Can not set config false paths: %v", paths) - } - } else if entry.ReadOnly() { - return nil, status.Errorf(codes.Internal, "It is not possible to change the value of a config false field at path: %s", entry.Path()) - } - - // 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 node != nil { - 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 - } - - logrus.Debug("doReplaceOrUpdate, node: ", node) - if curNode, schema = getChildNode(node, schema, elem, true); curNode == nil { - return nil, status.Errorf(codes.NotFound, "path elem not found: %v", elem) - } - } else { - logrus.Debugf("node is nil for path elem: %s", elem.String()) - } - 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) { @@ -432,51 +224,6 @@ func getGNMIServiceVersion() (*string, error) { 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} @@ -489,73 +236,6 @@ func gnmiFullPath(prefix, path *pb.Path) *pb.Path { 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() @@ -691,52 +371,38 @@ func (s *Server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, s.config.Lock() defer s.config.Unlock() - logrus.Debug("Current data: ", s.config.Data) - // Obtain current configuration as JSON - jsonTree, err := ygot.ConstructIETFJSON(s.config.Data, &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) - } + logrus.Debug("Incoming Set Request: ", req) - 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) + newConfig, err := ygot.DeepCopy(s.config.Data) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) } - 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) + + newSchema := &ytypes.Schema{ + Root: newConfig, + SchemaTree: s.model.schemaTree, + Unmarshal: s.model.jsonUnmarshaler, } - newConfig, err := s.toGoStruct(jsonTree) + err = ytypes.UnmarshalSetRequest(newSchema, req) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + valNewConfig, ok := newConfig.(ygot.ValidatedGoStruct) + if !ok { + return nil, status.Error(codes.Internal, fmt.Sprintf("wrong type for new config, expected: %T; got: %T", (ygot.ValidatedGoStruct)(nil), newConfig)) + } + currentConfig, err := ygot.DeepCopy(s.config.Data) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } // Apply the validated operations to the device. - diff, applyErr := s.callback(newConfig, currentConfig.(ygot.ValidatedGoStruct)) + diff, applyErr := s.callback(valNewConfig, currentConfig.(ygot.ValidatedGoStruct)) if applyErr != nil { if _, rollbackErr := s.callback(currentConfig.(ygot.ValidatedGoStruct), s.config.Data); rollbackErr != nil { return nil, status.Errorf(codes.Internal, "error in rollback the failed operation (%v): %v", applyErr, rollbackErr) @@ -744,7 +410,7 @@ func (s *Server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, return nil, status.Errorf(codes.Aborted, "error in applying operation to device: %v", applyErr) } - s.config.Data = newConfig + s.config.Data = valNewConfig // notify subscribers about the changes err = s.PublishNotificationsToSubscribers(diff) diff --git a/target.go b/target.go index 02230ecb6bf697abe9145ad9c476f635e317180a..67067c7980b79cb10df49da34e68751821c57a58 100644 --- a/target.go +++ b/target.go @@ -63,7 +63,7 @@ func (gt *GnmiTarget) Start(bindAddress string, certFile string, keyFile string, gnmiModel := server.NewModel(gt.modeldata, // NOTE: could be problematic reflect.TypeOf(gt.model), - gt.schema.RootSchema(), + gt.schema, gt.unmarshalFn, gt.ΛEnum)