make api.Channel as interface
[govpp.git] / core / channel_test.go
diff --git a/core/channel_test.go b/core/channel_test.go
new file mode 100644 (file)
index 0000000..d573f29
--- /dev/null
@@ -0,0 +1,587 @@
+// 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
+
+import (
+       "testing"
+       "time"
+
+       "git.fd.io/govpp.git/adapter/mock"
+       "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/memif"
+       "git.fd.io/govpp.git/examples/bin_api/tap"
+
+       "git.fd.io/govpp.git/api"
+       . "github.com/onsi/gomega"
+)
+
+type testCtx struct {
+       mockVpp *mock.VppAdapter
+       conn    *Connection
+       ch      api.Channel
+}
+
+func setupTest(t *testing.T) *testCtx {
+       RegisterTestingT(t)
+
+       ctx := &testCtx{
+               mockVpp: &mock.VppAdapter{},
+       }
+
+       var err error
+       ctx.conn, err = Connect(ctx.mockVpp)
+       Expect(err).ShouldNot(HaveOccurred())
+
+       ctx.ch, err = ctx.conn.NewAPIChannel()
+       Expect(err).ShouldNot(HaveOccurred())
+
+       return ctx
+}
+
+func (ctx *testCtx) teardownTest() {
+       ctx.ch.Close()
+       ctx.conn.Disconnect()
+}
+
+func TestRequestReplyTapConnect(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.mockVpp.MockReply(&tap.TapConnectReply{
+               Retval:    10,
+               SwIfIndex: 1,
+       })
+       request := &tap.TapConnect{
+               TapName:      []byte("test-tap-name"),
+               UseRandomMac: 1,
+       }
+       reply := &tap.TapConnectReply{}
+
+       err := ctx.ch.SendRequest(request).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(reply.Retval).To(BeEquivalentTo(10), "Incorrect retval value for TapConnectReply")
+       Expect(reply.SwIfIndex).To(BeEquivalentTo(1), "Incorrect SwIfIndex value for TapConnectReply")
+}
+
+func TestRequestReplyTapModify(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.mockVpp.MockReply(&tap.TapModifyReply{
+               Retval:    15,
+               SwIfIndex: 2,
+       })
+       request := &tap.TapModify{
+               TapName:           []byte("test-tap-modify"),
+               UseRandomMac:      1,
+               CustomDevInstance: 1,
+       }
+       reply := &tap.TapModifyReply{}
+
+       err := ctx.ch.SendRequest(request).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(reply.Retval).To(BeEquivalentTo(15), "Incorrect retval value for TapModifyReply")
+       Expect(reply.SwIfIndex).To(BeEquivalentTo(2), "Incorrect SwIfIndex value for TapModifyReply")
+}
+
+func TestRequestReplyTapDelete(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.mockVpp.MockReply(&tap.TapDeleteReply{
+               Retval: 20,
+       })
+       request := &tap.TapDelete{
+               SwIfIndex: 3,
+       }
+       reply := &tap.TapDeleteReply{}
+
+       err := ctx.ch.SendRequest(request).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(reply.Retval).To(BeEquivalentTo(20), "Incorrect retval value for TapDeleteReply")
+}
+
+func TestRequestReplySwInterfaceTapDump(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       byteName := []byte("dev-name-test")
+       ctx.mockVpp.MockReply(&tap.SwInterfaceTapDetails{
+               SwIfIndex: 25,
+               DevName:   byteName,
+       })
+       request := &tap.SwInterfaceTapDump{}
+       reply := &tap.SwInterfaceTapDetails{}
+
+       err := ctx.ch.SendRequest(request).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(reply.SwIfIndex).To(BeEquivalentTo(25), "Incorrect SwIfIndex value for SwInterfaceTapDetails")
+       Expect(reply.DevName).ToNot(BeNil(), "Incorrect DevName value for SwInterfaceTapDetails")
+}
+
+func TestRequestReplyMemifCreate(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.mockVpp.MockReply(&memif.MemifCreateReply{
+               Retval:    22,
+               SwIfIndex: 4,
+       })
+       request := &memif.MemifCreate{
+               Role:       10,
+               ID:         12,
+               RingSize:   8000,
+               BufferSize: 50,
+       }
+       reply := &memif.MemifCreateReply{}
+
+       err := ctx.ch.SendRequest(request).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(reply.Retval).To(BeEquivalentTo(22), "Incorrect Retval value for MemifCreate")
+       Expect(reply.SwIfIndex).To(BeEquivalentTo(4), "Incorrect SwIfIndex value for MemifCreate")
+}
+
+func TestRequestReplyMemifDelete(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.mockVpp.MockReply(&memif.MemifDeleteReply{
+               Retval: 24,
+       })
+       request := &memif.MemifDelete{
+               SwIfIndex: 15,
+       }
+       reply := &memif.MemifDeleteReply{}
+
+       err := ctx.ch.SendRequest(request).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(reply.Retval).To(BeEquivalentTo(24), "Incorrect Retval value for MemifDelete")
+}
+
+func TestRequestReplyMemifDetails(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.mockVpp.MockReply(&memif.MemifDetails{
+               SwIfIndex: 25,
+               IfName:    []byte("memif-name"),
+               Role:      0,
+       })
+       request := &memif.MemifDump{}
+       reply := &memif.MemifDetails{}
+
+       err := ctx.ch.SendRequest(request).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(reply.SwIfIndex).To(BeEquivalentTo(25), "Incorrect SwIfIndex value for MemifDetails")
+       Expect(reply.IfName).ToNot(BeEmpty(), "MemifDetails IfName is empty byte array")
+       Expect(reply.Role).To(BeEquivalentTo(0), "Incorrect Role value for MemifDetails")
+}
+
+func TestMultiRequestReplySwInterfaceTapDump(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       // mock reply
+       var msgs []api.Message
+       for i := 1; i <= 10; i++ {
+               msgs = append(msgs, &tap.SwInterfaceTapDetails{
+                       SwIfIndex: uint32(i),
+                       DevName:   []byte("dev-name-test"),
+               })
+       }
+       ctx.mockVpp.MockReply(msgs...)
+       ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+
+       reqCtx := ctx.ch.SendMultiRequest(&tap.SwInterfaceTapDump{})
+       cnt := 0
+       for {
+               msg := &tap.SwInterfaceTapDetails{}
+               stop, err := reqCtx.ReceiveReply(msg)
+               if stop {
+                       break // break out of the loop
+               }
+               Expect(err).ShouldNot(HaveOccurred())
+               cnt++
+       }
+       Expect(cnt).To(BeEquivalentTo(10))
+}
+
+func TestMultiRequestReplySwInterfaceMemifDump(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       // mock reply
+       var msgs []api.Message
+       for i := 1; i <= 10; i++ {
+               msgs = append(msgs, &memif.MemifDetails{
+                       SwIfIndex: uint32(i),
+               })
+       }
+       ctx.mockVpp.MockReply(msgs...)
+       ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+
+       reqCtx := ctx.ch.SendMultiRequest(&memif.MemifDump{})
+       cnt := 0
+       for {
+               msg := &memif.MemifDetails{}
+               stop, err := reqCtx.ReceiveReply(msg)
+               if stop {
+                       break // break out of the loop
+               }
+               Expect(err).ShouldNot(HaveOccurred())
+               cnt++
+       }
+       Expect(cnt).To(BeEquivalentTo(10))
+}
+
+func TestNotifications(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       // subscribe for notification
+       notifChan := make(chan api.Message, 1)
+       subs, err := ctx.ch.SubscribeNotification(notifChan, interfaces.NewSwInterfaceSetFlags)
+       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(""))
+
+       // receive the notification
+       var notif *interfaces.SwInterfaceSetFlags
+       Eventually(func() *interfaces.SwInterfaceSetFlags {
+               select {
+               case n := <-notifChan:
+                       notif = n.(*interfaces.SwInterfaceSetFlags)
+                       return notif
+               default:
+                       return nil
+               }
+       }).ShouldNot(BeNil())
+
+       // verify the received notifications
+       Expect(notif.SwIfIndex).To(BeEquivalentTo(3), "Incorrect SwIfIndex value for SwInterfaceSetFlags")
+       Expect(notif.AdminUpDown).To(BeEquivalentTo(1), "Incorrect AdminUpDown value for SwInterfaceSetFlags")
+
+       ctx.ch.UnsubscribeNotification(subs)
+}
+
+func TestNotificationEvent(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       // subscribe for notification
+       notifChan := make(chan api.Message, 1)
+       subs, err := ctx.ch.SubscribeNotification(notifChan, interfaces.NewSwInterfaceEvent)
+       Expect(err).ShouldNot(HaveOccurred())
+
+       // mock the notification and force its delivery
+       ctx.mockVpp.MockReply(&interfaces.SwInterfaceEvent{
+               SwIfIndex:  2,
+               LinkUpDown: 1,
+       })
+       ctx.mockVpp.SendMsg(0, []byte(""))
+
+       // receive the notification
+       var notif *interfaces.SwInterfaceEvent
+       Eventually(func() *interfaces.SwInterfaceEvent {
+               select {
+               case n := <-notifChan:
+                       notif = n.(*interfaces.SwInterfaceEvent)
+                       return notif
+               default:
+                       return nil
+               }
+       }).ShouldNot(BeNil())
+
+       // verify the received notifications
+       Expect(notif.SwIfIndex).To(BeEquivalentTo(2), "Incorrect SwIfIndex value for SwInterfaceSetFlags")
+       Expect(notif.LinkUpDown).To(BeEquivalentTo(1), "Incorrect LinkUpDown value for SwInterfaceSetFlags")
+
+       ctx.ch.UnsubscribeNotification(subs)
+}
+
+func TestCheckMessageCompatibility(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       err := ctx.ch.CheckMessageCompatibility(&interfaces.SwInterfaceSetFlags{})
+       Expect(err).ShouldNot(HaveOccurred())
+}
+func TestSetReplyTimeout(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.ch.SetReplyTimeout(time.Millisecond)
+
+       // first one request should work
+       ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+       err := ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).ShouldNot(HaveOccurred())
+
+       // no other reply ready - expect timeout
+       err = ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("timeout"))
+}
+
+func TestSetReplyTimeoutMultiRequest(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.ch.SetReplyTimeout(time.Millisecond)
+
+       var msgs []api.Message
+       for i := 1; i <= 3; i++ {
+               msgs = append(msgs, &interfaces.SwInterfaceDetails{
+                       SwIfIndex:     uint32(i),
+                       InterfaceName: []byte("if-name-test"),
+               })
+       }
+       ctx.mockVpp.MockReply(msgs...)
+       ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+
+       cnt := 0
+       sendMultiRequest := func() error {
+               reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
+               for {
+                       msg := &interfaces.SwInterfaceDetails{}
+                       stop, err := reqCtx.ReceiveReply(msg)
+                       if stop {
+                               break // break out of the loop
+                       }
+                       if err != nil {
+                               return err
+                       }
+                       cnt++
+               }
+               return nil
+       }
+
+       // first one request should work
+       err := sendMultiRequest()
+       Expect(err).ShouldNot(HaveOccurred())
+
+       // no other reply ready - expect timeout
+       err = sendMultiRequest()
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("timeout"))
+
+       Expect(cnt).To(BeEquivalentTo(3))
+}
+
+func TestReceiveReplyNegative(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       // invalid context 1
+       reqCtx1 := &requestCtxData{}
+       err := reqCtx1.ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("invalid request context"))
+
+       // invalid context 2
+       reqCtx2 := &multiRequestCtxData{}
+       _, err = reqCtx2.ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("invalid request context"))
+
+       // NU
+       reqCtx3 := &requestCtxData{}
+       err = reqCtx3.ReceiveReply(nil)
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("invalid request context"))
+}
+
+func TestMultiRequestDouble(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       // mock reply
+       var msgs []mock.MsgWithContext
+       for i := 1; i <= 3; i++ {
+               msgs = append(msgs, mock.MsgWithContext{
+                       Msg: &interfaces.SwInterfaceDetails{
+                               SwIfIndex:     uint32(i),
+                               InterfaceName: []byte("if-name-test"),
+                       },
+                       Multipart: true,
+                       SeqNum:    1,
+               })
+       }
+       msgs = append(msgs, mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, Multipart: true, SeqNum: 1})
+
+       for i := 1; i <= 3; i++ {
+               msgs = append(msgs,
+                       mock.MsgWithContext{
+                               Msg: &interfaces.SwInterfaceDetails{
+                                       SwIfIndex:     uint32(i),
+                                       InterfaceName: []byte("if-name-test"),
+                               },
+                               Multipart: true,
+                               SeqNum:    2,
+                       })
+       }
+       msgs = append(msgs, mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, Multipart: true, SeqNum: 2})
+
+       ctx.mockVpp.MockReplyWithContext(msgs...)
+
+       cnt := 0
+       var sendMultiRequest = func() error {
+               reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
+               for {
+                       msg := &interfaces.SwInterfaceDetails{}
+                       stop, err := reqCtx.ReceiveReply(msg)
+                       if stop {
+                               break // break out of the loop
+                       }
+                       if err != nil {
+                               return err
+                       }
+                       cnt++
+               }
+               return nil
+       }
+
+       err := sendMultiRequest()
+       Expect(err).ShouldNot(HaveOccurred())
+
+       err = sendMultiRequest()
+       Expect(err).ShouldNot(HaveOccurred())
+
+       Expect(cnt).To(BeEquivalentTo(6))
+}
+
+func TestReceiveReplyAfterTimeout(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.ch.SetReplyTimeout(time.Millisecond)
+
+       // first one request should work
+       ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, SeqNum: 1})
+       err := ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).ShouldNot(HaveOccurred())
+
+       err = ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("timeout"))
+
+       ctx.mockVpp.MockReplyWithContext(
+               // simulating late reply
+               mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, SeqNum: 2},
+               // normal reply for next request
+               mock.MsgWithContext{Msg: &tap.TapConnectReply{}, SeqNum: 3})
+
+       req := &tap.TapConnect{
+               TapName:      []byte("test-tap-name"),
+               UseRandomMac: 1,
+       }
+       reply := &tap.TapConnectReply{}
+
+       // should succeed
+       err = ctx.ch.SendRequest(req).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+}
+
+func TestReceiveReplyAfterTimeoutMultiRequest(t *testing.T) {
+       /*
+               TODO: fix mock adapter
+               This test will fail because mock adapter will stop sending replies
+               when it encounters control_ping_reply from multi request,
+               thus never sending reply for next request
+       */
+       t.Skip()
+
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       ctx.ch.SetReplyTimeout(time.Millisecond * 100)
+
+       // first one request should work
+       ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, SeqNum: 1})
+       err := ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).ShouldNot(HaveOccurred())
+
+       cnt := 0
+       var sendMultiRequest = func() error {
+               reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
+               for {
+                       msg := &interfaces.SwInterfaceDetails{}
+                       stop, err := reqCtx.ReceiveReply(msg)
+                       if stop {
+                               break // break out of the loop
+                       }
+                       if err != nil {
+                               return err
+                       }
+                       cnt++
+               }
+               return nil
+       }
+
+       err = sendMultiRequest()
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("timeout"))
+       Expect(cnt).To(BeEquivalentTo(0))
+
+       // simulating late replies
+       var msgs []mock.MsgWithContext
+       for i := 1; i <= 3; i++ {
+               msgs = append(msgs, mock.MsgWithContext{
+                       Msg: &interfaces.SwInterfaceDetails{
+                               SwIfIndex:     uint32(i),
+                               InterfaceName: []byte("if-name-test"),
+                       },
+                       Multipart: true,
+                       SeqNum:    2,
+               })
+       }
+       msgs = append(msgs, mock.MsgWithContext{Msg: &vpe.ControlPingReply{}, Multipart: true, SeqNum: 2})
+       ctx.mockVpp.MockReplyWithContext(msgs...)
+
+       // normal reply for next request
+       ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{Msg: &tap.TapConnectReply{}, SeqNum: 3})
+
+       req := &tap.TapConnect{
+               TapName:      []byte("test-tap-name"),
+               UseRandomMac: 1,
+       }
+       reply := &tap.TapConnectReply{}
+
+       // should succeed
+       err = ctx.ch.SendRequest(req).ReceiveReply(reply)
+       Expect(err).ShouldNot(HaveOccurred())
+}
+
+func TestInvalidMessageID(t *testing.T) {
+       ctx := setupTest(t)
+       defer ctx.teardownTest()
+
+       // first one request should work
+       ctx.mockVpp.MockReply(&vpe.ShowVersionReply{})
+       err := ctx.ch.SendRequest(&vpe.ShowVersion{}).ReceiveReply(&vpe.ShowVersionReply{})
+       Expect(err).ShouldNot(HaveOccurred())
+
+       // second should fail with error invalid message ID
+       ctx.mockVpp.MockReply(&vpe.ShowVersionReply{})
+       err = ctx.ch.SendRequest(&vpe.ControlPing{}).ReceiveReply(&vpe.ControlPingReply{})
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("invalid message ID"))
+}