go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/vppmock/vppmock.go (about)

     1  // Copyright (c) 2017 Cisco and/or its affiliates.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at:
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package vppmock
    16  
    17  import (
    18  	"context"
    19  	"strings"
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	"go.fd.io/govpp/adapter/mock"
    25  	govppapi "go.fd.io/govpp/api"
    26  	"go.fd.io/govpp/core"
    27  	govpp "go.fd.io/govpp/core"
    28  	log "go.ligato.io/cn-infra/v2/logging/logrus"
    29  
    30  	"go.ligato.io/vpp-agent/v3/plugins/vpp"
    31  )
    32  
    33  // TestCtx is a helper for unit testing.
    34  // It wraps VppAdapter which is used instead of real VPP.
    35  type TestCtx struct {
    36  	// *GomegaWithT
    37  	Context       context.Context
    38  	MockVpp       *mock.VppAdapter
    39  	MockStats     *mock.StatsAdapter
    40  	MockChannel   *mockedChannel
    41  	MockVPPClient *mockVPPClient
    42  	PingReplyMsg  govppapi.Message
    43  }
    44  
    45  // SetupTestCtx sets up all fields of TestCtx structure at the begining of test
    46  func SetupTestCtx(t *testing.T) *TestCtx {
    47  	RegisterTestingT(t)
    48  	// g := NewGomegaWithT(t) // TODO: use GomegaWithT
    49  
    50  	ctx := &TestCtx{
    51  		// GomegaWithT: g,
    52  		Context:      context.Background(),
    53  		MockVpp:      mock.NewVppAdapter(),
    54  		MockStats:    mock.NewStatsAdapter(),
    55  		PingReplyMsg: &govpp.ControlPingReply{VpePID: 123},
    56  	}
    57  
    58  	ctx.MockVPPClient = newMockVPPClient(ctx)
    59  
    60  	return ctx
    61  }
    62  
    63  // TeardownTestCtx politely close all used resources
    64  func (ctx *TestCtx) TeardownTestCtx() {
    65  	ctx.MockVPPClient.mockedChannel.Close()
    66  	ctx.MockVPPClient.conn.Disconnect()
    67  }
    68  
    69  // HandleReplies represents spec for MockReplyHandler.
    70  type HandleReplies struct {
    71  	Name     string
    72  	Ping     bool
    73  	Message  govppapi.Message
    74  	Messages []govppapi.Message
    75  }
    76  
    77  // MockReplies sets up reply handler for give HandleReplies.
    78  func (ctx *TestCtx) MockReplies(dataList []*HandleReplies) {
    79  	var sendControlPing bool
    80  
    81  	ctx.MockVpp.MockReplyHandler(func(request mock.MessageDTO) (reply []byte, msgID uint16, prepared bool) {
    82  		// Following types are not automatically stored in mock adapter's map and will be sent with empty MsgName
    83  		// TODO: initialize mock adapter's map with these
    84  		switch request.MsgID {
    85  		case 100:
    86  			request.MsgName = "control_ping"
    87  		case 101:
    88  			request.MsgName = "control_ping_reply"
    89  		case 200:
    90  			request.MsgName = "sw_interface_dump"
    91  		case 201:
    92  			request.MsgName = "sw_interface_details"
    93  		}
    94  
    95  		if request.MsgName == "" {
    96  			log.DefaultLogger().Fatalf("mockHandler received request (ID: %v) with empty MsgName, check if compatibility check is done before using this request", request.MsgID)
    97  		}
    98  
    99  		if sendControlPing {
   100  			sendControlPing = false
   101  			data := ctx.PingReplyMsg
   102  			reply, err := ctx.MockVpp.ReplyBytes(request, data)
   103  			Expect(err).To(BeNil())
   104  			msgID, err := ctx.MockVpp.GetMsgID(data.GetMessageName(), data.GetCrcString())
   105  			Expect(err).To(BeNil())
   106  			return reply, msgID, true
   107  		}
   108  
   109  		for _, dataMock := range dataList {
   110  			if request.MsgName == dataMock.Name {
   111  				// Send control ping next iteration if set
   112  				sendControlPing = dataMock.Ping
   113  				if len(dataMock.Messages) > 0 {
   114  					log.DefaultLogger().Infof(" MOCK HANDLER: mocking %d messages", len(dataMock.Messages))
   115  					ctx.MockVpp.MockReply(dataMock.Messages...)
   116  					return nil, 0, false
   117  				}
   118  				if dataMock.Message == nil {
   119  					return nil, 0, false
   120  				}
   121  				msgID, err := ctx.MockVpp.GetMsgID(dataMock.Message.GetMessageName(), dataMock.Message.GetCrcString())
   122  				Expect(err).To(BeNil())
   123  				reply, err := ctx.MockVpp.ReplyBytes(request, dataMock.Message)
   124  				Expect(err).To(BeNil())
   125  				return reply, msgID, true
   126  			}
   127  		}
   128  
   129  		if strings.HasSuffix(request.MsgName, "_dump") {
   130  			sendControlPing = true
   131  			return nil, 0, false
   132  		}
   133  
   134  		var err error
   135  		replyMsg, id, ok := ctx.MockVpp.ReplyFor("", request.MsgName)
   136  		if ok {
   137  			reply, err = ctx.MockVpp.ReplyBytes(request, replyMsg)
   138  			Expect(err).To(BeNil())
   139  			msgID = id
   140  			prepared = true
   141  		} else {
   142  			log.DefaultLogger().Warnf("NO REPLY FOR %v FOUND", request.MsgName)
   143  		}
   144  
   145  		return reply, msgID, prepared
   146  	})
   147  }
   148  
   149  // MockedChannel implements ChannelIntf for testing purposes
   150  type mockedChannel struct {
   151  	govppapi.Channel
   152  
   153  	// Last message which passed through method SendRequest
   154  	Msg govppapi.Message
   155  
   156  	// List of all messages which passed through method SendRequest
   157  	Msgs []govppapi.Message
   158  
   159  	RetErrs []error
   160  
   161  	notifications chan govppapi.Message
   162  }
   163  
   164  // SendRequest just save input argument to structure field for future check
   165  func (m *mockedChannel) SendRequest(msg govppapi.Message) govppapi.RequestCtx {
   166  	m.Msg = msg
   167  	m.Msgs = append(m.Msgs, msg)
   168  	reqCtx := m.Channel.SendRequest(msg)
   169  	var retErr error
   170  	if retErrsLen := len(m.RetErrs); retErrsLen > 0 {
   171  		retErr = m.RetErrs[retErrsLen-1]
   172  		m.RetErrs = m.RetErrs[:retErrsLen-1]
   173  	}
   174  	return &mockedContext{reqCtx, retErr}
   175  }
   176  
   177  // SendMultiRequest just save input argument to structure field for future check
   178  func (m *mockedChannel) SendMultiRequest(msg govppapi.Message) govppapi.MultiRequestCtx {
   179  	m.Msg = msg
   180  	m.Msgs = append(m.Msgs, msg)
   181  	return m.Channel.SendMultiRequest(msg)
   182  }
   183  
   184  func (m *mockedChannel) SubscribeNotification(notifChan chan govppapi.Message, event govppapi.Message) (govppapi.SubscriptionCtx, error) {
   185  	m.notifications = notifChan
   186  	return &mockSubscription{}, nil
   187  }
   188  
   189  func (m *mockedChannel) GetChannel() chan govppapi.Message {
   190  	return m.notifications
   191  }
   192  
   193  type mockSubscription struct{}
   194  
   195  func (s *mockSubscription) Unsubscribe() error {
   196  	return nil
   197  }
   198  
   199  type mockedContext struct {
   200  	requestCtx govppapi.RequestCtx
   201  	retErr     error
   202  }
   203  
   204  // ReceiveReply returns prepared error or nil
   205  func (m *mockedContext) ReceiveReply(msg govppapi.Message) error {
   206  	if m.retErr != nil {
   207  		return m.retErr
   208  	}
   209  	return m.requestCtx.ReceiveReply(msg)
   210  }
   211  
   212  type mockStream struct {
   213  	mockVPPClient *mockVPPClient
   214  	stream        govppapi.Stream
   215  }
   216  
   217  func (m *mockStream) Context() context.Context {
   218  	return m.stream.Context()
   219  }
   220  
   221  func (m *mockStream) SendMsg(msg govppapi.Message) error {
   222  	m.mockVPPClient.mockedChannel.Msg = msg
   223  	m.mockVPPClient.mockedChannel.Msgs = append(m.mockVPPClient.mockedChannel.Msgs, msg)
   224  	if retErrsLen := len(m.mockVPPClient.mockedChannel.RetErrs); retErrsLen > 0 {
   225  		retErr := m.mockVPPClient.mockedChannel.RetErrs[retErrsLen-1]
   226  		m.mockVPPClient.mockedChannel.RetErrs = m.mockVPPClient.mockedChannel.RetErrs[:retErrsLen-1]
   227  		return retErr
   228  	}
   229  	return m.stream.SendMsg(msg)
   230  }
   231  
   232  func (m *mockStream) RecvMsg() (govppapi.Message, error) {
   233  	return m.stream.RecvMsg()
   234  }
   235  
   236  func (m *mockStream) Close() error {
   237  	return m.stream.Close()
   238  }
   239  
   240  type mockVPPClient struct {
   241  	ctx  *TestCtx
   242  	conn *core.Connection
   243  	*mockedChannel
   244  	version         vpp.Version
   245  	unloadedPlugins map[string]bool
   246  }
   247  
   248  func newMockVPPClient(ctx *TestCtx) *mockVPPClient {
   249  	conn, err := govpp.Connect(ctx.MockVpp)
   250  	Expect(err).ShouldNot(HaveOccurred())
   251  	channel, err := conn.NewAPIChannel()
   252  	// TODO: From GoVPP version 0.7.0 onwards, default reply timeout for GoVPP
   253  	// channel is set to 0. But then some vppcalls tests hang indefinitely. So
   254  	// we set the reply timeout manually to 1 second.
   255  	channel.SetReplyTimeout(time.Second)
   256  	Expect(err).ShouldNot(HaveOccurred())
   257  
   258  	ctx.MockChannel = &mockedChannel{
   259  		Channel: channel,
   260  	}
   261  	return &mockVPPClient{
   262  		ctx:             ctx,
   263  		conn:            conn,
   264  		mockedChannel:   ctx.MockChannel,
   265  		unloadedPlugins: map[string]bool{},
   266  	}
   267  }
   268  
   269  func (m *mockVPPClient) NewStream(ctx context.Context, options ...govppapi.StreamOption) (govppapi.Stream, error) {
   270  	stream, err := m.conn.NewStream(ctx, options...)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	return &mockStream{
   275  		mockVPPClient: m,
   276  		stream:        stream,
   277  	}, nil
   278  }
   279  
   280  func (m *mockVPPClient) Invoke(ctx context.Context, req govppapi.Message, reply govppapi.Message) error {
   281  	m.Msg = req
   282  	m.Msgs = append(m.Msgs, req)
   283  	return m.conn.Invoke(ctx, req, reply)
   284  }
   285  
   286  func (m *mockVPPClient) WatchEvent(ctx context.Context, event govppapi.Message) (govppapi.Watcher, error) {
   287  	return m.conn.WatchEvent(ctx, event)
   288  }
   289  
   290  func (m *mockVPPClient) Version() vpp.Version {
   291  	return m.version
   292  }
   293  
   294  func (m *mockVPPClient) NewAPIChannel() (govppapi.Channel, error) {
   295  	return m.mockedChannel, nil
   296  }
   297  
   298  func (m *mockVPPClient) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (govppapi.Channel, error) {
   299  	return m.mockedChannel, nil
   300  }
   301  
   302  /*func (m *mockVPPClient) CheckCompatiblity(msgs ...govppapi.Message) error {
   303  	return m.mockedChannel.CheckCompatiblity(msgs...)
   304  }*/
   305  
   306  func (m *mockVPPClient) IsPluginLoaded(plugin string) bool {
   307  	return !m.unloadedPlugins[plugin]
   308  }
   309  
   310  func (m *mockVPPClient) BinapiVersion() vpp.Version {
   311  	return ""
   312  }
   313  
   314  func (m *mockVPPClient) Stats() govppapi.StatsProvider {
   315  	panic("implement me")
   316  }
   317  
   318  func (m *mockVPPClient) OnReconnect(h func()) {
   319  	panic("implement me")
   320  }