diff options
author | Rastislav Szabo <raszabo@cisco.com> | 2017-05-04 11:09:03 +0200 |
---|---|---|
committer | Rastislav Szabo <raszabo@cisco.com> | 2017-05-04 11:12:35 +0200 |
commit | a101d966133a70b8a76526be25070436d14fcf9f (patch) | |
tree | 75e2dbf20de615e58252b780b2ba5baae8fdcf82 /api | |
parent | a968ead74525125dff9ae90b1c9a9102e4327900 (diff) |
initial commit
Signed-off-by: Rastislav Szabo <raszabo@cisco.com>
Diffstat (limited to 'api')
-rw-r--r-- | api/api.go | 280 | ||||
-rw-r--r-- | api/api_test.go | 322 | ||||
-rw-r--r-- | api/doc.go | 94 | ||||
-rw-r--r-- | api/ifcounters/doc.go | 4 | ||||
-rw-r--r-- | api/ifcounters/ifcounters.go | 147 | ||||
-rw-r--r-- | api/ifcounters/ifcounters_test.go | 129 |
6 files changed, 976 insertions, 0 deletions
diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..783f97a --- /dev/null +++ b/api/api.go @@ -0,0 +1,280 @@ +// 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 api + +import ( + "errors" + "fmt" + "time" +) + +// MessageType represents the type of a VPP message. +type MessageType int + +const ( + // RequestMessage represents a VPP request message + RequestMessage MessageType = iota + // ReplyMessage represents a VPP reply message + ReplyMessage + // OtherMessage represents other VPP message (e.g. counters) + OtherMessage +) + +// Message is an interface that is implemented by all VPP Binary API messages generated by the binapi_generator. +type Message interface { + // GetMessageName returns the original VPP name of the message, as defined in the VPP API. + GetMessageName() string + + // GetMessageType returns the type of the VPP message. + GetMessageType() MessageType + + // GetCrcString returns the string with CRC checksum of the message definition (the string represents a hexadecimal number). + GetCrcString() string +} + +// DataType is an interface that is implemented by all VPP Binary API data types by the binapi_generator. +type DataType interface { + // GetTypeName returns the original VPP name of the data type, as defined in the VPP API. + GetTypeName() string + + // GetCrcString returns the string with CRC checksum of the data type definition (the string represents a hexadecimal number). + GetCrcString() string +} + +// ChannelProvider provides the communication channel with govpp core. +type ChannelProvider interface { + // NewAPIChannel returns a new channel for communication with VPP via govpp core. + // It uses default buffer sizes for the request and reply Go channels. + NewAPIChannel() (*Channel, error) + + // NewAPIChannel returns a new channel for communication with VPP via govpp core. + // It allows to specify custom buffer sizes for the request and reply Go channels. + NewAPIChannelBuffered() (*Channel, error) +} + +// MessageDecoder provides functionality for decoding binary data to generated API messages. +type MessageDecoder interface { + // DecodeMsg decodes binary-encoded data of a message into provided Message structure. + DecodeMsg(data []byte, msg Message) error +} + +// MessageIdentifier provides identification of generated API messages. +type MessageIdentifier interface { + // GetMessageID returns message identifier of given API message. + GetMessageID(msg Message) (uint16, error) +} + +// Channel is the main communication interface with govpp core. It contains two Go channels, one for sending the requests +// to VPP and one for receiving the replies from it. The user can access the Go channels directly, or use the helper +// methods provided inside of this package. +type Channel struct { + ReqChan chan *VppRequest // channel for sending the requests to VPP, closing this channel releases all resources in the ChannelProvider + ReplyChan chan *VppReply // channel where VPP replies are delivered to + + NotifSubsChan chan *NotifSubscribeRequest // channel for sending notification subscribe requests + NotifSubsReplyChan chan error // channel where replies to notification subscribe requests are delivered to + + MsgDecoder MessageDecoder // used to decode binary data to generated API messages + MsgIdentifier MessageIdentifier // used to retrieve message ID of a message + + replyTimeout time.Duration // maximum time that the API waits for a reply from VPP before returning an error, can be set with SetReplyTimeout + metadata interface{} // opaque metadata of the API channel +} + +// VppRequest is a request that will be sent to VPP. +type VppRequest struct { + Message Message // binary API message to be send to VPP + Multipart bool // true if multipart response is expected, false otherwise +} + +// VppReply is a reply received from VPP. +type VppReply struct { + MessageID uint16 // ID of the message + Data []byte // encoded data with the message - MessageDecoder can be used for decoding + LastReplyReceived bool // in case of multipart replies, true if the last reply has been already received and this one should be ignored + Error error // in case of error, data is nil and this member contains error description +} + +// NotifSubscribeRequest is a request to subscribe for delivery of specific notification messages. +type NotifSubscribeRequest struct { + Subscription *NotifSubscription // subscription details + Subscribe bool // true if this is a request to subscribe, false if unsubscribe +} + +// NotifSubscription represents a subscription for delivery of specific notification messages. +type NotifSubscription struct { + NotifChan chan Message // channel where notification messages will be delivered to + MsgFactory func() Message // function that returns a new instance of the specific message that is expected as a notification +} + +// RequestCtx is a context of a ongoing request (simple one - only one response is expected). +type RequestCtx struct { + ch *Channel +} + +// MultiRequestCtx is a context of a ongoing multipart request (multiple responses are expected). +type MultiRequestCtx struct { + ch *Channel +} + +const defaultReplyTimeout = time.Second * 1 // default timeout for replies from VPP, can be changed with SetReplyTimeout + +// NewChannelInternal returns a new channel structure with metadata field filled in with the provided argument. +// Note that this is just a raw channel not yet connected to VPP, it is not intended to be used directly. +// Use ChannelProvider to get an API channel ready for communication with VPP. +func NewChannelInternal(metadata interface{}) *Channel { + return &Channel{ + replyTimeout: defaultReplyTimeout, + metadata: metadata, + } +} + +// Metadata returns the metadata stored within the channel structure by the NewChannelInternal call. +func (ch *Channel) Metadata() interface{} { + return ch.metadata +} + +// SetReplyTimeout sets the timeout for replies from VPP. It represents the maximum time the API waits for a reply +// from VPP before returning an error. +func (ch *Channel) SetReplyTimeout(timeout time.Duration) { + ch.replyTimeout = timeout +} + +// Close closes the API channel and releases all API channel-related resources in the ChannelProvider. +func (ch *Channel) Close() { + if ch.ReqChan != nil { + close(ch.ReqChan) + } +} + +// SendRequest asynchronously sends a request to VPP. Returns a request context, that can be used to call ReceiveReply. +// In case of any errors by sending, the error will be delivered to ReplyChan (and returned by ReceiveReply). +func (ch *Channel) SendRequest(msg Message) *RequestCtx { + ch.ReqChan <- &VppRequest{ + Message: msg, + } + return &RequestCtx{ch: ch} +} + +// ReceiveReply receives a reply from VPP (blocks until a reply is delivered from VPP, or until an error occurs). +// The reply will be decoded into the msg argument. Error will be returned if the response cannot be received or decoded. +func (req *RequestCtx) ReceiveReply(msg Message) error { + if req == nil || req.ch == nil { + return errors.New("invalid request context") + } + + lastReplyReceived, err := req.ch.receiveReplyInternal(msg) + + if lastReplyReceived { + err = errors.New("multipart reply recieved while a simple reply expected") + } + return err +} + +// SendMultiRequest asynchronously sends a multipart request (request to which multiple responses are expected) to VPP. +// Returns a multipart request context, that can be used to call ReceiveReply. +// In case of any errors by sending, the error will be delivered to ReplyChan (and returned by ReceiveReply). +func (ch *Channel) SendMultiRequest(msg Message) *MultiRequestCtx { + ch.ReqChan <- &VppRequest{ + Message: msg, + Multipart: true, + } + return &MultiRequestCtx{ch: ch} +} + +// ReceiveReply receives a reply from VPP (blocks until a reply is delivered from VPP, or until an error occurs). +// The reply will be decoded into the msg argument. If the last reply has been already consumed, LastReplyReceived is +// set to true. Do not use the message itself if LastReplyReceived is true - it won't be filled with actual data. +// Error will be returned if the response cannot be received or decoded. +func (req *MultiRequestCtx) ReceiveReply(msg Message) (LastReplyReceived bool, err error) { + if req == nil || req.ch == nil { + return false, errors.New("invalid request context") + } + + return req.ch.receiveReplyInternal(msg) +} + +// receiveReplyInternal receives a reply from the reply channel into the provided msg structure. +func (ch *Channel) receiveReplyInternal(msg Message) (LastReplyReceived bool, err error) { + if msg == nil { + return false, errors.New("nil message passed in") + } + select { + // blocks until a reply comes to ReplyChan or until timeout expires + case vppReply := <-ch.ReplyChan: + if vppReply.Error != nil { + err = vppReply.Error + return + } + if vppReply.LastReplyReceived { + LastReplyReceived = true + return + } + // message checks + expMsgID, err := ch.MsgIdentifier.GetMessageID(msg) + if err != nil { + err = fmt.Errorf("message %s with CRC %s is not compatible with the VPP we are connected to", + msg.GetMessageName(), msg.GetCrcString()) + return false, err + } + if vppReply.MessageID != expMsgID { + err = fmt.Errorf("invalid message ID %d, expected %d", vppReply.MessageID, expMsgID) + return false, err + } + // decode the message + err = ch.MsgDecoder.DecodeMsg(vppReply.Data, msg) + + case <-time.After(ch.replyTimeout): + err = fmt.Errorf("no reply received within the timeout period %ds", ch.replyTimeout/time.Second) + } + return +} + +// SubscribeNotification subscribes for receiving of the specified notification messages via provided Go channel. +// Note that the caller is responsible for creating the Go channel with preferred buffer size. If the channel's +// buffer is full, the notifications will not be delivered into it. +func (ch *Channel) SubscribeNotification(notifChan chan Message, msgFactory func() Message) (*NotifSubscription, error) { + subscription := &NotifSubscription{ + NotifChan: notifChan, + MsgFactory: msgFactory, + } + ch.NotifSubsChan <- &NotifSubscribeRequest{ + Subscription: subscription, + Subscribe: true, + } + return subscription, <-ch.NotifSubsReplyChan +} + +// UnsubscribeNotification unsubscribes from receiving the notifications tied to the provided notification subscription. +func (ch *Channel) UnsubscribeNotification(subscription *NotifSubscription) error { + ch.NotifSubsChan <- &NotifSubscribeRequest{ + Subscription: subscription, + Subscribe: false, + } + return <-ch.NotifSubsReplyChan +} + +// CheckMessageCompatibility checks whether provided messages are compatible with the version of VPP +// which the library is connected to. +func (ch *Channel) CheckMessageCompatibility(messages ...Message) error { + for _, msg := range messages { + _, err := ch.MsgIdentifier.GetMessageID(msg) + if err != nil { + return fmt.Errorf("message %s with CRC %s is not compatible with the VPP we are connected to", + msg.GetMessageName(), msg.GetCrcString()) + } + } + return nil +} diff --git a/api/api_test.go b/api/api_test.go new file mode 100644 index 0000000..15d706d --- /dev/null +++ b/api/api_test.go @@ -0,0 +1,322 @@ +// 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 api_test + +import ( + "testing" + "time" + + "gerrit.fd.io/r/govpp" + "gerrit.fd.io/r/govpp/adapter/mock" + "gerrit.fd.io/r/govpp/api" + "gerrit.fd.io/r/govpp/core" + "gerrit.fd.io/r/govpp/core/bin_api/vpe" + "gerrit.fd.io/r/govpp/examples/bin_api/interfaces" + "gerrit.fd.io/r/govpp/examples/bin_api/memif" + "gerrit.fd.io/r/govpp/examples/bin_api/tap" + + . "github.com/onsi/gomega" +) + +type testCtx struct { + mockVpp *mock.VppAdapter + conn *core.Connection + ch *api.Channel +} + +func setupTest(t *testing.T) *testCtx { + RegisterTestingT(t) + + ctx := &testCtx{} + ctx.mockVpp = &mock.VppAdapter{} + govpp.SetAdapter(ctx.mockVpp) + + var err error + ctx.conn, err = govpp.Connect() + 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, + Key: 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 + for i := 1; i <= 10; i++ { + byteName := []byte("dev-name-test") + ctx.mockVpp.MockReply(&tap.SwInterfaceTapDetails{ + SwIfIndex: uint32(i), + DevName: byteName, + }) + } + 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 + for i := 1; i <= 10; i++ { + ctx.mockVpp.MockReply(&memif.MemifDetails{ + SwIfIndex: uint32(i), + }) + } + 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 + notif := (<-notifChan).(*interfaces.SwInterfaceSetFlags) + + // verify the received notifications + Expect(notif).ShouldNot(BeNil()) + 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 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 TestReceiveReplyNegative(t *testing.T) { + ctx := setupTest(t) + defer ctx.teardownTest() + + // invalid context 1 + reqCtx1 := &api.RequestCtx{} + err := reqCtx1.ReceiveReply(&vpe.ControlPingReply{}) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid request context")) + + // invalid context 2 + reqCtx2 := &api.MultiRequestCtx{} + _, err = reqCtx2.ReceiveReply(&vpe.ControlPingReply{}) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid request context")) + + // NU + reqCtx3 := &api.RequestCtx{} + err = reqCtx3.ReceiveReply(nil) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid request context")) +} diff --git a/api/doc.go b/api/doc.go new file mode 100644 index 0000000..e1abffe --- /dev/null +++ b/api/doc.go @@ -0,0 +1,94 @@ +// Package api provides API for communication with govpp core using Go channels, +// without the need of importing the govpp core package itself. +// +// The API offers two ways of communication with govpp core: using Go channels, or using convenient function +// wrappers over the Go channels. The latter should be sufficient for most of the use cases. +// +// The entry point to the API is the Channel structure, that can be obtained from the existing connection using +// the NewAPIChannel or NewAPIChannelBuffered functions: +// +// conn, err := govpp.Connect() +// if err != nil { +// // handle error! +// } +// defer conn.Disconnect() +// +// ch, err := conn.NewAPIChannel() +// if err != nil { +// // handle error! +// } +// defer ch.Close() +// +// +// Simple Request-Reply API +// +// The simple version of the API is based on blocking SendRequest / ReceiveReply calls, where a single request +// message is sent to VPP and a single reply message is filled in when the reply comes from VPP: +// +// req := &acl.ACLPluginGetVersion{} +// reply := &acl.ACLPluginGetVersionReply{} +// +// err := ch.SendRequest(req).ReceiveReply(reply) +// // process the reply +// +// Note that if the reply message type that comes from VPP does not match with provided one, you'll get an error. +// +// +// Multipart Reply API +// +// If multiple messages are expected as a reply to a request, SendMultiRequest API must be used: +// +// req := &interfaces.SwInterfaceDump{} +// reqCtx := ch.SendMultiRequest(req) +// +// for { +// reply := &interfaces.SwInterfaceDetails{} +// stop, err := reqCtx.ReceiveReply(reply) +// if stop { +// break // break out of the loop +// } +// // process the reply +// } +// +// Note that if the last reply has been already consumed, stop boolean return value is set to true. +// Do not use the message itself if stop is true - it won't be filled with actual data. +// +// +// Go Channels API +// +// The blocking API introduced above may be not sufficient for some management applications that strongly +// rely on usage of Go channels. In this case, the API allows to access the underlying Go channels directly, e.g. +// the following replacement of the SendRequest / ReceiveReply API: +// +// req := &acl.ACLPluginGetVersion{} +// // send the request to the request go channel +// ch.ReqChan <- &api.VppRequest{Message: req} +// +// // receive a reply from the reply go channel +// vppReply := <-ch.ReplyChan +// +// // decode the message +// reply := &acl.ACLPluginGetVersionReply{} +// err := ch.MsgDecoder.DecodeMsg(vppReply.Data, reply) +// +// // process the reply +// +// +// Notifications API +// +// to subscribe for receiving of the specified notification messages via provided Go channel, use the +// SubscribeNotification API: +// +// // subscribe for specific notification message +// notifChan := make(chan api.Message, 100) +// subs, _ := ch.SubscribeNotification(notifChan, interfaces.NewSwInterfaceSetFlags) +// +// // receive one notification +// notif := (<-notifChan).(*interfaces.SwInterfaceSetFlags) +// +// ch.UnsubscribeNotification(subs) +// +// Note that the caller is responsible for creating the Go channel with preferred buffer size. If the channel's +// buffer is full, the notifications will not be delivered into it. +// +package api diff --git a/api/ifcounters/doc.go b/api/ifcounters/doc.go new file mode 100644 index 0000000..c918941 --- /dev/null +++ b/api/ifcounters/doc.go @@ -0,0 +1,4 @@ +// Package ifcounters provides the helper API for decoding VnetInterfaceCounters binary API message +// that contains binary-encoded statistics data into the Go structs that are better consumable by the Go code. +// TODO: example usage - currently in the example_client.go +package ifcounters diff --git a/api/ifcounters/ifcounters.go b/api/ifcounters/ifcounters.go new file mode 100644 index 0000000..202fe6e --- /dev/null +++ b/api/ifcounters/ifcounters.go @@ -0,0 +1,147 @@ +// 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 ifcounters + +import ( + "bytes" + "errors" + "fmt" + + "github.com/lunixbochs/struc" +) + +// VnetInterfaceCounters is the input data type defined in the 'interface.api', with binary encoded Data field, +// that can be decoded into the InterfaceCounter or CombinedInterfaceCounter struct using this package. +type VnetInterfaceCounters struct { + VnetCounterType uint8 + IsCombined uint8 + FirstSwIfIndex uint32 + Count uint32 `struc:"sizeof=Data"` + Data []byte +} + +// CounterType is the basic counter type - contains only packet statistics. +type CounterType int + +// constants as defined in the vnet_interface_counter_type_t enum in 'vnet/interface.h' +const ( + Drop CounterType = 0 + Punt = 1 + IPv4 = 2 + IPv6 = 3 + RxNoBuf = 4 + RxMiss = 5 + RxError = 6 + TxError = 7 + MPLS = 8 +) + +// CombinedCounterType is the extended counter type - contains both packet and byte statistics. +type CombinedCounterType int + +// constants as defined in the vnet_interface_counter_type_t enum in 'vnet/interface.h' +const ( + Rx CombinedCounterType = 0 + Tx = 1 +) + +// InterfaceCounter contains basic counter data (contains only packet statistics). +type InterfaceCounter struct { + Type CounterType + SwIfIndex uint32 + Packets uint64 +} + +// CombinedInterfaceCounter contains extended counter data (contains both packet and byte statistics). +type CombinedInterfaceCounter struct { + Type CombinedCounterType + SwIfIndex uint32 + Packets uint64 + Bytes uint64 +} + +type counterData struct { + Packets uint64 +} + +type counter struct { + Count uint32 `struc:"sizeof=Data"` + Data []counterData +} + +type combinedCounterData struct { + Packets uint64 + Bytes uint64 +} + +type combinedCounter struct { + Count uint32 `struc:"sizeof=Data"` + Data []combinedCounterData +} + +// DecodeCounters decodes VnetInterfaceCounters struct content into the slice of InterfaceCounter structs. +func DecodeCounters(vnetCounters VnetInterfaceCounters) ([]InterfaceCounter, error) { + if vnetCounters.IsCombined == 1 { + return nil, errors.New("invalid argument - combined counter passed in") + } + + // decode into internal struct + var c counter + buf := bytes.NewReader(vnetCounters.Data) + err := struc.Unpack(buf, &c) + if err != nil { + return nil, fmt.Errorf("unable to decode counter data: %v", err) + } + + // prepare the slice + res := make([]InterfaceCounter, c.Count) + + // fill in the slice + for i := uint32(0); i < c.Count; i++ { + res[i].Type = CounterType(vnetCounters.VnetCounterType) + res[i].SwIfIndex = vnetCounters.FirstSwIfIndex + i + res[i].Packets = c.Data[i].Packets + } + + return res, nil +} + +// DecodeCombinedCounters decodes VnetInterfaceCounters struct content into the slice of CombinedInterfaceCounter structs. +func DecodeCombinedCounters(vnetCounters VnetInterfaceCounters) ([]CombinedInterfaceCounter, error) { + if vnetCounters.IsCombined != 1 { + return nil, errors.New("invalid argument - simple counter passed in") + } + + // decode into internal struct + var c combinedCounter + buf := bytes.NewReader(vnetCounters.Data) + err := struc.Unpack(buf, &c) + if err != nil { + return nil, fmt.Errorf("unable to decode counter data: %v", err) + } + + // prepare the slice + res := make([]CombinedInterfaceCounter, c.Count) + + // fill in the slice + for i := uint32(0); i < c.Count; i++ { + res[i].Type = CombinedCounterType(vnetCounters.VnetCounterType) + res[i].SwIfIndex = vnetCounters.FirstSwIfIndex + i + res[i].Packets = c.Data[i].Packets + res[i].Bytes = c.Data[i].Bytes + } + + return res, nil +} diff --git a/api/ifcounters/ifcounters_test.go b/api/ifcounters/ifcounters_test.go new file mode 100644 index 0000000..6f9a1d9 --- /dev/null +++ b/api/ifcounters/ifcounters_test.go @@ -0,0 +1,129 @@ +// 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 ifcounters + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestDecodeCounters(t *testing.T) { + RegisterTestingT(t) + + testCounters := VnetInterfaceCounters{ + VnetCounterType: 1, + IsCombined: 0, + FirstSwIfIndex: 5, + Count: 2, + Data: []byte{0, 0, 0, 2, // Count + 0, 0, 0, 0, 0, 0, 0, 10, // first counter + 0, 0, 0, 0, 0, 0, 0, 11}, // second counter + } + counters, err := DecodeCounters(testCounters) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(counters)).To(BeEquivalentTo(2), "Incorrect size of the returned slice.") + + Expect(counters[0].Type).To(BeEquivalentTo(1), "Incorrect counter type.") + Expect(counters[0].SwIfIndex).To(BeEquivalentTo(5), "Incorrect SwIfIndex.") + Expect(counters[0].Packets).To(BeEquivalentTo(10), "Incorrect Packets count.") + + Expect(counters[1].Type).To(BeEquivalentTo(1), "Incorrect counter type.") + Expect(counters[1].SwIfIndex).To(BeEquivalentTo(6), "Incorrect SwIfIndex.") + Expect(counters[1].Packets).To(BeEquivalentTo(11), "Incorrect Packets count.") +} + +func TestDecodeCombinedCounters(t *testing.T) { + RegisterTestingT(t) + + testCounters := VnetInterfaceCounters{ + VnetCounterType: 1, + IsCombined: 1, + FirstSwIfIndex: 20, + Count: 2, + Data: []byte{0, 0, 0, 2, // Count + 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 11, // first counter + 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 13}, // second counter + } + counters, err := DecodeCombinedCounters(testCounters) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(counters)).To(BeEquivalentTo(2), "Incorrect size of the returned slice.") + + Expect(counters[0].Type).To(BeEquivalentTo(1), "Incorrect counter type.") + Expect(counters[0].SwIfIndex).To(BeEquivalentTo(20), "Incorrect SwIfIndex.") + Expect(counters[0].Packets).To(BeEquivalentTo(10), "Incorrect Packets count.") + Expect(counters[0].Bytes).To(BeEquivalentTo(11), "Incorrect Bytes count.") + + Expect(counters[1].Type).To(BeEquivalentTo(1), "Incorrect counter type.") + Expect(counters[1].SwIfIndex).To(BeEquivalentTo(21), "Incorrect SwIfIndex.") + Expect(counters[1].Packets).To(BeEquivalentTo(12), "Incorrect Packets count.") + Expect(counters[1].Bytes).To(BeEquivalentTo(13), "Incorrect Bytes count.") +} + +func TestDecodeCountersNegative1(t *testing.T) { + RegisterTestingT(t) + + testCounters := VnetInterfaceCounters{ + IsCombined: 1, // invalid, should be 0 + } + counters, err := DecodeCounters(testCounters) + + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid argument")) + Expect(counters).To(BeNil()) +} + +func TestDecodeCombinedCountersNegative1(t *testing.T) { + RegisterTestingT(t) + + testCounters := VnetInterfaceCounters{ + IsCombined: 0, // invalid, should be 1 + } + counters, err := DecodeCombinedCounters(testCounters) + + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid argument")) + Expect(counters).To(BeNil()) +} + +func TestDecodeCountersNegative2(t *testing.T) { + RegisterTestingT(t) + + testCounters := VnetInterfaceCounters{ + IsCombined: 0, + // no data + } + counters, err := DecodeCounters(testCounters) + + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to decode")) + Expect(counters).To(BeNil()) +} + +func TestDecodeCombinedCountersNegative2(t *testing.T) { + RegisterTestingT(t) + + testCounters := VnetInterfaceCounters{ + IsCombined: 1, + // no data + } + counters, err := DecodeCombinedCounters(testCounters) + + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to decode")) + Expect(counters).To(BeNil()) +} |