mock adapter: Group all replies for one request under one call to MockReply
[govpp.git] / core / core_test.go
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 core_test
16
17 import (
18         "testing"
19
20         "git.fd.io/govpp.git/adapter/mock"
21         "git.fd.io/govpp.git/api"
22         "git.fd.io/govpp.git/core"
23         "git.fd.io/govpp.git/core/bin_api/vpe"
24         "git.fd.io/govpp.git/examples/bin_api/interfaces"
25         "git.fd.io/govpp.git/examples/bin_api/stats"
26
27         . "github.com/onsi/gomega"
28 )
29
30 type testCtx struct {
31         mockVpp *mock.VppAdapter
32         conn    *core.Connection
33         ch      *api.Channel
34 }
35
36 func setupTest(t *testing.T, bufferedChan bool) *testCtx {
37         RegisterTestingT(t)
38
39         ctx := &testCtx{}
40         ctx.mockVpp = &mock.VppAdapter{}
41
42         var err error
43         ctx.conn, err = core.Connect(ctx.mockVpp)
44         Expect(err).ShouldNot(HaveOccurred())
45
46         if bufferedChan {
47                 ctx.ch, err = ctx.conn.NewAPIChannelBuffered(100, 100)
48         } else {
49                 ctx.ch, err = ctx.conn.NewAPIChannel()
50         }
51         Expect(err).ShouldNot(HaveOccurred())
52
53         return ctx
54 }
55
56 func (ctx *testCtx) teardownTest() {
57         ctx.ch.Close()
58         ctx.conn.Disconnect()
59 }
60
61 func TestSimpleRequest(t *testing.T) {
62         ctx := setupTest(t, false)
63         defer ctx.teardownTest()
64
65         ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: -5})
66
67         req := &vpe.ControlPing{}
68         reply := &vpe.ControlPingReply{}
69
70         // send the request and receive a reply
71         ctx.ch.ReqChan <- &api.VppRequest{Message: req}
72         vppReply := <-ctx.ch.ReplyChan
73
74         Expect(vppReply).ShouldNot(BeNil())
75         Expect(vppReply.Error).ShouldNot(HaveOccurred())
76
77         // decode the message
78         err := ctx.ch.MsgDecoder.DecodeMsg(vppReply.Data, reply)
79         Expect(err).ShouldNot(HaveOccurred())
80
81         Expect(reply.Retval).To(BeEquivalentTo(-5))
82 }
83
84 func TestMultiRequest(t *testing.T) {
85         ctx := setupTest(t, false)
86         defer ctx.teardownTest()
87
88         msgs := []api.Message{}
89         for m := 0; m < 10; m++ {
90                 msgs = append(msgs, &interfaces.SwInterfaceDetails{})
91         }
92         ctx.mockVpp.MockReply(msgs...)
93         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
94
95         // send multipart request
96         ctx.ch.ReqChan <- &api.VppRequest{Message: &interfaces.SwInterfaceDump{}, Multipart: true}
97
98         cnt := 0
99         for {
100                 // receive a reply
101                 vppReply := <-ctx.ch.ReplyChan
102                 if vppReply.LastReplyReceived {
103                         break // break out of the loop
104                 }
105                 Expect(vppReply.Error).ShouldNot(HaveOccurred())
106
107                 // decode the message
108                 reply := &interfaces.SwInterfaceDetails{}
109                 err := ctx.ch.MsgDecoder.DecodeMsg(vppReply.Data, reply)
110                 Expect(err).ShouldNot(HaveOccurred())
111                 cnt++
112         }
113
114         Expect(cnt).To(BeEquivalentTo(10))
115 }
116
117 func TestNotifications(t *testing.T) {
118         ctx := setupTest(t, false)
119         defer ctx.teardownTest()
120
121         // subscribe for notification
122         notifChan := make(chan api.Message, 1)
123         subscription := &api.NotifSubscription{
124                 NotifChan:  notifChan,
125                 MsgFactory: interfaces.NewSwInterfaceSetFlags,
126         }
127         ctx.ch.NotifSubsChan <- &api.NotifSubscribeRequest{
128                 Subscription: subscription,
129                 Subscribe:    true,
130         }
131         err := <-ctx.ch.NotifSubsReplyChan
132         Expect(err).ShouldNot(HaveOccurred())
133
134         // mock the notification and force its delivery
135         ctx.mockVpp.MockReply(&interfaces.SwInterfaceSetFlags{
136                 SwIfIndex:   3,
137                 AdminUpDown: 1,
138         })
139         ctx.mockVpp.SendMsg(0, []byte{0})
140
141         // receive the notification
142         notif := (<-notifChan).(*interfaces.SwInterfaceSetFlags)
143
144         Expect(notif.SwIfIndex).To(BeEquivalentTo(3))
145
146         // unsubscribe notification
147         ctx.ch.NotifSubsChan <- &api.NotifSubscribeRequest{
148                 Subscription: subscription,
149                 Subscribe:    false,
150         }
151         err = <-ctx.ch.NotifSubsReplyChan
152         Expect(err).ShouldNot(HaveOccurred())
153 }
154
155 func TestNilConnection(t *testing.T) {
156         RegisterTestingT(t)
157         var conn *core.Connection
158
159         ch, err := conn.NewAPIChannel()
160         Expect(ch).Should(BeNil())
161         Expect(err).Should(HaveOccurred())
162         Expect(err.Error()).To(ContainSubstring("nil"))
163
164         ch, err = conn.NewAPIChannelBuffered(1, 1)
165         Expect(ch).Should(BeNil())
166         Expect(err).Should(HaveOccurred())
167         Expect(err.Error()).To(ContainSubstring("nil"))
168 }
169
170 func TestDoubleConnection(t *testing.T) {
171         ctx := setupTest(t, false)
172         defer ctx.teardownTest()
173
174         conn, err := core.Connect(ctx.mockVpp)
175         Expect(err).Should(HaveOccurred())
176         Expect(err.Error()).To(ContainSubstring("only one connection per process"))
177         Expect(conn).Should(BeNil())
178 }
179
180 func TestAsyncConnection(t *testing.T) {
181         ctx := setupTest(t, false)
182         defer ctx.teardownTest()
183
184         ctx.conn.Disconnect()
185         conn, ch, err := core.AsyncConnect(ctx.mockVpp)
186         ctx.conn = conn
187
188         Expect(err).ShouldNot(HaveOccurred())
189         Expect(conn).ShouldNot(BeNil())
190
191         ev := <-ch
192         Expect(ev.State).Should(BeEquivalentTo(core.Connected))
193 }
194
195 func TestFullBuffer(t *testing.T) {
196         ctx := setupTest(t, false)
197         defer ctx.teardownTest()
198
199         // close the default API channel
200         ctx.ch.Close()
201
202         // create a new channel with limited buffer sizes
203         var err error
204         ctx.ch, err = ctx.conn.NewAPIChannelBuffered(10, 1)
205         Expect(err).ShouldNot(HaveOccurred())
206
207         // send multiple requests, only one reply should be read
208         for i := 0; i < 20; i++ {
209                 ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
210                 ctx.ch.ReqChan <- &api.VppRequest{Message: &vpe.ControlPing{}}
211         }
212
213         vppReply := <-ctx.ch.ReplyChan
214         Expect(vppReply).ShouldNot(BeNil())
215
216         var received bool
217         select {
218         case <-ctx.ch.ReplyChan:
219                 received = true // this should not happen
220         default:
221                 received = false // no reply to be received
222         }
223         Expect(received).Should(BeFalse(), "A reply has been recieved, should had been ignored.")
224 }
225
226 func TestCodec(t *testing.T) {
227         RegisterTestingT(t)
228
229         codec := &core.MsgCodec{}
230
231         // request
232         data, err := codec.EncodeMsg(&interfaces.CreateLoopback{MacAddress: []byte{1, 2, 3, 4, 5, 6}}, 11)
233         Expect(err).ShouldNot(HaveOccurred())
234         Expect(data).ShouldNot(BeEmpty())
235
236         msg1 := &interfaces.CreateLoopback{}
237         err = codec.DecodeMsg(data, msg1)
238         Expect(err).ShouldNot(HaveOccurred())
239         Expect(msg1.MacAddress).To(BeEquivalentTo([]byte{1, 2, 3, 4, 5, 6}))
240
241         // reply
242         data, err = codec.EncodeMsg(&vpe.ControlPingReply{Retval: 55}, 22)
243         Expect(err).ShouldNot(HaveOccurred())
244         Expect(data).ShouldNot(BeEmpty())
245
246         msg2 := &vpe.ControlPingReply{}
247         err = codec.DecodeMsg(data, msg2)
248         Expect(err).ShouldNot(HaveOccurred())
249         Expect(msg2.Retval).To(BeEquivalentTo(55))
250
251         // other
252         data, err = codec.EncodeMsg(&stats.VnetIP4FibCounters{VrfID: 77}, 33)
253         Expect(err).ShouldNot(HaveOccurred())
254         Expect(data).ShouldNot(BeEmpty())
255
256         msg3 := &stats.VnetIP4FibCounters{}
257         err = codec.DecodeMsg(data, msg3)
258         Expect(err).ShouldNot(HaveOccurred())
259         Expect(msg3.VrfID).To(BeEquivalentTo(77))
260 }
261
262 func TestCodecNegative(t *testing.T) {
263         RegisterTestingT(t)
264
265         codec := &core.MsgCodec{}
266
267         // nil message for encoding
268         data, err := codec.EncodeMsg(nil, 15)
269         Expect(err).Should(HaveOccurred())
270         Expect(err.Error()).To(ContainSubstring("nil message"))
271         Expect(data).Should(BeNil())
272
273         // nil message for decoding
274         err = codec.DecodeMsg(data, nil)
275         Expect(err).Should(HaveOccurred())
276         Expect(err.Error()).To(ContainSubstring("nil message"))
277
278         // nil data for decoding
279         err = codec.DecodeMsg(nil, &vpe.ControlPingReply{})
280         Expect(err).Should(HaveOccurred())
281         Expect(err.Error()).To(ContainSubstring("EOF"))
282 }
283
284 func TestSimpleRequestsWithSequenceNumbers(t *testing.T) {
285         ctx := setupTest(t, false)
286         defer ctx.teardownTest()
287
288         var reqCtx []*api.RequestCtx
289         for i := 0; i < 10; i++ {
290                 ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: int32(i)})
291                 req := &vpe.ControlPing{}
292                 reqCtx = append(reqCtx, ctx.ch.SendRequest(req))
293         }
294
295         for i := 0; i < 10; i++ {
296                 reply := &vpe.ControlPingReply{}
297                 err := reqCtx[i].ReceiveReply(reply)
298                 Expect(err).ShouldNot(HaveOccurred())
299                 Expect(reply.Retval).To(BeEquivalentTo(i))
300         }
301 }
302
303 func TestMultiRequestsWithSequenceNumbers(t *testing.T) {
304         ctx := setupTest(t, false)
305         defer ctx.teardownTest()
306
307         msgs := []api.Message{}
308         for i := 0; i < 10; i++ {
309                 msgs = append(msgs, &interfaces.SwInterfaceDetails{SwIfIndex: uint32(i)})
310         }
311         ctx.mockVpp.MockReply(msgs...)
312         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
313
314         // send multipart request
315         reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
316
317         cnt := 0
318         for {
319                 Expect(cnt < 11).To(BeTrue())
320
321                 // receive a reply
322                 reply := &interfaces.SwInterfaceDetails{}
323                 lastReplyReceived, err := reqCtx.ReceiveReply(reply)
324
325                 if lastReplyReceived {
326                         break // break out of the loop
327                 }
328
329                 Expect(err).ShouldNot(HaveOccurred())
330                 Expect(reply.SwIfIndex).To(BeEquivalentTo(cnt))
331
332                 cnt++
333         }
334
335         Expect(cnt).To(BeEquivalentTo(10))
336 }
337
338 func TestSimpleRequestWithTimeout(t *testing.T) {
339         ctx := setupTest(t, true)
340         defer ctx.teardownTest()
341
342         // reply for a previous timeouted requests to be ignored
343         ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
344                 Msg:    &vpe.ControlPingReply{Retval: 1},
345                 SeqNum: 0,
346         })
347
348         // send reply later
349         req1 := &vpe.ControlPing{}
350         reqCtx1 := ctx.ch.SendRequest(req1)
351
352         reply := &vpe.ControlPingReply{}
353         err := reqCtx1.ReceiveReply(reply)
354         Expect(err).ToNot(BeNil())
355         Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
356
357         ctx.mockVpp.MockReplyWithContext(
358                 // reply for the previous request
359                 mock.MsgWithContext{
360                         Msg:    &vpe.ControlPingReply{Retval: 1},
361                         SeqNum: 1,
362                 },
363                 // reply for the next request
364                 mock.MsgWithContext{
365                         Msg:    &vpe.ControlPingReply{Retval: 2},
366                         SeqNum: 2,
367                 })
368
369         // next request
370         req2 := &vpe.ControlPing{}
371         reqCtx2 := ctx.ch.SendRequest(req2)
372
373         // second request should ignore the first reply and return the second one
374         reply = &vpe.ControlPingReply{}
375         err = reqCtx2.ReceiveReply(reply)
376         Expect(err).To(BeNil())
377         Expect(reply.Retval).To(BeEquivalentTo(2))
378 }
379
380 func TestSimpleRequestsWithMissingReply(t *testing.T) {
381         ctx := setupTest(t, false)
382         defer ctx.teardownTest()
383
384         // request without reply
385         req1 := &vpe.ControlPing{}
386         reqCtx1 := ctx.ch.SendRequest(req1)
387
388         // another request without reply
389         req2 := &vpe.ControlPing{}
390         reqCtx2 := ctx.ch.SendRequest(req2)
391
392         // third request with reply
393         ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
394                 Msg:    &vpe.ControlPingReply{Retval: 3},
395                 SeqNum: 3,
396         })
397         req3 := &vpe.ControlPing{}
398         reqCtx3 := ctx.ch.SendRequest(req3)
399
400         // the first two should fail, but not consume reply for the 3rd
401         reply := &vpe.ControlPingReply{}
402         err := reqCtx1.ReceiveReply(reply)
403         Expect(err).ToNot(BeNil())
404         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
405
406         reply = &vpe.ControlPingReply{}
407         err = reqCtx2.ReceiveReply(reply)
408         Expect(err).ToNot(BeNil())
409         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 2"))
410
411         // the second request should succeed
412         reply = &vpe.ControlPingReply{}
413         err = reqCtx3.ReceiveReply(reply)
414         Expect(err).To(BeNil())
415         Expect(reply.Retval).To(BeEquivalentTo(3))
416 }
417
418 func TestMultiRequestsWithErrors(t *testing.T) {
419         ctx := setupTest(t, false)
420         defer ctx.teardownTest()
421
422         // replies for a previous timeouted requests to be ignored
423         msgs := []mock.MsgWithContext{}
424         msgs = append(msgs,
425                 mock.MsgWithContext{
426                         Msg:    &vpe.ControlPingReply{Retval: 1},
427                         SeqNum: 0xffff - 1,
428                 },
429                 mock.MsgWithContext{
430                         Msg:    &vpe.ControlPingReply{Retval: 1},
431                         SeqNum: 0xffff,
432                 },
433                 mock.MsgWithContext{
434                         Msg:    &vpe.ControlPingReply{Retval: 1},
435                         SeqNum: 0,
436                 })
437
438         for i := 0; i < 10; i++ {
439                 msgs = append(msgs,
440                         mock.MsgWithContext{
441                                 Msg:       &interfaces.SwInterfaceDetails{SwIfIndex: uint32(i)},
442                                 SeqNum:    1,
443                                 Multipart: true,
444                         })
445         }
446         // missing finalizing control ping
447
448         // reply for a next request
449         msgs = append(msgs,
450                 mock.MsgWithContext{
451                         Msg:       &vpe.ControlPingReply{Retval: 2},
452                         SeqNum:    2,
453                         Multipart: false,
454                 })
455
456         // queue replies
457         ctx.mockVpp.MockReplyWithContext(msgs...)
458
459         // send multipart request
460         reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
461
462         for i := 0; i < 10; i++ {
463                 // receive multi-part replies
464                 reply := &interfaces.SwInterfaceDetails{}
465                 lastReplyReceived, err := reqCtx.ReceiveReply(reply)
466
467                 Expect(lastReplyReceived).To(BeFalse())
468                 Expect(err).ShouldNot(HaveOccurred())
469                 Expect(reply.SwIfIndex).To(BeEquivalentTo(i))
470         }
471
472         // missing closing control ping
473         reply := &interfaces.SwInterfaceDetails{}
474         _, err := reqCtx.ReceiveReply(reply)
475         Expect(err).ToNot(BeNil())
476         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
477
478         // try again - still fails and nothing consumed
479         _, err = reqCtx.ReceiveReply(reply)
480         Expect(err).ToNot(BeNil())
481         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
482
483         // reply for the second request has not been consumed
484         reqCtx2 := ctx.ch.SendRequest(&vpe.ControlPing{})
485         reply2 := &vpe.ControlPingReply{}
486         err = reqCtx2.ReceiveReply(reply2)
487         Expect(err).To(BeNil())
488         Expect(reply2.Retval).To(BeEquivalentTo(2))
489 }
490
491 func TestRequestsOrdering(t *testing.T) {
492         ctx := setupTest(t, false)
493         defer ctx.teardownTest()
494
495         // the orderings of SendRequest and ReceiveReply calls should match, otherwise
496         // some replies will get thrown away
497
498         // first request
499         ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: 1})
500         req1 := &vpe.ControlPing{}
501         reqCtx1 := ctx.ch.SendRequest(req1)
502
503         // second request
504         ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: 2})
505         req2 := &vpe.ControlPing{}
506         reqCtx2 := ctx.ch.SendRequest(req2)
507
508         // if reply for the second request is read first, the reply for the first
509         // request gets thrown away.
510         reply2 := &vpe.ControlPingReply{}
511         err := reqCtx2.ReceiveReply(reply2)
512         Expect(err).To(BeNil())
513         Expect(reply2.Retval).To(BeEquivalentTo(2))
514
515         // first request has already been considered closed
516         reply1 := &vpe.ControlPingReply{}
517         err = reqCtx1.ReceiveReply(reply1)
518         Expect(err).ToNot(BeNil())
519         Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
520 }
521
522 func TestCycleOverSetOfSequenceNumbers(t *testing.T) {
523         ctx := setupTest(t, true)
524         defer ctx.teardownTest()
525
526         numIters := 0xffff + 100
527         reqCtx := make(map[int]*api.RequestCtx)
528
529         for i := 0; i < numIters+30; /* receiver is 30 reqs behind */ i++ {
530                 if i < numIters {
531                         ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: int32(i)})
532                         req := &vpe.ControlPing{}
533                         reqCtx[i] = ctx.ch.SendRequest(req)
534                 }
535                 if i > 30 {
536                         reply := &vpe.ControlPingReply{}
537                         err := reqCtx[i-30].ReceiveReply(reply)
538                         Expect(err).ShouldNot(HaveOccurred())
539                         Expect(reply.Retval).To(BeEquivalentTo(i - 30))
540                 }
541         }
542 }