Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support leaf-lists in paths->protobuf. #926

Merged
merged 6 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions protomap/integration_tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,36 @@ func TestGRIBIAFTToStruct(t *testing.T) {
SrcIp: &wpb.StringValue{Value: "1.1.1.1"},
},
},
}, {
desc: "pushed mpls label stack",
inPaths: map[*gpb.Path]interface{}{
mustPath("state/pushed-mpls-label-stack"): &gpb.TypedValue{
Value: &gpb.TypedValue_LeaflistVal{
LeaflistVal: &gpb.ScalarArray{
Element: []*gpb.TypedValue{
mustValue(t, 20),
mustValue(t, 30),
mustValue(t, 40),
mustValue(t, 50),
},
},
},
},
},
inProto: &gribi_aft.Afts_NextHop{},
inPrefix: mustPath("afts/next-hops/next-hop"),
wantProto: &gribi_aft.Afts_NextHop{
PushedMplsLabelStack: []*gribi_aft.Afts_NextHop_PushedMplsLabelStackUnion{{
PushedMplsLabelStackUint64: 20,
}, {
PushedMplsLabelStackUint64: 30,
}, {
PushedMplsLabelStackUint64: 40,
}, {
PushedMplsLabelStackUint64: 50,
}},
},
wantErr: true, // Currently this is unhandled but was causing a panic, check that it doesn't panic.
}}

for _, tt := range tests {
Expand Down
159 changes: 144 additions & 15 deletions protomap/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strconv"
"strings"

"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
Expand Down Expand Up @@ -618,6 +619,11 @@ func protoFromPathsInternal(p proto.Message, vals map[*gpb.Path]any, valPrefix,
return false
}

leaflist, leaflistunion, err := annotatedYANGFieldInfo(fd)
if err != nil {
rangeErr = fmt.Errorf("cannot extract field information for %s, %v", fd.FullName(), err)
}

if len(directCh) != 0 {
for _, ap := range annotatedPath {
trimmedPrefix := schemaPath(protoPrefix)
Expand All @@ -632,17 +638,34 @@ func protoFromPathsInternal(p proto.Message, vals map[*gpb.Path]any, valPrefix,
if proto.Equal(trimmedAP, chp) {
switch fd.Kind() {
case protoreflect.MessageKind:
v, isWrap, err := makeWrapper(m, fd, chv)
if err != nil {
rangeErr = err
return false
}
// Only handle wrapper messages here, other embedded messages are covered by
// checking the field type below (since we must handle cases where there are
// indirect children).
if isWrap {
// If this is is a leaf-list or a leaf-list of union values, then we
// need to handle it like a scalar field rather than recursing into
// the child messages.
switch {
case leaflist:
v, err := makeSimpleLeafList(m, fd, chv)
if err != nil {
rangeErr = err
return false
}
mapped[chp] = true
m.Set(fd, protoreflect.ValueOfMessage(v))
m.Set(fd, v)
case leaflistunion:
rangeErr = fmt.Errorf("%s: unhandled leaf-list of unions", fd.FullName())
return false
default:
v, isWrap, err := makeWrapper(m, fd, chv)
if err != nil {
rangeErr = err
return false
}
// Only handle wrapper messages here, other embedded messages are covered by
// checking the field type below (since we must handle cases where there are
// indirect children).
if isWrap {
mapped[chp] = true
m.Set(fd, protoreflect.ValueOfMessage(v))
}
}
case protoreflect.EnumKind:
v, err := enumValue(fd, chv)
Expand All @@ -666,13 +689,9 @@ func protoFromPathsInternal(p proto.Message, vals map[*gpb.Path]any, valPrefix,
if fd.Kind() == protoreflect.MessageKind {
switch {
case fd.IsList():
leaflist, leaflistunion, err := annotatedYANGFieldInfo(fd)
if err != nil {
rangeErr = fmt.Errorf("cannot extract field information for %s, %v", fd.FullName(), err)
}
switch {
case leaflist, leaflistunion:
// TODO(robjs): Support these fields, silently dropped for backwards compatibility.
// These fields are handled earlier as individual fields above, thus, do nothing here.
default:
// This is a YANG list field which is a repeated within a protobuf. We need to extract the
// keys from this message and create a list in the entry.
Expand Down Expand Up @@ -920,6 +939,12 @@ func hasValuePathPrefix(opts []UnmapOpt) (*gpb.Path, error) {
// type of payload is provided for the message. The second, boolean, return argument specifies whether
// the message provided was a known wrapper type.
func makeWrapper(msg protoreflect.Message, fd protoreflect.FieldDescriptor, val interface{}) (protoreflect.Message, bool, error) {
robshakir marked this conversation as resolved.
Show resolved Hide resolved
// If this field was a repeated then it could be a typed value -- but this is handled
// separately, thus we simply return false here.
if fd.IsList() {
return nil, false, nil
}

wenovus marked this conversation as resolved.
Show resolved Hide resolved
var wasTypedVal bool
if tv, ok := val.(*gpb.TypedValue); ok {
pv, err := value.ToScalar(tv)
Expand Down Expand Up @@ -974,6 +999,110 @@ func isWrapper(msg protoreflect.Message, fd protoreflect.FieldDescriptor) bool {
}
}

// makeSimpleLeafList makes a repeated value of wrapper protobufs for the field fd of the message msg containing
// the values in chv. It returns an error if the type is unhandled.
func makeSimpleLeafList(msg protoreflect.Message, fd protoreflect.FieldDescriptor, chv any) (protoreflect.Value, error) {
if tv, ok := chv.(*gpb.TypedValue); ok {
if len(tv.GetLeaflistVal().GetElement()) == 0 {
return protoreflect.ValueOf(nil), fmt.Errorf("invalid leaf-list value, does not include a scalar array, %s", prototext.Format(tv))
}
}
// If this is not a union, then we need to return a list field that contains
// a slice of wrapper values for the specified type.
newV := msg.NewField(fd)
elem := newV.List().NewElement()
switch elem.Message().Interface().(type) {
case *wpb.StringValue:
// This is a repeated of strings.
switch lv := chv.(type) {
case *gpb.TypedValue:
for _, v := range lv.GetLeaflistVal().GetElement() {
if vf, ok := v.GetValue().(*gpb.TypedValue_StringVal); !ok {
return protoreflect.ValueOf(nil), fmt.Errorf("wrong typed value set got %T and expected string", vf)
}
newV.List().Append(protoreflect.ValueOf((&wpb.StringValue{Value: v.GetStringVal()}).ProtoReflect()))
}
case []string:
for _, v := range lv {
newV.List().Append(protoreflect.ValueOf((&wpb.StringValue{Value: v}).ProtoReflect()))
}
default:
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T (value: %v) for repeated string field", lv, lv)
}
return newV, nil
case *wpb.UintValue:
switch lv := chv.(type) {
case *gpb.TypedValue:
for _, v := range lv.GetLeaflistVal().GetElement() {
if vf, ok := v.GetValue().(*gpb.TypedValue_UintVal); !ok {
return protoreflect.ValueOf(nil), fmt.Errorf("wrong typed value set got %T and expected uint", vf)
}
newV.List().Append(protoreflect.ValueOf((&wpb.UintValue{Value: v.GetUintVal()}).ProtoReflect()))
}
case []uint64:
for _, v := range lv {
newV.List().Append(protoreflect.ValueOf((&wpb.UintValue{Value: v}).ProtoReflect()))
}
default:
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T (value: %v) for repeated uint field", lv, lv)
}
return newV, nil
case *wpb.BoolValue:
switch lv := chv.(type) {
case *gpb.TypedValue:
for _, v := range lv.GetLeaflistVal().GetElement() {
if vf, ok := v.GetValue().(*gpb.TypedValue_BoolVal); !ok {
return protoreflect.ValueOf(nil), fmt.Errorf("wrong typed value set got %T and expected bool", vf)
}
newV.List().Append(protoreflect.ValueOf((&wpb.BoolValue{Value: v.GetBoolVal()}).ProtoReflect()))
}
case []bool:
for _, v := range lv {
newV.List().Append(protoreflect.ValueOf((&wpb.BoolValue{Value: v}).ProtoReflect()))
}
default:
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T (value: %v) for repeated bool field", lv, lv)
}
return newV, nil
case *wpb.IntValue:
switch lv := chv.(type) {
case *gpb.TypedValue:
for _, v := range lv.GetLeaflistVal().GetElement() {
if vf, ok := v.GetValue().(*gpb.TypedValue_IntVal); !ok {
return protoreflect.ValueOf(nil), fmt.Errorf("wrong typed value set got %T and expected int", vf)
}
newV.List().Append(protoreflect.ValueOf((&wpb.IntValue{Value: v.GetIntVal()}).ProtoReflect()))
}
case []int64:
for _, v := range lv {
newV.List().Append(protoreflect.ValueOf((&wpb.IntValue{Value: v}).ProtoReflect()))
}
default:
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T (value: %v) for repeated int field", lv, lv)
}
return newV, nil
case *wpb.BytesValue:
switch lv := chv.(type) {
case *gpb.TypedValue:
for _, v := range lv.GetLeaflistVal().GetElement() {
if vf, ok := v.GetValue().(*gpb.TypedValue_BytesVal); !ok {
return protoreflect.ValueOf(nil), fmt.Errorf("wrong typed value set got %T and expected bytes", vf)
}
newV.List().Append(protoreflect.ValueOf((&wpb.BytesValue{Value: v.GetBytesVal()}).ProtoReflect()))
}
case [][]byte:
for _, v := range lv {
newV.List().Append(protoreflect.ValueOf((&wpb.BytesValue{Value: v}).ProtoReflect()))
}
default:
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T (value: %v) for repeated byte field", lv, lv)
}
return newV, nil
}

return protoreflect.ValueOf(nil), fmt.Errorf("unhandled leaf-list value, %v", chv)
}

// enumValue returns the concrete implementation of the enumeration with the yang_name annotation set
// to the string contained in val of the enumeration within the field descriptor fd. It returns an
// error if the value cannot be found, or the input value is not valid.
Expand Down
Loading
Loading