From 9d24a34cfc7cd93328bfd6e4d9c37aa6b336b287 Mon Sep 17 00:00:00 2001 From: Partha Dutta <51353699+dutta-partha@users.noreply.github.com> Date: Mon, 10 Aug 2020 06:25:28 +0530 Subject: [PATCH] CVL Changes #2: YValidator infra changes for evaluating xpath expression (#19) Why I did it Adding YValidator support so that xpath expression (must/when/leafref) can be evaluated using custom xpath engine. Custom xpath engine will be integrated in subsequent PR. How I did it N/A. Please refer to change log for more details. How to verify it No specific test cases to be executed. Run cvl go test cases using - 'make cvl-test'. Description for the changelog Modifying cvl.go for adding new data structure to store cache in CVL session cache, add YValidator data type. Adding API to get Redis table name from YANG list. Modifying cvl_api.go and cvl_cache.go due to change in session cache. Modifying cvl_syntax.go due to change in API name. Modifying cvl_semantics.go to add various API which can operate on YValidator nodes. --- cvl/cvl.go | 55 +++- cvl/cvl_api.go | 8 +- cvl/cvl_cache.go | 8 +- cvl/cvl_semantics.go | 657 +++++++++++++++++++++++++++++++++++++++++++ cvl/cvl_syntax.go | 2 +- 5 files changed, 714 insertions(+), 16 deletions(-) diff --git a/cvl/cvl.go b/cvl/cvl.go index 98922aa4b0ef..881a84744da8 100644 --- a/cvl/cvl.go +++ b/cvl/cvl.go @@ -78,6 +78,7 @@ type modelTableInfo struct { //multiple leafref possible for union mustExp map[string]string tablesForMustExp map[string]CVLOperation + dfltLeafVal map[string]string //map of leaf names and default value } @@ -94,14 +95,21 @@ type CVLErrorInfo struct { ErrAppTag string } +// Struct for request data and YANG data +type requestCacheType struct { + reqData CVLEditConfigData + yangData *xmlquery.Node +} + type CVL struct { redisClient *redis.Client yp *yparser.YParser tmpDbCache map[string]interface{} //map of table storing map of key-value pair - requestCache map[string]map[string][]CVLEditConfigData //Cache of validated data, - //might be used as dependent data in next request + requestCache map[string]map[string][]*requestCacheType//Cache of validated data, + //per table, per key. Can be used as dependent data in next request batchLeaf string chkLeafRefWithOthCache bool + yv *YValidator //Custom YANG validator for validating external dependencies } type modelNamespace struct { @@ -205,6 +213,11 @@ func Debug(on bool) { yparser.Debug(on) } +// isLeafListNode checks if the xml node represents a leaf-list field +func isLeafListNode(node *xmlquery.Node) bool { + return len(node.Attr) != 0 && node.Attr[0].Name.Local == "leaf-list" +} + //Get attribute value of xml node func getXmlNodeAttr(node *xmlquery.Node, attrName string) string { for _, attr := range node.Attr { @@ -216,6 +229,14 @@ func getXmlNodeAttr(node *xmlquery.Node, attrName string) string { return "" } +// getNodeName returns database field name for the xml node. +func getNodeName(node *xmlquery.Node) string { + if isLeafListNode(node) { + return node.Data + "@" + } + return node.Data +} + //Store useful schema data during initialization func storeModelInfo(modelFile string, module *yparser.YParserModule) { //such model info can be maintained in C code and fetched from there f, err := os.Open(CVL_SCHEMA + modelFile) @@ -394,6 +415,20 @@ func storeModelInfo(modelFile string, module *yparser.YParserModule) { //such mo } } +// Get YANG list to Redis table name +func getYangListToRedisTbl(yangListName string) string { + if (strings.HasSuffix(yangListName, "_LIST")) { + yangListName = yangListName[0:len(yangListName) - len("_LIST")] + } + tInfo, exists := modelInfo.tableInfo[yangListName] + + if exists && (tInfo.redisTableName != "") { + return tInfo.redisTableName + } + + return yangListName +} + //Find the tables names in must expression, these tables data need to be fetched //during semantic validation func addTableNamesForMustExp() { @@ -475,16 +510,22 @@ func splitRedisKey(key string) (string, string) { return tblName, key[prefixLen:] } -//Get the YANG list name from Redis key +//Get the YANG list name from Redis key and table name //This just returns same YANG list name as Redis table name //when 1:1 mapping is there. For one Redis table to //multiple YANG list, it returns appropriate YANG list name //INTERFACE:Ethernet12 returns ==> INTERFACE //INTERFACE:Ethernet12:1.1.1.0/32 ==> INTERFACE_IPADDR -func getRedisKeyToYangList(tableName, key string) string { +func getRedisTblToYangList(tableName, key string) (yangList string) { + defer func() { + pYangList := &yangList + TRACE_LOG(INFO_API, TRACE_SYNTAX, "Got YANG list '%s' " + + "from Redis Table '%s', Key '%s'", *pYangList, tableName, key) + }() + mapArr, exists := modelInfo.redisTableToYangList[tableName] - if exists == false { + if !exists || (len(mapArr) == 1) { //no map or only one //1:1 mapping case return tableName } @@ -492,7 +533,7 @@ func getRedisKeyToYangList(tableName, key string) string { //As of now determine the mapping based on number of keys var foundIdx int = -1 numOfKeys := 1 //Assume only one key initially - for keyDelim, _ := range modelInfo.allKeyDelims { + for keyDelim := range modelInfo.allKeyDelims { foundIdx = strings.Index(key, keyDelim) if (foundIdx >= 0) { //Matched with key delim @@ -505,7 +546,7 @@ func getRedisKeyToYangList(tableName, key string) string { //Check which list has number of keys as 'numOfKeys' for i := 0; i < len(mapArr); i++ { tblInfo, exists := modelInfo.tableInfo[mapArr[i]] - if exists == true { + if exists { if (len(tblInfo.keys) == numOfKeys) { //Found the YANG list matching the number of keys return mapArr[i] diff --git a/cvl/cvl_api.go b/cvl/cvl_api.go index 719467f31736..ad4af71e6328 100644 --- a/cvl/cvl_api.go +++ b/cvl/cvl_api.go @@ -195,7 +195,7 @@ func Finish() { func ValidationSessOpen() (*CVL, CVLRetCode) { cvl := &CVL{} cvl.tmpDbCache = make(map[string]interface{}) - cvl.requestCache = make(map[string]map[string][]CVLEditConfigData) + cvl.requestCache = make(map[string]map[string][]*requestCacheType) cvl.yp = &yparser.YParser{} if (cvl == nil || cvl.yp == nil) { @@ -324,10 +324,10 @@ func (c *CVL) ValidateEditConfig(cfgData []CVLEditConfigData) (CVLErrorInfo, CVL reqTbl, exists := c.requestCache[tbl] if (exists == false) { //Create new table key data - reqTbl = make(map[string][]CVLEditConfigData) + reqTbl = make(map[string][]*requestCacheType) } cfgDataItemArr, _ := reqTbl[key] - cfgDataItemArr = append(cfgDataItemArr, cfgDataItem) + cfgDataItemArr = append(cfgDataItemArr, &requestCacheType{cfgDataItem, nil}) reqTbl[key] = cfgDataItemArr c.requestCache[tbl] = reqTbl @@ -431,7 +431,7 @@ func (c *CVL) ValidateEditConfig(cfgData []CVLEditConfigData) (CVLErrorInfo, CVL deletedInSameSession := false if tbl != "" && key != "" { for _, cachedCfgData := range c.requestCache[tbl][key] { - if cachedCfgData.VOp == OP_DELETE { + if cachedCfgData.reqData.VOp == OP_DELETE { deletedInSameSession = true break } diff --git a/cvl/cvl_cache.go b/cvl/cvl_cache.go index 66cc22bc7928..ab2792df679c 100644 --- a/cvl/cvl_cache.go +++ b/cvl/cvl_cache.go @@ -46,10 +46,10 @@ func (c *CVL) fetchDataFromRequestCache(tableName string, key string) (d map[str if (cfgDataArr != nil) { for _, cfgReqData := range cfgDataArr { //Delete request doesn't have depedent data - if (cfgReqData.VOp == OP_CREATE) { - return cfgReqData.Data, false - } else if (cfgReqData.VOp == OP_UPDATE) { - return cfgReqData.Data, true + if (cfgReqData.reqData.VOp == OP_CREATE) { + return cfgReqData.reqData.Data, false + } else if (cfgReqData.reqData.VOp == OP_UPDATE) { + return cfgReqData.reqData.Data, true } } } diff --git a/cvl/cvl_semantics.go b/cvl/cvl_semantics.go index b7eab1f539e5..94270a16c111 100644 --- a/cvl/cvl_semantics.go +++ b/cvl/cvl_semantics.go @@ -21,7 +21,9 @@ package cvl import ( "strings" + "encoding/xml" "github.com/antchfx/xmlquery" + "github.com/antchfx/jsonquery" "github.com/Azure/sonic-mgmt-common/cvl/internal/yparser" //lint:ignore ST1001 This is safe to dot import for util package . "github.com/Azure/sonic-mgmt-common/cvl/internal/util" @@ -34,6 +36,661 @@ type YValidator struct { current *xmlquery.Node //Current position } +//Generate leaf/leaf-list YANG data +func (c *CVL) generateYangLeafData(tableName string, jsonNode *jsonquery.Node, +parent *xmlquery.Node) CVLRetCode { + + //Traverse fields + for jsonFieldNode := jsonNode.FirstChild; jsonFieldNode!= nil; + jsonFieldNode = jsonFieldNode.NextSibling { + //Add fields as leaf to the list + if (jsonFieldNode.Type == jsonquery.ElementNode && + jsonFieldNode.FirstChild != nil && + jsonFieldNode.FirstChild.Type == jsonquery.TextNode) { + + if (len(modelInfo.tableInfo[tableName].mapLeaf) == 2) {//mapping should have two leaf always + //Values should be stored inside another list as map table + listNode := c.addYangNode(tableName, parent, tableName, "") //Add the list to the top node + c.addYangNode(tableName, + listNode, modelInfo.tableInfo[tableName].mapLeaf[0], + jsonFieldNode.Data) + + c.addYangNode(tableName, + listNode, modelInfo.tableInfo[tableName].mapLeaf[1], + jsonFieldNode.FirstChild.Data) + + } else { + //check if it is hash-ref, then need to add only key from "TABLE|k1" + hashRefMatch := reHashRef.FindStringSubmatch(jsonFieldNode.FirstChild.Data) + + if len(hashRefMatch) == 3 { + c.addYangNode(tableName, + parent, jsonFieldNode.Data, + hashRefMatch[2]) //take hashref key value + } else { + c.addYangNode(tableName, + parent, jsonFieldNode.Data, + jsonFieldNode.FirstChild.Data) + } + } + + } else if (jsonFieldNode.Type == jsonquery.ElementNode && + jsonFieldNode.FirstChild != nil && + jsonFieldNode.FirstChild.Type == jsonquery.ElementNode) { + //Array data e.g. VLAN members@ or 'ports@' + for arrayNode:=jsonFieldNode.FirstChild; arrayNode != nil; + arrayNode = arrayNode.NextSibling { + + node := c.addYangNode(tableName, + parent, jsonFieldNode.Data, + arrayNode.FirstChild.Data) + + //mark these nodes as leaf-list + addAttrNode(node, "leaf-list", "") + } + } + } + + //Add all the default nodes required for must and when exps evaluation + for nodeName, valStr := range modelInfo.tableInfo[tableName].dfltLeafVal { + //Check if default node is already present in data + var child *xmlquery.Node + for child = parent.FirstChild; child != nil; child = child.NextSibling { + if (child.Data == nodeName) { + break + } + } + + if (child != nil) { + //node is already present, skip adding it + continue + } + + valArr := strings.Split(valStr, ",") + for idx := 0; idx < len(valArr); idx ++ { + node := c.addYangNode(tableName, + parent, nodeName, valArr[idx]) + + //mark these nodes as leaf-list + if (len(valArr) > 1) { + addAttrNode(node, "leaf-list", "") + } + } + } + + return CVL_SUCCESS +} + +//Add attribute YANG node +func addAttrNode(n *xmlquery.Node, key, val string) { + var attr xml.Attr = xml.Attr { + Name: xml.Name{Local: key}, + Value: val, + } + + n.Attr = append(n.Attr, attr) +} + +func getAttrNodeVal(node *xmlquery.Node, name string) string { + if (node == nil) { + return "" + } + + if len(node.Attr) == 0 { + return "" + } + + for idx := 0; idx < len(node.Attr); idx++ { + if (node.Attr[idx].Name.Local == name) { + return node.Attr[idx].Value + } + } + + return "" +} + +//Add YANG node with or without parent, with or without value +func (c *CVL) addYangNode(tableName string, parent *xmlquery.Node, + name string, value string) *xmlquery.Node { + + //Create the node + node := &xmlquery.Node{Parent: parent, Data: name, + Type: xmlquery.ElementNode} + + //Set prefix from parent + if (parent != nil) { + node.Prefix = parent.Prefix + } + + if (value != "") { + //Create the value node + textNode := &xmlquery.Node{Data: value, Type: xmlquery.TextNode} + node.FirstChild = textNode + node.LastChild = textNode + } + + if (parent == nil) { + //Creating top node + return node + } + + if parent.FirstChild == nil { + //Create as first child + parent.FirstChild = node + parent.LastChild = node + + } else { + //Append as sibling + tmp := parent.LastChild + tmp.NextSibling = node + node.PrevSibling = tmp + parent.LastChild = node + } + + return node +} + +//Generate YANG list data along with top container, +//table container. +//If needed, stores the list pointer against each request table/key +//in requestCahce so that YANG data can be reached +//directly on given table/key +func (c *CVL) generateYangListData(jsonNode *jsonquery.Node, + storeInReqCache bool)(*xmlquery.Node, CVLErrorInfo) { + var cvlErrObj CVLErrorInfo + + tableName := jsonNode.Data + //c.batchLeaf = nil + //c.batchLeaf = make([]*yparser.YParserLeafValue, 0) + + //Every Redis table is mapped as list within a container, + //E.g. ACL_RULE is mapped as + // container ACL_RULE { list ACL_RULE_LIST {} } + var topNode *xmlquery.Node + + if _, exists := modelInfo.tableInfo[tableName]; !exists { + CVL_LOG(ERROR, "Failed to find schema details for table %s", tableName) + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + cvlErrObj.TableName = tableName + cvlErrObj.Msg ="Schema details not found" + return nil, cvlErrObj + } + + // Add top most container e.g. 'container sonic-acl {...}' + topNode = c.addYangNode(tableName, nil, modelInfo.tableInfo[tableName].modelName, "") + //topNode.Prefix = modelInfo.modelNs[modelInfo.tableInfo[tableName].modelName].prefix + topNode.Prefix = modelInfo.tableInfo[tableName].modelName + topNode.NamespaceURI = modelInfo.modelNs[modelInfo.tableInfo[tableName].modelName].ns + + //Add the container node for each list + //e.g. 'container ACL_TABLE { list ACL_TABLE_LIST ...} + listConatinerNode := c.addYangNode(tableName, topNode, tableName, "") + + //Traverse each key instance + keyPresent := false + for jsonNode = jsonNode.FirstChild; jsonNode != nil; jsonNode = jsonNode.NextSibling { + //store the redis key + redisKey := jsonNode.Data + + //Mark at least one key is present + keyPresent = true + + //For each field check if is key + //If it is key, create list as child of top container + // Get all key name/value pairs + if yangListName := getRedisTblToYangList(tableName, redisKey); yangListName!= "" { + tableName = yangListName + } + keyValuePair := getRedisToYangKeys(tableName, redisKey) + keyCompCount := len(keyValuePair) + totalKeyComb := 1 + var keyIndices []int + + //Find number of all key combinations + //Each key can have one or more key values, which results in nk1 * nk2 * nk2 combinations + idx := 0 + for i := range keyValuePair { + totalKeyComb = totalKeyComb * len(keyValuePair[i].values) + keyIndices = append(keyIndices, 0) + } + + for ; totalKeyComb > 0 ; totalKeyComb-- { + //Get the YANG list name from Redis table name + //Ideally they are same except when one Redis table is split + //into multiple YANG lists + + //Add table i.e. create list element + listNode := c.addYangNode(tableName, listConatinerNode, tableName + "_LIST", "") //Add the list to the top node + addAttrNode(listNode, "key", redisKey) + + if storeInReqCache { + //store the list pointer in requestCache against the table/key + reqCache, exists := c.requestCache[tableName][redisKey] + if exists { + //Store same list instance in all requests under same table/key + for idx := 0; idx < len(reqCache); idx++ { + if (reqCache[idx].yangData == nil) { + //Save the YANG data tree for using it later + reqCache[idx].yangData = listNode + } + } + } + } + + //For each key combination + //Add keys as leaf to the list + for idx = 0; idx < keyCompCount; idx++ { + c.addYangNode(tableName, listNode, keyValuePair[idx].key, + keyValuePair[idx].values[keyIndices[idx]]) + } + + //Get all fields under the key field and add them as children of the list + c.generateYangLeafData(tableName, jsonNode, listNode) + + //Check which key elements left after current key element + var next int = keyCompCount - 1 + for ((next > 0) && ((keyIndices[next] +1) >= len(keyValuePair[next].values))) { + next-- + } + //No more combination possible + if (next < 0) { + break + } + + keyIndices[next]++ + + //Reset indices for all other key elements + for idx = next+1; idx < keyCompCount; idx++ { + keyIndices[idx] = 0 + } + } + } + + if !keyPresent { + return nil, cvlErrObj + } + + return topNode, cvlErrObj +} + +//Append given children to destNode +func (c *CVL) appendSubtree(dest, src *xmlquery.Node) CVLRetCode { + if (dest == nil || src == nil) { + return CVL_FAILURE + } + + var lastSibling *xmlquery.Node = nil + + for srcNode := src; srcNode != nil ; srcNode = srcNode.NextSibling { + //set parent for all nodes + srcNode.Parent = dest + lastSibling = srcNode + } + + if (dest.LastChild == nil) { + //No sibling in dest yet + dest.FirstChild = src + dest.LastChild = lastSibling + } else { + //Append to the last sibling + dest.LastChild.NextSibling = src + src.PrevSibling = dest.LastChild + dest.LastChild = lastSibling + } + + return CVL_SUCCESS +} + +//Return subtree after detaching from parent +func (c *CVL) detachSubtree(parent *xmlquery.Node) *xmlquery.Node { + + child := parent.FirstChild + + if (child != nil) { + //set children to nil + parent.FirstChild = nil + parent.LastChild = nil + } else { + //No children + return nil + } + + //Detach all children from parent + for node := child; node != nil; node = node.NextSibling { + node.Parent = nil + } + + return child +} + +//Detach a node from its parent +func (c *CVL) detachNode(node *xmlquery.Node) CVLRetCode { + if (node == nil) { + return CVL_FAILURE + } + + //get the parent node + parent := node.Parent + + if (parent == nil) { + //Already detached node + return CVL_SUCCESS + } + + //adjust siblings + if (parent.FirstChild == node && parent.LastChild == node) { + //this is the only node + parent.FirstChild = nil + parent.LastChild = nil + } else if (parent.FirstChild == node) { + //first child, set new first child + parent.FirstChild = node.NextSibling + node.NextSibling.PrevSibling = nil + } else { + node.PrevSibling.NextSibling = node.NextSibling + if (node.NextSibling != nil) { + //if remaining sibling + node.NextSibling.PrevSibling = node.PrevSibling + } else { + //this is last child getting detached, + //so set lastChild as node's prevSibling + parent.LastChild = node.PrevSibling + } + } + + //detach from parent and siblings + node.Parent = nil + node.PrevSibling = nil + node.NextSibling = nil + + return CVL_SUCCESS +} + +//Delete all leaf-list nodes in destination +//Leaf-list should be replaced from source +//destination +func (c *CVL) deleteDestLeafList(dest *xmlquery.Node) { + + TRACE_LOG(INFO_API, TRACE_CACHE, "Updating leaf-list by " + + "removing and then adding leaf-list") + + //find start and end of dest leaf list + leafListName := dest.Data + for node := dest; node != nil; { + tmpNextNode := node.NextSibling + + if (node.Data == leafListName) { + c.detachNode(node) + node = tmpNextNode + continue + } else { + //no more leaflist node + break + } + } +} + +// deleteLeafNodes removes specified child nodes from an xml node topNode +func (c *CVL) deleteLeafNodes(topNode *xmlquery.Node, cfgData map[string]string) { + for node := topNode.FirstChild; node != nil; { + if _, found := cfgData[getNodeName(node)]; found { + tmpNode := node.NextSibling + c.detachNode(node) + node = tmpNode + } else { + node = node.NextSibling + } + } +} + +//Check if the given list src node already exists in dest node +func (c *CVL) checkIfListNodeExists(dest, src *xmlquery.Node) *xmlquery.Node { + if (dest == nil) || (src == nil) { + return nil + } + + tableName := getYangListToRedisTbl(src.Data) + redisKey := getAttrNodeVal(src, "key") + + if (tableName == "" || redisKey == "") { + return nil + } + + entry, exists := c.requestCache[tableName][redisKey] + if !exists || (len(entry) == 0) { + return nil + } + + //CREATE/UPDATE/DELETE request for same table/key points to + //same yang list in request cache + yangList := entry[0].yangData + + if (yangList == nil || yangList.Parent == nil) { + //Source node does not exist in destination + return nil + } + + if (dest.Parent == yangList.Parent) { + //Same parent means yang list already exists in destination tree + return yangList + } + + return nil +} + +//Merge YANG data recursively from dest to src +//Leaf-list is always replaced and appeneded at +//the end of list's children +func (c *CVL) mergeYangData(dest, src *xmlquery.Node) CVLRetCode { + if (dest == nil) || (src == nil) { + return CVL_FAILURE + } + + TRACE_LOG(INFO_API, (TRACE_SYNTAX | TRACE_SEMANTIC), + "Merging YANG data %s %s", dest.Data, src.Data) + + if (dest.Type == xmlquery.TextNode) && (src.Type == xmlquery.TextNode) { + //handle leaf node by updating value + dest.Data = src.Data + return CVL_SUCCESS + } + + srcNode := src + + destLeafListDeleted := make(map[string]bool) + for srcNode != nil { + //Find all source nodes and attach to the matching destination node + ret := CVL_FAILURE + //TRACE_LOG((TRACE_SYNTAX | TRACE_SEMANTIC), "MergeData : src loop\n") +destLoop: + destNode := dest + for ; destNode != nil; destNode = destNode.NextSibling { + //TRACE_LOG((TRACE_SYNTAX | TRACE_SEMANTIC), "MergeData : dest loop\n") + if (destNode.Data != srcNode.Data) { + //Can proceed to subtree only if current node name matches + continue + } + + if (strings.HasSuffix(destNode.Data, "_LIST")){ + //Check if src list node already exists in destination + tmpNode := c.checkIfListNodeExists(destNode, srcNode) + if tmpNode != nil { + destNode = tmpNode + } else { + destNode = tmpNode + break + } + //find exact match for list instance + //check with key value, stored in attribute + /*if (len(destNode.Attr) == 0) || (len(srcNode.Attr) == 0) || + (destNode.Attr[0].Value != srcNode.Attr[0].Value) { + //move to next list + continue + }*/ + } else if (len(destNode.Attr) > 0) && (len(srcNode.Attr) > 0) && + (destNode.Attr[0].Name.Local == "leaf-list") && + (srcNode.Attr[0].Name.Local == "leaf-list") { // attribute has type + + delFlag, exists := destLeafListDeleted[srcNode.Data] + + if !exists || !delFlag { + //Replace all leaf-list nodes from destination first + c.deleteDestLeafList(destNode) + destLeafListDeleted[srcNode.Data] = true + //Note that 'dest' still points to list keys + //even though all leaf-list might have been deleted + //as we never delete key while merging + goto destLoop + } else { + //if all dest leaflist deleted, + //just break to add all leaflist + destNode = nil + break + } + } + + //Go to their children + ret = c.mergeYangData(destNode.FirstChild, srcNode.FirstChild) + + //Node matched break now + break + + } //dest node loop + + if (ret == CVL_FAILURE) { + if (destNode == nil) { + //destNode == nil -> node not found + //detach srcNode and append to dest + tmpNextSrcNode := srcNode.NextSibling + if CVL_SUCCESS == c.detachNode(srcNode) { + if (len(srcNode.Attr) > 0) && + (srcNode.Attr[0].Name.Local == "leaf-list") { + //set the flag so that we don't delete leaf-list + //from destNode further + destLeafListDeleted[srcNode.Data] = true + } + c.appendSubtree(dest.Parent, srcNode) + } + srcNode = tmpNextSrcNode + continue + } else { + //subtree merge failure ,, append subtree + subTree := c.detachSubtree(srcNode) + if (subTree != nil) { + c.appendSubtree(destNode, subTree) + } + } + } + + srcNode = srcNode.NextSibling + } //src node loop + + return CVL_SUCCESS +} + +func (c *CVL) findYangList(tableName string, redisKey string) *xmlquery.Node { + origCurrent := c.yv.current + tmpCurrent := c.moveToYangList(tableName, redisKey) + c.yv.current = origCurrent + + return tmpCurrent +} + +//Locate YANG list instance in root for given table name and key +func (c *CVL) moveToYangList(tableName string, redisKey string) *xmlquery.Node { + + var nodeTbl *xmlquery.Node = nil + + redisTableName := getYangListToRedisTbl(tableName) + modelName := modelInfo.tableInfo[tableName].modelName + + //move to the model first + for node := c.yv.root.FirstChild; node != nil; node = node.NextSibling { + if (node.Data != modelName) { + continue + } + + //Move to container + for nodeTbl = node.FirstChild; nodeTbl != nil; nodeTbl = nodeTbl.NextSibling { + if (nodeTbl.Data == redisTableName) { + break + } + } + + break + } + + if (nodeTbl == nil) { + TRACE_LOG(INFO_API, TRACE_SEMANTIC, "YANG data for table %s, key %s is not present in YANG tree", + tableName, redisKey) + return nil + } + + //Move to list + listName := tableName + "_LIST" + for nodeList := nodeTbl.FirstChild; nodeList != nil; nodeList = nodeList.NextSibling { + if (nodeList.Data != listName) { + continue + } + + c.yv.current = nodeList + //if no key specified or no other instance exists, + //just return the first list instance + if (redisKey == "" || nodeList.NextSibling == nil) { + return c.yv.current + } + + for ; (nodeList != nil); nodeList = nodeList.NextSibling { + if (len(nodeList.Attr) > 0) && + (nodeList.Attr[0].Value == redisKey) { + c.yv.current = nodeList + return nodeList + } + } + } + + CVL_LOG(WARNING, "No list entry matched, unable to find YANG data for table %s, key %s", + tableName, redisKey) + return nil +} + +//Set operation node value based on operation in request received +func (c *CVL) setOperation(op CVLOperation) { + + var node *xmlquery.Node + + for node = c.yv.root.FirstChild; node != nil; node = node.NextSibling { + if (node.Data == "operation") { + break + } + } + + //Add the operation container + if (node == nil) { + node = c.addYangNode("", c.yv.root, "operation", "") + node.Prefix = "sonic-common" //"cmn" + //modelInfo.modelNs["sonic-common"].prefix + node.NamespaceURI = modelInfo.modelNs["sonic-common"].ns + } + + opNode := node.FirstChild + if opNode == nil { + node.Prefix = "sonic-common"//"cmn" + opNode = c.addYangNode("", node, "operation", "NONE") + } + + switch op { + case OP_CREATE: + opNode.FirstChild.Data = "CREATE" + case OP_UPDATE: + opNode.FirstChild.Data = "UPDATE" + case OP_DELETE: + opNode.FirstChild.Data = "DELETE" + default: + opNode.FirstChild.Data = "NONE" + } +} + //Check delete constraint for leafref if key/field is deleted func (c *CVL) checkDeleteConstraint(cfgData []CVLEditConfigData, tableName, keyVal, field string) CVLRetCode { diff --git a/cvl/cvl_syntax.go b/cvl/cvl_syntax.go index 7cc812e0374a..9dfa8d98ead3 100644 --- a/cvl/cvl_syntax.go +++ b/cvl/cvl_syntax.go @@ -136,7 +136,7 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser. //For each field check if is key //If it is key, create list as child of top container // Get all key name/value pairs - if yangListName := getRedisKeyToYangList(tableName, jsonNode.Data); yangListName!= "" { + if yangListName := getRedisTblToYangList(tableName, jsonNode.Data); yangListName!= "" { tableName = yangListName } keyValuePair := getRedisToYangKeys(tableName, jsonNode.Data)