mock adapter: Group all replies for one request under one call to MockReply
[govpp.git] / api / api_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 api_test
16
17 import (
18         "testing"
19         "time"
20
21         "git.fd.io/govpp.git/adapter/mock"
22         "git.fd.io/govpp.git/api"
23         "git.fd.io/govpp.git/core"
24         "git.fd.io/govpp.git/core/bin_api/vpe"
25         "git.fd.io/govpp.git/examples/bin_api/interfaces"
26         "git.fd.io/govpp.git/examples/bin_api/memif"
27         "git.fd.io/govpp.git/examples/bin_api/tap"
28
29         . "github.com/onsi/gomega"
30 )
31
32 type testCtx struct {
33         mockVpp *mock.VppAdapter
34         conn    *core.Connection
35         ch      *api.Channel
36 }
37
38 func setupTest(t *testing.T) *testCtx {
39         RegisterTestingT(t)
40
41         ctx := &testCtx{
42                 mockVpp: &mock.VppAdapter{},
43         }
44
45         var err error
46         ctx.conn, err = core.Connect(ctx.mockVpp)
47         Expect(err).ShouldNot(HaveOccurred())
48
49         ctx.ch, err = ctx.conn.NewAPIChannel()
50         Expect(err).ShouldNot(HaveOccurred())
51
52         return ctx
53 }
54
55 func (ctx *testCtx) teardownTest() {
56         ctx.ch.Close()
57         ctx.conn.Disconnect()
58 }
59
60 func TestRequestReplyTapConnect(t *testing.T) {
61         ctx := setupTest(t)
62         defer ctx.teardownTest()
63
64         ctx.mockVpp.MockReply(&tap.TapConnectReply{
65                 Retval:    10,
66                 SwIfIndex: 1,
67         })
68         request := &tap.TapConnect{
69                 TapName:      []byte("test-tap-name"),
70                 UseRandomMac: 1,
71         }
72         reply := &tap.TapConnectReply{}
73
74         err := ctx.ch.SendRequest(request).ReceiveReply(reply)
75         Expect(err).ShouldNot(HaveOccurred())
76         Expect(reply.Retval).To(BeEquivalentTo(10), "Incorrect retval value for TapConnectReply")
77         Expect(reply.SwIfIndex).To(BeEquivalentTo(1), "Incorrect SwIfIndex value for TapConnectReply")
78 }
79
80 func TestRequestReplyTapModify(t *testing.T) {
81         ctx := setupTest(t)
82         defer ctx.teardownTest()
83
84         ctx.mockVpp.MockReply(&tap.TapModifyReply{
85                 Retval:    15,
86                 SwIfIndex: 2,
87         })
88         request := &tap.TapModify{
89                 TapName:           []byte("test-tap-modify"),
90                 UseRandomMac:      1,
91                 CustomDevInstance: 1,
92         }
93         reply := &tap.TapModifyReply{}
94
95         err := ctx.ch.SendRequest(request).ReceiveReply(reply)
96         Expect(err).ShouldNot(HaveOccurred())
97         Expect(reply.Retval).To(BeEquivalentTo(15), "Incorrect retval value for TapModifyReply")
98         Expect(reply.SwIfIndex).To(BeEquivalentTo(2), "Incorrect SwIfIndex value for TapModifyReply")
99 }
100
101 func TestRequestReplyTapDelete(t *testing.T) {
102         ctx := setupTest(t)
103         defer ctx.teardownTest()
104
105         ctx.mockVpp.MockReply(&tap.TapDeleteReply{
106                 Retval: 20,
107         })
108         request := &tap.TapDelete{
109                 SwIfIndex: 3,
110         }
111         reply := &tap.TapDeleteReply{}
112
113         err := ctx.ch.SendRequest(request).ReceiveReply(reply)
114         Expect(err).ShouldNot(HaveOccurred())
115         Expect(reply.Retval).To(BeEquivalentTo(20), "Incorrect retval value for TapDeleteReply")
116 }
117
118 func TestRequestReplySwInterfaceTapDump(t *testing.T) {
119         ctx := setupTest(t)
120         defer ctx.teardownTest()
121
122         byteName := []byte("dev-name-test")
123         ctx.mockVpp.MockReply(&tap.SwInterfaceTapDetails{
124                 SwIfIndex: 25,
125                 DevName:   byteName,
126         })
127         request := &tap.SwInterfaceTapDump{}
128         reply := &tap.SwInterfaceTapDetails{}
129
130         err := ctx.ch.SendRequest(request).ReceiveReply(reply)
131         Expect(err).ShouldNot(HaveOccurred())
132         Expect(reply.SwIfIndex).To(BeEquivalentTo(25), "Incorrect SwIfIndex value for SwInterfaceTapDetails")
133         Expect(reply.DevName).ToNot(BeNil(), "Incorrect DevName value for SwInterfaceTapDetails")
134 }
135
136 func TestRequestReplyMemifCreate(t *testing.T) {
137         ctx := setupTest(t)
138         defer ctx.teardownTest()
139
140         ctx.mockVpp.MockReply(&memif.MemifCreateReply{
141                 Retval:    22,
142                 SwIfIndex: 4,
143         })
144         request := &memif.MemifCreate{
145                 Role:       10,
146                 ID:         12,
147                 RingSize:   8000,
148                 BufferSize: 50,
149         }
150         reply := &memif.MemifCreateReply{}
151
152         err := ctx.ch.SendRequest(request).ReceiveReply(reply)
153         Expect(err).ShouldNot(HaveOccurred())
154         Expect(reply.Retval).To(BeEquivalentTo(22), "Incorrect Retval value for MemifCreate")
155         Expect(reply.SwIfIndex).To(BeEquivalentTo(4), "Incorrect SwIfIndex value for MemifCreate")
156 }
157
158 func TestRequestReplyMemifDelete(t *testing.T) {
159         ctx := setupTest(t)
160         defer ctx.teardownTest()
161
162         ctx.mockVpp.MockReply(&memif.MemifDeleteReply{
163                 Retval: 24,
164         })
165         request := &memif.MemifDelete{
166                 SwIfIndex: 15,
167         }
168         reply := &memif.MemifDeleteReply{}
169
170         err := ctx.ch.SendRequest(request).ReceiveReply(reply)
171         Expect(err).ShouldNot(HaveOccurred())
172         Expect(reply.Retval).To(BeEquivalentTo(24), "Incorrect Retval value for MemifDelete")
173 }
174
175 func TestRequestReplyMemifDetails(t *testing.T) {
176         ctx := setupTest(t)
177         defer ctx.teardownTest()
178
179         ctx.mockVpp.MockReply(&memif.MemifDetails{
180                 SwIfIndex: 25,
181                 IfName:    []byte("memif-name"),
182                 Role:      0,
183         })
184         request := &memif.MemifDump{}
185         reply := &memif.MemifDetails{}
186
187         err := ctx.ch.SendRequest(request).ReceiveReply(reply)
188         Expect(err).ShouldNot(HaveOccurred())
189         Expect(reply.SwIfIndex).To(BeEquivalentTo(25), "Incorrect SwIfIndex value for MemifDetails")
190         Expect(reply.IfName).ToNot(BeEmpty(), "MemifDetails IfName is empty byte array")
191         Expect(reply.Role).To(BeEquivalentTo(0), "Incorrect Role value for MemifDetails")
192 }
193
194 func TestMultiRequestReplySwInterfaceTapDump(t *testing.T) {
195         ctx := setupTest(t)
196         defer ctx.teardownTest()
197
198         // mock reply
199         msgs := []api.Message{}
200         for i := 1; i <= 10; i++ {
201                 msgs = append(msgs, &tap.SwInterfaceTapDetails{
202                         SwIfIndex: uint32(i),
203                         DevName:   []byte("dev-name-test"),
204                 })
205         }
206         ctx.mockVpp.MockReply(msgs...)
207         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
208
209         reqCtx := ctx.ch.SendMultiRequest(&tap.SwInterfaceTapDump{})
210         cnt := 0
211         for {
212                 msg := &tap.SwInterfaceTapDetails{}
213                 stop, err := reqCtx.ReceiveReply(msg)
214                 if stop {
215                         break // break out of the loop
216                 }
217                 Expect(err).ShouldNot(HaveOccurred())
218                 cnt++
219         }
220         Expect(cnt).To(BeEquivalentTo(10))
221 }
222
223 func TestMultiRequestReplySwInterfaceMemifDump(t *testing.T) {
224         ctx := setupTest(t)
225         defer ctx.teardownTest()
226
227         // mock reply
228         msgs := []api.Message{}
229         for i := 1; i <= 10; i++ {
230                 msgs = append(msgs, &memif.MemifDetails{
231                         SwIfIndex: uint32(i),
232                 })
233         }
234         ctx.mockVpp.MockReply(msgs...)
235         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
236
237         reqCtx := ctx.ch.SendMultiRequest(&memif.MemifDump{})
238         cnt := 0
239         for {
240                 msg := &memif.MemifDetails{}
241                 stop, err := reqCtx.ReceiveReply(msg)
242                 if stop {
243                         break // break out of the loop
244                 }
245                 Expect(err).ShouldNot(HaveOccurred())
246                 cnt++
247         }
248         Expect(cnt).To(BeEquivalentTo(10))
249 }
250
251 func TestNotifications(t *testing.T) {
252         ctx := setupTest(t)
253         defer ctx.teardownTest()
254
255         // subscribe for notification
256         notifChan := make(chan api.Message, 1)
257         subs, err := ctx.ch.SubscribeNotification(notifChan, interfaces.NewSwInterfaceSetFlags)
258         Expect(err).ShouldNot(HaveOccurred())
259
260         // mock the notification and force its delivery
261         ctx.mockVpp.MockReply(&interfaces.SwInterfaceSetFlags{
262                 SwIfIndex:   3,
263                 AdminUpDown: 1,
264         })
265         ctx.mockVpp.SendMsg(0, []byte(""))
266
267         // receive the notification
268         var notif *interfaces.SwInterfaceSetFlags
269         Eventually(func() *interfaces.SwInterfaceSetFlags {
270                 select {
271                 case n := <-notifChan:
272                         notif = n.(*interfaces.SwInterfaceSetFlags)
273                         return notif
274                 default:
275                         return nil
276                 }
277         }).ShouldNot(BeNil())
278
279         // verify the received notifications
280         Expect(notif.SwIfIndex).To(BeEquivalentTo(3), "Incorrect SwIfIndex value for SwInterfaceSetFlags")
281         Expect(notif.AdminUpDown).To(BeEquivalentTo(1), "Incorrect AdminUpDown value for SwInterfaceSetFlags")
282
283         ctx.ch.UnsubscribeNotification(subs)
284 }
285
286 func TestNotificationEvent(t *testing.T) {
287         ctx := setupTest(t)
288         defer ctx.teardownTest()
289
290         // subscribe for notification
291         notifChan := make(chan api.Message, 1)
292         subs, err := ctx.ch.SubscribeNotification(notifChan, interfaces.NewSwInterfaceEvent)
293         Expect(err).ShouldNot(HaveOccurred())
294
295         // mock the notification and force its delivery
296         ctx.mockVpp.MockReply(&interfaces.SwInterfaceEvent{
297                 SwIfIndex:  2,
298                 LinkUpDown: 1,
299         })
300         ctx.mockVpp.SendMsg(0, []byte(""))
301
302         // receive the notification
303         var notif *interfaces.SwInterfaceEvent
304         Eventually(func() *interfaces.SwInterfaceEvent {
305                 select {
306                 case n := <-notifChan:
307                         notif = n.(*interfaces.SwInterfaceEvent)
308                         return notif
309                 default:
310                         return nil
311                 }
312         }).ShouldNot(BeNil())
313
314         // verify the received notifications
315         Expect(notif.SwIfIndex).To(BeEquivalentTo(2), "Incorrect SwIfIndex value for SwInterfaceSetFlags")
316         Expect(notif.LinkUpDown).To(BeEquivalentTo(1), "Incorrect LinkUpDown value for SwInterfaceSetFlags")
317
318         ctx.ch.UnsubscribeNotification(subs)
319 }
320
321 func TestCheckMessageCompatibility(t *testing.T) {
322         ctx := setupTest(t)
323         defer ctx.teardownTest()
324
325         err := ctx.ch.CheckMessageCompatibility(&interfaces.SwInterfaceSetFlags{})
326         Expect(err).ShouldNot(HaveOccurred())
327 }
328 func TestSetReplyTimeout(t *testing.T) {
329         ctx := setupTest(t)
330         defer ctx.teardownTest()
331
332         ctx.ch.SetReplyTimeout(time.Millisecond)
333
334         // first one request should work
335         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
336         err := ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
337         Expect(err).ShouldNot(HaveOccurred())
338
339         // no other reply ready - expect timeout
340         err = ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
341         Expect(err).Should(HaveOccurred())
342         Expect(err.Error()).To(ContainSubstring("timeout"))
343 }
344
345 func TestSetReplyTimeoutMultiRequest(t *testing.T) {
346         ctx := setupTest(t)
347         defer ctx.teardownTest()
348
349         ctx.ch.SetReplyTimeout(time.Millisecond)
350
351         msgs := []api.Message{}
352         for i := 1; i <= 3; i++ {
353                 msgs = append(msgs, &interfaces.SwInterfaceDetails{
354                         SwIfIndex:     uint32(i),
355                         InterfaceName: []byte("if-name-test"),
356                 })
357         }
358         ctx.mockVpp.MockReply(msgs...)
359         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
360
361         cnt := 0
362         sendMultiRequest := func() error {
363                 reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
364                 for {
365                         msg := &interfaces.SwInterfaceDetails{}
366                         stop, err := reqCtx.ReceiveReply(msg)
367                         if stop {
368                                 break // break out of the loop
369                         }
370                         if err != nil {
371                                 return err
372                         }
373                         cnt++
374                 }
375                 return nil
376         }
377
378         // first one request should work
379         err := sendMultiRequest()
380         Expect(err).ShouldNot(HaveOccurred())
381
382         // no other reply ready - expect timeout
383         err = sendMultiRequest()
384         Expect(err).Should(HaveOccurred())
385         Expect(err.Error()).To(ContainSubstring("timeout"))
386
387         Expect(cnt).To(BeEquivalentTo(3))
388 }
389
390 func TestReceiveReplyNegative(t *testing.T) {
391         ctx := setupTest(t)
392         defer ctx.teardownTest()
393
394         // invalid context 1
395         reqCtx1 := &api.RequestCtx{}
396         err := reqCtx1.ReceiveReply(&vpe.ControlPingReply{})
397         Expect(err).Should(HaveOccurred())
398         Expect(err.Error()).To(ContainSubstring("invalid request context"))
399
400         // invalid context 2
401         reqCtx2 := &api.MultiRequestCtx{}
402         _, err = reqCtx2.ReceiveReply(&vpe.ControlPingReply{})
403         Expect(err).Should(HaveOccurred())
404         Expect(err.Error()).To(ContainSubstring("invalid request context"))
405
406         // NU
407         reqCtx3 := &api.RequestCtx{}
408         err = reqCtx3.ReceiveReply(nil)
409         Expect(err).Should(HaveOccurred())
410         Expect(err.Error()).To(ContainSubstring("invalid request context"))
411 }
412
413 func TestMultiRequestDouble(t *testing.T) {
414         ctx := setupTest(t)
415         defer ctx.teardownTest()
416
417         // mock reply
418         msgs := []mock.MsgWithContext{}
419         for i := 1; i <= 3; i++ {
420                 msgs = append(msgs, mock.MsgWithContext{
421                         Msg: &interfaces.SwInterfaceDetails{
422                                 SwIfIndex:     uint32(i),
423                                 InterfaceName: []byte("if-name-test"),
424                         },
425                         Multipart: true,
426                         SeqNum:    1,
427                 })
428         }
429         msgs = append(msgs, mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, Multipart: true, SeqNum: 1})
430
431         for i := 1; i <= 3; i++ {
432                 msgs = append(msgs,
433                         mock.MsgWithContext{
434                                 Msg: &interfaces.SwInterfaceDetails{
435                                         SwIfIndex:     uint32(i),
436                                         InterfaceName: []byte("if-name-test"),
437                                 },
438                                 Multipart: true,
439                                 SeqNum:    2,
440                         })
441         }
442         msgs = append(msgs, mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, Multipart: true, SeqNum: 2})
443
444         ctx.mockVpp.MockReplyWithContext(msgs...)
445
446         cnt := 0
447         var sendMultiRequest = func() error {
448                 reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
449                 for {
450                         msg := &interfaces.SwInterfaceDetails{}
451                         stop, err := reqCtx.ReceiveReply(msg)
452                         if stop {
453                                 break // break out of the loop
454                         }
455                         if err != nil {
456                                 return err
457                         }
458                         cnt++
459                 }
460                 return nil
461         }
462
463         err := sendMultiRequest()
464         Expect(err).ShouldNot(HaveOccurred())
465
466         err = sendMultiRequest()
467         Expect(err).ShouldNot(HaveOccurred())
468
469         Expect(cnt).To(BeEquivalentTo(6))
470 }
471
472 func TestReceiveReplyAfterTimeout(t *testing.T) {
473         ctx := setupTest(t)
474         defer ctx.teardownTest()
475
476         ctx.ch.SetReplyTimeout(time.Millisecond)
477
478         // first one request should work
479         ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, SeqNum: 1})
480         err := ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
481         Expect(err).ShouldNot(HaveOccurred())
482
483         err = ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
484         Expect(err).Should(HaveOccurred())
485         Expect(err.Error()).To(ContainSubstring("timeout"))
486
487         ctx.mockVpp.MockReplyWithContext(
488                 // simulating late reply
489                 mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, SeqNum: 2},
490                 // normal reply for next request
491                 mock.MsgWithContext{Msg: &tap.TapConnectReply{}, SeqNum: 3})
492
493         req := &tap.TapConnect{
494                 TapName:      []byte("test-tap-name"),
495                 UseRandomMac: 1,
496         }
497         reply := &tap.TapConnectReply{}
498
499         // should succeed
500         err = ctx.ch.SendRequest(req).ReceiveReply(reply)
501         Expect(err).ShouldNot(HaveOccurred())
502 }
503
504 func TestReceiveReplyAfterTimeoutMultiRequest(t *testing.T) {
505         /*
506                 TODO: fix mock adapter
507                 This test will fail because mock adapter will stop sending replies
508                 when it encounters control_ping_reply from multi request,
509                 thus never sending reply for next request
510         */
511         t.Skip()
512
513         ctx := setupTest(t)
514         defer ctx.teardownTest()
515
516         ctx.ch.SetReplyTimeout(time.Millisecond * 100)
517
518         // first one request should work
519         ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, SeqNum: 1})
520         err := ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
521         Expect(err).ShouldNot(HaveOccurred())
522
523         cnt := 0
524         var sendMultiRequest = func() error {
525                 reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
526                 for {
527                         msg := &interfaces.SwInterfaceDetails{}
528                         stop, err := reqCtx.ReceiveReply(msg)
529                         if stop {
530                                 break // break out of the loop
531                         }
532                         if err != nil {
533                                 return err
534                         }
535                         cnt++
536                 }
537                 return nil
538         }
539
540         err = sendMultiRequest()
541         Expect(err).Should(HaveOccurred())
542         Expect(err.Error()).To(ContainSubstring("timeout"))
543         Expect(cnt).To(BeEquivalentTo(0))
544
545         // simulating late replies
546         msgs := []mock.MsgWithContext{}
547         for i := 1; i <= 3; i++ {
548                 msgs = append(msgs, mock.MsgWithContext{
549                         Msg: &interfaces.SwInterfaceDetails{
550                                 SwIfIndex:     uint32(i),
551                                 InterfaceName: []byte("if-name-test"),
552                         },
553                         Multipart: true,
554                         SeqNum:    2,
555                 })
556         }
557         msgs = append(msgs, mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, Multipart: true, SeqNum: 2})
558         ctx.mockVpp.MockReplyWithContext(msgs...)
559
560         // normal reply for next request
561         ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{Msg: &tap.TapConnectReply{}, SeqNum: 3})
562
563         req := &tap.TapConnect{
564                 TapName:      []byte("test-tap-name"),
565                 UseRandomMac: 1,
566         }
567         reply := &tap.TapConnectReply{}
568
569         // should succeed
570         err = ctx.ch.SendRequest(req).ReceiveReply(reply)
571         Expect(err).ShouldNot(HaveOccurred())
572 }
573
574 func TestInvalidMessageID(t *testing.T) {
575         ctx := setupTest(t)
576         defer ctx.teardownTest()
577
578         // first one request should work
579         ctx.mockVpp.MockReply(&vpe.ShowVersionReply{})
580         err := ctx.ch.SendRequest(&vpe.ShowVersion{}).ReceiveReply(&vpe.ShowVersionReply{})
581         Expect(err).ShouldNot(HaveOccurred())
582
583         // second should fail with error invalid message ID
584         ctx.mockVpp.MockReply(&vpe.ShowVersionReply{})
585         err = ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
586         Expect(err).Should(HaveOccurred())
587         Expect(err.Error()).To(ContainSubstring("invalid message ID"))
588 }