Introduce Stream - experimental API for low-level access to VPP API
[govpp.git] / core / connection_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         . "github.com/onsi/gomega"
21
22         "git.fd.io/govpp.git/adapter/mock"
23         "git.fd.io/govpp.git/api"
24         "git.fd.io/govpp.git/codec"
25         "git.fd.io/govpp.git/core"
26         "git.fd.io/govpp.git/examples/binapi/interfaces"
27         "git.fd.io/govpp.git/examples/binapi/vpe"
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                 mockVpp: mock.NewVppAdapter(),
41         }
42
43         var err error
44         ctx.conn, err = core.Connect(ctx.mockVpp)
45         Expect(err).ShouldNot(HaveOccurred())
46
47         if bufferedChan {
48                 ctx.ch, err = ctx.conn.NewAPIChannelBuffered(100, 100)
49         } else {
50                 ctx.ch, err = ctx.conn.NewAPIChannel()
51         }
52         Expect(err).ShouldNot(HaveOccurred())
53
54         return ctx
55 }
56
57 func (ctx *testCtx) teardownTest() {
58         ctx.ch.Close()
59         ctx.conn.Disconnect()
60 }
61
62 func TestNilConnection(t *testing.T) {
63         RegisterTestingT(t)
64         var conn *core.Connection
65
66         ch, err := conn.NewAPIChannel()
67         Expect(ch).Should(BeNil())
68         Expect(err).Should(HaveOccurred())
69         Expect(err.Error()).To(ContainSubstring("nil"))
70
71         ch, err = conn.NewAPIChannelBuffered(1, 1)
72         Expect(ch).Should(BeNil())
73         Expect(err).Should(HaveOccurred())
74         Expect(err.Error()).To(ContainSubstring("nil"))
75 }
76
77 func TestAsyncConnection(t *testing.T) {
78         ctx := setupTest(t, false)
79         defer ctx.teardownTest()
80
81         ctx.conn.Disconnect()
82         conn, statusChan, err := core.AsyncConnect(ctx.mockVpp, core.DefaultMaxReconnectAttempts, core.DefaultReconnectInterval)
83         ctx.conn = conn
84
85         Expect(err).ShouldNot(HaveOccurred())
86         Expect(conn).ShouldNot(BeNil())
87
88         ev := <-statusChan
89         Expect(ev.State).Should(BeEquivalentTo(core.Connected))
90 }
91
92 func TestCodec(t *testing.T) {
93         RegisterTestingT(t)
94
95         var msgCodec = codec.DefaultCodec
96
97         // request
98         data, err := msgCodec.EncodeMsg(&interfaces.CreateLoopback{MacAddress: interfaces.MacAddress{1, 2, 3, 4, 5, 6}}, 11)
99         Expect(err).ShouldNot(HaveOccurred())
100         Expect(data).ShouldNot(BeEmpty())
101
102         msg1 := &interfaces.CreateLoopback{}
103         err = msgCodec.DecodeMsg(data, msg1)
104         Expect(err).ShouldNot(HaveOccurred())
105         Expect(msg1.MacAddress).To(BeEquivalentTo(interfaces.MacAddress{1, 2, 3, 4, 5, 6}))
106
107         // reply
108         data, err = msgCodec.EncodeMsg(&vpe.ControlPingReply{Retval: 55}, 22)
109         Expect(err).ShouldNot(HaveOccurred())
110         Expect(data).ShouldNot(BeEmpty())
111
112         msg2 := &vpe.ControlPingReply{}
113         err = msgCodec.DecodeMsg(data, msg2)
114         Expect(err).ShouldNot(HaveOccurred())
115         Expect(msg2.Retval).To(BeEquivalentTo(55))
116 }
117
118 func TestCodecNegative(t *testing.T) {
119         RegisterTestingT(t)
120
121         var msgCodec = codec.DefaultCodec
122
123         // nil message for encoding
124         data, err := msgCodec.EncodeMsg(nil, 15)
125         Expect(err).Should(HaveOccurred())
126         Expect(err.Error()).To(ContainSubstring("nil message"))
127         Expect(data).Should(BeNil())
128
129         // nil message for decoding
130         err = msgCodec.DecodeMsg(data, nil)
131         Expect(err).Should(HaveOccurred())
132         Expect(err.Error()).To(ContainSubstring("nil message"))
133
134         // nil data for decoding
135         err = msgCodec.DecodeMsg(nil, &vpe.ControlPingReply{})
136         Expect(err).Should(HaveOccurred())
137         Expect(err.Error()).To(ContainSubstring("panic"))
138 }
139
140 func TestSimpleRequestsWithSequenceNumbers(t *testing.T) {
141         ctx := setupTest(t, false)
142         defer ctx.teardownTest()
143
144         var reqCtx []api.RequestCtx
145         for i := 0; i < 10; i++ {
146                 ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
147                 req := &vpe.ControlPing{}
148                 reqCtx = append(reqCtx, ctx.ch.SendRequest(req))
149         }
150
151         for i := 0; i < 10; i++ {
152                 reply := &vpe.ControlPingReply{}
153                 err := reqCtx[i].ReceiveReply(reply)
154                 Expect(err).ShouldNot(HaveOccurred())
155         }
156 }
157
158 func TestMultiRequestsWithSequenceNumbers(t *testing.T) {
159         ctx := setupTest(t, false)
160         defer ctx.teardownTest()
161
162         var msgs []api.Message
163         for i := 0; i < 10; i++ {
164                 msgs = append(msgs, &interfaces.SwInterfaceDetails{SwIfIndex: interfaces.InterfaceIndex(i)})
165         }
166         ctx.mockVpp.MockReply(msgs...)
167         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
168
169         // send multipart request
170         reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
171
172         cnt := 0
173         for {
174                 Expect(cnt < 11).To(BeTrue())
175
176                 // receive a reply
177                 reply := &interfaces.SwInterfaceDetails{}
178                 lastReplyReceived, err := reqCtx.ReceiveReply(reply)
179
180                 if lastReplyReceived {
181                         break
182                 }
183
184                 Expect(err).ShouldNot(HaveOccurred())
185                 Expect(reply.SwIfIndex).To(BeEquivalentTo(cnt))
186
187                 cnt++
188         }
189
190         Expect(cnt).To(BeEquivalentTo(10))
191 }
192
193 func TestSimpleRequestWithTimeout(t *testing.T) {
194         ctx := setupTest(t, true)
195         defer ctx.teardownTest()
196
197         // reply for a previous timeouted requests to be ignored
198         ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
199                 Msg:    &vpe.ControlPingReply{},
200                 SeqNum: 0,
201         })
202
203         // send reply later
204         req1 := &vpe.ControlPing{}
205         reqCtx1 := ctx.ch.SendRequest(req1)
206
207         reply := &vpe.ControlPingReply{}
208         err := reqCtx1.ReceiveReply(reply)
209         Expect(err).ToNot(BeNil())
210         Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
211
212         ctx.mockVpp.MockReplyWithContext(
213                 // reply for the previous request
214                 mock.MsgWithContext{
215                         Msg:    &vpe.ControlPingReply{},
216                         SeqNum: 1,
217                 },
218                 // reply for the next request
219                 mock.MsgWithContext{
220                         Msg:    &vpe.ControlPingReply{},
221                         SeqNum: 2,
222                 })
223
224         // next request
225         req2 := &vpe.ControlPing{}
226         reqCtx2 := ctx.ch.SendRequest(req2)
227
228         // second request should ignore the first reply and return the second one
229         reply = &vpe.ControlPingReply{}
230         err = reqCtx2.ReceiveReply(reply)
231         Expect(err).To(BeNil())
232 }
233
234 func TestSimpleRequestsWithMissingReply(t *testing.T) {
235         ctx := setupTest(t, false)
236         defer ctx.teardownTest()
237
238         // request without reply
239         req1 := &vpe.ControlPing{}
240         reqCtx1 := ctx.ch.SendRequest(req1)
241
242         // another request without reply
243         req2 := &vpe.ControlPing{}
244         reqCtx2 := ctx.ch.SendRequest(req2)
245
246         // third request with reply
247         ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
248                 Msg:    &vpe.ControlPingReply{},
249                 SeqNum: 3,
250         })
251         req3 := &vpe.ControlPing{}
252         reqCtx3 := ctx.ch.SendRequest(req3)
253
254         // the first two should fail, but not consume reply for the 3rd
255         reply := &vpe.ControlPingReply{}
256         err := reqCtx1.ReceiveReply(reply)
257         Expect(err).ToNot(BeNil())
258         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
259
260         reply = &vpe.ControlPingReply{}
261         err = reqCtx2.ReceiveReply(reply)
262         Expect(err).ToNot(BeNil())
263         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 2"))
264
265         // the second request should succeed
266         reply = &vpe.ControlPingReply{}
267         err = reqCtx3.ReceiveReply(reply)
268         Expect(err).To(BeNil())
269 }
270
271 func TestMultiRequestsWithErrors(t *testing.T) {
272         ctx := setupTest(t, false)
273         defer ctx.teardownTest()
274
275         // replies for a previous timeouted requests to be ignored
276         msgs := []mock.MsgWithContext{
277                 {Msg: &vpe.ControlPingReply{}, SeqNum: 0xffff - 1},
278                 {Msg: &vpe.ControlPingReply{}, SeqNum: 0xffff},
279                 {Msg: &vpe.ControlPingReply{}, SeqNum: 0},
280         }
281         for i := 0; i < 10; i++ {
282                 msgs = append(msgs, mock.MsgWithContext{
283                         Msg:       &interfaces.SwInterfaceDetails{SwIfIndex: interfaces.InterfaceIndex(i)},
284                         SeqNum:    1,
285                         Multipart: true,
286                 })
287         }
288         // missing finalizing control ping
289
290         // reply for a next request
291         msgs = append(msgs, mock.MsgWithContext{
292                 Msg:    &vpe.ControlPingReply{},
293                 SeqNum: 2,
294         })
295
296         // queue replies
297         ctx.mockVpp.MockReplyWithContext(msgs...)
298
299         // send multipart request
300         reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
301
302         for i := 0; i < 10; i++ {
303                 // receive multi-part replies
304                 reply := &interfaces.SwInterfaceDetails{}
305                 lastReplyReceived, err := reqCtx.ReceiveReply(reply)
306
307                 Expect(lastReplyReceived).To(BeFalse())
308                 Expect(err).ShouldNot(HaveOccurred())
309                 Expect(reply.SwIfIndex).To(BeEquivalentTo(i))
310         }
311
312         // missing closing control ping
313         reply := &interfaces.SwInterfaceDetails{}
314         _, err := reqCtx.ReceiveReply(reply)
315         Expect(err).ToNot(BeNil())
316         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
317
318         // try again - still fails and nothing consumed
319         _, err = reqCtx.ReceiveReply(reply)
320         Expect(err).ToNot(BeNil())
321         Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
322
323         // reply for the second request has not been consumed
324         reqCtx2 := ctx.ch.SendRequest(&vpe.ControlPing{})
325         reply2 := &vpe.ControlPingReply{}
326         err = reqCtx2.ReceiveReply(reply2)
327         Expect(err).To(BeNil())
328 }
329
330 func TestRequestsOrdering(t *testing.T) {
331         ctx := setupTest(t, false)
332         defer ctx.teardownTest()
333
334         // the orderings of SendRequest and ReceiveReply calls should match, otherwise
335         // some replies will get thrown away
336
337         // first request
338         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
339         req1 := &vpe.ControlPing{}
340         reqCtx1 := ctx.ch.SendRequest(req1)
341
342         // second request
343         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
344         req2 := &vpe.ControlPing{}
345         reqCtx2 := ctx.ch.SendRequest(req2)
346
347         // if reply for the second request is read first, the reply for the first
348         // request gets thrown away.
349         reply2 := &vpe.ControlPingReply{}
350         err := reqCtx2.ReceiveReply(reply2)
351         Expect(err).To(BeNil())
352
353         // first request has already been considered closed
354         reply1 := &vpe.ControlPingReply{}
355         err = reqCtx1.ReceiveReply(reply1)
356         Expect(err).ToNot(BeNil())
357         Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
358 }
359
360 func TestCycleOverSetOfSequenceNumbers(t *testing.T) {
361         ctx := setupTest(t, false)
362         defer ctx.teardownTest()
363
364         numIters := 0xffff + 100
365         reqCtx := make(map[int]api.RequestCtx)
366
367         for i := 0; i < numIters+30; i++ {
368                 if i < numIters {
369                         ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
370                         req := &vpe.ControlPing{}
371                         reqCtx[i] = ctx.ch.SendRequest(req)
372                 }
373                 if i > 30 {
374                         reply := &vpe.ControlPingReply{}
375                         err := reqCtx[i-30].ReceiveReply(reply)
376                         Expect(err).ShouldNot(HaveOccurred())
377                 }
378         }
379 }