Skip to content

Commit 2713b98

Browse files
authored
Don't initialize leaf values when unmarshalling valid shadow paths. (#543)
* Don't initialize leaf values when unmarshalling valid shadow paths. Currently when `SetNode` is called on a path, missing elements are initialized as the function traverses through the `GoStruct`. This happens even for paths leading to a leaf as well as shadow paths, causing the default value to always be populated into these elements. This change prevents `SetNode` from initializing a leaf value when traversing a shadow path. * Remove support for arbitrarily-deep shadow paths to reduce complexity * Add tests for checking that shadow paths cannot be non-leaves
1 parent dcfe4c7 commit 2713b98

File tree

2 files changed

+284
-361
lines changed

2 files changed

+284
-361
lines changed

ytypes/node.go

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@ type retrieveNodeArgs struct {
5353
// specifically to deal with uint values being streamed as positive int
5454
// values.
5555
tolerateJSONInconsistenciesForVal bool
56-
// shadowPath is an implementation field indicating that the current
57-
// path being traversed is a shadow path, and should result in a silent
58-
// traversal, i.e. a normal traversal except any modifications to leaf
59-
// values are skipped (non-existing GoStructs are still initialized),
60-
// and any value retrieved is always nil.
61-
shadowPath bool
6256
// reverseShadowPath reverses the meaning of the "path" and
6357
// "shadow-path" tags when both are present while processing a
6458
// GoStruct.
@@ -80,12 +74,6 @@ func retrieveNode(schema *yang.Entry, root interface{}, path, traversedPath *gpb
8074
return nil, status.Errorf(codes.Unknown, "path %v points to a node with non-leaf schema %v", traversedPath, schema)
8175
}
8276
}
83-
// Shadow traversal returns the node with nil schema and data.
84-
if args.shadowPath {
85-
return []*TreeNode{{
86-
Path: traversedPath,
87-
}}, nil
88-
}
8977
return []*TreeNode{{
9078
Path: traversedPath,
9179
Schema: schema,
@@ -128,6 +116,8 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
128116
for i := 0; i < v.NumField(); i++ {
129117
fv, ft := v.Field(i), v.Type().Field(i)
130118

119+
// TODO(low priority): ChildSchema should return the shadow
120+
// schema if we're traversing a shadow path.
131121
cschema, err := util.ChildSchema(schema, ft)
132122
if !util.IsYgotAnnotation(ft) {
133123
switch {
@@ -138,12 +128,36 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
138128
}
139129
}
140130

141-
checkPath := func(p []string, args retrieveNodeArgs) ([]*TreeNode, error) {
131+
checkPath := func(p []string, args retrieveNodeArgs, shadowLeaf bool) ([]*TreeNode, error) {
142132
to := len(p)
143133
if util.IsTypeMap(ft.Type) {
144134
to--
145135
}
136+
np := &gpb.Path{}
137+
if traversedPath != nil {
138+
np = proto.Clone(traversedPath).(*gpb.Path)
139+
}
140+
for i := range p[0:to] {
141+
np.Elem = append(np.Elem, path.GetElem()[i])
142+
}
143+
144+
// If the current node is a shadow leaf, this means the input path is a shadow path
145+
// that the GoStruct recognizes, but doesn't have space for. We will therefore
146+
// silently ignore this path.
147+
if shadowLeaf {
148+
switch {
149+
case cschema == nil:
150+
return nil, status.Errorf(codes.InvalidArgument, "could not find schema for path %v", np)
151+
case !cschema.IsLeaf():
152+
return nil, status.Errorf(codes.InvalidArgument, "shadow path traverses a non-leaf node, this is not allowed, path: %v", np)
153+
default:
154+
return []*TreeNode{{
155+
Path: np,
156+
}}, nil
157+
}
158+
}
146159

160+
// If args.modifyRoot is true, then initialize the field before possibly searching further.
147161
if args.modifyRoot {
148162
if err := util.InitializeStructField(root, ft.Name); err != nil {
149163
return nil, status.Errorf(codes.Unknown, "failed to initialize struct field %s in %T, child schema %v, path %v", ft.Name, root, cschema, path)
@@ -154,17 +168,15 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
154168
// corresponding field to its zero value. The zero value is the unset value for
155169
// any node type, whether leaf or non-leaf.
156170
if args.delete && len(path.Elem) == to {
157-
if !args.shadowPath {
158-
fv.Set(reflect.Zero(ft.Type))
159-
}
171+
fv.Set(reflect.Zero(ft.Type))
160172
return nil, nil
161173
}
162174

163175
// If val in args is set to a non-nil value and the path is exhausted, we
164176
// may be dealing with a leaf or leaf list node. We should set the val
165177
// to the corresponding field in GoStruct. If the field is an annotation,
166178
// the field doesn't have a schema, so it is handled separately.
167-
if !util.IsValueNil(args.val) && len(path.Elem) == to && !args.shadowPath {
179+
if !util.IsValueNil(args.val) && len(path.Elem) == to {
168180
switch {
169181
case util.IsYgotAnnotation(ft):
170182
if err := util.UpdateField(root, ft.Name, args.val); err != nil {
@@ -184,13 +196,6 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
184196
}
185197
}
186198

187-
np := &gpb.Path{}
188-
if traversedPath != nil {
189-
np = proto.Clone(traversedPath).(*gpb.Path)
190-
}
191-
for i := range p[0:to] {
192-
np.Elem = append(np.Elem, path.GetElem()[i])
193-
}
194199
return retrieveNode(cschema, fv.Interface(), util.TrimGNMIPathPrefix(path, p[0:to]), np, args)
195200
}
196201

@@ -200,26 +205,26 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
200205
// Note that we first look through the non-shadow path, and if
201206
// no matches are found, we then look through the shadow path
202207
// to find matches. If the input path matches a shadow path,
203-
// then the traversal continues, although the final operation
204-
// is marked as a no-op.
208+
// then we're guaranteed to have reached a leaf, since shadow
209+
// paths can only occur for direct leaves under config/state.
205210
//
206211
// If the user has opted to reverse the "shadow-path" and
207212
// "path" tags, then the order of the look-ups is reversed.
208-
args := args
213+
var shadowLeaf bool
209214
if args.reverseShadowPath {
210215
// Look through shadow paths first instead.
211216
schPaths := util.ShadowSchemaPaths(ft)
212217
for _, p := range schPaths {
213218
if util.PathMatchesPrefix(path, p) {
214-
return checkPath(p, args)
219+
return checkPath(p, args, false)
215220
}
216221
}
217222

218223
if len(schPaths) != 0 {
219-
// Only if there exists shadow paths do we
220-
// treat the non-shadow paths as no-ops.
221-
// Otherwise, there is no reversal to do.
222-
args.shadowPath = true
224+
// Only if there exists shadow paths is there
225+
// such thing as doing a reverse look-up, i.e.
226+
// reversing non-shadow paths with shadow paths.
227+
shadowLeaf = true
223228
}
224229
}
225230
schPaths, err := util.SchemaPaths(ft)
@@ -228,16 +233,14 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
228233
}
229234
for _, p := range schPaths {
230235
if util.PathMatchesPrefix(path, p) {
231-
return checkPath(p, args)
236+
return checkPath(p, args, shadowLeaf)
232237
}
233238
}
234239
if !args.reverseShadowPath {
235-
// Look through shadow paths last, and mark operations
236-
// as no-ops.
237-
args.shadowPath = true
240+
// Look through shadow paths last.
238241
for _, p := range util.ShadowSchemaPaths(ft) {
239242
if util.PathMatchesPrefix(path, p) {
240-
return checkPath(p, args)
243+
return checkPath(p, args, true)
241244
}
242245
}
243246
}
@@ -303,7 +306,7 @@ func retrieveNodeList(schema *yang.Entry, root interface{}, path, traversedPath
303306
}
304307
if keyAsString == pathKey {
305308
remainingPath := util.PopGNMIPath(path)
306-
if args.delete && len(remainingPath.GetElem()) == 0 && !args.shadowPath {
309+
if args.delete && len(remainingPath.GetElem()) == 0 {
307310
rv.SetMapIndex(k, reflect.Value{})
308311
return nil, nil
309312
}
@@ -357,7 +360,7 @@ func retrieveNodeList(schema *yang.Entry, root interface{}, path, traversedPath
357360
return nil, status.Errorf(codes.Unknown, "could not extract keys from %v: %v", traversedPath, err)
358361
}
359362
remainingPath := util.PopGNMIPath(path)
360-
if args.delete && len(remainingPath.GetElem()) == 0 && !args.shadowPath {
363+
if args.delete && len(remainingPath.GetElem()) == 0 {
361364
rv.SetMapIndex(k, reflect.Value{})
362365
return nil, nil
363366
}
@@ -399,6 +402,10 @@ type GetOrCreateNodeOpt interface {
399402
// along the path if they are nil.
400403
// Function returns the value and schema of the node as well as error.
401404
// Note that this function may modify the supplied root even if the function fails.
405+
// Note that this function may create containers or list entries even if the input path is a shadow path.
406+
// TODO(wenbli): a traversal should remember what containers or list entries
407+
// were created so that a failed call or a call to a shadow path can later undo
408+
// this. This applies to SetNode as well.
402409
func GetOrCreateNode(schema *yang.Entry, root interface{}, path *gpb.Path, opts ...GetOrCreateNodeOpt) (interface{}, *yang.Entry, error) {
403410
nodes, err := retrieveNode(schema, root, path, nil, retrieveNodeArgs{
404411
modifyRoot: true,

0 commit comments

Comments
 (0)