From fdaad5f04f938642f4adbc708865102a1cc9b97f Mon Sep 17 00:00:00 2001 From: Brad House Date: Mon, 17 Feb 2025 20:17:51 -0500 Subject: [PATCH 1/4] port to libyang3 --- azure-pipelines.yml | 2 +- cvl/cvl.go | 10 +- cvl/cvl_api.go | 17 +- cvl/cvl_syntax.go | 72 ++-- cvl/internal/util/util.go | 11 +- cvl/internal/yparser/ly_path.go | 87 ++++ cvl/internal/yparser/yparser.go | 710 +++++++++++++++++--------------- 7 files changed, 524 insertions(+), 385 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 481647cfa..1a9231dc1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -64,7 +64,7 @@ stages: sudo service redis-server start # LIBYANG - sudo dpkg -i ../target/debs/bookworm/libyang*1.0.73*.deb + sudo dpkg -i ../target/debs/bookworm/libyang*3.*.deb displayName: "Install dependency" - script: | diff --git a/cvl/cvl.go b/cvl/cvl.go index 2a63b1196..03c3be949 100644 --- a/cvl/cvl.go +++ b/cvl/cvl.go @@ -885,6 +885,7 @@ func (c *CVL) generateYangParserData(jsonNode *jsonquery.Node, root **yparser.YP if *root, errObj = c.yp.MergeSubtree(*root, topNode); errObj.ErrCode != yparser.YP_SUCCESS { CVL_LOG(WARNING, "Unable to merge translated YANG data(libyang) "+ "while translating from request data to YANG format") + defer c.yp.FreeNode(topNode) cvlErrObj.ErrCode = CVL_SYNTAX_ERROR return cvlErrObj } @@ -945,6 +946,7 @@ func (c *CVL) translateToYang(jsonMap *map[string]interface{}, buildSyntaxDOM bo //Visit each top level list in a loop for creating table data cvlError = c.generateYangParserData(jsonNode, &root) if cvlError.ErrCode != CVL_SUCCESS { + defer c.yp.FreeNode(root) return nil, cvlError } } @@ -961,9 +963,10 @@ func (c *CVL) validate(data *yparser.YParserNode) CVLRetCode { if IsTraceAllowed(TRACE_LIBYANG) { TRACE_LOG(TRACE_LIBYANG, "\nValidate1 data=%v\n", c.yp.NodeDump(data)) } - errObj := c.yp.ValidateSyntax(data, depData) - if yparser.YP_SUCCESS != errObj.ErrCode { - return CVL_FAILURE + + errObj, _ := c.validateSyntax(data, depData) + if CVL_SUCCESS != errObj.ErrCode { + return errObj.ErrCode } cvlErrObj := c.validateCfgSemantics(c.yv.root) @@ -1041,6 +1044,7 @@ func (c *CVL) addCfgDataItem(configData *map[string]interface{}, var cfgData map[string]interface{} = *configData tblName, key := splitRedisKey(cfgDataItem.Key) + CVL_LOG(WARNING, "addCfgDataItem(): table: %s, key: %s", tblName, key) if tblName == "" || key == "" { //Bad redis key return "", "" diff --git a/cvl/cvl_api.go b/cvl/cvl_api.go index 18e7632cb..f1adf8705 100644 --- a/cvl/cvl_api.go +++ b/cvl/cvl_api.go @@ -200,7 +200,6 @@ func ValidationSessOpen(dbAccess cmn.DBAccess) (*CVL, CVLRetCode) { } func ValidationSessClose(c *CVL) CVLRetCode { - c.yp.DestroyCache() c = nil return CVL_SUCCESS @@ -233,12 +232,11 @@ func (c *CVL) ValidateIncrementalConfig(jsonData string) CVLRetCode { defer c.yp.FreeNode(root) if root == nil { return CVL_SYNTAX_ERROR - } - errObj := c.yp.ValidateSyntax(root, nil) - if yparser.YP_SUCCESS != errObj.ErrCode { - return CVL_FAILURE + errObj, _ := c.validateSyntax(root, nil) + if CVL_SUCCESS != errObj.ErrCode { + return errObj.ErrCode } //Add and fetch entries if already exists in Redis @@ -252,7 +250,7 @@ func (c *CVL) ValidateIncrementalConfig(jsonData string) CVLRetCode { //Merge existing data for update syntax or checking duplicate entries if existingData != nil { - if _, errObj = c.yp.MergeSubtree(root, existingData); errObj.ErrCode != yparser.YP_SUCCESS { + if _, err := c.yp.MergeSubtree(root, existingData); err.ErrCode != yparser.YP_SUCCESS { return CVL_ERROR } } @@ -313,7 +311,6 @@ func (c *CVL) ValidateEditConfig(cfgData []cmn.CVLEditConfigData) (cvlErr CVLErr c.clearTmpDbCache() c.yv.root = &xmlquery.Node{Type: xmlquery.DocumentNode} c.depDataCache = make(DepDataCacheType) - c.yp.DestroyCache() }() var cvlErrObj CVLErrorInfo @@ -517,8 +514,6 @@ func (c *CVL) ValidateEditConfig(cfgData []cmn.CVLEditConfigData) (cvlErr CVLErr CVL_LOG(WARNING, "\nValidateEditConfig(): OP_CREATE - Err = %v ", err1) } - c.yp.SetOperation("CREATE") - case cmn.OP_UPDATE: n, err1 := c.dbAccess.Exists(cfgData[i].Key).Result() if err1 != nil || n == 0 { //key must exists @@ -537,8 +532,6 @@ func (c *CVL) ValidateEditConfig(cfgData []cmn.CVLEditConfigData) (cvlErr CVLErr continue } - c.yp.SetOperation("UPDATE") - case cmn.OP_DELETE: n, err1 := c.dbAccess.Exists(cfgData[i].Key).Result() if err1 != nil || n == 0 { //key must exists @@ -550,8 +543,6 @@ func (c *CVL) ValidateEditConfig(cfgData []cmn.CVLEditConfigData) (cvlErr CVLErr cvlErrObj.Keys = splitKeyComponents(tbl, key) return cvlErrObj, CVL_SEMANTIC_KEY_NOT_EXIST } - - c.yp.SetOperation("DELETE") } yangListName := getRedisTblToYangList(tbl, key) diff --git a/cvl/cvl_syntax.go b/cvl/cvl_syntax.go index 87aa58f1d..5827afcaf 100644 --- a/cvl/cvl_syntax.go +++ b/cvl/cvl_syntax.go @@ -99,13 +99,12 @@ func (c *CVL) checkMaxElemConstraint(op cmn.CVLOperation, tableName string, key } // Add child node to a parent node -func (c *CVL) addChildNode(tableName string, parent *yparser.YParserNode, name string) *yparser.YParserNode { +func (c *CVL) addListNode(tableName string, parent *yparser.YParserNode, name string, keys []*yparser.YParserLeafValue) (*yparser.YParserNode, yparser.YParserError) { - //return C.lyd_new(parent, modelInfo.tableInfo[tableName].module, C.CString(name)) - return c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, parent, name) + return c.yp.AddListNode(modelInfo.tableInfo[tableName].module, parent, name, keys) } -func (c *CVL) addChildLeaf(config bool, tableName string, parent *yparser.YParserNode, name string, value string, multileaf *[]*yparser.YParserLeafValue) { +func (c *CVL) appendLeafValue(name string, value string, multileaf *[]*yparser.YParserLeafValue) { /* If there is no value then assign default space string. */ if len(value) == 0 { @@ -127,21 +126,23 @@ func (c *CVL) generateTableFieldsData(config bool, tableName string, jsonNode *j jsonFieldNode.FirstChild != nil && jsonFieldNode.FirstChild.Type == jsonquery.TextNode { - if len(modelInfo.tableInfo[tableName].mapLeaf) == 2 { //mapping should have two leaf always - batchInnerListLeaf := make([]*yparser.YParserLeafValue, 0) + if len(modelInfo.tableInfo[tableName].mapLeaf) == 2 { //mapping should have two leaf always, first is key + batchInnerListLeafKeys := make([]*yparser.YParserLeafValue, 0) + batchInnerListLeafNonKeys := 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, &batchInnerListLeaf) - c.addChildLeaf(config, tableName, - listNode, modelInfo.tableInfo[tableName].mapLeaf[1], - jsonFieldNode.FirstChild.Data, &batchInnerListLeaf) + c.appendLeafValue(modelInfo.tableInfo[tableName].mapLeaf[0], jsonFieldNode.Data, &batchInnerListLeafKeys) + c.appendLeafValue(modelInfo.tableInfo[tableName].mapLeaf[1], jsonFieldNode.FirstChild.Data, &batchInnerListLeafNonKeys) + + listNode, err := c.addListNode(tableName, parent, tableName, batchInnerListLeafKeys) + if err.ErrCode != yparser.YP_SUCCESS { + cvlErrObj = createCVLErrObj(err, jsonNode) + return cvlErrObj + } - if errObj := c.yp.AddMultiLeafNodes(modelInfo.tableInfo[tableName].module, listNode, batchInnerListLeaf); errObj.ErrCode != yparser.YP_SUCCESS { + if errObj := c.yp.AddMultiLeafNodes(modelInfo.tableInfo[tableName].module, listNode, batchInnerListLeafNonKeys); errObj.ErrCode != yparser.YP_SUCCESS { cvlErrObj = createCVLErrObj(errObj, jsonNode) - CVL_LOG(WARNING, "Failed to create innner list leaf nodes, data = %v", batchInnerListLeaf) return cvlErrObj } } else { @@ -150,12 +151,10 @@ func (c *CVL) generateTableFieldsData(config bool, tableName string, jsonNode *j if len(hashRefMatch) == 3 { - c.addChildLeaf(config, tableName, - parent, jsonFieldNode.Data, + c.appendLeafValue(jsonFieldNode.Data, hashRefMatch[2], multileaf) //take hashref key value } else { - c.addChildLeaf(config, tableName, - parent, jsonFieldNode.Data, + c.appendLeafValue(jsonFieldNode.Data, jsonFieldNode.FirstChild.Data, multileaf) } } @@ -165,8 +164,7 @@ func (c *CVL) generateTableFieldsData(config bool, tableName string, jsonNode *j jsonFieldNode.FirstChild.Type == jsonquery.ElementNode { //Array data e.g. VLAN members for arrayNode := jsonFieldNode.FirstChild; arrayNode != nil; arrayNode = arrayNode.NextSibling { - c.addChildLeaf(config, tableName, - parent, jsonFieldNode.Data, + c.appendLeafValue(jsonFieldNode.Data, arrayNode.FirstChild.Data, multileaf) } } @@ -189,17 +187,18 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node) (*yparser //E.g. ACL_RULE is mapped as // container ACL_RULE { list ACL_RULE_LIST {} } var topNode *yparser.YParserNode - var listConatinerNode *yparser.YParserNode + var listContainerNode *yparser.YParserNode + var err yparser.YParserError //Traverse each key instance for jsonNode = jsonNode.FirstChild; jsonNode != nil; jsonNode = jsonNode.NextSibling { - //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(origTableName, jsonNode.Data); yangListName != "" { tableName = yangListName } + if _, exists := modelInfo.tableInfo[tableName]; !exists { CVL_LOG(WARNING, "Schema details not found for %s", tableName) cvlErrObj.ErrCode = CVL_SYNTAX_ERROR @@ -209,13 +208,21 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node) (*yparser } if !topNodesAdded { // Add top most conatiner e.g. 'container sonic-acl {...}' - topNode = c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, + topNode, err = c.yp.AddContainerNode(modelInfo.tableInfo[tableName].module, nil, modelInfo.tableInfo[tableName].modelName) + if err.ErrCode != yparser.YP_SUCCESS { + cvlErrObj = createCVLErrObj(err, jsonNode) + return nil, cvlErrObj + } //Add the container node for each list //e.g. 'container ACL_TABLE { list ACL_TABLE_LIST ...} - listConatinerNode = c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, + listContainerNode, err = c.yp.AddContainerNode(modelInfo.tableInfo[tableName].module, topNode, origTableName) + if err.ErrCode != yparser.YP_SUCCESS { + cvlErrObj = createCVLErrObj(err, jsonNode) + return nil, cvlErrObj + } topNodesAdded = true } keyValuePair := getRedisToYangKeys(tableName, jsonNode.Data) @@ -236,15 +243,17 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node) (*yparser //Ideally they are same except when one Redis table is split //into multiple YANG lists - //Add table i.e. create list element - listNode := c.addChildNode(tableName, listConatinerNode, tableName+"_LIST") //Add the list to the top node - //For each key combination //Add keys as leaf to the list + keyList := make([]*yparser.YParserLeafValue, 0) for idx = 0; idx < keyCompCount; idx++ { - c.addChildLeaf(config, tableName, - listNode, keyValuePair[idx].key, - keyValuePair[idx].values[keyIndices[idx]], &c.batchLeaf) + c.appendLeafValue(keyValuePair[idx].key, + keyValuePair[idx].values[keyIndices[idx]], &keyList) + } + listNode, err := c.addListNode(tableName, listContainerNode, tableName+"_LIST", keyList) //Add the list to the top node + if err.ErrCode != yparser.YP_SUCCESS { + cvlErrObj = createCVLErrObj(err, jsonNode) + return nil, cvlErrObj } //Get all fields under the key field and add them as children of the list @@ -273,7 +282,6 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node) (*yparser //process batch leaf creation if errObj := c.yp.AddMultiLeafNodes(modelInfo.tableInfo[tableName].module, listNode, c.batchLeaf); errObj.ErrCode != yparser.YP_SUCCESS { cvlErrObj = createCVLErrObj(errObj, jsonNode) - CVL_LOG(WARNING, "Failed to create leaf nodes, data = %v", c.batchLeaf) return nil, cvlErrObj } diff --git a/cvl/internal/util/util.go b/cvl/internal/util/util.go index 34511946e..a1664f251 100644 --- a/cvl/internal/util/util.go +++ b/cvl/internal/util/util.go @@ -25,16 +25,17 @@ package util extern void customLogCallback(LY_LOG_LEVEL, char* msg, char* path); -static void customLogCb(LY_LOG_LEVEL level, const char* msg, const char* path) { - customLogCallback(level, (char*)msg, (char*)path); +static void customLogCb(LY_LOG_LEVEL level, const char* msg, const char* data_path, const char * schema_path, uint64_t line) { + (void)line; + customLogCallback(level, (char*)msg, (char*)data_path?data_path:schema_path); } static void ly_set_log_callback(int enable) { - ly_set_log_clb(customLogCb, 1); + ly_set_log_clb(customLogCb); if (enable == 1) { - ly_verb(LY_LLDBG); + ly_log_level(LY_LLDBG); } else { - ly_verb(LY_LLERR); + ly_log_level(LY_LLERR); } } diff --git a/cvl/internal/yparser/ly_path.go b/cvl/internal/yparser/ly_path.go index a282e127e..e94459276 100644 --- a/cvl/internal/yparser/ly_path.go +++ b/cvl/internal/yparser/ly_path.go @@ -22,8 +22,38 @@ package yparser import ( "regexp" "strings" + "unsafe" ) +/* +#cgo LDFLAGS: -lyang +#include +#include +#include +#include +#include + +const char *golys_module_from_prefix(const struct lys_module *module, const char *prefix) +{ + const struct lysp_module *mod; + LY_ARRAY_COUNT_TYPE u; + + if (module == NULL || prefix == NULL) { + return NULL; + } + + mod = module->parsed; + LY_ARRAY_FOR(mod->imports, u) { + if (strcmp(mod->imports[u].prefix, prefix) == 0) { + return mod->imports[u].name; + } + } + + return NULL; +} +*/ +import "C" + // parseLyPath parses a libyang formatted path; extracts table name, key components // and field name from it. Path should represent a sonic yang node. Path elements // are interpreted as follows: @@ -129,4 +159,61 @@ func parseLyMessage(s string, regex ...*regexp.Regexp) string { } } return "" + +// This function takes a when, must, or leafref path in its original form as +// written in the YANG schema files, and converts it into its fully qualified +// format. The format in the YANG schema uses import prefixes, so we have to +// replace each import prefix with the fully qualified module name. +// +// Libyang1 would do this for us automatically, but in Libyang3 we have to do +// this conversion ourselves. +// +// In this implementation we are cheating a bit. It would be quite a bit of +// effort to tokenize everything, especially when and must clauses, so we rely +// on the fact that the namespace can only contain alphanumeric or hypen +// characters and will always begin with either "/" or "[" and end with ":". +// So we extract each prefix using a regex, perform a lookup to determine the +// module name then replace each instance of the prefix with the module name, +// in a loop until all matched prefixes have been processed. +// +// Examples: +// +// Leafref: +// /po:sonic-portchannel/po:PORTCHANNEL/po:PORTCHANNEL_LIST/po:name +// -> +// /sonic-portchannel:sonic-portchannel/sonic-portchannel:PORTCHANNEL/sonic-portchannel:PORTCHANNEL_LIST/sonic-portchannel:name +// +// Must: +// (/cmn:operation/cmn:operation != 'CREATE') or +// (count(/si:sonic-interface/si:INTERFACE/si:INTERFACE_IPADDR_LIST[si:ip_prefix=current()/../ip_prefix] [si:portname=(/svl:sonic-vlan/svl:VLAN_MEMBER/svl:VLAN_MEMBER_LIST[svl:name=current()]/svl:ifname)]) = 0) +// -> +// (/sonic-common:operation/sonic-common:operation != 'CREATE') or +// (count(/sonic-interface:sonic-interface/sonic-interface:INTERFACE/sonic-interface:INTERFACE_IPADDR_LIST[sonic-interface:ip_prefix=current()/../ip_prefix] [sonic-interface:portname=(/sonic-vlan:sonic-vlan/sonic-vlan:VLAN_MEMBER/sonic-vlan:VLAN_MEMBER_LIST[sonic-vlan:name=current()]/sonic-vlan:ifname)]) = 0) +var lyXPathPrefix = regexp.MustCompile("[[/][A-Za-z0-9_-]+:") + +func rewriteXPathPrefix(module *YParserModule, xpath string) string { + hasPrefix := make(map[string]bool) + prefixes := lyXPathPrefix.FindAllString(xpath, -1) + + for _, prefix := range prefixes { + if _, ok := hasPrefix[prefix]; ok { + continue + } + hasPrefix[prefix] = true + + // strip / and : surrounding prefix + prefix = prefix[1 : len(prefix)-1] + + // Dereference it + Cprefix := C.CString(prefix) + defer C.free(unsafe.Pointer(Cprefix)) + module_name := C.golys_module_from_prefix((*C.struct_lys_module)(module), Cprefix) + if module_name == nil { + continue + } + xpath = strings.ReplaceAll(xpath, "/"+prefix+":", "/"+C.GoString(module_name)+":") + xpath = strings.ReplaceAll(xpath, "["+prefix+":", "["+C.GoString(module_name)+":") + } + + return xpath } diff --git a/cvl/internal/yparser/yparser.go b/cvl/internal/yparser/yparser.go index a6a867e35..396678956 100644 --- a/cvl/internal/yparser/yparser.go +++ b/cvl/internal/yparser/yparser.go @@ -40,21 +40,118 @@ import ( #include #include -extern int lyd_check_mandatory_tree(struct lyd_node *root, struct ly_ctx *ctx, const struct lys_module **modules, int mod_count, int options); +size_t golysc_ext_instance_array_count(struct lysc_ext_instance *arr) +{ + return LY_ARRAY_COUNT(arr); +} -int lyd_data_validate(struct lyd_node **node, int options, struct ly_ctx *ctx) +struct lysc_ext_instance *golysc_ext_instance_array_idx(struct lysc_ext_instance *arr, size_t idx) { - int ret = -1; + return &arr[idx]; +} - //Check mandatory elements as it is skipped for LYD_OPT_EDIT - ret = lyd_check_mandatory_tree(*node, ctx, NULL, 0, LYD_OPT_CONFIG | LYD_OPT_NOEXTDEPS); +size_t golysc_must_array_count(struct lysc_must *arr) +{ + return LY_ARRAY_COUNT(arr); +} - if (ret != 0) - { - return ret; +size_t golyd_value_array_count(struct lyd_value **arr) +{ + return LY_ARRAY_COUNT(arr); +} + +struct lyd_value *golyd_value_array_idx(struct lyd_value **arr, size_t idx) +{ + return arr[idx]; +} + +struct ly_ctx *goly_ctx_new(const char *search_dir, uint16_t options) +{ + struct ly_ctx *ctx = NULL; + if (ly_ctx_new(search_dir, options, &ctx) != LY_SUCCESS) { + return NULL; + } + return ctx; +} + +struct lyd_node *golyd_new_inner(struct lyd_node *parent, const struct lys_module *module, const char *name) +{ + struct lyd_node *node = NULL; + if (lyd_new_inner(parent, module, name, 0, &node) != LY_SUCCESS) { + return NULL; + } + return node; +} + +struct lyd_node *golyd_new_list2(struct lyd_node *parent, const struct lys_module *module, const char *name, const char *keylist, uint32_t options) +{ + struct lyd_node *node = NULL; + + if (lyd_new_list2(parent, module, name, keylist, options, &node) != LY_SUCCESS) { + return NULL; + } + return node; +} + +size_t golysc_node_list_keys_count(const struct lysc_node *node) +{ + const struct lysc_node *n; + const struct lysc_node_list *l; + size_t cnt = 0; + + if (node->nodetype != LYS_LIST) { + return 0; + } + + l = (const struct lysc_node_list *)node; + + for (n=l->child; n != NULL; n = n->next) { + if (n->flags & LYS_KEY) { + cnt++; + } + } + return cnt; +} + +const char *golysc_node_get_when(const struct lysc_node *node) +{ + struct lysc_when **when = NULL; + + switch (node->nodetype) { + case LYS_CHOICE: + const struct lysc_node_choice *ch = (const struct lysc_node_choice *)node; + when = ch->when; + case LYS_CASE: + const struct lysc_node_case *ca = (const struct lysc_node_case *)node; + when = ca->when; } - return lyd_validate(node, options, ctx); + if (when == NULL || LY_ARRAY_COUNT(when) == 0) { + return NULL; + } + return lyxp_get_expr(when[0]->cond); +} + +static ly_bool lysc_node_is_union(const struct lysc_node *node) +{ + struct lysc_type *type; + + if (node == NULL) { + return 0; + } + if (node->nodetype == LYS_LEAF) { + type = ((struct lysc_node_leaf *)node)->type; + } else if (node->nodetype == LYS_LEAFLIST) { + type = ((struct lysc_node_leaflist *)node)->type; + } else { + return 0; + } + + if (type->basetype != LY_TYPE_UNION) { + return 0; + } + + return 1; } struct leaf_value { @@ -65,9 +162,9 @@ struct leaf_value { int lyd_multi_new_leaf(struct lyd_node *parent, const struct lys_module *module, struct leaf_value *leafValArr, int size) { - const char *name, *val; + const char *name, *val; struct lyd_node *leaf; - struct lys_type *type = NULL; + struct lysc_type *type = NULL; int has_ptr_type = 0; int idx = 0; @@ -81,162 +178,140 @@ int lyd_multi_new_leaf(struct lyd_node *parent, const struct lys_module *module, name = leafValArr[idx].name; val = leafValArr[idx].value; - if (NULL == (leaf = lyd_new_leaf(parent, module, name, val))) + if (lyd_new_term(parent, module, name, val, 0, &leaf) != LY_SUCCESS) { + fprintf(stderr, "lyd_multi_new_leaf(): lyd_new_term(%s, %s) failed\n", name, val); return -1; } - - //Validate all union types as it is skipped for LYD_OPT_EDIT - if (((struct lys_node_leaflist*)leaf->schema)->type.base == LY_TYPE_UNION) - { - 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; - } - - //Restore has_ptr_type - type->info.uni.has_ptr_type = has_ptr_type; - } } - return 0; } -struct lyd_node *lyd_find_node(struct lyd_node *root, const char *xpath) +int lyd_node_leafref_match_in_union(const struct lys_module *module, const char *xpath, const char *value) { + const struct lysc_node *node = NULL; + int idx = 0; struct ly_set *set = NULL; - struct lyd_node *node = NULL; - if (root == NULL) + if (module == NULL) { - return NULL; - } - - set = lyd_find_path(root, xpath); - if (set == NULL || set->number == 0) { - return NULL; + return -1; } - node = set->set.d[0]; - ly_set_free(set); - - return node; -} - -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) - { + if (lys_find_xpath(module->ctx, NULL, xpath, 0, &set) != LY_SUCCESS || set == NULL) { return -1; } - set = lys_find_path(module, NULL, xpath); - if (set == NULL || set->number == 0) { - return -1; + if (set->count == 0) { + ly_set_free(set, NULL); + return -1; } - node = set->set.s[0]; - ly_set_free(set); + node = set->snodes[0]; + ly_set_free(set, NULL); - //Now check if it matches with any leafref node - lNode = (struct lys_node_leaflist*)node; + if (!lysc_node_is_union(node)) { + return -1; + } - for (idx = 0; idx < lNode->type.info.uni.count; idx++) + if (lysc_node_lref_targets(node, &set) != LY_SUCCESS || set == NULL) { - if (lNode->type.info.uni.types[idx].base != LY_TYPE_LEAFREF) - { - //Look for leafref type only - continue; - } + return -1; + } - if (0 == lyd_validate_value((struct lys_node*) - lNode->type.info.uni.types[idx].info.lref.target, value)) + for (idx = 0; idx < set->count; idx++) { + if (lyd_value_validate(module->ctx, set->snodes[idx], value, strlen(value), NULL, NULL, NULL) == LY_SUCCESS) { + ly_set_free(set, NULL); return 0; } } + ly_set_free(set, NULL); return -1; } -struct lys_node* lys_get_snode(struct ly_set *set, int idx) { - if (set == NULL || set->number == 0) { - return NULL; +struct lysc_xpath_targets { + const char **xpathlist; // path list + size_t count; // actual path count +}; + +void golys_xpath_targets_free(struct lysc_xpath_targets *paths) +{ + size_t i; + + if (paths == NULL) { + return; } - return set->set.s[idx]; + free(paths->xpathlist); + free(paths); } -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); +static struct lysc_xpath_targets *golys_xpath_targets_alloc(size_t cnt) +{ + struct lysc_xpath_targets *paths = malloc(sizeof(*paths)); + paths->xpathlist = malloc(sizeof(*paths->xpathlist) * cnt); + paths->count = cnt; + return paths; } -struct lys_leaf_ref_path { - const char *path[10]; //max 10 path - int count; //actual path count -}; +static const char *nonLeafRef = "non-leafref"; +struct lysc_xpath_targets *golys_xpath_targets_get(const struct lysc_node *node) +{ + struct lysc_type *type; + struct lysc_xpath_targets *paths = NULL; + LY_ARRAY_COUNT_TYPE u; -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++; - } + if (node == NULL) { + return NULL; + } + + type = ((struct lysc_node_leaf *)node)->type; + if (type->basetype == LY_TYPE_UNION) { + // union with possible leafrefs + struct lysc_type_union *type_un = (struct lysc_type_union *)type; + + paths = golys_xpath_targets_alloc(LY_ARRAY_COUNT(type_un->types)); + LY_ARRAY_FOR(type_un->types, u) { + struct lysc_type_leafref *lref_type; + + if (type_un->types[u]->basetype != LY_TYPE_LEAFREF) { + paths->xpathlist[u] = nonLeafRef; continue; } - leafrefs.path[leafrefs.count] = node->type.info.uni.types[typeCnt].info.lref.path; - leafrefs.count += 1; + lref_type = (struct lysc_type_leafref *)type_un->types[u]; + + if (lref_type->path && lyxp_get_expr(lref_type->path) != NULL) { + paths->xpathlist[u] = lyxp_get_expr(lref_type->path); + } else { + paths->xpathlist[u] = nonLeafRef; + } } - } + } else if (type->basetype == LY_TYPE_LEAFREF) { + struct lysc_type_leafref *lref_type = (struct lysc_type_leafref *)type; - if ((leafrefs.count - nonLeafRefCnt) > 0) { - return &leafrefs; - } else { - return NULL; + paths = golys_xpath_targets_alloc(1); + + if (lref_type->path && lyxp_get_expr(lref_type->path) != NULL) { + paths->xpathlist[0] = lyxp_get_expr(lref_type->path); + } else { + paths->xpathlist[0] = nonLeafRef; + } } + return paths; } - */ import "C" type YParserCtx C.struct_ly_ctx type YParserNode C.struct_lyd_node -type YParserSNode C.struct_lys_node +type YParserSNode C.struct_lysc_node type YParserModule C.struct_lys_module var ypCtx *YParserCtx -var ypOpModule *YParserModule -var ypOpRoot *YParserNode //Operation root -var ypOpNode *YParserNode //Operation node type XpathExpression struct { Expr string @@ -278,9 +353,7 @@ type YParserLeafValue struct { } type YParser struct { - //ctx *YParserCtx //Parser context - root *YParserNode //Top evel root for validation - operation string //Edit operation + // Empty } // YParserError YParser Error Structure @@ -321,13 +394,6 @@ const ( YP_INTERNAL_UNKNOWN ) -const ( - YP_NOP = 1 + iota - YP_OP_CREATE - YP_OP_UPDATE - YP_OP_DELETE -) - // cvl-yin generator adds this prefix to all user defined error messages. const customErrorPrefix = "[Error]" @@ -350,9 +416,9 @@ func init() { func Debug(on bool) { if on { - C.ly_verb(C.LY_LLDBG) + C.ly_log_level(C.LY_LLDBG) } else { - C.ly_verb(C.LY_LLERR) + C.ly_log_level(C.LY_LLERR) } } @@ -360,45 +426,70 @@ func Initialize() { if !yparserInitialized { cs := C.CString(CVL_SCHEMA) defer C.free(unsafe.Pointer(cs)) - ypCtx = (*YParserCtx)(C.ly_ctx_new(cs, 0)) - C.ly_verb(C.LY_LLERR) + ypCtx = (*YParserCtx)(C.goly_ctx_new(cs, 0)) + C.ly_log_level(C.LY_LLERR) // yparserInitialized = true } } func Finish() { if yparserInitialized { - C.ly_ctx_destroy((*C.struct_ly_ctx)(ypCtx), nil) + C.ly_ctx_destroy((*C.struct_ly_ctx)(ypCtx)) // yparserInitialized = false } } // ParseSchemaFile Parse YIN schema file func ParseSchemaFile(modelFile string) (*YParserModule, YParserError) { - module := C.lys_parse_path((*C.struct_ly_ctx)(ypCtx), C.CString(modelFile), C.LYS_IN_YIN) - if module == nil { + var module *C.struct_lys_module + csModelFile := C.CString(modelFile) + defer C.free(unsafe.Pointer(csModelFile)) + if C.lys_parse_path((*C.struct_ly_ctx)(ypCtx), csModelFile, C.LYS_IN_YIN, &module) != C.LY_SUCCESS { return nil, getErrorDetails() } - 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"))) + return (*YParserModule)(module), YParserError{ErrCode: YP_SUCCESS} +} + +func (yp *YParser) AddContainerNode(module *YParserModule, parent *YParserNode, name string) (*YParserNode, YParserError) { + nameCStr := C.CString(name) + defer C.free(unsafe.Pointer(nameCStr)) + ret := (*YParserNode)(C.golyd_new_inner((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), (*C.char)(nameCStr))) + if ret == nil { + TRACE_LOG(TRACE_YPARSER, "Failed parsing node %s", name) + return ret, getErrorDetails() } - return (*YParserModule)(module), YParserError{ErrCode: YP_SUCCESS} + return ret, YParserError{ErrCode: YP_SUCCESS} } -// AddChildNode Add child node to a parent node -func (yp *YParser) AddChildNode(module *YParserModule, parent *YParserNode, name string) *YParserNode { +func (yp *YParser) AddListNode(module *YParserModule, parent *YParserNode, name string, keys []*YParserLeafValue) (*YParserNode, YParserError) { + var keylist string + + // All key values predicate in the form of "[key1='val1'][key2='val2']...", they do not have to be ordered. + for index := 0; index < len(keys); index++ { + if (keys[index] == nil) || (keys[index].Name == "") { + break + } + + keylist += "[" + keylist += keys[index].Name + keylist += "='" + keylist += keys[index].Value + keylist += "']" + } + nameCStr := C.CString(name) defer C.free(unsafe.Pointer(nameCStr)) - ret := (*YParserNode)(C.lyd_new((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), (*C.char)(nameCStr))) + keylistCStr := C.CString(keylist) + defer C.free(unsafe.Pointer(keylistCStr)) + ret := (*YParserNode)(C.golyd_new_list2((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), (*C.char)(nameCStr), (*C.char)(keylistCStr), 0)) if ret == nil { TRACE_LOG(TRACE_YPARSER, "Failed parsing node %s", name) + return ret, getErrorDetails() } - return ret + return ret, YParserError{ErrCode: YP_SUCCESS} } // IsLeafrefMatchedInUnion Check if value matches with leafref node in union @@ -414,6 +505,9 @@ func (yp *YParser) IsLeafrefMatchedInUnion(module *YParserModule, xpath, value s // AddMultiLeafNodes dd child node to a parent node func (yp *YParser) AddMultiLeafNodes(module *YParserModule, parent *YParserNode, multiLeaf []*YParserLeafValue) YParserError { + if len(multiLeaf) == 0 { + return YParserError{ErrCode: YP_SUCCESS} + } leafValArr := make([]C.struct_leaf_value, len(multiLeaf)) tmpArr := make([]*C.char, len(multiLeaf)*2) @@ -423,7 +517,6 @@ func (yp *YParser) AddMultiLeafNodes(module *YParserModule, parent *YParserNode, if (multiLeaf[index] == nil) || (multiLeaf[index].Name == "") { break } - //Accumulate all name/value in array to be passed in lyd_multi_new_leaf() nameCStr := C.CString(multiLeaf[index].Name) valCStr := C.CString(multiLeaf[index].Value) @@ -458,7 +551,8 @@ func (yp *YParser) NodeDump(root *YParserNode) string { return "" } else { var outBuf *C.char - C.lyd_print_mem(&outBuf, (*C.struct_lyd_node)(root), C.LYD_XML, C.LYP_WITHSIBLINGS) + C.lyd_print_mem(&outBuf, (*C.struct_lyd_node)(root), C.LYD_JSON, C.LYD_PRINT_WITHSIBLINGS) + defer C.free(unsafe.Pointer(outBuf)) return C.GoString(outBuf) } } @@ -476,7 +570,7 @@ func (yp *YParser) MergeSubtree(root, node *YParserNode) (*YParserNode, YParserE TRACE_LOG(TRACE_YPARSER, "Root subtree = %v\n", rootdumpStr) } - if C.lyd_merge_to_ctx(&rootTmp, (*C.struct_lyd_node)(node), C.LYD_OPT_DESTRUCT, (*C.struct_ly_ctx)(ypCtx)) != 0 { + if C.lyd_merge_siblings(&rootTmp, (*C.struct_lyd_node)(node), C.LYD_MERGE_DESTRUCT) != C.LY_SUCCESS { return (*YParserNode)(rootTmp), getErrorDetails() } @@ -488,34 +582,16 @@ func (yp *YParser) MergeSubtree(root, node *YParserNode) (*YParserNode, YParserE return (*YParserNode)(rootTmp), YParserError{ErrCode: YP_SUCCESS} } -func (yp *YParser) DestroyCache() YParserError { - - if yp.root != nil { - C.lyd_free_withsiblings((*C.struct_lyd_node)(yp.root)) - yp.root = nil - } - - return YParserError{ErrCode: YP_SUCCESS} -} - -// SetOperation Set operation -func (yp *YParser) SetOperation(op string) YParserError { - if ypOpNode == nil { - return YParserError{ErrCode: YP_INTERNAL_UNKNOWN} - } +// createTempDepData merge depdata and data to create temp data. used in syntax, semantic and custom validation +func (yp *YParser) mergeDepData(data *(*C.struct_lyd_node), depData *YParserNode, destruct bool) YParserError { + var flags C.uint16_t - if C.lyd_change_leaf_data((*C.struct_lyd_node)(ypOpNode), C.CString(op)) != 0 { - return YParserError{ErrCode: YP_INTERNAL_UNKNOWN} + flags = 0 + if destruct { + flags |= C.LYD_MERGE_DESTRUCT } - yp.operation = op - return YParserError{ErrCode: YP_SUCCESS} -} - -// createTempDepData merge depdata and data to create temp data. used in syntax, semantic and custom validation -func (yp *YParser) createTempDepData(dataTmp *(*C.struct_lyd_node), depData *YParserNode) YParserError { - - if C.lyd_merge_to_ctx(dataTmp, (*C.struct_lyd_node)(depData), C.LYD_OPT_DESTRUCT, (*C.struct_ly_ctx)(ypCtx)) != 0 { + if C.lyd_merge_siblings(data, (*C.struct_lyd_node)(depData), flags) != C.LY_SUCCESS { TRACE_LOG((TRACE_SYNTAX | TRACE_LIBYANG), "Unable to merge dependent data\n") return getErrorDetails() } @@ -523,21 +599,27 @@ func (yp *YParser) createTempDepData(dataTmp *(*C.struct_lyd_node), depData *YPa } // ValidateSyntax Perform syntax checks -func (yp *YParser) ValidateSyntax(data, depData *YParserNode) YParserError { - dataTmp := (*C.struct_lyd_node)(data) +func (yp *YParser) ValidateSyntax(data *YParserNode, depData *YParserNode) YParserError { + if data == nil { + return YParserError{ErrCode: YP_INTERNAL_UNKNOWN} + } - if data != nil && depData != nil { - //merge ependent data for synatx validation - Update/Delete case - err := yp.createTempDepData(&dataTmp, depData) + dataPtr := (*C.struct_lyd_node)(data) + + if depData != nil { + // merge dependent data for syntax validation - Update/Delete case + // This is a destructive merge, depData is no longer valid. + err := yp.mergeDepData(&dataPtr, depData, true) + depData = nil if err.ErrCode != YP_SUCCESS { return err } } //Just validate syntax - if C.lyd_data_validate(&dataTmp, C.LYD_OPT_EDIT|C.LYD_OPT_NOEXTDEPS, (*C.struct_ly_ctx)(ypCtx)) != 0 { + if C.lyd_validate_all(&dataPtr, (*C.struct_ly_ctx)(ypCtx), C.LYD_VALIDATE_PRESENT|C.LYD_VALIDATE_NO_STATE|C.LYD_VALIDATE_NOEXTDEPS, nil) != C.LY_SUCCESS { if IsTraceAllowed(TRACE_ONERROR) { - strData := yp.NodeDump((*YParserNode)(dataTmp)) + strData := yp.NodeDump((*YParserNode)(dataPtr)) TRACE_LOG(TRACE_ONERROR, "Failed to validate Syntax, data = %v", strData) } return getErrorDetails() @@ -548,7 +630,7 @@ func (yp *YParser) ValidateSyntax(data, depData *YParserNode) YParserError { func (yp *YParser) FreeNode(node *YParserNode) YParserError { if node != nil { - C.lyd_free_withsiblings((*C.struct_lyd_node)(node)) + C.lyd_free_all((*C.struct_lyd_node)(node)) node = nil } @@ -562,41 +644,28 @@ func translateLYErrToYParserErr(LYErrcode int) YParserRetCode { switch LYErrcode { case C.LYVE_SUCCESS: /**< no error */ ypErrCode = YP_SUCCESS - case C.LYVE_XML_MISS, C.LYVE_INARG, C.LYVE_MISSELEM: /**< missing XML object */ - ypErrCode = YP_SYNTAX_MISSING_FIELD - case C.LYVE_XML_INVAL, C.LYVE_XML_INCHAR, C.LYVE_INMOD, C.LYVE_INELEM, C.LYVE_INVAL, C.LYVE_MCASEDATA: /**< invalid XML object */ - ypErrCode = YP_SYNTAX_INVALID_FIELD - case C.LYVE_EOF, C.LYVE_INSTMT, C.LYVE_INPAR, C.LYVE_INID, C.LYVE_MISSSTMT, C.LYVE_MISSARG: /**< invalid statement (schema) */ + case C.LYVE_SYNTAX: /**< generic syntax error */ ypErrCode = YP_SYNTAX_INVALID_INPUT_DATA - case C.LYVE_TOOMANY: /**< too many instances of some object */ - ypErrCode = YP_SYNTAX_MULTIPLE_INSTANCE - case C.LYVE_DUPID, C.LYVE_DUPLEAFLIST, C.LYVE_DUPLIST, C.LYVE_NOUNIQ: /**< duplicated identifier (schema) */ - ypErrCode = YP_SYNTAX_DUPLICATE - case C.LYVE_ENUM_INVAL: /**< invalid enum value (schema) */ - ypErrCode = YP_SYNTAX_ENUM_INVALID - case C.LYVE_ENUM_INNAME: /**< invalid enum name (schema) */ - ypErrCode = YP_SYNTAX_ENUM_INVALID_NAME - case C.LYVE_ENUM_WS: /**< enum name with leading/trailing whitespaces (schema) */ - ypErrCode = YP_SYNTAX_ENUM_WHITESPACE - case C.LYVE_KEY_NLEAF, C.LYVE_KEY_CONFIG, C.LYVE_KEY_TYPE: /**< list key is not a leaf (schema) */ - ypErrCode = YP_SEMANTIC_KEY_INVALID - case C.LYVE_KEY_MISS, C.LYVE_PATH_MISSKEY: /**< list key not found (schema) */ + case C.LYVE_SYNTAX_YANG: /**< YANG-related syntax error */ + ypErrCode = YP_SYNTAX_INVALID_INPUT_DATA + case C.LYVE_SYNTAX_YIN: /**< YIN-related syntax error */ + ypErrCode = YP_SYNTAX_INVALID_INPUT_DATA + case C.LYVE_REFERENCE: /**< invalid referencing or using an item */ + ypErrCode = YP_SEMANTIC_DEPENDENT_DATA_MISSING + case C.LYVE_XPATH: /**< invalid XPath expression */ ypErrCode = YP_SEMANTIC_KEY_NOT_EXIST - case C.LYVE_KEY_DUP: /**< duplicated key identifier (schema) */ - ypErrCode = YP_SEMANTIC_KEY_DUPLICATE - case C.LYVE_NOMIN: /**< min-elements constraint not honored (data) */ - ypErrCode = YP_SYNTAX_MINIMUM_INVALID - case C.LYVE_NOMAX: /**< max-elements constraint not honored (data) */ - ypErrCode = YP_SYNTAX_MAXIMUM_INVALID - case C.LYVE_NOMUST, C.LYVE_NOWHEN, C.LYVE_INWHEN, C.LYVE_NOLEAFREF: /**< unsatisfied must condition (data) */ + case C.LYVE_SEMANTICS: /**< generic semantic error */ + ypErrCode = YP_SEMANTIC_KEY_INVALID + case C.LYVE_SYNTAX_XML: /**< XML-related syntax error */ + ypErrCode = YP_SYNTAX_INVALID_FIELD + case C.LYVE_SYNTAX_JSON: /**< JSON-related syntax error */ + ypErrCode = YP_SYNTAX_INVALID_FIELD + case C.LYVE_DATA: /**< YANG data does not reflect some of the module restrictions */ ypErrCode = YP_SEMANTIC_DEPENDENT_DATA_MISSING - case C.LYVE_NOMANDCHOICE: /**< max-elements constraint not honored (data) */ - ypErrCode = YP_SEMANTIC_MANDATORY_DATA_MISSING - case C.LYVE_PATH_EXISTS: /**< target node already exists (path) */ - ypErrCode = YP_SEMANTIC_KEY_ALREADY_EXIST + case C.LYVE_OTHER: + ypErrCode = YP_INTERNAL_UNKNOWN default: ypErrCode = YP_INTERNAL_UNKNOWN - } return ypErrCode } @@ -613,25 +682,27 @@ func getErrorDetails() YParserError { var errMsg, errPath, errAppTag string ctx := (*C.struct_ly_ctx)(ypCtx) - ypErrFirst := C.ly_err_first(ctx) + ypErrLast := C.ly_err_last(ctx) - if ypErrFirst == nil { + if ypErrLast == nil { return YParserError{ ErrCode: ypErrCode, } } - if (ypErrFirst != nil) && ypErrFirst.prev.no == C.LY_SUCCESS { + if ypErrLast.err == C.LY_SUCCESS { return YParserError{ ErrCode: YP_SUCCESS, } } - if ypErrFirst != nil { - errMsg = C.GoString(ypErrFirst.prev.msg) - errPath = C.GoString(ypErrFirst.prev.path) - errAppTag = C.GoString(ypErrFirst.prev.apptag) + errMsg = C.GoString(ypErrLast.msg) + if ypErrLast.data_path != nil { + errPath = C.GoString(ypErrLast.data_path) + } else { + errPath = C.GoString(ypErrLast.schema_path) } + errAppTag = C.GoString(ypErrLast.apptag) // Try to resolve table, keys and field name from the error path. errtableName, key, ElemName = parseLyPath(errPath) @@ -649,10 +720,10 @@ func getErrorDetails() YParserError { errText = errMsg[len(customErrorPrefix):] } - switch C.ly_errno { + switch ypErrLast.err { case C.LY_EVALID: // validation failure - ypErrCode = translateLYErrToYParserErr(int(ypErrFirst.prev.vecode)) + ypErrCode = translateLYErrToYParserErr(int(ypErrLast.vecode)) if len(ElemName) != 0 { errMessage = "Field \"" + ElemName + "\" has invalid value" if len(ElemVal) != 0 { @@ -695,110 +766,82 @@ func getErrorDetails() YParserError { return errObj } -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, +func getModelChildInfo(l *YParserListInfo, node *C.struct_lysc_node, module *YParserModule, underWhen bool, whenExpr *WhenExpression) { - for sChild := node.child; sChild != nil; sChild = sChild.next { + for sChild := C.lysc_node_child(node); sChild != nil; sChild = sChild.next { switch sChild.nodetype { case C.LYS_LIST: - nodeInnerList := (*C.struct_lys_node_list)(unsafe.Pointer(sChild)) - innerListkeys := (*[10]*C.struct_lys_node_leaf)(unsafe.Pointer(nodeInnerList.keys)) - if nodeInnerList.keys_size == 1 { - keyName := C.GoString(innerListkeys[0].name) - l.MapLeaf = append(l.MapLeaf, keyName) + keysCnt := C.golysc_node_list_keys_count(sChild) + if keysCnt == 1 { + // fetch key leaf + for sChildInner := C.lysc_node_child(sChild); sChildInner != nil; sChildInner = sChildInner.next { + if sChildInner.nodetype == C.LYS_LEAF && (sChildInner.flags&C.LYS_KEY) != 0 { + keyName := C.GoString(sChildInner.name) + l.MapLeaf = append(l.MapLeaf, keyName) + break + } + } // Now, find and add the first non-key leaf. - for sChildInner := nodeInnerList.child; sChildInner != nil; sChildInner = sChildInner.next { - if sChildInner.nodetype == C.LYS_LEAF { - // Check if the leaf is not a key. - if name := C.GoString(sChildInner.name); name != keyName { - l.MapLeaf = append(l.MapLeaf, name) - break - } + for sChildInner := C.lysc_node_child(sChild); sChildInner != nil; sChildInner = sChildInner.next { + if sChildInner.nodetype == C.LYS_LEAF && (sChildInner.flags&C.LYS_KEY) == 0 { + name := C.GoString(sChildInner.name) + l.MapLeaf = append(l.MapLeaf, name) + break } } } else { // should never hit here, as linter does the validation - listName := C.GoString(nodeInnerList.name) - TRACE_LOG(TRACE_YPARSER, "Inner List %s for Dynamic fields has %d keys", listName, nodeInnerList.keys_size) + listName := C.GoString(sChild.name) + TRACE_LOG(TRACE_YPARSER, "Inner List %s for Dynamic fields has %d keys", listName, keysCnt) } - 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), + case C.LYS_CHOICE, C.LYS_CASE: + when := C.golysc_node_get_when(sChild) + if when != nil { + cWhenExp := WhenExpression{ + Expr: C.GoString(when), } listName := l.ListName + "_LIST" l.WhenExpr[listName] = append(l.WhenExpr[listName], - &usesWhenExp) - getModelChildInfo(l, sChild, true, &usesWhenExp) + &cWhenExp) + getModelChildInfo(l, sChild, module, true, &cWhenExp) } 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) + if underWhen && sChild.nodetype == C.LYS_CASE { + // Why only nodetype == C.LYS_CASE? old code was like that + getModelChildInfo(l, sChild, module, underWhen, whenExpr) } else { - getModelChildInfo(l, sChild, false, nil) + getModelChildInfo(l, sChild, module, 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) - + leafName := C.GoString(sChild.name) + var nodeMusts (*C.struct_lysc_must) + var nodeWhen (**C.struct_lysc_when) if sChild.nodetype == C.LYS_LEAF { + sleaf := (*C.struct_lysc_node_leaf)(unsafe.Pointer(sChild)) + nodeMusts = sleaf.musts + nodeWhen = sleaf.when if sleaf.dflt != nil { - l.DfltLeafVal[leafName] = C.GoString(sleaf.dflt) + l.DfltLeafVal[leafName] = C.GoString(C.lyd_value_get_canonical((*C.struct_ly_ctx)(ypCtx), 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)) - + sLeafList := (*C.struct_lysc_node_leaflist)(unsafe.Pointer(sChild)) + nodeMusts = sLeafList.musts + nodeWhen = sLeafList.when + if sLeafList.dflts != nil { tmpValStr := "" - for idx := 0; idx < int(sLeafList.dflt_size); idx++ { + for idx := 0; idx < int(C.golyd_value_array_count(sLeafList.dflts)); idx++ { if idx > 0 { //Separate multiple values by , tmpValStr = tmpValStr + "," } - tmpValStr = tmpValStr + C.GoString(dfltValArr[idx]) + tmpValStr = tmpValStr + C.GoString(C.lyd_value_get_canonical((*C.struct_ly_ctx)(ypCtx), C.golyd_value_array_idx(sLeafList.dflts, (C.size_t)(idx)))) } //Remove last ',' @@ -821,21 +864,22 @@ func getModelChildInfo(l *YParserListInfo, node *C.struct_lys_node, } //Check for leafref expression - leafRefs := C.lys_get_leafrefs(sleaf) + leafRefs := C.golys_xpath_targets_get(sChild) + defer C.golys_xpath_targets_free(leafRefs) if leafRefs != nil { - leafRefPaths := (*[10]*C.char)(unsafe.Pointer(&leafRefs.path)) + leafRefPaths := (*[256]*C.char)(unsafe.Pointer(leafRefs.xpathlist)) for idx := 0; idx < int(leafRefs.count); idx++ { - l.LeafRef[leafName] = append(l.LeafRef[leafName], - C.GoString(leafRefPaths[idx])) + path := rewriteXPathPrefix(module, C.GoString(leafRefPaths[idx])) + l.LeafRef[leafName] = append(l.LeafRef[leafName], path) } } //Check for must expression; one must expession only per leaf - if sleaf.must_size > 0 { - must := (*[20]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 nodeMusts != nil { + must := (*[20]C.struct_lysc_must)(unsafe.Pointer(nodeMusts)) + for idx := 0; idx < int(C.golysc_must_array_count(nodeMusts)); idx++ { + mustexpr := rewriteXPathPrefix(module, C.GoString(C.lyxp_get_expr(must[idx].cond))) + exp := XpathExpression{Expr: mustexpr} if must[idx].eapptag != nil { exp.ErrCode = C.GoString(must[idx].eapptag) } @@ -849,20 +893,22 @@ func getModelChildInfo(l *YParserListInfo, node *C.struct_lys_node, } //Check for when expression - if sleaf.when != nil { + if nodeWhen != nil { + when := (*[20]*C.struct_lysc_when)(unsafe.Pointer(nodeWhen)) + whenexpr := rewriteXPathPrefix(module, C.GoString(C.lyxp_get_expr(when[0].cond))) l.WhenExpr[leafName] = append(l.WhenExpr[leafName], &WhenExpression{ - Expr: C.GoString(sleaf.when.cond), + Expr: whenexpr, 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 sChild.exts != nil { + for idx := 0; idx < int(C.golysc_ext_instance_array_count(sChild.exts)); idx++ { + ext := C.golysc_ext_instance_array_idx(sChild.exts, (C.size_t)(idx)) + if C.GoString(ext.def.name) == "custom-validation" { + argVal := C.GoString(ext.argument) if argVal != "" { l.CustValidation[leafName] = append(l.CustValidation[leafName], argVal) } @@ -885,24 +931,29 @@ 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 { + if mod == nil || mod.compiled == nil || mod.compiled.data == nil { return nil } - for idx := 0; idx < int(set.number); idx++ { //for each container + // Each container has a base container, then a set of containers under that. + // We need to skip over the base container + if mod.compiled.data.nodetype != C.LYS_CONTAINER { + return nil + } + cbase := (*C.struct_lysc_node_container)(unsafe.Pointer(mod.compiled.data)) - 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 snode := cbase.child; snode != nil; snode = snode.next { //for each container + if snode.nodetype != C.LYS_CONTAINER { + continue + } + snodec := (*C.struct_lysc_node_container)(unsafe.Pointer(snode)) //for each list - for ; slist != nil; slist = (*C.struct_lys_node_list)(unsafe.Pointer(slist.next)) { + for n := snodec.child; n != nil; n = n.next { var l YParserListInfo - listName := C.GoString(slist.name) - l.RedisTableName = C.GoString(snodec.name) + slist := (*C.struct_lysc_node_list)(unsafe.Pointer(n)) + listName := C.GoString(n.name) + l.RedisTableName = C.GoString(snode.name) tableName := listName if strings.HasSuffix(tableName, "_LIST") { @@ -929,17 +980,18 @@ func GetModelListInfo(module *YParserModule) []*YParserListInfo { l.MandatoryNodes = make(map[string]bool) //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) + for child := slist.child; child != nil; child = child.next { + if (child.flags & C.LYS_KEY) != 0 { + l.Keys = append(l.Keys, C.GoString(child.name)) + } } //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 slist.musts != nil { + must := (*[10]C.struct_lysc_must)(unsafe.Pointer(slist.musts)) + for idx := 0; idx < int(C.golysc_must_array_count(slist.musts)); idx++ { + mustexp := rewriteXPathPrefix(module, C.GoString(C.lyxp_get_expr(must[idx].cond))) + exp := XpathExpression{Expr: mustexp} if must[idx].eapptag != nil { exp.ErrCode = C.GoString(must[idx].eapptag) } @@ -953,13 +1005,11 @@ func GetModelListInfo(module *YParserModule) []*YParserListInfo { } //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) - + if n.exts != nil { + for idx := 0; idx < int(C.golysc_ext_instance_array_count(n.exts)); idx++ { + ext := C.golysc_ext_instance_array_idx(n.exts, (C.size_t)(idx)) + extName := C.GoString(ext.def.name) + argVal := C.GoString(ext.argument) switch extName { case "custom-validation": if argVal != "" { @@ -990,12 +1040,10 @@ func GetModelListInfo(module *YParserModule) []*YParserListInfo { } getModelChildInfo(&l, - (*C.struct_lys_node)(unsafe.Pointer(slist)), false, nil) + (*C.struct_lysc_node)(unsafe.Pointer(slist)), module, false, nil) list = append(list, &l) } //each list inside a container } //each container - - C.free(unsafe.Pointer(set)) return list } From d8fff764abe5ce8285a5f4528c05447221d10f90 Mon Sep 17 00:00:00 2001 From: Brad House Date: Fri, 21 Feb 2025 08:48:04 -0500 Subject: [PATCH 2/4] update error mappings --- cvl/internal/yparser/ly_path.go | 91 ++++++++++++++++++++++++++++++--- cvl/internal/yparser/yparser.go | 41 +++++++++++++-- 2 files changed, 119 insertions(+), 13 deletions(-) diff --git a/cvl/internal/yparser/ly_path.go b/cvl/internal/yparser/ly_path.go index e94459276..0c9a57ed4 100644 --- a/cvl/internal/yparser/ly_path.go +++ b/cvl/internal/yparser/ly_path.go @@ -136,18 +136,92 @@ type lyPathElem struct { // Such predicates cannot be parsed. var lyPredicatePattern = regexp.MustCompile(`^\[([^=]*)=('[^']*'|"[^"]*")]`) +/* Extracting error details requires knowledge of text strings within libyang. + * Enumeration of known text strings (extracted from libyang 3.7.8) below: + * + * apptag: + * - missing-choice + * - too-few-elements + * - too-many-elements + * - data-not-unique + * - must-violation + * - not-left + * - instance-required + * + * error messages: + * NOTE: Mappings not listed here are in ly_common.h as defines prefixed with LY_VCODE_ + * - "Invalid non-empty-encoded %s value \"%.*s\"." + * - "Invalid non-boolean-encoded %s value \"%.*s\"." + * - "Invalid non-string-encoded %s value \"%.*s\"." + * - "Invalid non-num64-encoded %s value \"%.*s\"." + * - "Invalid non-number-encoded %s value \"%.*s\"." + * - "Invalid %zu. character of decimal64 value \"%.*s\"." + * - "Invalid empty decimal64 value." + * - "Invalid empty value length %zu." + * - "Invalid type %s empty value." + * - "Invalid type %s value \"%.*s\"." + * - "Invalid %" PRIu64 ". character of decimal64 value \"%.*s\"." + * - "Invalid type %s value \"%.*s\"." + * - "Invalid enumeration value \"%.*s\"." + * - "Invalid enumeration value % " PRIi32 "." + * - "Invalid boolean value \"%.*s\"." + * - "Invalid bit \"%.*s\"." + * - "Invalid Base64 character 0x%x." + * - "Invalid Base64 character '%c'." + * - "Invalid character 0x%hhx." + * - "Invalid node-instance-identifier \"%.*s\" value - semantic error." + * - "Invalid node-instance-identifier \"%.*s\" value - syntax error." + * - "Invalid first character '%c', list key predicates expected." + * - "Invalid LYB boolean value size %zu (expected 1)." + * - "Invalid LYB date-and-time character '%c' (expected a digit)." + * - "Invalid LYB date-and-time value size %zu (expected at least 8)." + * - "Invalid LYB bits value size %zu (expected %zu)." + * - "Invalid LYB union value size %zu (expected at least 4)." + * - "Invalid LYB union type index %" PRIu64 " (type count %" LY_PRI_ARRAY_COUNT_TYPE ")." + * - "Invalid LYB signed integer value size %zu (expected %zu)." + * - "Invalid LYB unsigned integer value size %zu (expected %zu)." + * - "Invalid LYB enumeration value size %zu (expected 4)." + * - "Invalid LYB decimal64 value size %zu (expected 8)." + * - "Invalid LYB ipv6-address-no-zone value size %zu (expected 16)." + * - "Invalid LYB ipv6-address zone character 0x%x." + * - "Invalid LYB ipv6-address value size %zu (expected at least 16)." + * - "Invalid LYB ipv6-prefix value size %zu (expected %d)." + * - "Invalid LYB ipv6-prefix prefix length %" PRIu8 "." + * - "Invalid LYB ipv4-address value size %zu (expected at least 4)." + * - "Invalid LYB ipv4-address zone character 0x%x." + * - "Invalid LYB ipv4-address-no-zone value size %zu (expected 4)." + * - "Invalid LYB ipv4-prefix value size %zu (expected %d)." + * - "Invalid identityref \"%.*s\" value - identity is disabled by if-feature." + * - "Invalid identityref \"%.*s\" value - identity found in non-implemented module \"%s\"." + * - "Invalid identityref \"%.*s\" value - identity not derived from all the bases %s." + * - "Invalid identityref \"%.*s\" value - identity not derived from the base %s." + * - "Invalid identityref \"%.*s\" value - identity not found in module \"%s\"." + * - "Invalid identityref \"%.*s\" value - unable to map prefix to YANG schema." + * - "Invalid empty identityref value." + * - "Unsatisfied range - value \"%.*s\" is out of the allowed range." + * - "Unsatisfied length - string \"%.*s\" length is not allowed." + * - "Unsatisfied pattern - \"%.*s\" does not conform to %s\"%s\"." + * - "Value \"%.*s\" of decimal64 type exceeds defined number (%u) of fraction digits." + * - "Value \"%.*s\" is out of type %s min/max bounds." + * - "A %s definition \"%s\" is not allowed to reference %s value \"%s\"." + * - "Failed to convert IPv6 address \"%s\"." + * - "Failed to convert IPv4 address \"%s\"." + * - "Failed to resolve prefix \"%.*s\"." + * - "Failed to convert IPv4 address \"%s\"." + * - "Duplicate bit \"%s\"." + * - "Newlines are expected every 64 Base64 characters." + * - "Base64 encoded value length must be divisible by 4." + * - "No memory." + */ + // Regex patterns to extract target node name and value from libyang error message. -// Example messages: -// - Invalid value "9999999" in "vlanid" element -// - Missing required element "vlanid" in "VLAN_LIST" -// - Value "xyz" does not satisfy the constraint "Ethernet([1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])" (range, length, or pattern) -// - Leafref "/sonic-port:sonic-port/sonic-port:PORT/sonic-port:ifname" of value "Ethernet668" points to a non-existing leaf -// - Failed to find "extra" as a sibling to "sonic-acl:aclname" var ( - lyBadValue = regexp.MustCompile(`[Vv]alue "([^"]*)" `) + lyBadValue = regexp.MustCompile(`[Vv]alue "([^"]*)"[ \.]`) + lyUnsatisfied = regexp.MustCompile(`Unsatisfied (?:pattern|range|length) - (?:value |string |)"([^"]*)" `) lyElemPrefix = regexp.MustCompile(`[Ee]lement "([^"]*)"`) lyElemSuffix = regexp.MustCompile(`"([^"]*)" element`) - lyUnknownElem = regexp.MustCompile(`Failed to find "([^"]*)" `) + lyUnknownElem = regexp.MustCompile(`Term node "([^"]*)" not found\.`) + lyMandatory = regexp.MustCompile(`Mandatory node "([^"]*)" instance does not exist\.`) ) // parseLyMessage matches a libyang returned log message using given @@ -159,6 +233,7 @@ func parseLyMessage(s string, regex ...*regexp.Regexp) string { } } return "" +} // This function takes a when, must, or leafref path in its original form as // written in the YANG schema files, and converts it into its fully qualified diff --git a/cvl/internal/yparser/yparser.go b/cvl/internal/yparser/yparser.go index 396678956..ab74fc81f 100644 --- a/cvl/internal/yparser/yparser.go +++ b/cvl/internal/yparser/yparser.go @@ -638,9 +638,28 @@ func (yp *YParser) FreeNode(node *YParserNode) YParserError { } /* This function translates LIBYANG error code to valid YPARSER error code. */ -func translateLYErrToYParserErr(LYErrcode int) YParserRetCode { +func translateLYErrToYParserErr(LYErrcode int, apptag string, msg string) YParserRetCode { var ypErrCode YParserRetCode + // YP_SYNTAX_MISSING_FIELD + // YP_SYNTAX_INVALID_FIELD /* Invalid Field */ + // YP_SYNTAX_INVALID_INPUT_DATA /* Invalid Input Data */ + // YP_SYNTAX_MULTIPLE_INSTANCE /* Multiple Field Instances */ + // YP_SYNTAX_DUPLICATE /* Duplicate Fields */ + // YP_SYNTAX_ENUM_INVALID /* Invalid enum value */ + // YP_SYNTAX_ENUM_INVALID_NAME /* Invalid enum name */ + // YP_SYNTAX_ENUM_WHITESPACE /* Enum name with leading/trailing whitespaces */ + // YP_SYNTAX_OUT_OF_RANGE /* Value out of range/length/pattern (data) */ + // YP_SYNTAX_MINIMUM_INVALID /* min-elements constraint not honored */ + // YP_SYNTAX_MAXIMUM_INVALID /* max-elements constraint not honored */ + // YP_SEMANTIC_DEPENDENT_DATA_MISSING /* Dependent Data is missing */ + // YP_SEMANTIC_MANDATORY_DATA_MISSING /* Mandatory Data is missing */ + // YP_SEMANTIC_KEY_ALREADY_EXIST /* Key already existing */ + // YP_SEMANTIC_KEY_NOT_EXIST /* Key is missing */ + // YP_SEMANTIC_KEY_DUPLICATE /* Duplicate key */ + // YP_SEMANTIC_KEY_INVALID /* Invalid key */ + // YP_INTERNAL_UNKNOWN + switch LYErrcode { case C.LYVE_SUCCESS: /**< no error */ ypErrCode = YP_SUCCESS @@ -661,7 +680,19 @@ func translateLYErrToYParserErr(LYErrcode int) YParserRetCode { case C.LYVE_SYNTAX_JSON: /**< JSON-related syntax error */ ypErrCode = YP_SYNTAX_INVALID_FIELD case C.LYVE_DATA: /**< YANG data does not reflect some of the module restrictions */ - ypErrCode = YP_SEMANTIC_DEPENDENT_DATA_MISSING + if apptag == "too-few-elements" { + ypErrCode = YP_SYNTAX_MINIMUM_INVALID + } else if apptag == "too-many-elements" { + ypErrCode = YP_SYNTAX_MAXIMUM_INVALID + } else if strings.HasPrefix(msg, "Invalid enumeration value") { + ypErrCode = YP_SYNTAX_ENUM_INVALID + } else if strings.HasPrefix(msg, "Unsatisfied") { + ypErrCode = YP_SYNTAX_OUT_OF_RANGE + } else if strings.HasPrefix(msg, "Mandatory") { + ypErrCode = YP_SYNTAX_MISSING_FIELD + } else { + ypErrCode = YP_SYNTAX_INVALID_INPUT_DATA + } case C.LYVE_OTHER: ypErrCode = YP_INTERNAL_UNKNOWN default: @@ -709,9 +740,9 @@ func getErrorDetails() YParserError { if !strings.HasPrefix(errMsg, customErrorPrefix) { // libyang generated error message.. try to extract the field value & name - ElemVal = parseLyMessage(errMsg, lyBadValue) + ElemVal = parseLyMessage(errMsg, lyBadValue, lyUnsatisfied) if len(ElemName) == 0 { // if not resolved from path - ElemName = parseLyMessage(errMsg, lyElemPrefix, lyElemSuffix) + ElemName = parseLyMessage(errMsg, lyMandatory) } } else { /* Custom contraint error message like in must statement. @@ -723,7 +754,7 @@ func getErrorDetails() YParserError { switch ypErrLast.err { case C.LY_EVALID: // validation failure - ypErrCode = translateLYErrToYParserErr(int(ypErrLast.vecode)) + ypErrCode = translateLYErrToYParserErr(int(ypErrLast.vecode), errAppTag, errMsg) if len(ElemName) != 0 { errMessage = "Field \"" + ElemName + "\" has invalid value" if len(ElemVal) != 0 { From 266918ceb9d4b448fcca00d711c63b1f674ad7db Mon Sep 17 00:00:00 2001 From: Brad House Date: Fri, 28 Feb 2025 15:31:02 -0500 Subject: [PATCH 3/4] getRedisToYangKeys() needs to return an error object --- cvl/cvl.go | 17 ++++++++++++++--- cvl/cvl_semantics.go | 9 ++++++--- cvl/cvl_syntax.go | 5 ++++- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/cvl/cvl.go b/cvl/cvl.go index 03c3be949..fb64c46d6 100644 --- a/cvl/cvl.go +++ b/cvl/cvl.go @@ -800,7 +800,9 @@ func getRedisTblToYangList(tableName, key string) (yangList string) { // Convert Redis key to Yang keys, if multiple key components are there, // they are separated based on Yang schema -func getRedisToYangKeys(tableName string, redisKey string) []keyValuePairStruct { +func getRedisToYangKeys(tableName string, redisKey string) ([]keyValuePairStruct, CVLErrorInfo) { + var cvlErrObj CVLErrorInfo + keyNames := modelInfo.tableInfo[tableName].keys //First split all the keys components keyVals := strings.Split(redisKey, modelInfo.tableInfo[tableName].redisKeyDelim) //split by DB separator @@ -809,7 +811,16 @@ func getRedisToYangKeys(tableName string, redisKey string) []keyValuePairStruct modelInfo.tableInfo[tableName].redisKeyDelim) //split by DB separator if len(keyNames) != len(keyVals) { - return nil //number key names and values does not match + cvlErrObj.ErrCode = CVL_SEMANTIC_KEY_INVALID + cvlErrObj.TableName = tableName + cvlErrObj.Keys = keyNames + if len(keyNames) < len(keyVals) { + cvlErrObj.Msg = "Too many keys specified." + } else { + cvlErrObj.Msg = "Missing Key \"" + keyNames[len(keyVals)] + "\"." + cvlErrObj.Field = keyNames[len(keyVals)] + } + return nil, cvlErrObj //number key names and values does not match } mkeys := []keyValuePairStruct{} @@ -829,7 +840,7 @@ func getRedisToYangKeys(tableName string, redisKey string) []keyValuePairStruct TRACE_LOG(TRACE_SYNTAX, "getRedisToYangKeys() returns %v "+ "from Redis Table '%s', Key '%s'", mkeys, tableName, redisKey) - return mkeys + return mkeys, cvlErrObj } // Checks field map values and removes "NULL" entry, create array for leaf-list diff --git a/cvl/cvl_semantics.go b/cvl/cvl_semantics.go index dbe252e8a..48968179a 100644 --- a/cvl/cvl_semantics.go +++ b/cvl/cvl_semantics.go @@ -281,7 +281,10 @@ func (c *CVL) generateYangListData(jsonNode *jsonquery.Node, listConatinerNode = c.addYangNode(origTableName, topNode, origTableName, "") topNodesAdded = true } - keyValuePair := getRedisToYangKeys(tableName, redisKey) + keyValuePair, cvlErrObj := getRedisToYangKeys(tableName, redisKey) + if cvlErrObj.ErrCode != CVL_SUCCESS { + return nil, cvlErrObj + } keyCompCount := len(keyValuePair) totalKeyComb := 1 var keyIndices []int @@ -1527,8 +1530,8 @@ func (c *CVL) checkDepDataCompatible(tblName, key, reftblName, refTblKey, leafRe } // Fetch key value pair for both current table and ref table - tblKeysKVP := getRedisToYangKeys(tblName, key) - refTblKeysKVP := getRedisToYangKeys(reftblName, refTblKey) + tblKeysKVP, _ := getRedisToYangKeys(tblName, key) + refTblKeysKVP, _ := getRedisToYangKeys(reftblName, refTblKey) // Determine the leafref leafRef := getLeafRefInfo(reftblName, leafRefField, tblName) diff --git a/cvl/cvl_syntax.go b/cvl/cvl_syntax.go index 5827afcaf..0d9862969 100644 --- a/cvl/cvl_syntax.go +++ b/cvl/cvl_syntax.go @@ -225,7 +225,10 @@ func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node) (*yparser } topNodesAdded = true } - keyValuePair := getRedisToYangKeys(tableName, jsonNode.Data) + keyValuePair, cvlErrObj := getRedisToYangKeys(tableName, jsonNode.Data) + if cvlErrObj.ErrCode != CVL_SUCCESS { + return nil, cvlErrObj + } keyCompCount := len(keyValuePair) totalKeyComb := 1 var keyIndices []int From 10f9078924a9c2e2156684fbcb0f367df0917098 Mon Sep 17 00:00:00 2001 From: Brad House Date: Fri, 28 Feb 2025 15:49:30 -0500 Subject: [PATCH 4/4] fix test cases --- cvl/cvl_error_test.go | 18 ++++++++++++------ cvl/cvl_test.go | 18 +++++++++--------- cvl/cvl_when_test.go | 12 +++++++----- cvl/internal/yparser/yparser.go | 4 ---- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/cvl/cvl_error_test.go b/cvl/cvl_error_test.go index 30a433253..b6043a171 100644 --- a/cvl/cvl_error_test.go +++ b/cvl/cvl_error_test.go @@ -67,18 +67,24 @@ func expandMessagePatterns(ex *CVLErrorInfo) { ex.Msg = strings.ReplaceAll(ex.Msg, "{{field}}", ex.Field) ex.Msg = strings.ReplaceAll(ex.Msg, "{{value}}", ex.Value) ex.Msg = strings.TrimSuffix(ex.Msg, " \"\"") // if value is empty + case invalidValueNoValueErrMessage: + ex.Msg = strings.ReplaceAll(ex.Msg, "{{field}}", ex.Field) case unknownFieldErrMessage: ex.Msg = strings.ReplaceAll(ex.Msg, "{{field}}", ex.Field) + case missingKeyErrMessage: + ex.Msg = strings.ReplaceAll(ex.Msg, "{{field}}", ex.Field) } } const ( - invalidValueErrMessage = "Field \"{{field}}\" has invalid value \"{{value}}\"" - unknownFieldErrMessage = "Unknown field \"{{field}}\"" - genericValueErrMessage = "Data validation failed" - mustExpressionErrMessage = "Must expression validation failed" - whenExpressionErrMessage = "When expression validation failed" - instanceInUseErrMessage = "Validation failed for Delete operation, given instance is in use" + invalidValueErrMessage = "Field \"{{field}}\" has invalid value \"{{value}}\"" + invalidValueNoValueErrMessage = "Field \"{{field}}\" has invalid value" + unknownFieldErrMessage = "Unknown field \"{{field}}\"" + genericValueErrMessage = "Data validation failed" + mustExpressionErrMessage = "Must expression validation failed" + whenExpressionErrMessage = "When expression validation failed" + instanceInUseErrMessage = "Validation failed for Delete operation, given instance is in use" + missingKeyErrMessage = "Missing Key \"{{field}}\"." ) func verifyValidateEditConfig(t *testing.T, data []CVLEditConfigData, exp CVLErrorInfo) { diff --git a/cvl/cvl_test.go b/cvl/cvl_test.go index 8bbd400c8..b5349fec1 100644 --- a/cvl/cvl_test.go +++ b/cvl/cvl_test.go @@ -1559,10 +1559,10 @@ func TestValidateEditConfig_Create_Syntax_IncompleteKey_Negative(t *testing.T) { } verifyValidateEditConfig(t, cfgData, CVLErrorInfo{ - ErrCode: cvl.CVL_SYNTAX_MISSING_FIELD, + ErrCode: CVL_SYNTAX_ERROR, TableName: "ACL_RULE", - Field: "aclname", - Msg: invalidValueErrMessage, + Field: "rulename", + Msg: missingKeyErrMessage, }) } @@ -1783,10 +1783,10 @@ func TestValidateEditConfig_Delete_InvalidKey_Negative(t *testing.T) { } verifyValidateEditConfig(t, cfgData, CVLErrorInfo{ - ErrCode: cvl.CVL_SYNTAX_MISSING_FIELD, + ErrCode: CVL_SYNTAX_ERROR, TableName: "ACL_RULE", - Field: "aclname", - Msg: invalidValueErrMessage, + Field: "rulename", + Msg: missingKeyErrMessage, }) } @@ -1811,10 +1811,10 @@ func TestValidateEditConfig_Update_Semantic_Invalid_Key_Negative(t *testing.T) { } verifyValidateEditConfig(t, cfgData, CVLErrorInfo{ - ErrCode: cvl.CVL_SYNTAX_MISSING_FIELD, + ErrCode: CVL_SYNTAX_ERROR, TableName: "ACL_RULE", - Field: "aclname", - Msg: invalidValueErrMessage, + Field: "rulename", + Msg: missingKeyErrMessage, }) } diff --git a/cvl/cvl_when_test.go b/cvl/cvl_when_test.go index 10b83586a..2d3f69086 100644 --- a/cvl/cvl_when_test.go +++ b/cvl/cvl_when_test.go @@ -22,6 +22,7 @@ package cvl_test import ( "testing" + "github.com/Azure/sonic-mgmt-common/cvl" cmn "github.com/Azure/sonic-mgmt-common/cvl/common" ) @@ -44,7 +45,8 @@ func TestValidateEditConfig_When_Exp_In_Choice_Negative(t *testing.T) { map[string]string{ "PACKET_ACTION": "FORWARD", "IP_TYPE": "IPV6", - "SRC_IP": "10.1.1.1/32", //Invalid field + "SRC_IP": "10.1.1.1/32", //Invalid field due to IP_TYPE + "DST_IP": "10.1.1.2/32", //Invalid too "L4_SRC_PORT": "1909", "IP_PROTOCOL": "103", "L4_DST_PORT_RANGE": "9000-12000", @@ -54,11 +56,11 @@ func TestValidateEditConfig_When_Exp_In_Choice_Negative(t *testing.T) { } verifyValidateEditConfig(t, cfgDataRule, CVLErrorInfo{ - ErrCode: CVL_SEMANTIC_ERROR, + ErrCode: cvl.CVL_SEMANTIC_ERROR, TableName: "ACL_RULE", - //Keys: []string{"TestACL1", "Rule1"}, <<< BUG: cvl is not populating the key - Field: "SRC_IP", - Value: "10.1.1.1/32", + //Keys: []string{"TestACL1", "Rule1"}, <<< BUG: cvl is not populating the key + Field: "DST_IP", + Value: "10.1.1.2/32", Msg: whenExpressionErrMessage, }) } diff --git a/cvl/internal/yparser/yparser.go b/cvl/internal/yparser/yparser.go index ab74fc81f..0e1e67e07 100644 --- a/cvl/internal/yparser/yparser.go +++ b/cvl/internal/yparser/yparser.go @@ -600,10 +600,6 @@ func (yp *YParser) mergeDepData(data *(*C.struct_lyd_node), depData *YParserNode // ValidateSyntax Perform syntax checks func (yp *YParser) ValidateSyntax(data *YParserNode, depData *YParserNode) YParserError { - if data == nil { - return YParserError{ErrCode: YP_INTERNAL_UNKNOWN} - } - dataPtr := (*C.struct_lyd_node)(data) if depData != nil {