Skip to content

Commit 57d8e7c

Browse files
authored
Field numbers for multi-key leafs pointing to the same field should be unique. (#610)
* Field numbers for multi-key leafs pointing to the same field should be unique. This OpenConfig snippet is causing duplicate field numbers to be generated due to the list keys both pointing to the same path: ```yang list inter-area-propagation-policy { key "src-area dst-area"; description "A list of connections between pairs of areas - routes are propagated from the source (src) area to the destination (dst) area according to the policy specified"; leaf src-area { type leafref { path "../config/src-area"; } description "Reference to the source area"; } leaf dst-area { type leafref { path "../config/dst-area"; } description "Reference to the destination area"; } ``` This change makes `genListKeyProto` use the non-resolved entry path when determining the field number of its oneof fields. The new `proto-test-g.yang` file has the following diff after this change: --- want +++ got @@ -28,12 +28,12 @@ } message ElistKey { oneof one { - sint64 one_sint64 = 380657501; - string one_string = 434013286; + sint64 one_sint64 = 278603474; + string one_string = 515705169; } oneof two { - sint64 two_sint64 = 380657501; - string two_string = 434013286; + sint64 two_sint64 = 322077556; + string two_string = 157954079; } Elist elist = 3; } I've verified that two fields in a container that both point to the same leaf doesn't have this problem. * Only change tag numbers when collision occurs to preserve backwards compatibility * Update integration test files * Add comment on what path we're using to generate proto tag numbers
1 parent 426a1fd commit 57d8e7c

File tree

5 files changed

+152
-9
lines changed

5 files changed

+152
-9
lines changed

ygen/codegen_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,6 +2152,18 @@ func TestGenerateProto3(t *testing.T) {
21522152
"openconfig.proto_test_f.a": filepath.Join(TestRoot, "testdata", "proto", "proto_test_f.uncompressed.proto_test_f.a.formatted-txt"),
21532153
"openconfig.proto_test_f.a.c": filepath.Join(TestRoot, "testdata", "proto", "proto_test_f.uncompressed.proto_test_f.a.c.formatted-txt"),
21542154
},
2155+
}, {
2156+
name: "yang schema with leafrefs that point to the same path",
2157+
inFiles: []string{filepath.Join(TestRoot, "testdata", "proto", "proto-test-g.yang")},
2158+
inConfig: GeneratorConfig{
2159+
ProtoOptions: ProtoOpts{
2160+
GoPackageBase: "github.com/foo/baz",
2161+
NestedMessages: true,
2162+
},
2163+
},
2164+
wantOutputFiles: map[string]string{
2165+
"openconfig.proto_test_g": filepath.Join(TestRoot, "testdata", "proto", "proto-test-g.proto-test-g.formatted-txt"),
2166+
},
21552167
}, {
21562168
name: "yang schema with fake root, path compression and union list key",
21572169
inFiles: []string{filepath.Join(TestRoot, "testdata", "proto", "proto-union-list-key.yang")},

ygen/protogen.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ func protoLeafDefinition(leafName string, args *protoDefinitionArgs, useDefining
10381038
case util.IsEnumeratedType(args.field.Type):
10391039
d.globalEnum = true
10401040
case protoType.UnionTypes != nil:
1041-
u, err := unionFieldToOneOf(leafName, args.field, protoType, args.cfg.annotateEnumNames, useDefiningModuleForTypedefEnumNames, useConsistentNamesForProtoUnionEnums)
1041+
u, err := unionFieldToOneOf(leafName, args.field, args.field.Path(), protoType, args.cfg.annotateEnumNames, useDefiningModuleForTypedefEnumNames, useConsistentNamesForProtoUnionEnums)
10421042
if err != nil {
10431043
return nil, err
10441044
}
@@ -1128,6 +1128,10 @@ func genListKeyProto(listPackage string, listName string, args *protoDefinitionA
11281128

11291129
definedFieldNames := map[string]bool{}
11301130
ctag := uint32(1)
1131+
// unionEntries keeps track of union keys such that if two keys point
1132+
// to the same union entry, such a conflict when creating field tags
1133+
// for them can be detected to avoid a tag collision.
1134+
unionEntries := map[*yang.Entry]bool{}
11311135
for _, k := range strings.Fields(args.field.Key) {
11321136
kf, ok := args.directory.Fields[k]
11331137
if !ok {
@@ -1209,7 +1213,18 @@ func genListKeyProto(listPackage string, listName string, args *protoDefinitionA
12091213
km.Enums[tn] = enum
12101214
case unionEntry != nil:
12111215
fd.IsOneOf = true
1212-
u, err := unionFieldToOneOf(fd.Name, unionEntry, scalarType, args.cfg.annotateEnumNames, useDefiningModuleForTypedefEnumNames, useConsistentNamesForProtoUnionEnums)
1216+
path := unionEntry.Path()
1217+
if unionEntries[unionEntry] {
1218+
// It is possible for two keys to point to the same resolved unionEntry.
1219+
// In this case, the path we use to generate the proto tag numbers needs
1220+
// to be different to avoid a collision, and here we use the path of the
1221+
// (leafref) key field. The reason the first instance uses the resolved
1222+
// unionEntry is for backwards compatibility
1223+
// (https://github.com/openconfig/ygot/pull/610#discussion_r781510037).
1224+
path = kf.Path()
1225+
}
1226+
unionEntries[unionEntry] = true
1227+
u, err := unionFieldToOneOf(fd.Name, unionEntry, path, scalarType, args.cfg.annotateEnumNames, useDefiningModuleForTypedefEnumNames, useConsistentNamesForProtoUnionEnums)
12131228
if err != nil {
12141229
return nil, fmt.Errorf("error generating type for union list key %s in list %s", k, args.field.Path())
12151230
}
@@ -1312,11 +1327,12 @@ type protoUnionField struct {
13121327
hadGlobalEnums bool // hadGlobalEnums determines whether there was a global scope enum (typedef, identityref) in the message.
13131328
}
13141329

1315-
// unionFieldToOneOf takes an input name, a yang.Entry containing a field definition and a MappedType
1330+
// unionFieldToOneOf takes an input name, a yang.Entry containing a field
1331+
// definition, a path argument used to compute the field tag numbers, and a MappedType
13161332
// containing the proto type that the entry has been mapped to, and returns a definition of a union
13171333
// field within the protobuf message. If the annotateEnumNames boolean is set, then any enumerated types
13181334
// within the union have their original names within the YANG schema appended.
1319-
func unionFieldToOneOf(fieldName string, e *yang.Entry, mtype *MappedType, annotateEnumNames, useDefiningModuleForTypedefEnumNames, useConsistentNamesForProtoUnionEnums bool) (*protoUnionField, error) {
1335+
func unionFieldToOneOf(fieldName string, e *yang.Entry, path string, mtype *MappedType, annotateEnumNames, useDefiningModuleForTypedefEnumNames, useConsistentNamesForProtoUnionEnums bool) (*protoUnionField, error) {
13201336
enums, err := enumInProtoUnionField(fieldName, resolveTypeArgs{yangType: e.Type, contextEntry: e}, annotateEnumNames, useDefiningModuleForTypedefEnumNames, useConsistentNamesForProtoUnionEnums)
13211337
if err != nil {
13221338
return nil, err
@@ -1344,7 +1360,7 @@ func unionFieldToOneOf(fieldName string, e *yang.Entry, mtype *MappedType, annot
13441360
// such that we have unique inputs for each option. We make the name lower-case
13451361
// as it is conventional that protobuf field names are lowercase separated by
13461362
// underscores.
1347-
ft, err := fieldTag(fmt.Sprintf("%s_%s", e.Path(), strings.ToLower(tn)))
1363+
ft, err := fieldTag(fmt.Sprintf("%s_%s", path, strings.ToLower(tn)))
13481364
if err != nil {
13491365
return nil, fmt.Errorf("could not calculate tag number for %s, type %s in oneof", e.Path(), tn)
13501366
}

ygen/protogen_test.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,9 +1822,11 @@ func TestUnionFieldToOneOf(t *testing.T) {
18221822
}
18231823

18241824
tests := []struct {
1825-
name string
1826-
inName string
1827-
inEntry *yang.Entry
1825+
name string
1826+
inName string
1827+
inEntry *yang.Entry
1828+
// inPath is populated with in.Entry.Path() if not set.
1829+
inPath string
18281830
inMappedType *MappedType
18291831
inAnnotateEnumNames bool
18301832
wantFields []*protoMsgField
@@ -1859,6 +1861,35 @@ func TestUnionFieldToOneOf(t *testing.T) {
18591861
Type: "string",
18601862
}},
18611863
wantEnums: map[string]*protoMsgEnum{},
1864+
}, {
1865+
name: "simple string union with a non-empty path argument",
1866+
inName: "FieldName",
1867+
inEntry: &yang.Entry{
1868+
Name: "field-name",
1869+
Type: &yang.YangType{
1870+
Type: []*yang.YangType{
1871+
{Kind: yang.Ystring},
1872+
{Kind: yang.Yint8},
1873+
},
1874+
},
1875+
},
1876+
inMappedType: &MappedType{
1877+
UnionTypes: map[string]int{
1878+
"string": 0,
1879+
"sint64": 0,
1880+
},
1881+
},
1882+
inPath: "a/b/c/d",
1883+
wantFields: []*protoMsgField{{
1884+
Tag: 352411621,
1885+
Name: "FieldName_sint64",
1886+
Type: "sint64",
1887+
}, {
1888+
Tag: 156680110,
1889+
Name: "FieldName_string",
1890+
Type: "string",
1891+
}},
1892+
wantEnums: map[string]*protoMsgEnum{},
18621893
}, {
18631894
name: "decimal64 union",
18641895
inName: "FieldName",
@@ -2001,7 +2032,10 @@ func TestUnionFieldToOneOf(t *testing.T) {
20012032
}}
20022033

20032034
for _, tt := range tests {
2004-
got, err := unionFieldToOneOf(tt.inName, tt.inEntry, tt.inMappedType, tt.inAnnotateEnumNames, true, true)
2035+
if tt.inPath == "" {
2036+
tt.inPath = tt.inEntry.Path()
2037+
}
2038+
got, err := unionFieldToOneOf(tt.inName, tt.inEntry, tt.inPath, tt.inMappedType, tt.inAnnotateEnumNames, true, true)
20052039
if (err != nil) != tt.wantErr {
20062040
t.Errorf("%s: unionFieldToOneOf(%s, %v, %v, %v): did not get expected error, got: %v, wanted err: %v", tt.name, tt.inName, tt.inEntry, tt.inMappedType, tt.inAnnotateEnumNames, err, tt.wantErr)
20072041
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// openconfig.proto_test_g is generated by codegen-tests as a protobuf
2+
// representation of a YANG schema.
3+
//
4+
// Input schema modules:
5+
// - testdata/proto/proto-test-g.yang
6+
syntax = "proto3";
7+
8+
package openconfig.proto_test_g;
9+
10+
option go_package = "github.com/foo/baz/openconfig/proto_test_g";
11+
12+
message Elists {
13+
message Elist {
14+
message Config {
15+
oneof one {
16+
sint64 one_sint64 = 380657501;
17+
string one_string = 434013286;
18+
}
19+
}
20+
message State {
21+
oneof one {
22+
sint64 one_sint64 = 134694452;
23+
string one_string = 507493599;
24+
}
25+
}
26+
Config config = 521512235;
27+
State state = 30995436;
28+
}
29+
message ElistKey {
30+
oneof one {
31+
sint64 one_sint64 = 380657501;
32+
string one_string = 434013286;
33+
}
34+
oneof two {
35+
sint64 two_sint64 = 322077556;
36+
string two_string = 157954079;
37+
}
38+
Elist elist = 3;
39+
}
40+
repeated ElistKey elist = 113229370;
41+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module proto-test-g {
2+
prefix "proto-g";
3+
namespace "urn:proto-g";
4+
5+
grouping grp {
6+
leaf one {
7+
type union {
8+
type string;
9+
type int32;
10+
}
11+
}
12+
}
13+
14+
container elists {
15+
list elist {
16+
key "one two";
17+
18+
leaf one {
19+
type leafref {
20+
path "../config/one";
21+
}
22+
}
23+
24+
leaf two {
25+
type leafref {
26+
path "../config/one";
27+
}
28+
}
29+
30+
container config {
31+
uses grp;
32+
}
33+
34+
container state {
35+
config false;
36+
uses grp;
37+
}
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)