Skip to content

Commit dabf231

Browse files
authored
CVL Changes #7: 'leafref' evaluation (sonic-net#28)
Adding support for evaluating 'leafref' expression based on customized xpath engine.
1 parent 6f9535f commit dabf231

File tree

2 files changed

+259
-8
lines changed

2 files changed

+259
-8
lines changed

cvl/cvl.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,11 @@ var luaScripts map[string]*redis.Script
6565

6666
type leafRefInfo struct {
6767
path string //leafref path
68+
exprTree *xpath.Expr //compiled expression tree
6869
yangListNames []string //all yang list in path
6970
targetNodeName string //target node name
7071
}
7172

72-
//var tmpDbCache map[string]interface{} //map of table storing map of key-value pair
73-
//m["PORT_TABLE] = {"key" : {"f1": "v1"}}
7473
//Important schema information to be loaded at bootup time
7574
type modelTableInfo struct {
7675
dbNum uint8
@@ -262,6 +261,43 @@ func getNodeName(node *xmlquery.Node) string {
262261
return node.Data
263262
}
264263

264+
//Get list of YANG list names used in xpath expression
265+
func getYangListNamesInExpr(expr string) []string {
266+
tbl := []string{}
267+
268+
//Check with all table names
269+
for tblName := range modelInfo.tableInfo {
270+
271+
//Match 1 - Prefix is used in path
272+
//Match 2 - Prefix is not used in path, it is in same YANG model
273+
if strings.Contains(expr, ":" + tblName + "_LIST") || strings.Contains(expr, "/" + tblName + "_LIST") {
274+
tbl = append(tbl, tblName)
275+
}
276+
}
277+
278+
return tbl
279+
}
280+
281+
//Get all YANG lists referred and the target node for leafref
282+
//Ex: leafref { path "../../../ACL_TABLE/ACL_TABLE_LIST[aclname=current()]/aclname";}
283+
//will return [ACL_TABLE] and aclname
284+
func getLeafRefTargetInfo(path string) ([]string, string) {
285+
target := ""
286+
287+
//Get list of all YANG list used in the path
288+
tbl := getYangListNamesInExpr(path)
289+
290+
//Get the target node name from end of the path
291+
idx := strings.LastIndex(path, ":") //check with prefix first
292+
if idx > 0 {
293+
target = path[idx+1:]
294+
} else if idx = strings.LastIndex(path, "/"); idx > 0{ //no prefix there
295+
target = path[idx+1:]
296+
}
297+
298+
return tbl, target
299+
}
300+
265301
//Store useful schema data during initialization
266302
func storeModelInfo(modelFile string, module *yparser.YParserModule) { //such model info can be maintained in C code and fetched from there
267303
f, err := os.Open(CVL_SCHEMA + modelFile)
@@ -384,7 +420,8 @@ func storeModelInfo(modelFile string, module *yparser.YParserModule) { //such mo
384420
continue
385421
}
386422

387-
tableInfo.leafRef = make(map[string][]*leafRefInfo)
423+
tableInfo.leafRef = make(map[string][]*leafRefInfo)
424+
388425
for _, leafRefNode := range leafRefNodes {
389426
if (leafRefNode.Parent == nil || leafRefNode.FirstChild == nil) {
390427
continue

cvl/cvl_semantics.go

Lines changed: 219 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
package cvl
2121

2222
import (
23-
"strings"
23+
"fmt"
2424
"encoding/xml"
2525
"encoding/json"
26+
"strings"
27+
"regexp"
28+
"github.com/antchfx/xpath"
2629
"github.com/antchfx/xmlquery"
2730
"github.com/antchfx/jsonquery"
2831
"github.com/Azure/sonic-mgmt-common/cvl/internal/yparser"
@@ -201,16 +204,16 @@ func (c *CVL) generateYangListData(jsonNode *jsonquery.Node,
201204
var cvlErrObj CVLErrorInfo
202205

203206
tableName := jsonNode.Data
204-
//c.batchLeaf = nil
205-
//c.batchLeaf = make([]*yparser.YParserLeafValue, 0)
207+
c.batchLeaf = nil
208+
c.batchLeaf = make([]*yparser.YParserLeafValue, 0)
206209

207210
//Every Redis table is mapped as list within a container,
208211
//E.g. ACL_RULE is mapped as
209212
// container ACL_RULE { list ACL_RULE_LIST {} }
210213
var topNode *xmlquery.Node
211214

212215
if _, exists := modelInfo.tableInfo[tableName]; !exists {
213-
CVL_LOG(ERROR, "Failed to find schema details for table %s", tableName)
216+
CVL_LOG(WARNING, "Failed to find schema details for table %s", tableName)
214217
cvlErrObj.ErrCode = CVL_SYNTAX_ERROR
215218
cvlErrObj.TableName = tableName
216219
cvlErrObj.Msg ="Schema details not found"
@@ -756,7 +759,7 @@ func (c *CVL) addDepYangData(redisKeys []string, redisKeyFilter,
756759

757760
for field := redisKey.FirstChild; field != nil;
758761
field = field.NextSibling {
759-
if (field.Data == fields) {
762+
if (field.Data == fields && field.FirstChild != nil) {
760763
//Single field requested
761764
singleLeaf = singleLeaf + field.FirstChild.Data + ","
762765
break
@@ -783,6 +786,217 @@ func (c *CVL) addDepYangData(redisKeys []string, redisKeyFilter,
783786
return ""
784787
}
785788

789+
func compileLeafRefPath() {
790+
reMultiPred := regexp.MustCompile(`\][ ]*\[`)
791+
792+
for _, tInfo := range modelInfo.tableInfo {
793+
if (len(tInfo.leafRef) == 0) { //no leafref
794+
continue
795+
}
796+
797+
//for nodeName, leafRefArr := range tInfo.leafRef {
798+
for _, leafRefArr := range tInfo.leafRef {
799+
for _, leafRefArrItem := range leafRefArr {
800+
if (leafRefArrItem.path == "non-leafref") {
801+
//Leaf type has at-least one non-learef data type
802+
continue
803+
}
804+
805+
//first store the referred table and target node
806+
leafRefArrItem.yangListNames, leafRefArrItem.targetNodeName =
807+
getLeafRefTargetInfo(leafRefArrItem.path)
808+
//check if predicate is used in path
809+
//for complex expression, xpath engine is
810+
//used for evaluation,
811+
//else don't build expression tree,
812+
//it is handled by just checking redis entry
813+
if strings.Contains(leafRefArrItem.path, "[") &&
814+
strings.Contains(leafRefArrItem.path, "]") {
815+
//Compile the xpath in leafref
816+
tmpExp := reMultiPred.ReplaceAllString(leafRefArrItem.path, " and ")
817+
//tmpExp = nodeName + " = " + tmpExp
818+
tmpExp = "current() = " + tmpExp
819+
leafRefArrItem.exprTree = xpath.MustCompile(tmpExp)
820+
}
821+
}
822+
}
823+
}
824+
}
825+
826+
//Validate leafref
827+
//Convert leafref to must expression
828+
//type leafref { path "../../../ACL_TABLE/ACL_TABLE_LIST/aclname";} converts to
829+
// "current() = ../../../ACL_TABLE/ACL_TABLE_LIST[aclname=current()]/aclname"
830+
func (c *CVL) validateLeafRef(node *xmlquery.Node,
831+
tableName, key string, op CVLOperation) (r CVLErrorInfo) {
832+
defer func() {
833+
ret := &r
834+
CVL_LOG(INFO_API, "validateLeafRef(): table name = %s, " +
835+
"return value = %v", tableName, *ret)
836+
}()
837+
838+
if (op == OP_DELETE) {
839+
//No new node getting added so skip leafref validation
840+
return CVLErrorInfo{ErrCode:CVL_SUCCESS}
841+
}
842+
843+
//Set xpath callback for retreiving dependent data
844+
xpath.SetDepDataClbk(c, func(ctxt interface{}, redisKeys []string,
845+
redisKeyFilter, keyNames, pred, fields, count string) string {
846+
c := ctxt.(*CVL)
847+
TRACE_LOG(INFO_API, TRACE_SEMANTIC, "validateLeafRef(): calling addDepYangData()")
848+
return c.addDepYangData(redisKeys, redisKeyFilter, keyNames, pred, fields, "")
849+
})
850+
851+
listNode := node
852+
if (listNode == nil || listNode.FirstChild == nil) {
853+
return CVLErrorInfo{
854+
TableName: tableName,
855+
Keys: strings.Split(key, modelInfo.tableInfo[tableName].redisKeyDelim),
856+
ErrCode: CVL_SEMANTIC_ERROR,
857+
CVLErrDetails: cvlErrorMap[CVL_SEMANTIC_ERROR],
858+
Msg: "Failed to find YANG data for leafref expression validation",
859+
}
860+
}
861+
862+
tblInfo := modelInfo.tableInfo[tableName]
863+
864+
for nodeName, leafRefs := range tblInfo.leafRef { //for each leafref node
865+
866+
//Reach to the node where leafref is present
867+
ctxNode := listNode.FirstChild
868+
for ;(ctxNode !=nil) && (ctxNode.Data != nodeName);
869+
ctxNode = ctxNode.NextSibling {
870+
}
871+
872+
if (ctxNode == nil) {
873+
//No leafref instance present, proceed to next leafref
874+
continue
875+
}
876+
877+
//Check leafref for each leaf-list node
878+
for ;(ctxNode != nil) && (ctxNode.Data == nodeName);
879+
ctxNode = ctxNode.NextSibling {
880+
//Load first data for each referred table.
881+
//c.yv.root has all requested data merged and any depdendent
882+
//data needed for leafref validation should be available from this.
883+
884+
leafRefSuccess := false
885+
nonLeafRefPresent := false //If leaf has non-leafref data type due to union
886+
nodeValMatchedWithLeafref := false
887+
888+
ctxtVal := ""
889+
//Get the leaf value
890+
if (ctxNode.FirstChild != nil) {
891+
ctxtVal = ctxNode.FirstChild.Data
892+
}
893+
894+
//Excute all leafref checks, multiple leafref for unions
895+
leafRefLoop:
896+
for _, leafRefPath := range leafRefs {
897+
if (leafRefPath.path == "non-leafref") {
898+
//Leaf has at-least one non-leaferf data type in union
899+
nonLeafRefPresent = true
900+
continue
901+
}
902+
903+
//Add dependent data for all referred tables
904+
for _, refListName := range leafRefPath.yangListNames {
905+
refRedisTableName := getYangListToRedisTbl(refListName)
906+
907+
filter := ""
908+
var err error
909+
var tableKeys []string
910+
if (leafRefPath.exprTree == nil) { //no predicate, single key case
911+
//Context node used for leafref
912+
//Keys -> ACL_TABLE|TestACL1
913+
filter = refRedisTableName +
914+
modelInfo.tableInfo[refListName].redisKeyDelim + ctxtVal
915+
tableKeys, err = redisClient.Keys(filter).Result()
916+
} else {
917+
//Keys -> ACL_TABLE|*
918+
filter = refRedisTableName +
919+
modelInfo.tableInfo[refListName].redisKeyDelim + "*"
920+
//tableKeys, _, err = redisClient.Scan(0, filter, 1).Result()
921+
tableKeys, err = redisClient.Keys(filter).Result()
922+
}
923+
924+
if (err != nil) || (len(tableKeys) == 0) {
925+
//There must be at least one entry in the ref table
926+
TRACE_LOG(INFO_API, TRACE_SEMANTIC, "Leafref dependent data " +
927+
"table %s, key %s not found in Redis", refRedisTableName,
928+
ctxtVal)
929+
930+
if (leafRefPath.exprTree == nil) {
931+
//Check the key in request cache also
932+
if _, exists := c.requestCache[refRedisTableName][ctxtVal]; exists {
933+
//no predicate and single key is referred
934+
leafRefSuccess = true
935+
break leafRefLoop
936+
} else if node := c.findYangList(refListName, ctxtVal);
937+
node != nil {
938+
//Found in the request tree
939+
leafRefSuccess = true
940+
break leafRefLoop
941+
}
942+
}
943+
continue
944+
} else {
945+
if (leafRefPath.exprTree == nil) {
946+
//no predicate and single key is referred
947+
leafRefSuccess = true
948+
break leafRefLoop
949+
}
950+
}
951+
952+
//Now add the first data
953+
c.addDepYangData([]string{}, tableKeys[0],
954+
strings.Join(modelInfo.tableInfo[refListName].keys, "|"),
955+
"true", "", "")
956+
}
957+
958+
//Excute xpath expression for complex leafref path
959+
if xmlquery.Eval(c.yv.root, ctxNode, leafRefPath.exprTree) {
960+
leafRefSuccess = true
961+
break leafRefLoop
962+
}
963+
} //for loop for all leafref check for a leaf - union case
964+
965+
if !leafRefSuccess && nonLeafRefPresent && (len(leafRefs) > 1) {
966+
//If union has mixed type with base and leafref type,
967+
//check if node value matched with any leafref.
968+
//If so non-existence of leafref in DB will be treated as failure.
969+
if (ctxtVal != "") {
970+
nodeValMatchedWithLeafref = c.yp.IsLeafrefMatchedInUnion(tblInfo.module,
971+
fmt.Sprintf("/%s:%s/%s/%s_LIST/%s", tblInfo.modelName,
972+
tblInfo.modelName, tblInfo.redisTableName,
973+
tableName, nodeName),
974+
ctxtVal)
975+
}
976+
}
977+
978+
if !leafRefSuccess && (!nonLeafRefPresent || nodeValMatchedWithLeafref) {
979+
//Return failure if none of the leafref exists
980+
return CVLErrorInfo{
981+
TableName: tableName,
982+
Keys: strings.Split(key,
983+
modelInfo.tableInfo[tableName].redisKeyDelim),
984+
ErrCode: CVL_SEMANTIC_DEPENDENT_DATA_MISSING,
985+
CVLErrDetails: cvlErrorMap[CVL_SEMANTIC_DEPENDENT_DATA_MISSING],
986+
ErrAppTag: "instance-required",
987+
ConstraintErrMsg: "No instance found for '" + ctxtVal + "'",
988+
}
989+
} else if !leafRefSuccess {
990+
TRACE_LOG(INFO_API, TRACE_SEMANTIC, "validateLeafRef(): " +
991+
"Leafref dependent data not found but leaf has " +
992+
"other data type in union, returning success.")
993+
}
994+
} //for each leaf-list node
995+
}
996+
997+
return CVLErrorInfo{ErrCode:CVL_SUCCESS}
998+
}
999+
7861000
//Check delete constraint for leafref if key/field is deleted
7871001
func (c *CVL) checkDeleteConstraint(cfgData []CVLEditConfigData,
7881002
tableName, keyVal, field string) CVLRetCode {

0 commit comments

Comments
 (0)