Skip to content
Snippets Groups Projects
gnmi_transport_test.go 12.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Manuel Kieweg's avatar
    Manuel Kieweg committed
    package nucleus
    
    
    import (
    
    Andre Sterba's avatar
    Andre Sterba committed
    	"context"
    	"errors"
    	"reflect"
    	"testing"
    
    
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/change"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/types"
    
    	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
    
    	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
    
    	"code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
    
    	openconfig "code.fbi.h-da.de/danet/gosdn/models/generated/openconfig"
    	pluginSDK "code.fbi.h-da.de/danet/gosdn/plugins/sdk"
    
    	gpb "github.com/openconfig/gnmi/proto/gnmi"
    
    	"github.com/openconfig/ygot/ygot"
    
    	"github.com/stretchr/testify/mock"
    
    // testSetupGnmi bootstraps tests for gnmi transport.
    
    func testSetupGnmi() {
    	// TODO: Set sane defaults
    	gnmiConfig = &gnmi.Config{
    		Username: "test",
    		Password: "test",
    		Addr:     "localhost:13371",
    		Encoding: gpb.Encoding_PROTO,
    	}
    
    	startGnmiTarget = make(chan string)
    	stopGnmiTarget = make(chan bool)
    	go targetRunner()
    }
    
    func TestGnmi_Capabilities(t *testing.T) {
    
    	capabilityResponse := &gpb.CapabilityResponse{
    		SupportedModels:    nil,
    		SupportedEncodings: []gpb.Encoding{gpb.Encoding_PROTO, gpb.Encoding_JSON_IETF, gpb.Encoding_JSON},
    		GNMIVersion:        "0.6.0",
    		Extension:          nil,
    	}
    
    	capabilityRequest := &gpb.CapabilityRequest{}
    
    	transport.client.(*mocks.GNMIClient).
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		On("NewContext", mockContext, mock.Anything).
    		Return(mockContext)
    	transport.client.(*mocks.GNMIClient).
    		On("Capabilities", mockContext, capabilityRequest).
    
    		Return(capabilityResponse, nil)
    
    
    	type fields struct {
    
    		transport *Gnmi
    
    	}
    	type args struct {
    
    		runEndpoint bool
    
    	}
    	tests := []struct {
    		name    string
    		fields  fields
    		args    args
    		want    *gpb.CapabilityResponse
    		wantErr bool
    	}{
    		{
    
    			name:   "mock",
    
    			fields: fields{transport: &transport},
    
    			args: args{
    
    				endpoint:    gnmiConfig.Addr,
    
    				runEndpoint: false,
    
    			want:    capabilityResponse,
    			wantErr: false,
    
    		},
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    
    			if tt.args.runEndpoint {
    				startGnmiTarget <- tt.args.endpoint
    			}
    
    
    			got, err := tt.fields.transport.Capabilities(context.Background())
    
    			if (err != nil) != tt.wantErr {
    				t.Errorf("Capabilities() error = %v, wantErr %v", err, tt.wantErr)
    				return
    			}
    
    			if !reflect.DeepEqual(got, tt.want) {
    				t.Errorf("Get() got = %v, want %v", got, tt.want)
    
    			if tt.args.runEndpoint {
    				stopGnmiTarget <- true
    			}
    
    		})
    	}
    }
    
    func TestGnmi_Close(t *testing.T) {
    	type fields struct {
    
    		SetNode  func(path *gpb.Path, value *gpb.TypedValue) error
    
    		RespChan chan *gpb.SubscribeResponse
    	}
    	tests := []struct {
    		name    string
    		fields  fields
    		wantErr bool
    	}{
    
    		{name: "dummy", fields: fields{}, wantErr: false},
    
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    			g := &Gnmi{
    				SetNode:  tt.fields.SetNode,
    				RespChan: tt.fields.RespChan,
    			}
    			if err := g.Close(); (err != nil) != tt.wantErr {
    				t.Errorf("Close() error = %v, wantErr %v", err, tt.wantErr)
    			}
    		})
    	}
    }
    
    func TestGnmi_Get(t *testing.T) {
    
    	getRequest := &gpb.GetRequest{
    		Prefix:    nil,
    		Path:      nil,
    		Type:      0,
    		Encoding:  0,
    		UseModels: nil,
    		Extension: nil,
    	}
    
    
    	getResponse := &gpb.GetResponse{
    
    		Notification: nil,
    		Error:        nil,
    		Extension:    nil,
    	}
    
    	transport.client.(*mocks.GNMIClient).
    		On("NewContext", mockContext, mock.Anything).
    		Return(mockContext)
    	transport.client.(*mocks.GNMIClient).
    		On("NewGetRequest", mockContext, mock.Anything, mock.Anything).
    		Return(getRequest, nil)
    	transport.client.(*mocks.GNMIClient).
    		On("Get", mockContext, mock.Anything).
    
    		Return(getResponse, nil)
    
    	type fields struct {
    
    		transport *Gnmi
    
    	}
    	type args struct {
    
    		runEndpoint bool
    
    	}
    	tests := []struct {
    		name    string
    		fields  fields
    		args    args
    		want    interface{}
    		wantErr bool
    	}{
    
    			name:   "uninitialised",
    
    			fields: fields{&Gnmi{}},
    			args: args{
    				params: nil,
    			},
    
    			fields:  fields{transport: &transport},
    
    			args:    args{},
    			want:    getResponse,
    
    			fields: fields{transport: &transport},
    
    			args: args{
    				runEndpoint: true,
    			},
    
    			wantErr: false,
    		},
    
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    
    			if tt.args.runEndpoint {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    				startGnmiTarget <- tt.fields.transport.config.Addr
    
    			got, err := tt.fields.transport.Get(context.Background(), tt.args.params...)
    
    			if (err != nil) != tt.wantErr {
    				t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
    				return
    			}
    			if !reflect.DeepEqual(got, tt.want) {
    				t.Errorf("Get() got = %v, want %v", got, tt.want)
    			}
    
    			if tt.args.runEndpoint {
    				stopGnmiTarget <- true
    			}
    		})
    
    	}
    }
    
    func TestGnmi_ProcessResponse(t *testing.T) {
    	type args struct {
    
    	}
    	tests := []struct {
    		name    string
    		args    args
    		wantErr bool
    	}{
    
    				path: "../test/proto/resp-interfaces-interface-arista-ceos",
    
    				path: "../test/proto/resp-interfaces-wildcard",
    
    				path: "../test/proto/resp-full-node-arista-ceos",
    
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    
    			networkElementModel, err := pluginSDK.NewDeviceModel(openconfig.Schema, openconfig.Unmarshal, openconfig.SchemaTreeGzip)
    
    			if err != nil {
    				t.Errorf("ProcessResponse() error = %v", err)
    			}
    
    
    			g := &Gnmi{
    				SetNode:   networkElementModel.SetNode,
    				Unmarshal: networkElementModel.Unmarshal,
    			}
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			resp := gnmiMessages[tt.args.path]
    
    			if err := g.ProcessResponse(resp); (err != nil) != tt.wantErr {
    
    				t.Errorf("ProcessResponse() error = %v, wantErr %v", err, tt.wantErr)
    			}
    		})
    	}
    }
    
    func TestGnmi_Set(t *testing.T) {
    
    	hostnamePath, err := ygot.StringToStructuredPath("system/config/hostname")
    	if err != nil {
    		t.Fatal(err)
    
    	type fields struct {
    
    		transport           Gnmi
    		mockArgumentMatcher any
    
    	}
    	type args struct {
    
    		payload change.Payload
    
    	}
    	tests := []struct {
    		name    string
    		fields  fields
    		args    args
    		wantErr bool
    	}{
    
    		{
    			name: "updateValue",
    			fields: fields{
    
    				mockArgumentMatcher: mock.MatchedBy(func(input *gpb.SetRequest) bool {
    					if len(input.Update) == 0 {
    						return false
    					}
    
    					path, err := ygot.PathToString(input.Update[0].Path)
    					if err != nil {
    						return false
    					}
    					value := input.Update[0].Val.GetStringVal()
    
    					valCheck := value == "newName"
    					pathCheck := path == "/system/config/hostname"
    
    					return valCheck && pathCheck
    
    				}),
    			},
    			args: args{
    				payload: change.Payload{
    
    					Original: []byte("update"),
    					Modified: []byte("update2"),
    
    					Diff: &gpb.Notification{
    						Update: []*gpb.Update{
    							{
    								Path: hostnamePath,
    								Val: &gpb.TypedValue{
    									Value: &gpb.TypedValue_StringVal{
    										StringVal: "newName",
    									},
    								},
    							},
    						},
    					},
    
    				},
    				path: "/system/config/hostname",
    
    				ctx:  context.WithValue(context.Background(), types.CtxKeyOperation, mnepb.ApiOperation_API_OPERATION_UPDATE), // nolint
    
    			},
    			wantErr: false,
    		},
    		{
    			name: "removeValue",
    			fields: fields{
    
    				mockArgumentMatcher: mock.MatchedBy(func(input *gpb.SetRequest) bool {
    					if len(input.Delete) == 0 {
    						return false
    					}
    					test, _ := ygot.PathToString(input.Delete[0])
    					return test == "/system/config/hostname"
    				}),
    			},
    			args: args{
    				payload: change.Payload{
    
    					Original: []byte("delete"),
    					Modified: []byte("delete2"),
    
    					Diff: &gpb.Notification{
    						Delete: []*gpb.Path{
    							hostnamePath,
    						},
    					},
    
    				},
    				path: "/system/config/hostname",
    
    				ctx:  context.WithValue(context.Background(), types.CtxKeyOperation, mnepb.ApiOperation_API_OPERATION_DELETE), // nolint
    
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    
    			tt.fields.transport.client.(*mocks.GNMIClient).
    				On("Set", mockContext, tt.fields.mockArgumentMatcher).Return(setResponse, nil)
    
    			err := tt.fields.transport.Set(tt.args.ctx, tt.args.payload)
    
    			if (err != nil) != tt.wantErr {
    				t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
    			}
    		})
    	}
    }
    
    func TestGnmi_Subscribe(t *testing.T) {
    	type args struct {
    		ctx    context.Context
    		params []string
    	}
    	tests := []struct {
    		name    string
    		args    args
    		wantErr bool
    	}{
    
    		{name: "nil client", args: args{}, wantErr: true},
    
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			g := &Gnmi{}
    
    			if err := g.Subscribe(tt.args.ctx, tt.args.params...); (err != nil) != tt.wantErr {
    				t.Errorf("Subscribe() error = %v, wantErr %v", err, tt.wantErr)
    			}
    		})
    	}
    }
    
    func TestGnmi_Type(t *testing.T) {
    	tests := []struct {
    
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    
    			if got := g.Type(); got != tt.want {
    				t.Errorf("Type() = %v, want %v", got, tt.want)
    			}
    		})
    	}
    }
    
    
    func TestGnmi_getWithRequest(t *testing.T) {
    
    	reqFullNode := gnmiMessages["../test/proto/req-full-node"].(*gpb.GetRequest)
    	reqInterfacesWildcard := gnmiMessages["../test/proto/req-interfaces-wildcard"].(*gpb.GetRequest)
    	respFullNode := gnmiMessages["../test/proto/resp-full-node"].(*gpb.GetResponse)
    	respInterfacesWildcard := gnmiMessages["../test/proto/resp-interfaces-wildcard"].(*gpb.GetResponse)
    
    	transport.client.(*mocks.GNMIClient).
    		On("Get", mockContext, reqFullNode).
    		Return(respFullNode, nil)
    
    	transport.client.(*mocks.GNMIClient).
    		On("Get", mockContext, reqInterfacesWildcard).
    		Return(respInterfacesWildcard, nil)
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	transport.client.(*mocks.GNMIClient).
    		On("Get", mockContext, mock.Anything).
    		Return(nil, errors.New("expected mock gnmi error"))
    
    
    	type fields struct {
    
    		transport *Gnmi
    
    	}
    	type args struct {
    
    		request     *gpb.GetRequest
    
    		runEndpoint bool
    
    	}
    	tests := []struct {
    		name    string
    		fields  fields
    		args    args
    		want    interface{}
    		wantErr bool
    	}{
    
    			name:   "getFullNode",
    
    			fields: fields{transport: &transport},
    
    				request: reqFullNode,
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			want:    respFullNode,
    
    			name:   "getInterfacesWildcard",
    
    			fields: fields{transport: &transport},
    
    			args: args{
    				request: reqInterfacesWildcard,
    			},
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			want:    respInterfacesWildcard,
    
    			wantErr: false,
    		},
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		{
    			name:   "invalid request",
    			fields: fields{transport: &transport},
    			args: args{
    				request:     nil,
    				runEndpoint: false,
    			},
    			want:    nil,
    			wantErr: true,
    		},
    
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    
    			got, err := tt.fields.transport.getWithRequest(context.Background(), tt.args.request)
    
    			if (err != nil) != tt.wantErr {
    				t.Errorf("getWithRequest() error = %v, wantErr %v", err, tt.wantErr)
    				return
    			}
    			if !reflect.DeepEqual(got, tt.want) {
    				t.Errorf("getWithRequest() got = %v, want %v", got, tt.want)
    			}
    		})
    	}
    }
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func TestNewGnmiTransport(t *testing.T) {
    	type args struct {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		opts *tpb.TransportOption
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	}
    	tests := []struct {
    		name    string
    		args    args
    		want    *Gnmi
    		wantErr bool
    	}{
    		{
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			name: "unsupported compression",
    			args: args{
    				opts: &tpb.TransportOption{
    					TransportOption: &tpb.TransportOption_GnmiTransportOption{
    						GnmiTransportOption: &tpb.GnmiTransportOption{
    							Compression: "brotli",
    						},
    					}}},
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			want:    nil,
    			wantErr: true,
    		},
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    			if tt.name == "default" {
    				startGnmiTarget <- gnmiConfig.Addr
    			}
    
    			mockPlugin := mockPlugin(t)
    
    			got, err := newGnmiTransport(tt.args.opts, mockPlugin)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			if (err != nil) != tt.wantErr {
    				t.Errorf("NewGnmiTransport() error = %v, wantErr %v", err, tt.wantErr)
    				return
    			}
    			if tt.name == "default" && got != nil {
    				tt.want.client = got.client
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    				tt.want.config = got.config
    				tt.want.SetNode = got.SetNode
    				tt.want.RespChan = got.RespChan
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			}
    			if !reflect.DeepEqual(got, tt.want) {
    
    				t.Errorf("NewGnmiTransport() got = %#v, want %#v", got, tt.want)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			}
    			if tt.name == "default" {
    				stopGnmiTarget <- true
    			}
    		})
    	}