Skip to content

Commit cd51894

Browse files
authored
Path validator API implementation to validate the gnmi path (sonic-net#76)
* Path validator API implementation to validate the gnmi path
1 parent 72e2cec commit cd51894

File tree

2 files changed

+534
-0
lines changed

2 files changed

+534
-0
lines changed

translib/path/path_validator.go

+373
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
// //
3+
// Copyright 2021 Broadcom. The term Broadcom refers to Broadcom Inc. and/or //
4+
// its subsidiaries. //
5+
// //
6+
// Licensed under the Apache License, Version 2.0 (the "License"); //
7+
// you may not use this file except in compliance with the License. //
8+
// You may obtain a copy of the License at //
9+
// //
10+
// http://www.apache.org/licenses/LICENSE-2.0 //
11+
// //
12+
// Unless required by applicable law or agreed to in writing, software //
13+
// distributed under the License is distributed on an "AS IS" BASIS, //
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15+
// See the License for the specific language governing permissions and //
16+
// limitations under the License. //
17+
// //
18+
////////////////////////////////////////////////////////////////////////////////
19+
20+
package path
21+
22+
import (
23+
"reflect"
24+
"strings"
25+
26+
"fmt"
27+
28+
"github.com/Azure/sonic-mgmt-common/translib/ocbinds"
29+
"github.com/Azure/sonic-mgmt-common/translib/tlerr"
30+
log "github.com/golang/glog"
31+
"github.com/kylelemons/godebug/pretty"
32+
"github.com/openconfig/gnmi/proto/gnmi"
33+
"github.com/openconfig/goyang/pkg/yang"
34+
"github.com/openconfig/ygot/util"
35+
"github.com/openconfig/ygot/ygot"
36+
"github.com/openconfig/ygot/ytypes"
37+
)
38+
39+
type pathValidator struct {
40+
gPath *gnmi.Path
41+
rootObj *ocbinds.Device
42+
sField *reflect.StructField
43+
sValIntf interface{}
44+
parentIntf interface{}
45+
opts []PathValidatorOpt
46+
parentSchema *yang.Entry
47+
err error
48+
}
49+
50+
// NewPathValidator returns the PathValidator struct to validate the gnmi path and also add the missing module
51+
// prefix and key names and wild card values in the gnmi path based on the given PathValidatorOpt
52+
func NewPathValidator(opts ...PathValidatorOpt) *pathValidator {
53+
return &pathValidator{rootObj: &(ocbinds.Device{}), opts: opts}
54+
}
55+
56+
func (pv *pathValidator) init(gPath *gnmi.Path) {
57+
gPath.Element = nil
58+
pv.gPath = gPath
59+
pv.sField = nil
60+
pv.sValIntf = pv.rootObj
61+
pv.parentIntf = pv.rootObj
62+
pv.parentSchema = nil
63+
pv.err = nil
64+
}
65+
66+
// Validate the path and also add the module prefix / wild card keys in the gnmi path
67+
// based on the given PathValidatorOpt while creating the path validator.
68+
func (pv *pathValidator) Validate(gPath *gnmi.Path) error {
69+
if pv == nil {
70+
log.Warningf("error: pathValidator is nil")
71+
return fmt.Errorf("pathValidator is nil")
72+
}
73+
if gPath == nil {
74+
log.Warningf("error: gnmi path nil")
75+
pv.err = fmt.Errorf("gnmi path nil")
76+
return pv.err
77+
}
78+
if log.V(4) {
79+
log.Info("Validate: path: ", gPath.Elem)
80+
}
81+
pv.init(gPath)
82+
if pv.err = pv.validatePath(); pv.err != nil {
83+
log.Warningf("error in validating the path: ", pv.err)
84+
}
85+
return pv.err
86+
}
87+
88+
func (pv *pathValidator) getYangSchema() (*yang.Entry, error) {
89+
sVal := reflect.ValueOf(pv.sValIntf)
90+
if sVal.Elem().Type().Kind() == reflect.Struct {
91+
objName := sVal.Elem().Type().Name()
92+
if log.V(4) {
93+
log.Info("getYangSchema: ygot object name: ", objName)
94+
}
95+
ygSchema := ocbinds.SchemaTree[objName]
96+
if ygSchema == nil {
97+
log.Warningf("error: ygot object name %v not found in the schema for the given path: %v", objName, pv.gPath)
98+
return ygSchema, tlerr.NotFoundError{Format: fmt.Sprintf("invalid path %v", pv.gPath)}
99+
}
100+
if log.V(4) {
101+
log.Infof("getYangSchema: found schema: %v for the field: %v", ygSchema.Name, *pv.sField)
102+
}
103+
return ygSchema, nil
104+
}
105+
106+
ygSchema, err := util.ChildSchema(pv.parentSchema, *pv.sField)
107+
if err != nil {
108+
return nil, tlerr.NotFoundError{Format: fmt.Sprintf("invalid path %v; could not find schema for the field name: %v", pv.gPath, err)}
109+
}
110+
if ygSchema == nil {
111+
return nil, tlerr.NotFoundError{Format: fmt.Sprintf("invalid path %v; could not find schema for the field name: %s", pv.gPath, pv.sField.Name)}
112+
}
113+
if log.V(4) {
114+
log.Infof("getYangSchema:ChildSchema - found schema: %v for the field: %v", ygSchema.Name, *pv.sField)
115+
}
116+
117+
return ygSchema, nil
118+
}
119+
120+
func (pv *pathValidator) getStructField(nodeName string) *reflect.StructField {
121+
var sField *reflect.StructField
122+
sval := reflect.ValueOf(pv.sValIntf).Elem()
123+
if sval.Kind() != reflect.Struct {
124+
return nil
125+
}
126+
stype := sval.Type()
127+
for i := 0; i < sval.NumField(); i++ {
128+
fType := stype.Field(i)
129+
if pathName, ok := fType.Tag.Lookup("path"); ok && pathName == nodeName {
130+
if log.V(4) {
131+
log.Infof("getStructField: found struct field: %v for the node name: %v ", fType, nodeName)
132+
}
133+
sField = &fType
134+
break
135+
}
136+
}
137+
return sField
138+
}
139+
140+
func (pv *pathValidator) getModuleName() string {
141+
modName, ok := pv.sField.Tag.Lookup("module")
142+
if !ok {
143+
modName = ""
144+
}
145+
return modName
146+
}
147+
148+
func (pv *pathValidator) validatePath() error {
149+
if log.V(4) {
150+
log.Info("validatePath: path: ", pv.gPath.Elem)
151+
}
152+
isApnndModPrefix := pv.hasAppendModulePrefixOption()
153+
isAddWcKey := pv.hasAddWildcardKeyOption()
154+
isIgnoreKey := pv.hasIgnoreKeyValidationOption()
155+
prevModName := ""
156+
for idx, pathElem := range pv.gPath.Elem {
157+
nodeName := pathElem.Name
158+
modName := ""
159+
names := strings.Split(pathElem.Name, ":")
160+
if len(names) > 1 {
161+
modName = names[0]
162+
nodeName = names[1]
163+
if log.V(4) {
164+
log.Infof("validatePath: modName %v, and node name %v in the given path", modName, nodeName)
165+
}
166+
}
167+
168+
pv.sField = pv.getStructField(nodeName)
169+
if pv.sField == nil {
170+
return fmt.Errorf("Node %v not found in the given gnmi path %v: ", pathElem.Name, pv.gPath)
171+
}
172+
173+
ygModName := pv.getModuleName()
174+
if len(ygModName) == 0 {
175+
return fmt.Errorf("Module name not found for the node %v in the given gnmi path %v: ", pathElem.Name, pv.gPath)
176+
}
177+
178+
if log.V(4) {
179+
log.Infof("validatePath: module name: %v found for the node %v: ", ygModName, pathElem.Name)
180+
}
181+
182+
if len(modName) > 0 {
183+
if ygModName != modName {
184+
return fmt.Errorf("Invalid yang module prefix in the path node %v", pathElem.Name)
185+
}
186+
} else if isApnndModPrefix && (prevModName != ygModName || idx == 0) {
187+
pv.gPath.Elem[idx].Name = ygModName + ":" + pathElem.Name
188+
if log.V(4) {
189+
log.Info("validatePath: appeneded the module prefix name for the path node: ", pv.gPath.Elem[idx])
190+
}
191+
}
192+
193+
pv.updateStructFieldVal()
194+
195+
ygSchema, err := pv.getYangSchema()
196+
if err != nil {
197+
return fmt.Errorf("yang schema not found for the node %v in the given path; %v", pathElem.Name, pv.gPath)
198+
}
199+
200+
if !isIgnoreKey {
201+
if ygSchema.IsList() {
202+
if len(pathElem.Key) > 0 {
203+
// validate the key names
204+
keysMap := make(map[string]bool)
205+
isWc := false
206+
for kn, kv := range pathElem.Key {
207+
keysMap[kn] = true
208+
if kv == "*" && !isWc {
209+
isWc = true
210+
}
211+
}
212+
if log.V(4) {
213+
log.Info("validatePath: validating the list key names for the node path: ", pathElem.Key)
214+
}
215+
if err := pv.validateListKeyNames(ygSchema, keysMap, idx); err != nil {
216+
return err
217+
}
218+
219+
if !isWc {
220+
// validate the key values
221+
gpath := &gnmi.Path{}
222+
pElem := *pathElem
223+
paths := strings.Split(pElem.Name, ":")
224+
pElem.Name = paths[len(paths)-1]
225+
gpath.Elem = append(gpath.Elem, &pElem)
226+
if log.V(4) {
227+
log.Info("validatePath: validating the list key values for the path: ", gpath)
228+
}
229+
if err := pv.validateListKeyValues(pv.parentSchema, gpath); err != nil {
230+
return err
231+
}
232+
}
233+
} else if isAddWcKey {
234+
pv.gPath.Elem[idx].Key = make(map[string]string)
235+
for _, kn := range strings.Fields(ygSchema.Key) {
236+
pv.gPath.Elem[idx].Key[kn] = "*"
237+
}
238+
if log.V(4) {
239+
log.Info("validatePath: added the key names and wild cards for the list node path: ", pv.gPath.Elem[idx])
240+
}
241+
}
242+
}
243+
}
244+
prevModName = ygModName
245+
pv.parentSchema = ygSchema
246+
}
247+
return nil
248+
}
249+
250+
func (pv *pathValidator) validateListKeyNames(ygSchema *yang.Entry, keysMap map[string]bool, pathIdx int) error {
251+
keyNames := strings.Fields(ygSchema.Key)
252+
if len(keyNames) != len(keysMap) {
253+
return tlerr.NotFoundError{Format: fmt.Sprintf("Invalid key names since number of keys present in the node: %v does not match with the given keys", ygSchema.Name)}
254+
}
255+
for _, kn := range keyNames {
256+
if !keysMap[kn] {
257+
return tlerr.NotFoundError{Format: fmt.Sprintf("Invalid key name: %v in the list node path: %v", pv.gPath.Elem[pathIdx].Key, pv.gPath.Elem[pathIdx].Name)}
258+
}
259+
}
260+
return nil
261+
}
262+
263+
func (pv *pathValidator) validateListKeyValues(schema *yang.Entry, gPath *gnmi.Path) error {
264+
sVal := reflect.ValueOf(pv.parentIntf)
265+
if log.V(4) {
266+
log.Infof("validateListKeyValues: schema name: %v, and parent ygot type name: %v: ", schema.Name, sVal.Elem().Type().Name())
267+
}
268+
objIntf, _, err := ytypes.GetOrCreateNode(schema, pv.parentIntf, gPath)
269+
if err != nil {
270+
log.Warningf("error in GetOrCreateNode: %v", err)
271+
return tlerr.NotFoundError{Format: fmt.Sprintf("Invalid key present in the node path: %v; error: %v", gPath, err)}
272+
}
273+
if log.V(4) {
274+
log.Infof("validateListKeyValues: objIntf: %v", reflect.ValueOf(objIntf).Elem())
275+
}
276+
277+
ygotStruct, ok := objIntf.(ygot.ValidatedGoStruct)
278+
if !ok {
279+
errStr := fmt.Sprintf("could not validate the gnmi path: %v since casting to ValidatedGoStruct fails", gPath)
280+
log.Warningf(errStr)
281+
return tlerr.NotFoundError{Format: fmt.Sprintf("Invalid key present in the node path: %v; error: %s", gPath, errStr)}
282+
}
283+
if log.V(4) {
284+
pretty.Print(ygotStruct)
285+
}
286+
if err := ygotStruct.Validate(&ytypes.LeafrefOptions{IgnoreMissingData: true}); err != nil {
287+
errStr := fmt.Sprintf("error in ValidatedGoStruct.Validate: %v", err)
288+
log.Warningf(errStr)
289+
return tlerr.NotFoundError{Format: fmt.Sprintf("Invalid key present in the node path: %v; error: %s", gPath, errStr)}
290+
}
291+
return nil
292+
}
293+
294+
func (pv *pathValidator) updateStructFieldVal() {
295+
if log.V(4) {
296+
log.Infof("updateStructFieldVal: struct field: %v; struct field type: %v", pv.sField, pv.sField.Type)
297+
}
298+
var sVal reflect.Value
299+
300+
if util.IsTypeMap(pv.sField.Type) {
301+
if log.V(4) {
302+
log.Info("updateStructFieldVal: field type is map")
303+
}
304+
sVal = reflect.New(pv.sField.Type.Elem().Elem())
305+
} else if pv.sField.Type.Kind() == reflect.Ptr {
306+
if log.V(4) {
307+
log.Info("updateStructFieldVal: field type is pointer")
308+
}
309+
sVal = reflect.New(pv.sField.Type.Elem())
310+
} else {
311+
sVal = reflect.New(pv.sField.Type)
312+
}
313+
if log.V(4) {
314+
log.Info("updateStructFieldVal: sVal", sVal)
315+
}
316+
pv.parentIntf = pv.sValIntf
317+
pv.sValIntf = sVal.Interface()
318+
}
319+
320+
// PathValidatorOpt is an interface used for any option to be supplied
321+
type PathValidatorOpt interface {
322+
IsPathValidatorOpt()
323+
}
324+
325+
// AppendModulePrefix is an PathValidator option that indicates that
326+
// the missing module prefix in the given gnmi path will be added
327+
// during the path validation
328+
type AppendModulePrefix struct{}
329+
330+
// IsPathValidatorOpt marks AppendModulePrefix as a valid PathValidatorOpt.
331+
func (*AppendModulePrefix) IsPathValidatorOpt() {}
332+
333+
// AddWildcardKeys is an PathValidator option that indicates that
334+
// the missing wild card keys in the given gnmi path will be added
335+
// during the path validation
336+
type AddWildcardKeys struct{}
337+
338+
// IsPathValidatorOpt marks AddWildcardKeys as a valid PathValidatorOpt.
339+
func (*AddWildcardKeys) IsPathValidatorOpt() {}
340+
341+
// IgnoreKeyValidation is an PathValidator option to indicate the
342+
// validator to ignore the key validation in the given gnmi path during the path validation
343+
type IgnoreKeyValidation struct{}
344+
345+
// IsPathValidatorOpt marks IgnoreKeyValidation as a valid PathValidatorOpt.
346+
func (*IgnoreKeyValidation) IsPathValidatorOpt() {}
347+
348+
func (pv *pathValidator) hasIgnoreKeyValidationOption() bool {
349+
for _, o := range pv.opts {
350+
if _, ok := o.(*IgnoreKeyValidation); ok {
351+
return true
352+
}
353+
}
354+
return false
355+
}
356+
357+
func (pv *pathValidator) hasAppendModulePrefixOption() bool {
358+
for _, o := range pv.opts {
359+
if _, ok := o.(*AppendModulePrefix); ok {
360+
return true
361+
}
362+
}
363+
return false
364+
}
365+
366+
func (pv *pathValidator) hasAddWildcardKeyOption() bool {
367+
for _, o := range pv.opts {
368+
if _, ok := o.(*AddWildcardKeys); ok {
369+
return true
370+
}
371+
}
372+
return false
373+
}

0 commit comments

Comments
 (0)