Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
gnmi_transport_test.go 12.34 KiB
package nucleus

import (
	"context"
	"errors"
	"reflect"
	"testing"

	"code.fbi.h-da.de/cocsn/gosdn/interfaces/southbound"

	spb "code.fbi.h-da.de/cocsn/api/go/gosdn/southbound"
	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"

	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
	"code.fbi.h-da.de/cocsn/gosdn/mocks"
	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
	gpb "github.com/openconfig/gnmi/proto/gnmi"
	"github.com/openconfig/gnmi/proto/gnmi_ext"
	"github.com/openconfig/goyang/pkg/yang"
	"github.com/openconfig/ygot/ytypes"
	"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) {
	transport := mockTransport()
	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).
		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 {
		endpoint    string
		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(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) 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) {
	transport := mockTransport()
	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 {
		params      []string
		runEndpoint bool
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    interface{}
		wantErr bool
	}{
		{
			name:   "uninitialised",
			fields: fields{&Gnmi{}},
			args: args{
				params: nil,
			},
			want:    nil,
			wantErr: true,
		},
		{
			name:    "mock",
			fields:  fields{transport: &transport},
			args:    args{},
			want:    getResponse,
			wantErr: false,
		},
		{
			name:   "endpoint",
			fields: fields{transport: &transport},
			args: args{
				runEndpoint: true,
			},
			want:    getResponse,
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.args.runEndpoint {
				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 fields struct {
		Sbi southbound.SouthboundInterface
	}
	type args struct {
		path string
		root interface{}
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		wantErr bool
	}{
		{
			name:   "Interfaces Interface",
			fields: fields{Sbi: &OpenConfig{}},
			args: args{
				path: "../test/proto/resp-interfaces-interface-arista-ceos",
				root: &openconfig.Device{},
			},
			wantErr: true,
		},
		{
			name:   "Interfaces Wildcard",
			fields: fields{Sbi: &OpenConfig{}},
			args: args{
				path: "../test/proto/resp-interfaces-wildcard",
				root: &openconfig.Device{},
			},
			wantErr: false,
		},
		{
			name:   "Root",
			fields: fields{Sbi: &OpenConfig{}},
			args: args{
				path: "../test/proto/resp-full-node-arista-ceos",
				root: &openconfig.Device{},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := &Gnmi{
				SetNode:   tt.fields.Sbi.SetNode,
				Unmarshal: tt.fields.Sbi.(*OpenConfig).Unmarshal,
			}
			s := tt.fields.Sbi.Schema()
			resp := gnmiMessages[tt.args.path]
			if err := g.ProcessResponse(resp, tt.args.root, s); (err != nil) != tt.wantErr {
				t.Errorf("ProcessResponse() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestGnmi_Set(t *testing.T) {
	type fields struct {
		transport *Gnmi
	}
	type args struct {
		params []interface{}
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		wantErr bool
	}{
		{
			name:   "uninitialised",
			fields: fields{&Gnmi{}},
			args: args{
				params: nil,
			},
			wantErr: true,
		},
		// TODO: Positive test cases
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.fields.transport.Set(context.Background(), tt.args.params...)
			if (err != nil) != tt.wantErr {
				t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

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
		RespChan chan *gpb.SubscribeResponse
	}
	type args struct {
		ctx    context.Context
		params []string
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		wantErr bool
	}{
		{name: "nil client", fields: fields{}, args: args{}, wantErr: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			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) {
	type fields struct {
		SetNode  func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error
		RespChan chan *gpb.SubscribeResponse
	}
	tests := []struct {
		name   string
		fields fields
		want   string
	}{
		{name: "dummy", fields: fields{}, want: "gnmi"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := &Gnmi{
				SetNode:  tt.fields.SetNode,
				RespChan: tt.fields.RespChan,
			}
			if got := g.Type(); got != tt.want {
				t.Errorf("Type() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGnmi_getWithRequest(t *testing.T) {
	transport := mockTransport()
	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)

	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},
			args: args{
				request: reqFullNode,
			},
			want:    respFullNode,
			wantErr: false,
		},
		{
			name:   "getInterfacesWildcard",
			fields: fields{transport: &transport},
			args: args{
				request: reqInterfacesWildcard,
			},
			want:    respInterfacesWildcard,
			wantErr: false,
		},
		{
			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)
			}
		})
	}
}

func TestNewGnmiTransport(t *testing.T) {
	type args struct {
		opts *tpb.TransportOption
	}
	tests := []struct {
		name    string
		args    args
		want    *Gnmi
		wantErr bool
	}{
		{
			name: "unsupported compression",
			args: args{
				opts: &tpb.TransportOption{
					TransportOption: &tpb.TransportOption_GnmiTransportOption{
						GnmiTransportOption: &tpb.GnmiTransportOption{
							Compression: "brotli",
						},
					}}},
			want:    nil,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.name == "default" {
				startGnmiTarget <- gnmiConfig.Addr
			}

			got, err := newGnmiTransport(tt.args.opts, NewSBI(spb.Type_OPENCONFIG))
			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
				tt.want.config = got.config
				tt.want.SetNode = got.SetNode
				tt.want.RespChan = got.RespChan
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("NewGnmiTransport() got = %#v, want %#v", got, tt.want)
			}
			if tt.name == "default" {
				stopGnmiTarget <- true
			}
		})
	}
}

func TestGnmi_set(t *testing.T) {
	transport := mockTransport()
	mockResponse := &gpb.SetResponse{}

	transport.client.(*mocks.GNMIClient).
		On("NewContext", mockContext, mock.Anything).
		Return(mockContext)
	transport.client.(*mocks.GNMIClient).
		On("Set", mockContext, mock.Anything, mock.Anything).
		Return(mockResponse, nil)

	type fields struct {
		transport *Gnmi
	}
	type args struct {
		ctx    context.Context
		setOps []*gnmi.Operation
		exts   []*gnmi_ext.Extension
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   *gpb.SetResponse

		wantErr bool
	}{
		{
			name:   "default",
			fields: fields{transport: &transport},
			args: args{
				ctx: context.Background(),
				setOps: []*gnmi.Operation{
					{
						Type: "update",
						Path: []string{"interfaces", "interface", "name"},
						Val:  "test0",
					},
				},
				exts: nil,
			},
			want:    &gpb.SetResponse{},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := tt.fields.transport.set(tt.args.ctx, tt.args.setOps, tt.args.exts...)
			if (err != nil) != tt.wantErr {
				t.Errorf("set() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("set() got = %v, want %v", got, tt.want)
			}
		})
	}
}