-
Notifications
You must be signed in to change notification settings - Fork 107
/
yang.go
597 lines (537 loc) · 18.6 KB
/
yang.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
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package util implements utlity functions not specific to any ygot package.
package util
import (
"bytes"
"fmt"
"reflect"
"strings"
"github.com/openconfig/goyang/pkg/yang"
)
// CompressedSchemaAnnotation stores the name of the annotation indicating
// whether a set of structs were built with -compress_path. It is appended
// to the yang.Entry struct of the root entity of the structs within the
// SchemaTree.
const CompressedSchemaAnnotation string = "isCompressedSchema"
// Children returns all child elements of a directory element e that are not
// RPC entries.
func Children(e *yang.Entry) []*yang.Entry {
var entries []*yang.Entry
for _, e := range e.Dir {
if e.RPC == nil {
entries = append(entries, e)
}
}
return entries
}
// TopLevelModule returns the module in which the root node of the schema tree
// in which the input node was instantiated was declared. It returns nil if
// schema is nil.
//
// In this example, container 'con' has TopLevelModule "openconfig-simple".
//
// module openconfig-augment {
// import openconfig-simple { prefix "s"; }
// import openconfig-grouping { prefix "g"; }
//
// augment "/s:parent/child/state" {
// uses g:group;
// }
// }
//
// module openconfig-grouping {
// grouping group {
// container con {
// leaf zero { type string; }
// }
// }
// }
func TopLevelModule(schema *yang.Entry) *yang.Entry {
if schema == nil {
return nil
}
root := schema
for root.Parent != nil {
root = root.Parent
}
return root
}
// HasOnlyChild returns true if the directory passed to it only has a single
// element below it.
func HasOnlyChild(e *yang.Entry) bool {
return e.Dir != nil && len(Children(e)) == 1
}
// IsRoot returns true if the entry is an entity at the root of the tree.
func IsRoot(e *yang.Entry) bool {
return e.Parent == nil
}
// IsConfigState returns true if the entry is an entity that represents a
// container called config or state.
func IsConfigState(e *yang.Entry) bool {
return e.IsDir() && (e.Name == "config" || e.Name == "state")
}
// IsChoiceOrCase returns true if the entry is either a 'case' or a 'choice'
// node within the schema. These are schema nodes only, and the code generation
// operates on data tree paths.
func IsChoiceOrCase(e *yang.Entry) bool {
if e == nil {
return false
}
return e.IsChoice() || e.IsCase()
}
// IsUnionType returns true if the entry is a union within the YANG schema,
// checked by determining the length of the Type slice within the YangType.
func IsUnionType(t *yang.YangType) bool {
if t == nil {
return false
}
return len(t.Type) > 0
}
// IsEnumeratedType returns true if the entry is an enumerated type within the
// YANG schema - i.e., an enumeration or identityref leaf.
func IsEnumeratedType(t *yang.YangType) bool {
if t == nil {
return false
}
return t.Kind == yang.Yenum || t.Kind == yang.Yidentityref
}
// IsAnydata returns true if the entry is an Anydata node.
func IsAnydata(e *yang.Entry) bool {
if e == nil {
return false
}
return e.Kind == yang.AnyDataEntry
}
// IsLeafRef reports whether schema is a leafref schema node type.
func IsLeafRef(schema *yang.Entry) bool {
if schema == nil || schema.Type == nil {
return false
}
return schema.Type.Kind == yang.Yleafref
}
// IsFakeRoot reports whether the supplied yang.Entry represents the synthesised
// root entity in the generated code.
func IsFakeRoot(e *yang.Entry) bool {
if e == nil {
return false
}
if _, ok := e.Annotation["isFakeRoot"]; ok {
return true
}
return false
}
// IsKeyedList returns true if the supplied yang.Entry represents a keyed list.
func IsKeyedList(e *yang.Entry) bool {
if e == nil {
return false
}
return e.IsList() && e.Key != ""
}
// IsUnkeyedList reports whether e is an unkeyed list.
func IsUnkeyedList(e *yang.Entry) bool {
if e == nil {
return false
}
return e.IsList() && e.Key == ""
}
// IsOCCompressedValidElement returns true if the element would be output in the
// compressed YANG code.
func IsOCCompressedValidElement(e *yang.Entry) bool {
switch {
case HasOnlyChild(e) && Children(e)[0].IsList():
// This is a surrounding container for a list which is removed from the
// structure.
return false
case IsRoot(e):
// This is a top-level module within the goyang structure, so is not output
return false
case IsConfigState(e):
// This is a container that is called config or state, which is removed from
// a compressed OpenConfig schema.
return false
case IsChoiceOrCase(e):
// This is a choice or case node that is removed from the overall schema
// so code generation does not occur for it.
return false
}
return true
}
// IsCompressedSchema determines whether the yang.Entry s provided is part of a
// generated set of structs that have schema compression enabled. It traverses
// to the schema root, and determines the presence of an annotation with the name
// CompressedSchemaAnnotation which is added by ygen.
func IsCompressedSchema(s *yang.Entry) bool {
var e *yang.Entry
for e = s; e.Parent != nil; e = e.Parent {
}
_, ok := e.Annotation[CompressedSchemaAnnotation]
return ok
}
// IsYgotAnnotation reports whether struct field s is an annotation field.
func IsYgotAnnotation(s reflect.StructField) bool {
_, ok := s.Tag.Lookup("ygotAnnotation")
return ok
}
// IsYangPresence reports whether struct field s is a YANG presence container.
func IsYangPresence(s reflect.StructField) bool {
_, ok := s.Tag.Lookup("yangPresence")
return ok
}
// IsSimpleEnumerationType returns true when the type supplied is a simple
// enumeration (i.e., a leaf that is defined as type enumeration { ... },
// and is not a typedef that contains an enumeration, or a union that
// contains an enumeration which may have enum values specified. The type
// name enumeration is used in these cases by goyang.
func IsSimpleEnumerationType(t *yang.YangType) bool {
if t == nil {
return false
}
return t.Kind == yang.Yenum && t.Name == yang.TypeKindToName[yang.Yenum]
}
// IsIdentityrefLeaf returns true if the supplied yang.Entry represents an
// identityref.
func IsIdentityrefLeaf(e *yang.Entry) bool {
return e.Type.IdentityBase != nil
}
// IsYANGBaseType determines whether the supplied YangType is a built-in type
// in YANG, or a derived type (i.e., typedef).
func IsYANGBaseType(t *yang.YangType) bool {
_, ok := yang.TypeKindFromName[t.Name]
return ok
}
// SanitizedPattern returns the values of the posix-pattern extension
// statements for the YangType. If it's empty, then it returns the values from
// the pattern statements with anchors attached (if missing).
// It also returns whether the patterns are POSIX.
func SanitizedPattern(t *yang.YangType) ([]string, bool) {
if len(t.POSIXPattern) != 0 {
return t.POSIXPattern, true
}
var pat []string
for _, p := range t.Pattern {
// fixYangRegexp adds ^(...)$ around the pattern - the result is
// equivalent to a full match of whole string.
pat = append(pat, fixYangRegexp(p))
}
return pat, false
}
// fixYangRegexp takes a pattern regular expression from a YANG module and
// returns it into a format which can be used by the Go regular expression
// library. YANG uses a W3C standard that is defined to be implicitly anchored
// at the head or tail of the expression. See
// https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#regexs for details.
func fixYangRegexp(pattern string) string {
var buf bytes.Buffer
var inEscape bool
var prevChar rune
addParens := false
for i, ch := range pattern {
if i == 0 && ch != '^' {
buf.WriteRune('^')
// Add parens around entire expression to prevent logical
// subexpressions associating with leading/trailing ^ / $.
buf.WriteRune('(')
addParens = true
}
switch ch {
case '$':
// Dollar signs need to be escaped unless they are at
// the end of the pattern, or are already escaped.
if !inEscape && i != len(pattern)-1 {
buf.WriteRune('\\')
}
case '^':
// Carets need to be escaped unless they are already
// escaped, indicating set negation ([^.*]) or at the
// start of the string.
if !inEscape && prevChar != '[' && i != 0 {
buf.WriteRune('\\')
}
}
// If the previous character was an escape character, then we
// leave the escape, otherwise check whether this is an escape
// char and if so, then enter escape.
inEscape = !inEscape && ch == '\\'
if i == len(pattern)-1 && addParens && ch == '$' {
buf.WriteRune(')')
}
buf.WriteRune(ch)
if i == len(pattern)-1 && ch != '$' {
if addParens {
buf.WriteRune(')')
}
buf.WriteRune('$')
}
prevChar = ch
}
return buf.String()
}
// IsConfig takes a yang.Entry and traverses up the tree to find the config
// state of that element. In YANG, if the config parameter is unset, then it is
// is inherited from the parent of the element - hence we must walk up the tree to find
// the state. If the element at the top of the tree does not have config set, then config
// is true. See https://tools.ietf.org/html/rfc6020#section-7.19.1.
func IsConfig(e *yang.Entry) bool {
return !e.ReadOnly()
}
// isPathChild takes an input slice of strings representing a path and determines
// whether b is a child of a within the YANG schema.
func isPathChild(a, b []string) bool {
// If b does not have a greater path length than a, it cannot be a child. If
// b has more than one element than a, it must be at least a grandchild.
if len(b) <= len(a) || len(b) > len(a)+1 {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// IsDirectEntryChild determines whether the entry c is a direct child of the
// entry p within the output code. If compressPaths is set, a check to determine
// whether c would be a direct child after schema compression is performed.
func IsDirectEntryChild(p, c *yang.Entry, compressPaths bool) bool {
ppp := strings.Split(p.Path(), "/")
cpp := strings.Split(c.Path(), "/")
dc := isPathChild(ppp, cpp)
// If we are not compressing paths, then directly return whether the child
// is a path of the parent.
if !compressPaths {
return dc
}
// If the length of the child path is greater than two larger than the
// parent path, then this means that it cannot be a direct child, since all
// path compression will remove only one level of hierarchy (config/state or
// a surrounding container at maximum). We also check that the length of
// the child path is more specific than or equal to the length of the parent
// path in which case this cannot be a child.
if len(cpp) > len(ppp)+2 || len(cpp) <= len(ppp) {
return false
}
if IsConfigState(c.Parent) {
// If the parent of this entity was the config/state container, then this
// level of the hierarchy will have been removed so we check whether the
// parent of both are equal and return this.
return p.Path() == c.Parent.Parent.Path()
}
// If the child is a list, then we check whether the parent has only one
// child (i.e., is a surrounding container) and then check whether the
// single child is the child we were provided.
if c.IsList() {
ppe, ok := p.Dir[c.Parent.Name]
if !ok {
// Can't be a valid child because the parent of the entity doesn't exist
// within this container.
return false
}
if !HasOnlyChild(ppe) {
return false
}
// We are guaranteed to have 1 child (and not zero) since HasOnlyChild will
// return false for directories with 0 children.
return Children(ppe)[0].Path() == c.Path()
}
return dc
}
// FindFirstNonChoiceOrCase recursively traverses the schema tree and returns a
// map with the set of the first nodes in every path that are neither case nor
// choice nodes. The keys in the map are the paths from root to the matching
// elements. If the path to the parent data struct is needed, since it always
// has length 1, this is simply the last path element of the key.
func FindFirstNonChoiceOrCase(e *yang.Entry) map[string]*yang.Entry {
m := make(map[string]*yang.Entry)
for _, ch := range e.Dir {
addToEntryMap(m, findFirstNonChoiceOrCaseInternal(ch))
}
return m
}
// findFirstNonChoiceOrCaseInternal is an internal part of
// FindFirstNonChoiceOrCase.
func findFirstNonChoiceOrCaseInternal(e *yang.Entry) map[string]*yang.Entry {
m := make(map[string]*yang.Entry)
switch {
case !IsChoiceOrCase(e):
m[e.Path()] = e
case e.IsDir():
for _, ch := range e.Dir {
addToEntryMap(m, findFirstNonChoiceOrCaseInternal(ch))
}
}
return m
}
// findFirstNonChoiceOrCaseEntry recursively traverses the schema tree and returns a
// map with the set of the first nodes in every path that are neither case nor
// choice nodes. The keys in the map are the identifiers of the non-choice or case
// elements, since the identifiers of all these child nodes MUST be unique
// within all cases in a choice. If there are duplicate elements, then an error
// is returned.
// https://datatracker.ietf.org/doc/html/rfc7950#section-7.9.2
func findFirstNonChoiceOrCaseEntry(e *yang.Entry) (map[string]*yang.Entry, error) {
m := make(map[string]*yang.Entry)
for _, ch := range e.Dir {
m2, err := findFirstNonChoiceOrCaseEntryInternal(ch)
if err != nil {
return nil, nil
}
addToEntryMap(m, m2)
}
return m, nil
}
// findFirstNonChoiceOrCaseEntryInternal is an internal part of
// findFirstNonChoiceOrCaseEntry.
func findFirstNonChoiceOrCaseEntryInternal(e *yang.Entry) (map[string]*yang.Entry, error) {
m := make(map[string]*yang.Entry)
switch {
case !IsChoiceOrCase(e):
m[e.Name] = e
case e.IsDir():
for _, ch := range e.Dir {
m2, err := findFirstNonChoiceOrCaseEntryInternal(ch)
if err != nil {
return nil, nil
}
addToEntryMap(m, m2)
}
}
return m, nil
}
// addToEntryMap merges from into to, overwriting overlapping key-value pairs.
func addToEntryMap(to, from map[string]*yang.Entry) map[string]*yang.Entry {
for k, v := range from {
to[k] = v
}
return to
}
// FlattenedTypes returns in tree order (in-order) the subtypes of a union type.
func FlattenedTypes(types []*yang.YangType) []*yang.YangType {
var ret []*yang.YangType
for _, t := range types {
if IsUnionType(t) {
ret = append(ret, FlattenedTypes(t.Type)...)
} else {
ret = append(ret, t)
}
}
return ret
}
// EnumeratedUnionTypes recursively searches the set of yang.YangTypes supplied
// to extract the enumerated types that are within a union. The set of input
// yang.YangTypes is expected to be the slice of types of the union type.
// It returns the enumerated types in tree order of appearance.
func EnumeratedUnionTypes(types []*yang.YangType) []*yang.YangType {
var eTypes []*yang.YangType
for _, t := range types {
switch {
case IsEnumeratedType(t):
eTypes = append(eTypes, t)
case IsUnionType(t):
eTypes = append(eTypes, EnumeratedUnionTypes(t.Type)...)
}
}
return eTypes
}
// DefiningType returns the type of definition of a subtype within a leaf type.
// In the trivial case that the subtype is the leaf type itself, the leaf type
// is returned; otherwise, subtype refers to a terminal union subtype within
// the leaf's union type. An error is returned if the type does not belong to the
// leaf type.
//
// The "defining type" of a union subtype is the closest, or innermost defining
// type to which the subtype belongs. The "defining type" can either mean a
// typedef-defined type or a leaf-defined type.
//
// Examples of the defining type of union subtypes within a top-level union
// used under a leaf:
// - a typedef within any kind or level of unions.
// - defining type is the typedef itself -- the closest place of definition.
//
// - a non-typedef within a non-typedef union.
// - defining type is the union (i.e. type of the leaf, which defines it)
//
// - a non-typedef within a non-typedef union within a non-typedef union.
// - defining type is the outer union (i.e. type of the leaf, which defines it).
//
// - a non-typedef within a typedef union within a non-typedef union.
// - defining type is the (inner) typedef union.
func DefiningType(subtype *yang.YangType, leafType *yang.YangType) (*yang.YangType, error) {
if subtype == leafType {
// Trivial case where the subtype is the leaf type itself.
// The leaf type is a place of definition, and it's also the closest.
return leafType, nil
}
return unionDefiningType(subtype, leafType, leafType)
}
// unionDefiningType returns the type of definition of a union subtype.
// subtype is the union subtype, unionType is the current union type where
// we're looking for the subtype, and definingType is the defining type of
// unionType. An error is returned if the subtype was not found within the union.
func unionDefiningType(subtype *yang.YangType, unionType *yang.YangType, definingType *yang.YangType) (*yang.YangType, error) {
for _, t := range unionType.Type {
definingType := definingType
if !IsYANGBaseType(t) {
definingType = t
}
switch {
case t == subtype:
return definingType, nil
case IsUnionType(t):
if defType, err := unionDefiningType(subtype, t, definingType); err == nil {
return defType, nil
}
}
}
return nil, fmt.Errorf("ygot/util: subtype %q not found within provided containing type %q", subtype.Name, unionType.Name)
}
// ResolveIfLeafRef returns a ptr to the schema pointed to by the leaf-ref path
// in schema if it's a leafref, or schema itself if it's not.
func ResolveIfLeafRef(schema *yang.Entry) (*yang.Entry, error) {
if schema == nil {
return nil, nil
}
// fakeroot or test cases may have this unset. They are definitely not
// leafrefs.
if schema.Type == nil {
return schema, nil
}
orig := schema
s := schema
for ykind := s.Type.Kind; ykind == yang.Yleafref; {
ns, err := FindLeafRefSchema(s, s.Type.Path)
if err != nil {
return schema, err
}
s = ns
ykind = s.Type.Kind
}
if s != orig {
DbgPrint("follow schema leaf-ref from %s to %s, type %v", orig.Name, s.Name, s.Type.Kind)
}
return s, nil
}
// ListKeyFieldsMap returns a map[string]bool where the keys of the map
// are the fields that are the keys of the list described by the supplied
// yang.Entry. In the case the yang.Entry does not described a keyed list,
// an empty map is returned.
func ListKeyFieldsMap(e *yang.Entry) map[string]bool {
r := map[string]bool{}
for _, k := range strings.Fields(e.Key) {
if k != "" {
r[k] = true
}
}
return r
}