Skip to content

Commit 473d130

Browse files
Presence Containers (#645)
* This commit adds support for YANG presence containers. It adds a tag to the field of generated Go Struct. When rendering JSON documents, before deleting/omitting empty containers, it checks for this struct field tag. As per the OpenConfig style guide, YANG presence containers are not used, as such this would only be used with 3rd party YANG models. As such a CLI knob is added to generator.go to enable this behavior with -yangpresence=true * fix broken test case because of resync with master * Changes Requested #1 in #645 * Adding #645 requested changes: - adding tests in ygot/render_test.go: - testing a presence container in a list entry (ISIS overload usecase) - testing a presence container in a non-presence container (system/ssh-server usecase) - adding tests in ytypes/container_test.go: - adding tests in TestUnmarshalContainer * Updated minor comments in #645
1 parent c15884d commit 473d130

File tree

12 files changed

+1008
-11
lines changed

12 files changed

+1008
-11
lines changed

generator/generator.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ var (
8686
generateRename = flag.Bool("generate_rename", false, "If set to true, rename methods are generated for lists within the Go code.")
8787
addAnnotations = flag.Bool("annotations", false, "If set to true, metadata annotations are added within the generated structs.")
8888
annotationPrefix = flag.String("annotation_prefix", ygen.DefaultAnnotationPrefix, "String to be appended to each metadata field within the generated structs if annoations is set to true.")
89+
addYangPresence = flag.Bool("yangpresence", false, "If set to true, a tag will be added to the field of a generated Go struct to indicate when a YANG presence container is being used.")
8990
generateAppend = flag.Bool("generate_append", false, "If set to true, append methods are generated for YANG lists (Go maps) within the Go code.")
9091
generateGetters = flag.Bool("generate_getters", false, "If set to true, getter methdos that retrieve or create an element are generated for YANG container (Go struct pointer) or list (Go map) fields within the generated code.")
9192
generateDelete = flag.Bool("generate_delete", false, "If set to true, delete methods are generated for YANG lists (Go maps) within the Go code.")
@@ -349,6 +350,7 @@ func main() {
349350
GenerateRenameMethod: *generateRename,
350351
AddAnnotationFields: *addAnnotations,
351352
AnnotationPrefix: *annotationPrefix,
353+
AddYangPresence: *addYangPresence,
352354
GenerateGetters: *generateGetters,
353355
GenerateDeleteMethod: *generateDelete,
354356
GenerateAppendMethod: *generateAppend,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module presence-container-example {
2+
prefix "pc";
3+
namespace "urn:pc";
4+
description
5+
"A simple test module with a YANG presence container";
6+
7+
grouping parent-config {
8+
leaf one { type string; }
9+
leaf three {
10+
type enumeration {
11+
enum ONE;
12+
enum TWO;
13+
}
14+
}
15+
leaf four {
16+
type binary;
17+
}
18+
}
19+
20+
container parent {
21+
description
22+
"I am a parent container
23+
that has 4 children.";
24+
container child {
25+
presence "This is an example presence container";
26+
container config {
27+
uses parent-config;
28+
}
29+
container state {
30+
config false;
31+
uses parent-config;
32+
leaf two { type string; }
33+
}
34+
}
35+
}
36+
}
37+
38+

util/yang.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ func IsYgotAnnotation(s reflect.StructField) bool {
188188
return ok
189189
}
190190

191+
// IsYangPresence reports whether struct field s is a YANG presence container.
192+
func IsYangPresence(s reflect.StructField) bool {
193+
_, ok := s.Tag.Lookup("yangPresence")
194+
return ok
195+
}
196+
191197
// IsSimpleEnumerationType returns true when the type supplied is a simple
192198
// enumeration (i.e., a leaf that is defined as type enumeration { ... },
193199
// and is not a typedef that contains an enumeration, or a union that

util/yang_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,41 @@ func TestIsYgotAnnotation(t *testing.T) {
10191019
}
10201020
}
10211021

1022+
func TestIsYangPresence(t *testing.T) {
1023+
type testStruct struct {
1024+
Yes *string `yangPresence:"true"`
1025+
No *string
1026+
}
1027+
structFieldYes, ok := reflect.TypeOf(testStruct{}).FieldByName("Yes")
1028+
if !ok {
1029+
t.Fatalf("Cannot find field Yes in testStruct")
1030+
}
1031+
1032+
structFieldNo, ok := reflect.TypeOf(testStruct{}).FieldByName("No")
1033+
if !ok {
1034+
t.Fatalf("Cannot find field No in testStruct")
1035+
}
1036+
tests := []struct {
1037+
name string
1038+
in reflect.StructField
1039+
want bool
1040+
}{{
1041+
name: "yangPresence container/field",
1042+
in: structFieldYes,
1043+
want: true,
1044+
}, {
1045+
name: "standard field",
1046+
in: structFieldNo,
1047+
want: false,
1048+
}}
1049+
1050+
for _, tt := range tests {
1051+
if got := IsYangPresence(tt.in); got != tt.want {
1052+
t.Errorf("%s: IsYangPresence(%#v): did not get expected result, got: %v, want: %v", tt.name, tt.in, got, tt.want)
1053+
}
1054+
}
1055+
}
1056+
10221057
// complexUnionTypeName is the name used to refer to the name of the union
10231058
// type containing the slice of input types to the functions.
10241059
const complexUnionTypeName = "complexUnionTypeName"

ygen/codegen.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ type GoOpts struct {
191191
// AnnotationPrefix specifies the string which is prefixed to the name of
192192
// annotation fields. It defaults to Λ.
193193
AnnotationPrefix string
194+
// AddYangPresence specifies whether tags should be added to the generated
195+
// fields of a struct. When set to true, a struct tag will be added to the field
196+
// when a YANG container is a presence container
197+
// https://datatracker.ietf.org/doc/html/rfc6020#section-7.5.1
198+
// a field tag of `yangPresence="true"` will only be added if the container is
199+
// a YANG presence container, and will be omitted if this is not the case.
200+
AddYangPresence bool
194201
// GenerateGetters specifies whether GetOrCreate* methods should be created
195202
// for struct pointer (YANG container) and map (YANG list) fields of generated
196203
// structs.

ygen/codegen_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,22 @@ func TestSimpleStructs(t *testing.T) {
11711171
},
11721172
},
11731173
wantErrSubstring: "has a union key containing a binary",
1174+
}, {
1175+
name: "module with presence containers",
1176+
inFiles: []string{filepath.Join(datapath, "presence-container-example.yang")},
1177+
inConfig: GeneratorConfig{
1178+
TransformationOptions: TransformationOpts{
1179+
GenerateFakeRoot: true,
1180+
FakeRootName: "device",
1181+
},
1182+
GoOptions: GoOpts{
1183+
GenerateSimpleUnions: true,
1184+
GenerateLeafGetters: true,
1185+
GeneratePopulateDefault: true,
1186+
AddYangPresence: true,
1187+
},
1188+
},
1189+
wantStructsCodeFile: filepath.Join(TestRoot, "testdata/structs/presence-container-example.formatted-txt"),
11741190
}}
11751191

11761192
for _, tt := range tests {

ygen/gogen.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,15 @@ func writeGoStruct(targetStruct *Directory, goStructElements map[string]*Directo
16991699

17001700
metadataTagBuf.WriteString(` ygotAnnotation:"true"`)
17011701

1702+
if goOpts.AddYangPresence {
1703+
// TODO(wenovus):
1704+
// a presence container is an unimplemented keyword in goyang.
1705+
// if and when this changes, the field lookup below would need to change as well.
1706+
if field.IsContainer() && len(field.Extra["presence"]) > 0 {
1707+
tagBuf.WriteString(` yangPresence:"true"`)
1708+
}
1709+
}
1710+
17021711
fieldDef.Tags = tagBuf.String()
17031712

17041713
// Append the generated field definition to the set of fields of the struct.

ygen/gogen_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,135 @@ func (t *InputStruct) Validate(opts ...ygot.ValidationOption) error {
536536
// that are included in the generated code.
537537
func (t *InputStruct) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
538538
539+
// ΛBelongingModule returns the name of the module in whose namespace
540+
// InputStruct belongs.
541+
func (*InputStruct) ΛBelongingModule() string {
542+
return "exmod"
543+
}
544+
`,
545+
},
546+
}, {
547+
name: "nested container in struct with presence container",
548+
inStructToMap: &Directory{
549+
Name: "InputStruct",
550+
Entry: &yang.Entry{
551+
Name: "input-struct",
552+
Parent: &yang.Entry{
553+
Name: "root-module",
554+
Node: &yang.Module{
555+
Name: "exmod",
556+
Namespace: &yang.Value{
557+
Name: "u:exmod",
558+
},
559+
Modules: modules,
560+
},
561+
},
562+
Node: &yang.Module{
563+
Name: "exmod",
564+
Namespace: &yang.Value{
565+
Name: "u:exmod",
566+
},
567+
Modules: modules,
568+
},
569+
Extra: map[string][]interface{}{
570+
"presence": {&yang.Value{Name: "presence c1"}},
571+
},
572+
},
573+
Fields: map[string]*yang.Entry{
574+
"c1": {
575+
Name: "c1",
576+
Dir: map[string]*yang.Entry{},
577+
Kind: yang.DirectoryEntry,
578+
Parent: &yang.Entry{
579+
Name: "input-struct",
580+
Parent: &yang.Entry{
581+
Name: "root-module",
582+
Node: &yang.Module{
583+
Name: "exmod",
584+
Namespace: &yang.Value{
585+
Name: "u:exmod",
586+
},
587+
Modules: modules,
588+
},
589+
},
590+
},
591+
Node: &yang.Leaf{
592+
Parent: &yang.Module{
593+
Name: "exmod",
594+
Namespace: &yang.Value{
595+
Name: "u:exmod",
596+
},
597+
Modules: modules,
598+
},
599+
},
600+
Extra: map[string][]interface{}{
601+
"presence": {&yang.Value{Name: "presence c1"}},
602+
},
603+
},
604+
},
605+
Path: []string{"", "root-module", "input-struct"},
606+
},
607+
inGoOpts: GoOpts{
608+
AddYangPresence: true,
609+
},
610+
inUniqueDirectoryNames: map[string]string{"/root-module/input-struct/c1": "InputStruct_C1"},
611+
wantCompressed: wantGoStructOut{
612+
structs: `
613+
// InputStruct represents the /root-module/input-struct YANG schema element.
614+
type InputStruct struct {
615+
C1 *InputStruct_C1 ` + "`" + `path:"c1" module:"exmod" yangPresence:"true"` + "`" + `
616+
}
617+
618+
// IsYANGGoStruct ensures that InputStruct implements the yang.GoStruct
619+
// interface. This allows functions that need to handle this struct to
620+
// identify it as being generated by ygen.
621+
func (*InputStruct) IsYANGGoStruct() {}
622+
`,
623+
methods: `
624+
// Validate validates s against the YANG schema corresponding to its type.
625+
func (t *InputStruct) Validate(opts ...ygot.ValidationOption) error {
626+
if err := ytypes.Validate(SchemaTree["InputStruct"], t, opts...); err != nil {
627+
return err
628+
}
629+
return nil
630+
}
631+
632+
// ΛEnumTypeMap returns a map, keyed by YANG schema path, of the enumerated types
633+
// that are included in the generated code.
634+
func (t *InputStruct) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
635+
636+
// ΛBelongingModule returns the name of the module in whose namespace
637+
// InputStruct belongs.
638+
func (*InputStruct) ΛBelongingModule() string {
639+
return "exmod"
640+
}
641+
`,
642+
},
643+
wantUncompressed: wantGoStructOut{
644+
structs: `
645+
// InputStruct represents the /root-module/input-struct YANG schema element.
646+
type InputStruct struct {
647+
C1 *InputStruct_C1 ` + "`" + `path:"c1" module:"exmod" yangPresence:"true"` + "`" + `
648+
}
649+
650+
// IsYANGGoStruct ensures that InputStruct implements the yang.GoStruct
651+
// interface. This allows functions that need to handle this struct to
652+
// identify it as being generated by ygen.
653+
func (*InputStruct) IsYANGGoStruct() {}
654+
`,
655+
methods: `
656+
// Validate validates s against the YANG schema corresponding to its type.
657+
func (t *InputStruct) Validate(opts ...ygot.ValidationOption) error {
658+
if err := ytypes.Validate(SchemaTree["InputStruct"], t, opts...); err != nil {
659+
return err
660+
}
661+
return nil
662+
}
663+
664+
// ΛEnumTypeMap returns a map, keyed by YANG schema path, of the enumerated types
665+
// that are included in the generated code.
666+
func (t *InputStruct) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
667+
539668
// ΛBelongingModule returns the name of the module in whose namespace
540669
// InputStruct belongs.
541670
func (*InputStruct) ΛBelongingModule() string {

0 commit comments

Comments
 (0)