-
Notifications
You must be signed in to change notification settings - Fork 0
/
grouping.go
564 lines (488 loc) · 15.6 KB
/
grouping.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
// Copyright (c) 2017-2021, AT&T Intellectual Property. All rights reserved.
//
// Copyright (c) 2014-2017 by Brocade Communications Systems, Inc.
// All rights reserved.
//
// SPDX-License-Identifier: MPL-2.0
package compile
import (
"encoding/xml"
"fmt"
"github.com/sdcio/yang-parser/parse"
"github.com/sdcio/yang-parser/schema"
)
func (c *Compiler) validateModuleGroupings(m parse.Node) error {
return c.validateGroupingsWalk(m, m)
}
func (c *Compiler) validateGroupingsWalk(m parse.Node, n parse.Node) error {
if err := c.validateAllGroupings(m, n); err != nil {
return err
}
for _, d := range n.Children() {
if err := c.validateGroupingsWalk(m, d); err != nil {
return err
}
}
return nil
}
func (c *Compiler) validateAllGroupings(m parse.Node, n parse.Node) error {
for _, g := range n.ChildrenByType(parse.NodeGrouping) {
group_map := make(map[string]bool)
if err := c.validateGrouping(m, g, group_map); err != nil {
return err
}
}
return nil
}
func (c *Compiler) validateGrouping(
m parse.Node,
g parse.Node,
group_map map[string]bool) error {
if _, present := group_map[g.Name()]; present {
return fmt.Errorf("Grouping cycle detected in: grouping %s", g.Name())
}
group_map[g.Name()] = true
for _, u := range g.ChildrenByType(parse.NodeUses) {
gname := u.ArgIdRef()
mod, err := u.GetModuleByPrefix(
gname.Space, c.modules, c.skipUnknown)
if err != nil {
c.error(u, err)
}
if m != mod {
// Not a local grouping so ignore it. We only have to check for
// cycles within this module because a cross-module cycle is
// prevented by protecting against import cycles.
continue
}
ug, ok := g.LookupGrouping(gname.Local)
if !ok {
return fmt.Errorf(
"Unknown grouping (grouping %s) referenced from grouping %s",
gname.Local, g.Name())
}
if err := c.validateGrouping(m, ug, group_map); err != nil {
return err
}
}
return nil
}
func isMandatory(nod parse.Node) bool {
switch nod.Type() {
case parse.NodeLeaf, parse.NodeChoice:
return nod.Mandatory()
case parse.NodeLeafList, parse.NodeList:
// List/Leaf-List is mandatory if min-elements > 0
// A Lists children are ignored when determining if
// it is mandatory
return nod.Min() > 0
case parse.NodeContainer:
fallthrough
default:
// default catches such things as tree roots
if nod.Presence() {
// Presence on a container limits the scope
// of mandatory nodes
return false
}
for _, ch := range nod.Children() {
if isMandatory(ch) {
return true
}
}
}
return false
}
// Only some node types are augmentable - data (leaf, list, leaf-list and
// cont), Input and Output. RPC is also needed here as we have to include
// nodes that may be parents of augmentable nodes.
func getAugmentableNodesForModule(applyToMod parse.Node) []parse.Node {
allowedNodes := applyToMod.ChildrenByType(parse.NodeDataDef)
allowedNodes = append(allowedNodes,
applyToMod.ChildrenByType(parse.NodeCase)...)
allowedNodes = append(allowedNodes,
applyToMod.ChildrenByType(parse.NodeOpdDef)...)
allowedNodes = append(allowedNodes,
applyToMod.ChildrenByType(parse.NodeRpc)...)
allowedNodes = append(allowedNodes,
applyToMod.ChildrenByType(parse.NodeInput)...)
allowedNodes = append(allowedNodes,
applyToMod.ChildrenByType(parse.NodeOutput)...)
return allowedNodes
}
func (c *Compiler) applyAugment(
a parse.Node, allowedNodes []parse.Node, applyToPath []xml.Name, parentStatus schema.Status,
) {
assertRef := func(dst parse.Node) {
c.assertReferenceStatus(a, dst, parentStatus)
}
applyToNode := c.getDataDescendant(
a, allowedNodes, applyToPath, assertRef)
if applyToNode == nil {
if !c.skipUnknown {
c.error(a, fmt.Errorf("Invalid path: %s",
xmlPathString(applyToPath)))
}
return
}
c.assertReferenceStatus(a, applyToNode, parentStatus)
for _, ch := range a.Children() {
if ch.Type().IsDataNode() || ch.Type().IsOpdDefNode() || ch.Type().IsExtensionNode() {
inheritCommonProperties(a, ch, true)
c.applyChange(a, applyToNode, ch)
}
}
for _, kid := range a.Children() {
if kid.Type() == parse.NodeUses {
// Handle a uses within an augment which is augmenting
// a node in a parent uses
applyToPath := a.ArgSchema()
applyToPfx := applyToPath[0].Space
applyToMod, _ := kid.GetModuleByPrefix(
applyToPfx, c.modules, c.skipUnknown)
if err := c.expandGroupings(applyToMod, applyToNode, schema.Current); err != nil {
c.error(applyToNode, err)
return
}
}
}
}
func (c *Compiler) expandModule(module *parse.Module) {
nod := module.GetModule()
// Expand Groupings
if err := c.expandGroupings(nod, nod, schema.Current); err != nil {
c.error(nod, err)
}
for _, sm := range module.GetSubmodules() {
if err := c.expandGroupings(nod, sm, schema.Current); err != nil {
c.error(sm, err)
}
}
// Apply augments
children := nod.ChildrenByType(parse.NodeAugment)
children = append(children, nod.ChildrenByType(parse.NodeOpdAugment)...)
for _, a := range children {
if _, ok := a.Argument().(*parse.AbsoluteSchemaArg); !ok {
c.error(a,
fmt.Errorf("invalid argument %s expected absolute schema id",
a.Argument().String()))
}
applyToPath := a.ArgSchema()
applyToPfx := applyToPath[0].Space
applyToMod, err := nod.GetModuleByPrefix(
applyToPfx, c.modules, c.skipUnknown)
if err != nil {
c.error(nod, err)
}
if applyToMod != nod {
if isMandatory(a) {
c.error(a, fmt.Errorf("Cannot add mandatory nodes to another module: %s",
applyToPfx))
}
}
// In this mode we add paths we might need
if c.skipUnknown {
var nc parse.NodeCardinality
if c.extensions != nil {
nc = c.extensions.NodeCardinality
}
c.addFakePathToNode(nc, applyToMod, applyToPath)
}
allowedNodes := getAugmentableNodesForModule(applyToMod)
c.applyAugment(a, allowedNodes, applyToPath, schema.Current) //AGJ
nod.ReplaceChild(a)
}
}
func (c *Compiler) expandGroupings(mod, nod parse.Node, parentStatus schema.Status) error {
status := parentStatus
if statusStatement := nod.ChildByType(parse.NodeStatus); statusStatement != nil {
status = parseStatus(statusStatement)
}
// Expand any groupings found in any children before applying refines
for _, kid := range nod.Children() {
// If any expanded grouping contains a 'uses' at the top-level,
// we need to expand this directly. Otherwise we will pass the
// 'uses' into expandGroupings (instead of as child of the node
// passed in) and won't expand it.
if kid.Type() == parse.NodeUses {
if err := c.applyUsesToNode(mod, nod, kid, status); err != nil {
return err
}
}
if err := c.expandGroupings(mod, kid, status); err != nil {
return err
}
}
// Paranoia: generate error if we have failed to expand a uses statement.
if len(nod.ChildrenByType(parse.NodeUses)) > 0 {
panic(fmt.Errorf("Uses should be eliminated"))
}
return nil
}
func (c *Compiler) getNext(
srcNode parse.Node, // See comment in function
nods []parse.Node,
name xml.Name,
) parse.Node {
for _, next := range nods {
// We need to get the namespace for 'next', and then see if our
// augment path (name) matches up with it.
nextNS := next.GetNodeNamespace(nil, c.modules)
if nextNS == "" {
continue
}
// Getting the namespace for 'name' (the path / node we are looking
// for) requires a lookup using the prefix->namespace map for the
// node that had the uses / augment statement on it, as that is the
// correct lookup context. This node is passed in as 'srcNode'.
namespace, _ := srcNode.YangPrefixToNamespace(
name.Space, c.modules, c.skipUnknown)
if name.Local == next.Name() && (namespace == nextNS) {
return next
}
}
return nil
}
func (c *Compiler) getDataDescendant(
srcNode parse.Node,
nods []parse.Node, // allowed nodes that we could augment at current level
path []xml.Name, // path we are trying to augment
checker func(parse.Node),
) parse.Node {
if len(path) == 0 {
return nil
}
next := c.getNext(srcNode, nods, path[0])
if next == nil {
return nil
}
checker(next)
if len(path) == 1 {
return next
}
// Need to differentiate between augment and refine.
return c.getDataDescendant(srcNode, getAugmentableNodesForModule(next),
path[1:], checker)
}
func (c *Compiler) addFakePathToNode(
extCard parse.NodeCardinality,
n parse.Node,
path []xml.Name,
) {
if len(path) == 0 {
return
}
next := c.getNext(n /* check! */, n.Children(), path[0])
if next == nil {
next = parse.NewFakeNodeByType(extCard, parse.NodeContainer, path[0].Local)
n.AddChildren(next)
}
c.addFakePathToNode(extCard, next, path[1:])
}
func (c *Compiler) refinementIsValid(refine, applyToNode, refinement parse.Node) error {
if refinement.Type().IsExtensionNode() {
return nil
}
switch refinement.Type() {
case parse.NodeDescription, parse.NodeReference, parse.NodeConfig,
parse.NodeMandatory, parse.NodePresence, parse.NodeMust,
parse.NodeDefault, parse.NodeMinElements, parse.NodeMaxElements:
return nil
}
return fmt.Errorf("invalid refinement %s for statement %s",
refinement.Type(), applyToNode.Statement())
}
// Check applyToNode is augmentable
// The target node MUST be either an opd command, option or argument
func (c *Compiler) augmentationOpdIsValid(node, ref parse.Node) error {
switch node.Type() {
case parse.NodeOpdCommand, parse.NodeOpdOption, parse.NodeOpdArgument:
return nil
default:
return fmt.Errorf("Augment not permitted for target %s", node.Type())
}
}
func (c *Compiler) augmentationIsValid(node, ref parse.Node) error {
switch node.Type() {
// Check applyToNode is augmentable
// The target node MUST be either a container, list, choice, case, input,
// output, or notification node.
case parse.NodeContainer, parse.NodeList, parse.NodeChoice, parse.NodeCase,
parse.NodeOpdCommand, parse.NodeOpdOption, parse.NodeOpdArgument,
parse.NodeInput, parse.NodeOutput, parse.NodeNotification:
return nil
default:
return fmt.Errorf("Augment not permitted for target %s", node.Type())
}
}
func (c *Compiler) applyChange(modifier, applyToNode, refinement parse.Node) {
switch modifier.Type() {
case parse.NodeRefine:
if err := c.refinementIsValid(modifier, applyToNode, refinement); err != nil {
c.error(modifier, err)
return
}
case parse.NodeAugment:
if err := c.augmentationIsValid(applyToNode, refinement); err != nil {
c.error(modifier, err)
return
}
case parse.NodeOpdAugment:
if err := c.augmentationIsValid(applyToNode, refinement); err != nil {
c.error(modifier, err)
return
}
default:
c.error(modifier, fmt.Errorf("Unexpected modifier: %s", modifier.Type()))
}
switch applyToNode.GetCardinalityEnd(refinement.Type()) {
case '0':
// Skip unknown extensions
case '1':
applyToNode.ReplaceChildByType(refinement.Type(), refinement)
case 'n':
applyToNode.AddChildren(refinement)
default:
c.error(modifier,
fmt.Errorf("invalid refinement %s for statement %s",
refinement.Type(), applyToNode.Statement()))
}
}
// Certain statements in the uses and augment apply to each child.
// We store the 'when' statements on the children (so these children can
// have 2 'when' statements despite the cardinality of 1, but they must
// be executed as if on the parent - hence the special AddWhenChildren()
// function.
func inheritCommonProperties(parent, child parse.Node, fromAugment bool) {
// (agj) Should we use applyChange here?
child.AddChildren(parent.ChildrenByType(parse.NodeIfFeature)...)
child.AddWhenChildren(fromAugment, parent.ChildrenByType(parse.NodeWhen)...)
child.AddChildren(parent.ChildrenByType(parse.NodeStatus)...)
}
func (c *Compiler) assertReferenceStatus(src, dst parse.Node, parentStatus schema.Status) {
// Only check within the same module
if src.Root() != dst.Root() {
return
}
srcStatus := c.getStatus(src, parentStatus)
dstStatus := c.getStatus(dst, schema.Current)
if srcStatus < dstStatus {
c.error(
src,
fmt.Errorf("%s node cannot reference %s node within same module",
srcStatus, dstStatus))
}
}
func (c *Compiler) refChecker(
src parse.Node, parentStatus schema.Status,
) func(parse.Node) {
return func(dst parse.Node) {
c.assertReferenceStatus(src, dst, parentStatus)
}
}
func (c *Compiler) applyUsesToNode(mod, nod, use parse.Node, parentStatus schema.Status) error {
gname := use.ArgIdRef()
var group parse.Node
var ok bool
gmod, err := use.GetModuleByPrefix(
gname.Space, c.modules, c.skipUnknown)
if err != nil {
c.error(use, err)
}
if gmod == mod {
// Local grouping. Search the grouping space of the local node,
// not just the module globals. Also check for status conflicts
group, ok = nod.LookupGrouping(gname.Local)
} else {
group, ok = gmod.LookupGrouping(gname.Local)
}
if !ok {
if c.skipUnknown {
// Skip unknown grouping
nod.ReplaceChild(use)
return nil
}
return fmt.Errorf(
"Unknown grouping (grouping %s) referenced from %s",
gname.Local, nod.Name())
}
var assertRef func(parse.Node)
if use.Root() == group.Root() {
c.assertReferenceStatus(use, group, parentStatus)
assertRef = func(dst parse.Node) {
c.assertReferenceStatus(use, dst, parentStatus)
}
} else {
assertRef = func(parse.Node) {}
}
// To handle any groupings that have a uses statement as a direct
// descendant (not grandchild) we must apply them here. If we pass
// them back to expandGroupings they will be ignored as that function
// only deals with 'uses' on child nodes of the node passed in.
for _, kid := range group.Children() {
if kid.Type() == parse.NodeUses {
if err := c.applyUsesToNode(gmod, group, kid, parentStatus); err != nil {
return err
}
}
}
// Clone the children of the group, apply the refine statements and
// then replace the uses node with the refined children
// Replace once refines are done here. This preserves order.
//
// When dealing with 'uses' in a submodule, we need to ensure that the
// cloned 'kid' is associated with the submodule rather than the parent
// module.
kidmod := mod
if ur := use.Root(); ur != nil && ur.Type() == parse.NodeSubmodule {
kidmod = c.submodules[ur.Name()].GetModule()
}
refinedNodes := []parse.Node{}
for _, kid := range group.Children() {
newKid := kid.Clone(kidmod)
inheritCommonProperties(use, newKid, false)
// Deal with 'double' forward reference of grouping where first
// forward referenced grouping contains a second forward reference
// that is not at top level of grouping (that scenario is dealt with
// in expandGroupings())
if err := c.expandGroupings(gmod, newKid, schema.Current); err != nil {
c.error(newKid, err)
}
refinedNodes = append(refinedNodes, newKid)
}
for _, r := range use.ChildrenByType(parse.NodeRefine) {
applyToPath := r.ArgDescendantSchema()
applyToNode := c.getDataDescendant(
use, refinedNodes, applyToPath, assertRef)
if applyToNode == nil {
c.error(r, fmt.Errorf("Invalid path: %s", xmlPathString(applyToPath)))
}
// Special case for begin and end. If we are applying a new configd:begin/end
// then get rid of the old ones. We don't do this for create/update/delete.
for _, ch := range r.Children() {
if ch.Type().IsExtensionNode() {
remove := applyToNode.ChildrenByType(ch.Type())
for _, rm := range remove {
applyToNode.ReplaceChild(rm)
}
}
}
for _, ch := range r.Children() {
c.applyChange(r, applyToNode, ch)
}
}
status := parentStatus
if st := use.ChildByType(parse.NodeStatus); st != nil {
status = parseStatus(st)
}
for _, a := range use.ChildrenByType(parse.NodeAugment) {
applyToPath := a.ArgDescendantSchema()
c.applyAugment(a, refinedNodes, applyToPath, status)
}
for _, a := range use.ChildrenByType(parse.NodeOpdAugment) {
applyToPath := a.ArgDescendantSchema()
c.applyAugment(a, refinedNodes, applyToPath, status)
}
nod.ReplaceChild(use, refinedNodes...)
return nil
}