From 80f369e11ec490aa0db9b7ed456def1ab2156e5d Mon Sep 17 00:00:00 2001 From: Partha Dutta <51353699+dutta-partha@users.noreply.github.com> Date: Thu, 1 Oct 2020 05:15:26 +0530 Subject: [PATCH] CVL Changes #5: YParser enhancement (#23) Adding following YParser enhancement : Add multiple nodes to the tree from array instead of using "#" delimiter Read YIN file schema Read leafref, must, when expression details API IsLeafrefMatchedInUnion() to check if data instance type in union is of leafref type. --- cvl/cvl.go | 6 +- cvl/cvl_syntax.go | 66 +++-- cvl/cvl_test.go | 2 +- cvl/internal/yparser/yparser.go | 508 ++++++++++++++++++++++++++++++-- 4 files changed, 522 insertions(+), 60 deletions(-) diff --git a/cvl/cvl.go b/cvl/cvl.go index 881a84744da8..05c331cd0b22 100644 --- a/cvl/cvl.go +++ b/cvl/cvl.go @@ -106,8 +106,8 @@ type CVL struct { yp *yparser.YParser tmpDbCache map[string]interface{} //map of table storing map of key-value pair 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 + //per table, per key. Can be used as dependent data in next request + batchLeaf []*yparser.YParserLeafValue //field name and value chkLeafRefWithOthCache bool yv *YValidator //Custom YANG validator for validating external dependencies } @@ -1159,7 +1159,7 @@ func (c *CVL) validate (data *yparser.YParserNode) CVLRetCode { return CVL_SUCCESS } -func CreateCVLErrObj(errObj yparser.YParserError) CVLErrorInfo { +func createCVLErrObj(errObj yparser.YParserError) CVLErrorInfo { cvlErrObj := CVLErrorInfo { TableName : errObj.TableName, diff --git a/cvl/cvl_syntax.go b/cvl/cvl_syntax.go index 9dfa8d98ead3..81009bd18132 100644 --- a/cvl/cvl_syntax.go +++ b/cvl/cvl_syntax.go @@ -19,7 +19,6 @@ package cvl import ( - "fmt" "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 @@ -33,7 +32,7 @@ func(c *CVL) addChildNode(tableName string, parent *yparser.YParserNode, name st return c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, parent, name) } -func (c *CVL) addChildLeaf(config bool, tableName string, parent *yparser.YParserNode, name string, value string) { +func (c *CVL) addChildLeaf(config bool, tableName string, parent *yparser.YParserNode, name string, value string, multileaf *[]*yparser.YParserLeafValue) { /* If there is no value then assign default space string. */ if len(value) == 0 { @@ -41,7 +40,8 @@ func (c *CVL) addChildLeaf(config bool, tableName string, parent *yparser.YParse } //Batch leaf creation - c.batchLeaf = c.batchLeaf + name + "#" + value + "#" + *multileaf = append(*multileaf, &yparser.YParserLeafValue{Name: name, Value: value}) + //Check if this leaf has leafref, //If so add the add redis key to its table so that those // details can be fetched for dependency validation @@ -50,7 +50,8 @@ func (c *CVL) addChildLeaf(config bool, tableName string, parent *yparser.YParse } func (c *CVL) generateTableFieldsData(config bool, tableName string, jsonNode *jsonquery.Node, -parent *yparser.YParserNode) CVLRetCode { +parent *yparser.YParserNode, multileaf *[]*yparser.YParserLeafValue) CVLErrorInfo { + var cvlErrObj CVLErrorInfo //Traverse fields for jsonFieldNode := jsonNode.FirstChild; jsonFieldNode!= nil; @@ -61,32 +62,35 @@ parent *yparser.YParserNode) CVLRetCode { jsonFieldNode.FirstChild.Type == jsonquery.TextNode) { if (len(modelInfo.tableInfo[tableName].mapLeaf) == 2) {//mapping should have two leaf always + batchInnerListLeaf := make([]*yparser.YParserLeafValue, 0) //Values should be stored inside another list as map table listNode := c.addChildNode(tableName, parent, tableName) //Add the list to the top node c.addChildLeaf(config, tableName, listNode, modelInfo.tableInfo[tableName].mapLeaf[0], - jsonFieldNode.Data) + jsonFieldNode.Data, &batchInnerListLeaf) c.addChildLeaf(config, tableName, listNode, modelInfo.tableInfo[tableName].mapLeaf[1], - jsonFieldNode.FirstChild.Data) + jsonFieldNode.FirstChild.Data, &batchInnerListLeaf) + if errObj := c.yp.AddMultiLeafNodes(modelInfo.tableInfo[tableName].module, listNode, batchInnerListLeaf); errObj.ErrCode != yparser.YP_SUCCESS { + cvlErrObj = createCVLErrObj(errObj) + CVL_LOG(ERROR, "Failed to create innner list leaf nodes, data = %v", batchInnerListLeaf) + return cvlErrObj + } } else { //check if it is hash-ref, then need to add only key from "TABLE|k1" hashRefMatch := reHashRef.FindStringSubmatch(jsonFieldNode.FirstChild.Data) - if (hashRefMatch != nil && len(hashRefMatch) == 3) { - /*if (strings.HasPrefix(jsonFieldNode.FirstChild.Data, "[")) && - (strings.HasSuffix(jsonFieldNode.FirstChild.Data, "]")) && - (strings.Index(jsonFieldNode.FirstChild.Data, "|") > 0) {*/ + if len(hashRefMatch) == 3 { c.addChildLeaf(config, tableName, parent, jsonFieldNode.Data, - hashRefMatch[2]) //take hashref key value + hashRefMatch[2], multileaf) //take hashref key value } else { c.addChildLeaf(config, tableName, parent, jsonFieldNode.Data, - jsonFieldNode.FirstChild.Data) + jsonFieldNode.FirstChild.Data, multileaf) } } @@ -99,19 +103,21 @@ parent *yparser.YParserNode) CVLRetCode { arrayNode = arrayNode.NextSibling { c.addChildLeaf(config, tableName, parent, jsonFieldNode.Data, - arrayNode.FirstChild.Data) + arrayNode.FirstChild.Data, multileaf) } } } - return CVL_SUCCESS + cvlErrObj.ErrCode = CVL_SUCCESS + return cvlErrObj } func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser.YParserNode, CVLErrorInfo) { var cvlErrObj CVLErrorInfo - tableName := fmt.Sprintf("%s",jsonNode.Data) - c.batchLeaf = "" + 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 @@ -119,8 +125,12 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser. var topNode *yparser.YParserNode // Add top most conatiner e.g. 'container sonic-acl {...}' - if _, exists := modelInfo.tableInfo[tableName]; exists == false { - return nil, cvlErrObj + if _, exists := modelInfo.tableInfo[tableName]; !exists { + CVL_LOG(ERROR, "Schema details not found for %s", tableName) + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + cvlErrObj.TableName = tableName + cvlErrObj.Msg ="Schema details not found" + return nil, cvlErrObj } topNode = c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, nil, modelInfo.tableInfo[tableName].modelName) @@ -147,7 +157,7 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser. //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 { + for i := range keyValuePair { totalKeyComb = totalKeyComb * len(keyValuePair[i].values) keyIndices = append(keyIndices, 0) } @@ -165,11 +175,13 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser. for idx = 0; idx < keyCompCount; idx++ { c.addChildLeaf(config, tableName, listNode, keyValuePair[idx].key, - keyValuePair[idx].values[keyIndices[idx]]) + keyValuePair[idx].values[keyIndices[idx]], &c.batchLeaf) } //Get all fields under the key field and add them as children of the list - c.generateTableFieldsData(config, tableName, jsonNode, listNode) + if fldDataErrObj := c.generateTableFieldsData(config, tableName, jsonNode, listNode, &c.batchLeaf); fldDataErrObj.ErrCode != CVL_SUCCESS { + return nil, fldDataErrObj + } //Check which key elements left after current key element var next int = keyCompCount - 1 @@ -188,13 +200,17 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser. keyIndices[idx] = 0 } - TRACE_LOG(INFO_API, TRACE_CACHE, "Starting batch leaf creation - %s\n", c.batchLeaf) + TRACE_LOG(INFO_API, TRACE_CACHE, "Starting batch leaf creation - %v\n", c.batchLeaf) //process batch leaf creation if errObj := c.yp.AddMultiLeafNodes(modelInfo.tableInfo[tableName].module, listNode, c.batchLeaf); errObj.ErrCode != yparser.YP_SUCCESS { - cvlErrObj = CreateCVLErrObj(errObj) - return nil, cvlErrObj + cvlErrObj = createCVLErrObj(errObj) + CVL_LOG(ERROR, "Failed to create leaf nodes, data = %v", + c.batchLeaf) + return nil, cvlErrObj } - c.batchLeaf = "" + + c.batchLeaf = nil + c.batchLeaf = make([]*yparser.YParserLeafValue, 0) } } diff --git a/cvl/cvl_test.go b/cvl/cvl_test.go index cef8ae0b4d1b..0af107d4525f 100644 --- a/cvl/cvl_test.go +++ b/cvl/cvl_test.go @@ -759,7 +759,7 @@ func TestValidateEditConfig_Create_Syntax_CableLength(t *testing.T) { map[string]string{ "Ethernet8": "5m", "Ethernet12": "5m", - "Ethernet16": "5m", + "PortChannel16": "5m", }, }, } diff --git a/cvl/internal/yparser/yparser.go b/cvl/internal/yparser/yparser.go index a35868fa4b13..3a7eb5f66858 100644 --- a/cvl/internal/yparser/yparser.go +++ b/cvl/internal/yparser/yparser.go @@ -23,9 +23,12 @@ package yparser import ( "os" + "fmt" "strings" log "github.com/golang/glog" + //lint:ignore ST1001 This is safe to dot import for util package . "github.com/Azure/sonic-mgmt-common/cvl/internal/util" + "unsafe" ) /* @@ -92,27 +95,54 @@ int lyd_data_validate_all(const char *data, const char *depData, const char *oth return lyd_validate(&pData, LYD_OPT_CONFIG, ctx); } -int lyd_multi_new_leaf(struct lyd_node *parent, const struct lys_module *module, const char *leafVal) +struct leaf_value { + const char *name; + const char *value; +}; + +int lyd_multi_new_leaf(struct lyd_node *parent, const struct lys_module *module, + struct leaf_value *leafValArr, int size) { - char s[4048]; - char *name, *val, *saveptr; + const char *name, *val; + struct lyd_node *leaf; + struct lys_type *type = NULL; + int has_ptr_type = 0; + int idx = 0; - strcpy(s, leafVal); + for (idx = 0; idx < size; idx++) + { + if ((leafValArr[idx].name == NULL) || (leafValArr[idx].value == NULL)) + { + continue; + } - name = strtok_r(s, "#", &saveptr); + name = leafValArr[idx].name; + val = leafValArr[idx].value; + + if (NULL == (leaf = lyd_new_leaf(parent, module, name, val))) + { + return -1; + } - while (name != NULL) - { - val = strtok_r(NULL, "#", &saveptr); - if (val != NULL) + //Validate all union types as it is skipped for LYD_OPT_EDIT + if (((struct lys_node_leaflist*)leaf->schema)->type.base == LY_TYPE_UNION) { - if (NULL == lyd_new_leaf(parent, module, name, val)) + type = &((struct lys_node_leaflist*)leaf->schema)->type; + + //save the has_ptr_type field + has_ptr_type = type->info.uni.has_ptr_type; + + //Work around, set to 0 to check all union types + type->info.uni.has_ptr_type = 0; + + if (lyd_validate_value(leaf->schema, val)) { return -1; } - } - name = strtok_r(NULL, "#", &saveptr); + //Restore has_ptr_type + type->info.uni.has_ptr_type = has_ptr_type; + } } return 0; @@ -139,16 +169,106 @@ struct lyd_node *lyd_find_node(struct lyd_node *root, const char *xpath) return node; } -int lyd_change_leaf_data(struct lyd_node *leaf, const char *val_str) +int lyd_node_leafref_match_in_union(struct lys_module *module, const char *xpath, const char *value) { + struct ly_set *set = NULL; + struct lys_node *node = NULL; + int idx = 0; + struct lys_node_leaflist* lNode; + + if (module == NULL) + { + return -1; + } + + set = lys_find_path(module, NULL, xpath); + if (set == NULL || set->number == 0) { + return -1; + } + + node = set->set.s[0]; + ly_set_free(set); + + //Now check if it matches with any leafref node + lNode = (struct lys_node_leaflist*)node; + + for (idx = 0; idx < lNode->type.info.uni.count; idx++) + { + if (lNode->type.info.uni.types[idx].base != LY_TYPE_LEAFREF) + { + //Look for leafref type only + continue; + } + + if (0 == lyd_validate_value((struct lys_node*) + lNode->type.info.uni.types[idx].info.lref.target, value)) + { + return 0; + } + } + + return -1; +} + +struct lys_node* lys_get_snode(struct ly_set *set, int idx) { + if (set == NULL || set->number == 0) { + return NULL; + } + + return set->set.s[idx]; +} + +int lyd_change_leaf_data(struct lyd_node *leaf, const char *val_str) { return lyd_change_leaf((struct lyd_node_leaf_list *)leaf, val_str); } +struct lys_leaf_ref_path { + const char *path[10]; //max 10 path + int count; //actual path count +}; + +const char *nonLeafRef = "non-leafref"; +struct lys_leaf_ref_path* lys_get_leafrefs(struct lys_node_leaf *node) { + static struct lys_leaf_ref_path leafrefs; + memset(&leafrefs, 0, sizeof(leafrefs)); + + int nonLeafRefCnt = 0; + + if (node->type.base == LY_TYPE_LEAFREF) { + leafrefs.path[0] = node->type.info.lref.path; + leafrefs.count = 1; + + } else if (node->type.base == LY_TYPE_UNION) { + int typeCnt = 0; + for (; typeCnt < node->type.info.uni.count; typeCnt++) { + if (node->type.info.uni.types[typeCnt].base != LY_TYPE_LEAFREF) { + if (nonLeafRefCnt == 0) { + leafrefs.path[leafrefs.count] = nonLeafRef; //data type, not leafref + leafrefs.count += 1; + nonLeafRefCnt++; + } + + continue; + } + + leafrefs.path[leafrefs.count] = node->type.info.uni.types[typeCnt].info.lref.path; + leafrefs.count += 1; + } + } + + if ((leafrefs.count - nonLeafRefCnt) > 0) { + return &leafrefs; + } else { + return NULL; + } +} + */ import "C" type YParserCtx C.struct_ly_ctx type YParserNode C.struct_lyd_node +type YParserSNode C.struct_lys_node type YParserModule C.struct_lys_module var ypCtx *YParserCtx @@ -156,6 +276,41 @@ var ypOpModule *YParserModule var ypOpRoot *YParserNode //Operation root var ypOpNode *YParserNode //Operation node +type XpathExpression struct { + Expr string + ErrCode string + ErrStr string +} + +type WhenExpression struct { + Expr string //when expression + NodeNames []string //node names under when condition +} + +//YParserListInfo Important schema information to be loaded at bootup time +type YParserListInfo struct { + ListName string + Module *YParserModule + DbName string + ModelName string + RedisTableName string //To which Redis table it belongs to, used for 1 Redis to N Yang List + Keys []string + RedisKeyDelim string + RedisKeyPattern string + RedisTableSize int + MapLeaf []string //for 'mapping list' + LeafRef map[string][]string //for storing all leafrefs for a leaf in a table, + //multiple leafref possible for union + DfltLeafVal map[string]string //Default value for leaf/leaf-list + XpathExpr map[string][]*XpathExpression + CustValidation map[string]string + WhenExpr map[string][]*WhenExpression //multiple when expression for choice/case etc +} + +type YParserLeafValue struct { + Name string + Value string +} type YParser struct { ctx *YParserCtx //Parser context @@ -163,7 +318,7 @@ type YParser struct { operation string //Edit operation } -/* YParser Error Structure */ +//YParserError YParser Error Structure type YParserError struct { ErrCode YParserRetCode /* Error Code describing type of error. */ Msg string /* Detailed error message. */ @@ -200,6 +355,13 @@ const ( YP_INTERNAL_UNKNOWN ) +const ( + YP_NOP = 1 + iota + YP_OP_CREATE + YP_OP_UPDATE + YP_OP_DELETE +) + var yparserInitialized bool = false func TRACE_LOG(level log.Level, tracelevel CVLTraceLevel, fmtStr string, args ...interface{}) { @@ -218,7 +380,7 @@ func init() { } func Debug(on bool) { - if (on == true) { + if on { C.ly_verb(C.LY_LLDBG) } else { C.ly_verb(C.LY_LLERR) @@ -226,7 +388,7 @@ func Debug(on bool) { } func Initialize() { - if (yparserInitialized != true) { + if !yparserInitialized { ypCtx = (*YParserCtx)(C.ly_ctx_new(C.CString(CVL_SCHEMA), 0)) C.ly_verb(C.LY_LLERR) // yparserInitialized = true @@ -234,13 +396,13 @@ func Initialize() { } func Finish() { - if (yparserInitialized == true) { + if yparserInitialized { C.ly_ctx_destroy((*C.struct_ly_ctx)(ypCtx), nil) // yparserInitialized = false } } -//Parse YIN schema file +//ParseSchemaFile Parse YIN schema file func ParseSchemaFile(modelFile string) (*YParserModule, YParserError) { /* schema */ TRACE_LOG(INFO_DEBUG, TRACE_YPARSER, "Parsing schema file %s ...\n", modelFile) @@ -250,7 +412,7 @@ func ParseSchemaFile(modelFile string) (*YParserModule, YParserError) { return nil, getErrorDetails() } - if (strings.Contains(modelFile, "sonic-common.yin") == true) { + if strings.Contains(modelFile, "sonic-common.yin") { ypOpModule = (*YParserModule)(module) ypOpRoot = (*YParserNode)(C.lyd_new(nil, (*C.struct_lys_module)(ypOpModule), C.CString("operation"))) ypOpNode = (*YParserNode)(C.lyd_new_leaf((*C.struct_lyd_node)(ypOpRoot), (*C.struct_lys_module)(ypOpModule), C.CString("operation"), C.CString("NOP"))) @@ -259,7 +421,7 @@ func ParseSchemaFile(modelFile string) (*YParserModule, YParserError) { return (*YParserModule)(module), YParserError {ErrCode : YP_SUCCESS,} } -//Add child node to a parent node +//AddChildNode Add child node to a parent node func(yp *YParser) AddChildNode(module *YParserModule, parent *YParserNode, name string) *YParserNode { ret := (*YParserNode)(C.lyd_new((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), C.CString(name))) @@ -270,10 +432,31 @@ func(yp *YParser) AddChildNode(module *YParserModule, parent *YParserNode, name return ret } -//Add child node to a parent node -func(yp *YParser) AddMultiLeafNodes(module *YParserModule, parent *YParserNode, multiLeaf string) YParserError { - if (0 != C.lyd_multi_new_leaf((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), C.CString(multiLeaf))) { - if (Tracing == true) { +//IsLeafrefMatchedInUnion Check if value matches with leafref node in union +func (yp *YParser) IsLeafrefMatchedInUnion(module *YParserModule, xpath, value string) bool { + + return C.lyd_node_leafref_match_in_union((*C.struct_lys_module)(module), C.CString(xpath), C.CString(value)) == 0 +} + +//AddMultiLeafNodes Add child node to a parent node +func(yp *YParser) AddMultiLeafNodes(module *YParserModule, parent *YParserNode, multiLeaf []*YParserLeafValue) YParserError { + + leafValArr := make([]C.struct_leaf_value, len(multiLeaf)) + + size := C.int(0) + for index := 0; index < len(multiLeaf); index++ { + if (multiLeaf[index] == nil) || (multiLeaf[index].Name == "") { + break + } + + //Accumulate all name/value in array to be passed in lyd_multi_new_leaf() + leafValArr[index].name = C.CString(multiLeaf[index].Name) + leafValArr[index].value = C.CString(multiLeaf[index].Value) + size++ + } + + if C.lyd_multi_new_leaf((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), (*C.struct_leaf_value)(unsafe.Pointer(&leafValArr[0])), size) != 0 { + if Tracing { TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to create Multi Leaf Data = %v", multiLeaf) } return getErrorDetails() @@ -283,7 +466,7 @@ func(yp *YParser) AddMultiLeafNodes(module *YParserModule, parent *YParserNode, } -//Return entire subtree in XML format in string +//NodeDump Return entire subtree in XML format in string func (yp *YParser) NodeDump(root *YParserNode) string { if (root == nil) { return "" @@ -294,7 +477,7 @@ func (yp *YParser) NodeDump(root *YParserNode) string { } } -//Merge source with destination +//MergeSubtree Merge source with destination func (yp *YParser) MergeSubtree(root, node *YParserNode) (*YParserNode, YParserError) { rootTmp := (*C.struct_lyd_node)(root) @@ -302,17 +485,16 @@ func (yp *YParser) MergeSubtree(root, node *YParserNode) (*YParserNode, YParserE return root, YParserError {ErrCode: YP_SUCCESS} } - if (Tracing == true) { + if Tracing { rootdumpStr := yp.NodeDump((*YParserNode)(rootTmp)) TRACE_LOG(INFO_API, TRACE_YPARSER, "Root subtree = %v\n", rootdumpStr) } - if (0 != C.lyd_merge_to_ctx(&rootTmp, (*C.struct_lyd_node)(node), C.LYD_OPT_DESTRUCT, - (*C.struct_ly_ctx)(ypCtx))) { + if C.lyd_merge_to_ctx(&rootTmp, (*C.struct_lyd_node)(node), C.LYD_OPT_DESTRUCT, (*C.struct_ly_ctx)(ypCtx)) != 0 { return (*YParserNode)(rootTmp), getErrorDetails() } - if (Tracing == true) { + if Tracing { dumpStr := yp.NodeDump((*YParserNode)(rootTmp)) TRACE_LOG(INFO_API, TRACE_YPARSER, "Merged subtree = %v\n", dumpStr) } @@ -363,7 +545,7 @@ func (yp *YParser) DestroyCache() YParserError { return YParserError {ErrCode : YP_SUCCESS,} } -//Set operation +//SetOperation Set operation func (yp *YParser) SetOperation(op string) YParserError { if (ypOpNode == nil) { return YParserError {ErrCode : YP_INTERNAL_UNKNOWN,} @@ -697,3 +879,267 @@ func getErrorDetails() YParserError { func FindNode(root *YParserNode, xpath string) *YParserNode { return (*YParserNode)(C.lyd_find_node((*C.struct_lyd_node)(root), C.CString(xpath))) } + +func GetModelNs(module *YParserModule) (ns, prefix string) { + return C.GoString(((*C.struct_lys_module)(module)).ns), + C.GoString(((*C.struct_lys_module)(module)).prefix) +} + +//Get model details for child under list/choice/case +func getModelChildInfo(l *YParserListInfo, node *C.struct_lys_node, + underWhen bool, whenExpr *WhenExpression) { + + for sChild := node.child; sChild != nil; sChild = sChild.next { + switch sChild.nodetype { + case C.LYS_USES: + nodeUses := (*C.struct_lys_node_uses)(unsafe.Pointer(sChild)) + if (nodeUses.when != nil) { + usesWhenExp := WhenExpression { + Expr: C.GoString(nodeUses.when.cond), + } + listName := l.ListName + "_LIST" + l.WhenExpr[listName] = append(l.WhenExpr[listName], + &usesWhenExp) + getModelChildInfo(l, sChild, true, &usesWhenExp) + } else { + getModelChildInfo(l, sChild, false, nil) + } + case C.LYS_CHOICE: + nodeChoice := (*C.struct_lys_node_choice)(unsafe.Pointer(sChild)) + if (nodeChoice.when != nil) { + chWhenExp := WhenExpression { + Expr: C.GoString(nodeChoice.when.cond), + } + listName := l.ListName + "_LIST" + l.WhenExpr[listName] = append(l.WhenExpr[listName], + &chWhenExp) + getModelChildInfo(l, sChild, true, &chWhenExp) + } else { + getModelChildInfo(l, sChild, false, nil) + } + case C.LYS_CASE: + nodeCase := (*C.struct_lys_node_case)(unsafe.Pointer(sChild)) + if (nodeCase.when != nil) { + csWhenExp := WhenExpression { + Expr: C.GoString(nodeCase.when.cond), + } + listName := l.ListName + "_LIST" + l.WhenExpr[listName] = append(l.WhenExpr[listName], + &csWhenExp) + getModelChildInfo(l, sChild, true, &csWhenExp) + } else { + if underWhen { + getModelChildInfo(l, sChild, underWhen, whenExpr) + } else { + getModelChildInfo(l, sChild, false, nil) + } + } + case C.LYS_LEAF, C.LYS_LEAFLIST: + sleaf := (*C.struct_lys_node_leaf)(unsafe.Pointer(sChild)) + if sleaf == nil { + continue + } + + leafName := C.GoString(sleaf.name) + + if (sChild.nodetype == C.LYS_LEAF) { + if (sleaf.dflt != nil) { + l.DfltLeafVal[leafName] = C.GoString(sleaf.dflt) + } + } else { + sLeafList := (*C.struct_lys_node_leaflist)(unsafe.Pointer(sChild)) + if (sleaf.dflt != nil) { + //array of default values + dfltValArr := (*[256]*C.char)(unsafe.Pointer(sLeafList.dflt)) + + tmpValStr := "" + for idx := 0; idx < int(sLeafList.dflt_size); idx++ { + if (idx > 0) { + //Separate multiple values by , + tmpValStr = tmpValStr + "," + } + + tmpValStr = tmpValStr + C.GoString(dfltValArr[idx]) + } + + //Remove last ',' + l.DfltLeafVal[leafName] = tmpValStr + } + } + + //If parent has when expression, + //just add leaf to when expression node list + if underWhen { + whenExpr.NodeNames = append(whenExpr.NodeNames, leafName) + } + + //Check for leafref expression + leafRefs := C.lys_get_leafrefs(sleaf) + if (leafRefs != nil) { + leafRefPaths := (*[10]*C.char)(unsafe.Pointer(&leafRefs.path)) + for idx := 0; idx < int(leafRefs.count); idx++ { + l.LeafRef[leafName] = append(l.LeafRef[leafName], + C.GoString(leafRefPaths[idx])) + } + } + + //Check for must expression; one must expession only per leaf + if (sleaf.must_size > 0) { + must := (*[10]C.struct_lys_restr)(unsafe.Pointer(sleaf.must)) + for idx := 0; idx < int(sleaf.must_size); idx++ { + exp := XpathExpression{Expr: C.GoString(must[idx].expr)} + + if (must[idx].eapptag != nil) { + exp.ErrCode = C.GoString(must[idx].eapptag) + } + if (must[idx].emsg != nil) { + exp.ErrStr = C.GoString(must[idx].emsg) + } + + l.XpathExpr[leafName] = append(l.XpathExpr[leafName], + &exp) + } + } + + //Check for when expression + if (sleaf.when != nil) { + l.WhenExpr[leafName] = append(l.WhenExpr[leafName], + &WhenExpression { + Expr: C.GoString(sleaf.when.cond), + NodeNames: []string{leafName}, + }) + } + + //Check for custom extension + if (sleaf.ext_size > 0) { + exts := (*[10]*C.struct_lys_ext_instance)(unsafe.Pointer(sleaf.ext)) + for idx := 0; idx < int(sleaf.ext_size); idx++ { + if (C.GoString(exts[idx].def.name) == "custom-validation") { + argVal := C.GoString(exts[idx].arg_value) + if (argVal != "") { + l.CustValidation[leafName] = argVal + } + } + } + } + } + } +} + +//GetModelListInfo Get model info for YANG list and its subtree +func GetModelListInfo(module *YParserModule) []*YParserListInfo { + var list []*YParserListInfo + + mod := (*C.struct_lys_module)(module) + set := C.lys_find_path(mod, nil, + C.CString(fmt.Sprintf("/%s/*", C.GoString(mod.name)))) + + if (set == nil) { + return nil + } + + for idx := 0; idx < int(set.number); idx++ { //for each container + + snode := C.lys_get_snode(set, C.int(idx)) + snodec := (*C.struct_lys_node_container)(unsafe.Pointer(snode)) + slist := (*C.struct_lys_node_list)(unsafe.Pointer(snodec.child)) + + //for each list + for ; slist != nil; slist = (*C.struct_lys_node_list)(unsafe.Pointer(slist.next)) { + var l YParserListInfo + listName := C.GoString(slist.name) + l.RedisTableName = C.GoString(snodec.name) + + tableName := listName + if (strings.HasSuffix(tableName, "_LIST")) { + tableName = tableName[0:len(tableName) - len("_LIST")] + } + l.ListName = tableName + l.ModelName = C.GoString(mod.name) + //Default database is CONFIG_DB since CVL works with config db mainly + l.Module = module + l.DbName = "CONFIG_DB" + //default delim '|' + l.RedisKeyDelim = "|" + //Default table size is -1 i.e. size limit + l.RedisTableSize = -1 + if (slist.max > 0) { + l.RedisTableSize = int(slist.max) + } + + l.LeafRef = make(map[string][]string) + l.XpathExpr = make(map[string][]*XpathExpression) + l.CustValidation = make(map[string]string) + l.WhenExpr = make(map[string][]*WhenExpression) + l.DfltLeafVal = make(map[string]string) + + //Add keys + keys := (*[10]*C.struct_lys_node_leaf)(unsafe.Pointer(slist.keys)) + for idx := 0; idx < int(slist.keys_size); idx++ { + keyName := C.GoString(keys[idx].name) + l.Keys = append(l.Keys, keyName) + } + + //Check for must expression + if (slist.must_size > 0) { + must := (*[10]C.struct_lys_restr)(unsafe.Pointer(slist.must)) + for idx := 0; idx < int(slist.must_size); idx++ { + exp := XpathExpression{Expr: C.GoString(must[idx].expr)} + if (must[idx].eapptag != nil) { + exp.ErrCode = C.GoString(must[idx].eapptag) + } + if (must[idx].emsg != nil) { + exp.ErrStr = C.GoString(must[idx].emsg) + } + + l.XpathExpr[listName] = append(l.XpathExpr[listName], + &exp) + } + } + + //Check for custom extension + if (slist.ext_size > 0) { + exts := (*[10]*C.struct_lys_ext_instance)(unsafe.Pointer(slist.ext)) + for idx := 0; idx < int(slist.ext_size); idx++ { + + extName := C.GoString(exts[idx].def.name) + argVal := C.GoString(exts[idx].arg_value) + + switch extName { + case "custom-validation": + if (argVal != "") { + l.CustValidation[listName] = argVal + } + case "db-name": + l.DbName = argVal + case "key-delim": + l.RedisKeyDelim = argVal + case "key-pattern": + l.RedisKeyPattern = argVal + case "map-leaf": + l.MapLeaf = strings.Split(argVal, " ") + } + } + + } + + //Add default key pattern + if l.RedisKeyPattern == "" { + keyPattern := []string{tableName} + for idx := 0; idx < len(l.Keys); idx++ { + keyPattern = append(keyPattern, fmt.Sprintf("{%s}", l.Keys[idx])) + } + l.RedisKeyPattern = strings.Join(keyPattern, l.RedisKeyDelim) + } + + getModelChildInfo(&l, + (*C.struct_lys_node)(unsafe.Pointer(slist)), false, nil) + + list = append(list, &l) + }//each list inside a container + }//each container + + C.free(unsafe.Pointer(set)) + return list +} +