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 }