summaryrefslogtreecommitdiffstats
path: root/core/connection_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/connection_test.go')
-rw-r--r--core/connection_test.go543
1 files changed, 543 insertions, 0 deletions
diff --git a/core/connection_test.go b/core/connection_test.go
new file mode 100644
index 0000000..b7c3aa0
--- /dev/null
+++ b/core/connection_test.go
@@ -0,0 +1,543 @@
+// Copyright (c) 2017 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package core_test
+
+import (
+ "testing"
+
+ "git.fd.io/govpp.git/adapter/mock"
+ "git.fd.io/govpp.git/api"
+ "git.fd.io/govpp.git/core"
+ "git.fd.io/govpp.git/core/bin_api/vpe"
+ "git.fd.io/govpp.git/examples/bin_api/interfaces"
+ "git.fd.io/govpp.git/examples/bin_api/stats"
+
+ "git.fd.io/govpp.git/codec"
+ . "github.com/onsi/gomega"
+)
+
+type testCtx struct {
+ mockVpp *mock.VppAdapter
+ conn *core.Connection
+ ch api.Channel
+}
+
+func setupTest(t *testing.T, bufferedChan bool) *testCtx {
+ RegisterTestingT(t)
+
+ ctx := &testCtx{}
+ ctx.mockVpp = &mock.VppAdapter{}
+
+ var err error
+ ctx.conn, err = core.Connect(ctx.mockVpp)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ if bufferedChan {
+ ctx.ch, err = ctx.conn.NewAPIChannelBuffered(100, 100)
+ } else {
+ ctx.ch, err = ctx.conn.NewAPIChannel()
+ }
+ Expect(err).ShouldNot(HaveOccurred())
+
+ return ctx
+}
+
+func (ctx *testCtx) teardownTest() {
+ ctx.ch.Close()
+ ctx.conn.Disconnect()
+}
+
+func TestSimpleRequest(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: -5})
+
+ req := &vpe.ControlPing{}
+ reply := &vpe.ControlPingReply{}
+
+ // send the request and receive a reply
+ ctx.ch.GetRequestChannel() <- &api.VppRequest{Message: req}
+ vppReply := <-ctx.ch.GetReplyChannel()
+
+ Expect(vppReply).ShouldNot(BeNil())
+ Expect(vppReply.Error).ShouldNot(HaveOccurred())
+
+ // decode the message
+ err := ctx.ch.GetMessageDecoder().DecodeMsg(vppReply.Data, reply)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ Expect(reply.Retval).To(BeEquivalentTo(-5))
+}
+
+func TestMultiRequest(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ msgs := []api.Message{}
+ for m := 0; m < 10; m++ {
+ msgs = append(msgs, &interfaces.SwInterfaceDetails{})
+ }
+ ctx.mockVpp.MockReply(msgs...)
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+
+ // send multipart request
+ ctx.ch.GetRequestChannel() <- &api.VppRequest{Message: &interfaces.SwInterfaceDump{}, Multipart: true}
+
+ cnt := 0
+ for {
+ // receive a reply
+ vppReply := <-ctx.ch.GetReplyChannel()
+ if vppReply.LastReplyReceived {
+ break // break out of the loop
+ }
+ Expect(vppReply.Error).ShouldNot(HaveOccurred())
+
+ // decode the message
+ reply := &interfaces.SwInterfaceDetails{}
+ err := ctx.ch.GetMessageDecoder().DecodeMsg(vppReply.Data, reply)
+ Expect(err).ShouldNot(HaveOccurred())
+ cnt++
+ }
+
+ Expect(cnt).To(BeEquivalentTo(10))
+}
+
+func TestNotifications(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ // subscribe for notification
+ notifChan := make(chan api.Message, 1)
+ subscription := &api.NotifSubscription{
+ NotifChan: notifChan,
+ MsgFactory: interfaces.NewSwInterfaceSetFlags,
+ }
+ ctx.ch.GetNotificationChannel() <- &api.NotifSubscribeRequest{
+ Subscription: subscription,
+ Subscribe: true,
+ }
+ err := <-ctx.ch.GetNotificationReplyChannel()
+ Expect(err).ShouldNot(HaveOccurred())
+
+ // mock the notification and force its delivery
+ ctx.mockVpp.MockReply(&interfaces.SwInterfaceSetFlags{
+ SwIfIndex: 3,
+ AdminUpDown: 1,
+ })
+ ctx.mockVpp.SendMsg(0, []byte{0})
+
+ // receive the notification
+ notif := (<-notifChan).(*interfaces.SwInterfaceSetFlags)
+
+ Expect(notif.SwIfIndex).To(BeEquivalentTo(3))
+
+ // unsubscribe notification
+ ctx.ch.GetNotificationChannel() <- &api.NotifSubscribeRequest{
+ Subscription: subscription,
+ Subscribe: false,
+ }
+ err = <-ctx.ch.GetNotificationReplyChannel()
+ Expect(err).ShouldNot(HaveOccurred())
+}
+
+func TestNilConnection(t *testing.T) {
+ RegisterTestingT(t)
+ var conn *core.Connection
+
+ ch, err := conn.NewAPIChannel()
+ Expect(ch).Should(BeNil())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("nil"))
+
+ ch, err = conn.NewAPIChannelBuffered(1, 1)
+ Expect(ch).Should(BeNil())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("nil"))
+}
+
+func TestDoubleConnection(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ conn, err := core.Connect(ctx.mockVpp)
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("only one connection per process"))
+ Expect(conn).Should(BeNil())
+}
+
+func TestAsyncConnection(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ ctx.conn.Disconnect()
+ conn, ch, err := core.AsyncConnect(ctx.mockVpp)
+ ctx.conn = conn
+
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(conn).ShouldNot(BeNil())
+
+ ev := <-ch
+ Expect(ev.State).Should(BeEquivalentTo(core.Connected))
+}
+
+func TestFullBuffer(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ // close the default API channel
+ ctx.ch.Close()
+
+ // create a new channel with limited buffer sizes
+ var err error
+ ctx.ch, err = ctx.conn.NewAPIChannelBuffered(10, 1)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ // send multiple requests, only one reply should be read
+ for i := 0; i < 20; i++ {
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+ ctx.ch.GetRequestChannel() <- &api.VppRequest{Message: &vpe.ControlPing{}}
+ }
+
+ vppReply := <-ctx.ch.GetReplyChannel()
+ Expect(vppReply).ShouldNot(BeNil())
+
+ var received bool
+ select {
+ case <-ctx.ch.GetReplyChannel():
+ received = true // this should not happen
+ default:
+ received = false // no reply to be received
+ }
+ Expect(received).Should(BeFalse(), "A reply has been recieved, should had been ignored.")
+}
+
+func TestCodec(t *testing.T) {
+ RegisterTestingT(t)
+
+ msgCodec := &codec.MsgCodec{}
+
+ // request
+ data, err := msgCodec.EncodeMsg(&interfaces.CreateLoopback{MacAddress: []byte{1, 2, 3, 4, 5, 6}}, 11)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(data).ShouldNot(BeEmpty())
+
+ msg1 := &interfaces.CreateLoopback{}
+ err = msgCodec.DecodeMsg(data, msg1)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(msg1.MacAddress).To(BeEquivalentTo([]byte{1, 2, 3, 4, 5, 6}))
+
+ // reply
+ data, err = msgCodec.EncodeMsg(&vpe.ControlPingReply{Retval: 55}, 22)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(data).ShouldNot(BeEmpty())
+
+ msg2 := &vpe.ControlPingReply{}
+ err = msgCodec.DecodeMsg(data, msg2)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(msg2.Retval).To(BeEquivalentTo(55))
+
+ // other
+ data, err = msgCodec.EncodeMsg(&stats.VnetIP4FibCounters{VrfID: 77}, 33)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(data).ShouldNot(BeEmpty())
+
+ msg3 := &stats.VnetIP4FibCounters{}
+ err = msgCodec.DecodeMsg(data, msg3)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(msg3.VrfID).To(BeEquivalentTo(77))
+}
+
+func TestCodecNegative(t *testing.T) {
+ RegisterTestingT(t)
+
+ msgCodec := &codec.MsgCodec{}
+
+ // nil message for encoding
+ data, err := msgCodec.EncodeMsg(nil, 15)
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("nil message"))
+ Expect(data).Should(BeNil())
+
+ // nil message for decoding
+ err = msgCodec.DecodeMsg(data, nil)
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("nil message"))
+
+ // nil data for decoding
+ err = msgCodec.DecodeMsg(nil, &vpe.ControlPingReply{})
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("EOF"))
+}
+
+func TestSimpleRequestsWithSequenceNumbers(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ var reqCtx []api.RequestCtx
+ for i := 0; i < 10; i++ {
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: int32(i)})
+ req := &vpe.ControlPing{}
+ reqCtx = append(reqCtx, ctx.ch.SendRequest(req))
+ }
+
+ for i := 0; i < 10; i++ {
+ reply := &vpe.ControlPingReply{}
+ err := reqCtx[i].ReceiveReply(reply)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(reply.Retval).To(BeEquivalentTo(i))
+ }
+}
+
+func TestMultiRequestsWithSequenceNumbers(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ msgs := []api.Message{}
+ for i := 0; i < 10; i++ {
+ msgs = append(msgs, &interfaces.SwInterfaceDetails{SwIfIndex: uint32(i)})
+ }
+ ctx.mockVpp.MockReply(msgs...)
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+
+ // send multipart request
+ reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
+
+ cnt := 0
+ for {
+ Expect(cnt < 11).To(BeTrue())
+
+ // receive a reply
+ reply := &interfaces.SwInterfaceDetails{}
+ lastReplyReceived, err := reqCtx.ReceiveReply(reply)
+
+ if lastReplyReceived {
+ break // break out of the loop
+ }
+
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(reply.SwIfIndex).To(BeEquivalentTo(cnt))
+
+ cnt++
+ }
+
+ Expect(cnt).To(BeEquivalentTo(10))
+}
+
+func TestSimpleRequestWithTimeout(t *testing.T) {
+ ctx := setupTest(t, true)
+ defer ctx.teardownTest()
+
+ // reply for a previous timeouted requests to be ignored
+ ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 1},
+ SeqNum: 0,
+ })
+
+ // send reply later
+ req1 := &vpe.ControlPing{}
+ reqCtx1 := ctx.ch.SendRequest(req1)
+
+ reply := &vpe.ControlPingReply{}
+ err := reqCtx1.ReceiveReply(reply)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
+
+ ctx.mockVpp.MockReplyWithContext(
+ // reply for the previous request
+ mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 1},
+ SeqNum: 1,
+ },
+ // reply for the next request
+ mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 2},
+ SeqNum: 2,
+ })
+
+ // next request
+ req2 := &vpe.ControlPing{}
+ reqCtx2 := ctx.ch.SendRequest(req2)
+
+ // second request should ignore the first reply and return the second one
+ reply = &vpe.ControlPingReply{}
+ err = reqCtx2.ReceiveReply(reply)
+ Expect(err).To(BeNil())
+ Expect(reply.Retval).To(BeEquivalentTo(2))
+}
+
+func TestSimpleRequestsWithMissingReply(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ // request without reply
+ req1 := &vpe.ControlPing{}
+ reqCtx1 := ctx.ch.SendRequest(req1)
+
+ // another request without reply
+ req2 := &vpe.ControlPing{}
+ reqCtx2 := ctx.ch.SendRequest(req2)
+
+ // third request with reply
+ ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 3},
+ SeqNum: 3,
+ })
+ req3 := &vpe.ControlPing{}
+ reqCtx3 := ctx.ch.SendRequest(req3)
+
+ // the first two should fail, but not consume reply for the 3rd
+ reply := &vpe.ControlPingReply{}
+ err := reqCtx1.ReceiveReply(reply)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
+
+ reply = &vpe.ControlPingReply{}
+ err = reqCtx2.ReceiveReply(reply)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 2"))
+
+ // the second request should succeed
+ reply = &vpe.ControlPingReply{}
+ err = reqCtx3.ReceiveReply(reply)
+ Expect(err).To(BeNil())
+ Expect(reply.Retval).To(BeEquivalentTo(3))
+}
+
+func TestMultiRequestsWithErrors(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ // replies for a previous timeouted requests to be ignored
+ msgs := []mock.MsgWithContext{}
+ msgs = append(msgs,
+ mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 1},
+ SeqNum: 0xffff - 1,
+ },
+ mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 1},
+ SeqNum: 0xffff,
+ },
+ mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 1},
+ SeqNum: 0,
+ })
+
+ for i := 0; i < 10; i++ {
+ msgs = append(msgs,
+ mock.MsgWithContext{
+ Msg: &interfaces.SwInterfaceDetails{SwIfIndex: uint32(i)},
+ SeqNum: 1,
+ Multipart: true,
+ })
+ }
+ // missing finalizing control ping
+
+ // reply for a next request
+ msgs = append(msgs,
+ mock.MsgWithContext{
+ Msg: &vpe.ControlPingReply{Retval: 2},
+ SeqNum: 2,
+ Multipart: false,
+ })
+
+ // queue replies
+ ctx.mockVpp.MockReplyWithContext(msgs...)
+
+ // send multipart request
+ reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
+
+ for i := 0; i < 10; i++ {
+ // receive multi-part replies
+ reply := &interfaces.SwInterfaceDetails{}
+ lastReplyReceived, err := reqCtx.ReceiveReply(reply)
+
+ Expect(lastReplyReceived).To(BeFalse())
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(reply.SwIfIndex).To(BeEquivalentTo(i))
+ }
+
+ // missing closing control ping
+ reply := &interfaces.SwInterfaceDetails{}
+ _, err := reqCtx.ReceiveReply(reply)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
+
+ // try again - still fails and nothing consumed
+ _, err = reqCtx.ReceiveReply(reply)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
+
+ // reply for the second request has not been consumed
+ reqCtx2 := ctx.ch.SendRequest(&vpe.ControlPing{})
+ reply2 := &vpe.ControlPingReply{}
+ err = reqCtx2.ReceiveReply(reply2)
+ Expect(err).To(BeNil())
+ Expect(reply2.Retval).To(BeEquivalentTo(2))
+}
+
+func TestRequestsOrdering(t *testing.T) {
+ ctx := setupTest(t, false)
+ defer ctx.teardownTest()
+
+ // the orderings of SendRequest and ReceiveReply calls should match, otherwise
+ // some replies will get thrown away
+
+ // first request
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: 1})
+ req1 := &vpe.ControlPing{}
+ reqCtx1 := ctx.ch.SendRequest(req1)
+
+ // second request
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: 2})
+ req2 := &vpe.ControlPing{}
+ reqCtx2 := ctx.ch.SendRequest(req2)
+
+ // if reply for the second request is read first, the reply for the first
+ // request gets thrown away.
+ reply2 := &vpe.ControlPingReply{}
+ err := reqCtx2.ReceiveReply(reply2)
+ Expect(err).To(BeNil())
+ Expect(reply2.Retval).To(BeEquivalentTo(2))
+
+ // first request has already been considered closed
+ reply1 := &vpe.ControlPingReply{}
+ err = reqCtx1.ReceiveReply(reply1)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
+}
+
+func TestCycleOverSetOfSequenceNumbers(t *testing.T) {
+ ctx := setupTest(t, true)
+ defer ctx.teardownTest()
+
+ numIters := 0xffff + 100
+ reqCtx := make(map[int]api.RequestCtx)
+
+ for i := 0; i < numIters+30; /* receiver is 30 reqs behind */ i++ {
+ if i < numIters {
+ ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: int32(i)})
+ req := &vpe.ControlPing{}
+ reqCtx[i] = ctx.ch.SendRequest(req)
+ }
+ if i > 30 {
+ reply := &vpe.ControlPingReply{}
+ err := reqCtx[i-30].ReceiveReply(reply)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(reply.Retval).To(BeEquivalentTo(i - 30))
+ }
+ }
+}