diff --git a/cmd/gnmi-capabilities/capabilities.go b/cmd/gnmi-capabilities/capabilities.go index 54601b8a7ede5805a0d1a3ad633424020a8fc736..cabad2624ac914da3829e5f03fe98ee89bc06d38 100644 --- a/cmd/gnmi-capabilities/capabilities.go +++ b/cmd/gnmi-capabilities/capabilities.go @@ -11,13 +11,14 @@ import ( ) func main() { - cfg := &gnmi.Config{ - Addr: "[2003:e6:1722:fed0:0:242:ac11:5]:6030", + cfg := gnmi.Config{ + Addr: "portainer.danet.fbi.h-da.de:6030", Username: "admin", Password: "arista", Encoding: gpb.Encoding_JSON_IETF, } - transport, err := nucleus.NewGnmiTransport(cfg) + opts := &nucleus.GnmiTransportOptions{Config: cfg} + transport, err := nucleus.NewGnmiTransport(opts) if err != nil { log.Error(err) } diff --git a/cmd/gnmi-set/set.go b/cmd/gnmi-set/set.go index 2e5a2ee693a92049a51a65643c153a345350496a..d80ced7378110c9a6c2ac43da4320b02ca9b89a1 100644 --- a/cmd/gnmi-set/set.go +++ b/cmd/gnmi-set/set.go @@ -17,10 +17,11 @@ func main() { } opts := &nucleus.GnmiTransportOptions{ - - Addr: a, - Username: "admin", - Password: "arista", + Config: gnmi.Config{ + Addr: a, + Username: "admin", + Password: "arista", + }, } t, err := nucleus.NewGnmiTransport(opts) if err != nil { diff --git a/cmd/gnmi-telemetry/telemetry.go b/cmd/gnmi-telemetry/telemetry.go index fc61043ec99297a6a344ffc94da5b3baf8ce43eb..fc686a0792552273e701886d7c6bf0394a05c8d7 100644 --- a/cmd/gnmi-telemetry/telemetry.go +++ b/cmd/gnmi-telemetry/telemetry.go @@ -5,6 +5,7 @@ import ( "code.fbi.h-da.de/cocsn/gosdn/nucleus" "context" "fmt" + "github.com/google/uuid" gpb "github.com/openconfig/gnmi/proto/gnmi" log "github.com/sirupsen/logrus" "os" @@ -17,14 +18,16 @@ func main() { log.SetLevel(log.DebugLevel) sbi := &nucleus.OpenConfig{} - device, err := nucleus.NewDevice("gnmi", sbi, + device, err := nucleus.NewDevice(sbi, &nucleus.GnmiTransportOptions{ - Addr: "portainer.danet.fbi.h-da.de:6030", - Username: "admin", - Password: "arista", + Config: gnmi.Config{ + Addr: "portainer.danet.fbi.h-da.de:6030", + Username: "admin", + Password: "arista", + Encoding: gpb.Encoding_JSON_IETF, + }, SetNode: sbi.SetNode(), RespChan: make(chan *gpb.SubscribeResponse), - Encoding: gpb.Encoding_JSON_IETF, }) if err != nil { log.Debug(err) @@ -32,8 +35,6 @@ func main() { pnd, err := nucleus.NewPND("openconfig", "a simple openconfig PND", uuid.New(), sbi) if err != nil { log.Fatal(err) - } - log.Fatal(err) } if err := pnd.AddDevice(device); err != nil { log.Fatal(err) diff --git a/cmd/gnmi/gnmi.go b/cmd/gnmi/gnmi.go index 050c89d8f5c38e430562fc73eb2d1e813351d495..da82fb20a4e5a0b256840282c77697e82024b6d3 100644 --- a/cmd/gnmi/gnmi.go +++ b/cmd/gnmi/gnmi.go @@ -1,7 +1,9 @@ package main import ( + "code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi" "code.fbi.h-da.de/cocsn/gosdn/nucleus" + "github.com/google/uuid" gpb "github.com/openconfig/gnmi/proto/gnmi" log "github.com/sirupsen/logrus" ) @@ -12,16 +14,18 @@ import ( */ func main() { - log.SetLevel(log.DebugLevel) sbi := &nucleus.OpenConfig{} - device, err := nucleus.NewDevice("gnmi", sbi, - &nucleus.GnmiTransportOptions{ + opts := &nucleus.GnmiTransportOptions{ + Config: gnmi.Config{ Addr: "portainer.danet.fbi.h-da.de:6030", Username: "admin", Password: "arista", - SetNode: sbi.SetNode(), Encoding: gpb.Encoding_JSON_IETF, - }) + }, + SetNode: sbi.SetNode(), + } + log.SetLevel(log.DebugLevel) + device, err := nucleus.NewDevice(sbi, opts) if err != nil { log.Debug(err) } diff --git a/forks/goarista/gnmi/client.go b/forks/goarista/gnmi/client.go index 4ed4998b8ce935504fe9a1ae8928b8d4c6ffdacd..f9ed05b53fcd4852b7f5fa4c61d40e25c3688532 100644 --- a/forks/goarista/gnmi/client.go +++ b/forks/goarista/gnmi/client.go @@ -9,7 +9,6 @@ import ( "crypto/tls" "crypto/x509" "errors" - "flag" "fmt" "math" "net" @@ -82,59 +81,6 @@ type SubscribeOptions struct { Target string } -// ParseFlags reads arguments from stdin and returns a populated Config object and a list of -// paths to subscribe to -func ParseFlags() (*Config, []string) { - // flags - var ( - addrsFlag = flag.String("addrs", "localhost:6030", - "Comma-separated list of addresses of OpenConfig gRPC servers. The address 'HOSTNAME' "+ - "is replaced by the current hostname.") - - caFileFlag = flag.String("cafile", "", - "Path to server TLS certificate file") - - certFileFlag = flag.String("certfile", "", - "Path to ciena TLS certificate file") - - keyFileFlag = flag.String("keyfile", "", - "Path to ciena TLS private key file") - - passwordFlag = flag.String("password", "", - "Password to authenticate with") - - usernameFlag = flag.String("username", "", - "Username to authenticate with") - - tlsFlag = flag.Bool("tls", false, - "Enable TLS") - - compressionFlag = flag.String("compression", "", - "Type of compression to use") - - subscribeFlag = flag.String("subscribe", "", - "Comma-separated list of paths to subscribe to upon connecting to the server") - - token = flag.String("token", "", - "Authentication token") - ) - flag.Parse() - cfg := &Config{ - Addr: *addrsFlag, - CAFile: *caFileFlag, - CertFile: *certFileFlag, - KeyFile: *keyFileFlag, - Password: *passwordFlag, - Username: *usernameFlag, - TLS: *tlsFlag, - Compression: *compressionFlag, - Token: *token, - } - subscriptions := strings.Split(*subscribeFlag, ",") - return cfg, subscriptions - -} - // accessTokenCred implements credentials.PerRPCCredentials, the gRPC // interface for credentials that need to attach security information // to every RPC. diff --git a/nucleus/cli-handling.go b/nucleus/cli-handling.go index bc53846565793b40d2727312cfef025adc1e953d..55136026e9ebd4af850f43e5647b6425f596f86e 100644 --- a/nucleus/cli-handling.go +++ b/nucleus/cli-handling.go @@ -18,6 +18,7 @@ import ( "github.com/spf13/viper" pb "code.fbi.h-da.de/cocsn/gosdn/api/proto" + "code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi" gpb "github.com/openconfig/gnmi/proto/gnmi" log "github.com/sirupsen/logrus" "google.golang.org/grpc" @@ -239,8 +240,16 @@ func (s *server) AddDevice(ctx context.Context, in *pb.AddDeviceRequest) (*pb.Ad return &pb.AddDeviceReply{Message: err.Error()}, err } - newDevice, err := NewDevice(sbi, &GnmiTransportOptions{in.Device.Address, in.Device.Username, - in.Device.Password, sbi.SetNode(), nil, gpb.Encoding_JSON_IETF}) + newDevice, err := NewDevice(sbi, + &GnmiTransportOptions{ + Config: gnmi.Config{ + Addr: in.Device.Address, + Username: in.Device.Username, + Password: in.Device.Password, + Encoding: gpb.Encoding_JSON_IETF, + }, + SetNode: sbi.SetNode(), + }) if err != nil { log.Info(err) return &pb.AddDeviceReply{Message: err.Error()}, err diff --git a/nucleus/device_test.go b/nucleus/device_test.go index 2aed3c2dc7ba89e120c16a6a55bae29aa8b92148..d6391e830b584836b8d5d6a8928d5320d05e2e36 100644 --- a/nucleus/device_test.go +++ b/nucleus/device_test.go @@ -14,7 +14,7 @@ func TestDevice_Id(t *testing.T) { GoStruct ygot.GoStruct SBI SouthboundInterface Transport Transport - Uuid uuid.UUID + Uuid uuid.UUID } tests := []struct { name string @@ -24,7 +24,7 @@ func TestDevice_Id(t *testing.T) { { name: "default", fields: fields{ - Uuid: did, + Uuid: did, }, want: did, }, @@ -35,7 +35,7 @@ func TestDevice_Id(t *testing.T) { GoStruct: tt.fields.GoStruct, SBI: tt.fields.SBI, Transport: tt.fields.Transport, - Uuid: tt.fields.Uuid, + Uuid: tt.fields.Uuid, } if got := d.Id(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Id() = %v, want %v", got, tt.want) @@ -47,7 +47,7 @@ func TestDevice_Id(t *testing.T) { func TestNewDevice(t *testing.T) { sbi := &OpenConfig{} type args struct { - sbi SouthboundInterface + sbi SouthboundInterface opts TransportOptions } tests := []struct { @@ -59,32 +59,27 @@ func TestNewDevice(t *testing.T) { { name: "default", args: args{ - sbi: sbi, - opts: &GnmiTransportOptions{ - Addr: "test:///", - Username: "test", - Password: "test", + sbi: sbi, + opts: &GnmiTransportOptions{ + Config: gnmi.Config{ + Addr: "test:///", + Username: "test", + Password: "test", + }, }, }, want: &Device{ GoStruct: &openconfig.Device{}, SBI: sbi, - Uuid: uuid.New(), + Uuid: uuid.New(), Transport: &Gnmi{ - SetNode: nil, - RespChan: nil, - Unmarshal: nil, - Options: &GnmiTransportOptions{ - Addr: "test:///", - Username: "test", - Password: "test", - }, - config: &gnmi.Config{ - Addr: "test:///", - Username: "test", - Password: "test", + Options: &GnmiTransportOptions{ + Config: gnmi.Config{ + Addr: "test:///", + Username: "test", + Password: "test", + }, }, - client: nil, }, }, }, @@ -95,6 +90,7 @@ func TestNewDevice(t *testing.T) { if err != nil { t.Error(err) } + tt.want.Transport.(*Gnmi).client = got.Transport.(*Gnmi).client tt.want.Uuid = got.Id() if (err != nil) != tt.wantErr { t.Errorf("NewDevice() error = %v, wantErr %v", err, tt.wantErr) diff --git a/nucleus/gnmi_transport.go b/nucleus/gnmi_transport.go index 4bdef0618499aaa31389c237707a5cadba5a6022..30af29f2320e673a0f013e5738fa00ff63cfdbe4 100644 --- a/nucleus/gnmi_transport.go +++ b/nucleus/gnmi_transport.go @@ -12,39 +12,26 @@ import ( ) type Gnmi struct { - SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error - RespChan chan *gpb.SubscribeResponse + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse Unmarshal func([]byte, []string, interface{}, ...ytypes.UnmarshalOpt) error - Options *GnmiTransportOptions - config *gnmi.Config - client gpb.GNMIClient + Options *GnmiTransportOptions + client gpb.GNMIClient } func NewGnmiTransport(opts *GnmiTransportOptions) (*Gnmi, error) { - config := createConfig(opts) - c, err := gnmi.Dial(config) + c, err := gnmi.Dial(&opts.Config) if err != nil { return nil, err } return &Gnmi{ SetNode: opts.SetNode, RespChan: opts.RespChan, - config: config, Options: opts, client: c, }, nil } -//SetConfig sets the config of gnmi -func (g *Gnmi) SetConfig(config *gnmi.Config) { - g.config = config -} - -//GetConfig returns the gnmi config -func (g *Gnmi) GetConfig() *gnmi.Config { - return g.config -} - //SetConfig sets the config of gnmi func (g *Gnmi) SetOptions(to TransportOptions) { g.Options = to.(*GnmiTransportOptions) @@ -55,16 +42,6 @@ func (g *Gnmi) GetOptions() interface{} { return g.Options } -//CreateConfig creates a new gnmi config based on the given parameters -func createConfig(opts *GnmiTransportOptions) *gnmi.Config { - return &gnmi.Config{ - Addr: opts.Addr, - Username: opts.Username, - Password: opts.Password, - Encoding: opts.Encoding, - } -} - // interface satisfaction for now // TODO: Convert to meaningfiul calls func (g *Gnmi) Get(ctx context.Context, params ...string) (interface{}, error) { @@ -92,7 +69,7 @@ func (g *Gnmi) Set(ctx context.Context, params ...interface{}) (interface{}, err // Invalid params cause unhealable error ops := make([]*gnmi.Operation, 0) exts := make([]*gnmi_ext.Extension, 0) - for _,p := range params{ + for _, p := range params { switch p.(type) { case *gnmi.Operation: ops = append(ops, p.(*gnmi.Operation)) @@ -163,8 +140,8 @@ func extraxtPathElements(path *gpb.Path) []string { // Capabilities calls GNMI capabilities func (g *Gnmi) Capabilities(ctx context.Context) (interface{}, error) { - ctx = gnmi.NewContext(ctx, g.config) - ctx = context.WithValue(ctx, "config", g.config) + ctx = gnmi.NewContext(ctx, &g.Options.Config) + ctx = context.WithValue(ctx, "config", &g.Options.Config) resp, err := g.client.Capabilities(ctx, &gpb.CapabilityRequest{}) if err != nil { return nil, err @@ -174,8 +151,8 @@ func (g *Gnmi) Capabilities(ctx context.Context) (interface{}, error) { // get calls GNMI get func (g *Gnmi) get(ctx context.Context, paths [][]string, origin string) (interface{}, error) { - ctx = gnmi.NewContext(ctx, g.config) - ctx = context.WithValue(ctx, "config", g.config) + ctx = gnmi.NewContext(ctx, &g.Options.Config) + ctx = context.WithValue(ctx, "config", &g.Options.Config) req, err := gnmi.NewGetRequest(ctx, paths, origin) if err != nil { return nil, err @@ -196,13 +173,13 @@ func (g *Gnmi) getWithRequest(ctx context.Context, req *gpb.GetRequest) (interfa // Set calls GNMI set func (g *Gnmi) set(ctx context.Context, setOps []*gnmi.Operation, exts ...*gnmi_ext.Extension) (*gpb.SetResponse, error) { - ctx = gnmi.NewContext(ctx, g.config) + ctx = gnmi.NewContext(ctx, &g.Options.Config) return gnmi.Set(ctx, g.client, setOps, exts...) } // Subscribe calls GNMI subscribe func (g *Gnmi) subscribe(ctx context.Context) error { - ctx = gnmi.NewContext(ctx, g.config) + ctx = gnmi.NewContext(ctx, &g.Options.Config) opts := ctx.Value("opts").(*gnmi.SubscribeOptions) go func() { for { @@ -224,13 +201,12 @@ func (g *Gnmi) Close() error { type GnmiTransportOptions struct { // all needed gnmi transport parameters - Addr string - Username string - Password string - SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, + gnmi.Config + + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error - RespChan chan *gpb.SubscribeResponse - Encoding gpb.Encoding + Unmarshal func([]byte, []string, interface{}, ...ytypes.UnmarshalOpt) error + RespChan chan *gpb.SubscribeResponse } func (gto *GnmiTransportOptions) GetAddress() string { diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go index f53df098a611507d7973a798e1ad28766b4e0ded..206e231a9d1666022f7ef6fa83b2b43ded9ac858 100644 --- a/nucleus/gnmi_transport_test.go +++ b/nucleus/gnmi_transport_test.go @@ -79,19 +79,20 @@ func mockTransport() Gnmi { SetNode: nil, RespChan: make(chan *gpb.SubscribeResponse), Options: newGnmiTransportOptions(), - config: gnmiConfig, client: &mocks.GNMIClient{}, } } func newGnmiTransportOptions() *GnmiTransportOptions { return &GnmiTransportOptions{ - Username: "test", - Password: "test", - Addr: "localhost:13371", + Config: gnmi.Config{ + Username: "test", + Password: "test", + Addr: "localhost:13371", + Encoding: gpb.Encoding_PROTO, + }, SetNode: nil, RespChan: make(chan *gpb.SubscribeResponse), - Encoding: gpb.Encoding_PROTO, } } @@ -183,7 +184,6 @@ func TestGnmi_Close(t *testing.T) { g := &Gnmi{ SetNode: tt.fields.SetNode, RespChan: tt.fields.RespChan, - config: tt.fields.config, } if err := g.Close(); (err != nil) != tt.wantErr { t.Errorf("Close() error = %v, wantErr %v", err, tt.wantErr) @@ -262,7 +262,7 @@ func TestGnmi_Get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.args.runEndpoint { - startGnmiTarget <- tt.fields.transport.config.Addr + startGnmiTarget <- tt.fields.transport.Options.Addr } got, err := tt.fields.transport.Get(context.Background(), tt.args.params...) if (err != nil) != tt.wantErr { @@ -279,31 +279,6 @@ func TestGnmi_Get(t *testing.T) { } } -func TestGnmi_GetConfig(t *testing.T) { - transport := mockTransport() - type fields struct { - transport *Gnmi - } - tests := []struct { - name string - fields fields - want *gnmi.Config - }{ - { - name: "default", - fields: fields{transport: &transport}, - want: gnmiConfig, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.fields.transport.GetConfig(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetConfig() = %v, want %v", got, tt.want) - } - }) - } -} - func TestGnmi_ProcessResponse(t *testing.T) { type fields struct { Sbi SouthboundInterface @@ -401,47 +376,6 @@ func TestGnmi_Set(t *testing.T) { } } -func TestGnmi_SetConfig(t *testing.T) { - type fields struct { - SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error - RespChan chan *gpb.SubscribeResponse - config *gnmi.Config - } - type args struct { - config *gnmi.Config - } - tests := []struct { - name string - fields fields - args args - }{ - { - name: "default", - fields: fields{ - SetNode: nil, - RespChan: nil, - config: nil, - }, - args: args{ - config: &gnmi.Config{ - Addr: "test:///", - Password: "test", - Username: "test", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := &Gnmi{} - g.SetConfig(tt.args.config) - if !reflect.DeepEqual(g.config, tt.args.config) { - t.Errorf("Type() = %v, want %v", g.config, tt.args.config) - } - }) - } -} - func TestGnmi_Subscribe(t *testing.T) { type fields struct { SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error @@ -488,7 +422,6 @@ func TestGnmi_Type(t *testing.T) { g := &Gnmi{ SetNode: tt.fields.SetNode, RespChan: tt.fields.RespChan, - config: tt.fields.config, } if got := g.Type(); got != tt.want { t.Errorf("Type() = %v, want %v", got, tt.want) @@ -575,8 +508,7 @@ func TestGnmi_getWithRequest(t *testing.T) { func TestNewGnmiTransport(t *testing.T) { type args struct { - opts *GnmiTransportOptions - config *gnmi.Config + opts *GnmiTransportOptions } tests := []struct { name string @@ -587,37 +519,41 @@ func TestNewGnmiTransport(t *testing.T) { { name: "default", args: args{opts: &GnmiTransportOptions{ - Username: "test", - Password: "test", - Addr: "localhost:13371", - Encoding: gpb.Encoding_PROTO, - }}, - want: &Gnmi{ - config: &gnmi.Config{ + Config: gnmi.Config{ Username: "test", Password: "test", Addr: "localhost:13371", Encoding: gpb.Encoding_PROTO, }, + }}, + want: &Gnmi{ + Options: &GnmiTransportOptions{ + Config: gnmi.Config{ + Username: "test", + Password: "test", + Addr: "localhost:13371", + Encoding: gpb.Encoding_PROTO, + }, + }, client: nil, }, wantErr: false, }, { name: "unsupported compression", - args: args{config: &gnmi.Config{Compression: "brotli"}}, + args: args{opts: &GnmiTransportOptions{Config: gnmi.Config{Compression: "brotli"}}}, want: nil, wantErr: true, }, { name: "certificate error no key file", - args: args{config: &gnmi.Config{TLS: true, CertFile: "invalid", KeyFile: ""}}, + args: args{opts: &GnmiTransportOptions{Config: gnmi.Config{TLS: true, CertFile: "invalid", KeyFile: ""}}}, want: nil, wantErr: true, }, { name: "certificate error no ca file", - args: args{config: &gnmi.Config{TLS: true, CAFile: "invalid"}}, + args: args{opts: &GnmiTransportOptions{Config: gnmi.Config{TLS: true, CAFile: "invalid"}}}, want: nil, wantErr: true, }, @@ -730,7 +666,6 @@ func TestGnmi_subscribe(t *testing.T) { SetNode: tt.fields.SetNode, Unmarshal: tt.fields.Unmarshal, RespChan: tt.fields.RespChan, - config: tt.fields.config, client: tt.fields.client, } if err := g.subscribe(tt.args.ctx); (err != nil) != tt.wantErr { diff --git a/nucleus/integration_test.go b/nucleus/integration_test.go index 9327c47297e341b9daddb7c7708062ede9593924..c57e2827f36abb7541cf4ec9b5b0df967e91cd62 100644 --- a/nucleus/integration_test.go +++ b/nucleus/integration_test.go @@ -6,6 +6,7 @@ import ( gpb "github.com/openconfig/gnmi/proto/gnmi" "os" "reflect" + "sort" "testing" "time" ) @@ -20,10 +21,12 @@ func testSetupIntegration() { } opt = &GnmiTransportOptions{ - Addr: address, - Username: "admin", - Password: "arista", - Encoding: gpb.Encoding_JSON_IETF, + Config: gnmi.Config{ + Addr: address, + Username: "admin", + Password: "arista", + Encoding: gpb.Encoding_JSON_IETF, + }, } } @@ -48,21 +51,23 @@ func TestGnmi_SetIntegration(t *testing.T) { { name: "destination unreachable", fields: fields{opt: &GnmiTransportOptions{ - Addr: "203.0.113.10:6030", + Config: gnmi.Config{ + Addr: "203.0.113.10:6030", + }, }, }, - args: args{ - ctx: context.Background(), + args: args{ + ctx: context.Background(), params: []interface{}{&gnmi.Operation{}}, }, want: nil, wantErr: true, }, { - name: "valid update", - fields: fields{opt: opt}, - args: args{ - ctx: context.Background(), + name: "valid update", + fields: fields{opt: opt}, + args: args{ + ctx: context.Background(), params: []interface{}{ &gnmi.Operation{ Type: "update", @@ -206,7 +211,9 @@ func TestGnmi_CapabilitiesIntegration(t *testing.T) { { name: "destination unreachable", fields: fields{opt: &GnmiTransportOptions{ - Addr: "203.0.113.10:6030", + Config: gnmi.Config{ + Addr: "203.0.113.10:6030", + }, }, }, args: args{ctx: context.Background()}, @@ -232,6 +239,12 @@ func TestGnmi_CapabilitiesIntegration(t *testing.T) { got = resp.(*gpb.CapabilityResponse).SupportedEncodings case "supported models": got = resp.(*gpb.CapabilityResponse).SupportedModels + sort.Slice(got.([]*gpb.ModelData), func(i, j int) bool { + return got.([]*gpb.ModelData)[i].Name < got.([]*gpb.ModelData)[j].Name + }) + sort.Slice(tt.want.([]*gpb.ModelData), func(i, j int) bool { + return tt.want.([]*gpb.ModelData)[i].Name < tt.want.([]*gpb.ModelData)[j].Name + }) case "gnmi version": got = resp.(*gpb.CapabilityResponse).GNMIVersion default: