aboutsummaryrefslogtreecommitdiffstats
path: root/adapter
diff options
context:
space:
mode:
authorRastislav Szabo <raszabo@cisco.com>2017-05-04 11:09:03 +0200
committerRastislav Szabo <raszabo@cisco.com>2017-05-04 11:12:35 +0200
commita101d966133a70b8a76526be25070436d14fcf9f (patch)
tree75e2dbf20de615e58252b780b2ba5baae8fdcf82 /adapter
parenta968ead74525125dff9ae90b1c9a9102e4327900 (diff)
initial commit
Signed-off-by: Rastislav Szabo <raszabo@cisco.com>
Diffstat (limited to 'adapter')
-rw-r--r--adapter/adapter.go33
-rw-r--r--adapter/doc.go9
-rw-r--r--adapter/mock/binapi_reflect/binapi_reflect.go74
-rw-r--r--adapter/mock/mock_adapter.go302
-rw-r--r--adapter/vppapiclient/empty_adapter.go52
-rw-r--r--adapter/vppapiclient/vppapiclient_adapter.go149
6 files changed, 619 insertions, 0 deletions
diff --git a/adapter/adapter.go b/adapter/adapter.go
new file mode 100644
index 0000000..2843d2c
--- /dev/null
+++ b/adapter/adapter.go
@@ -0,0 +1,33 @@
+// 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 adapter
+
+// VppAdapter provides connection to VPP. It is responsible for sending and receiving of binary-encoded messages to/from VPP.
+type VppAdapter interface {
+ // Connect connects the process to VPP.
+ Connect() error
+
+ // Disconnect disconnects the process from VPP.
+ Disconnect()
+
+ // GetMsgID returns a runtime message ID for the given message name and CRC.
+ GetMsgID(msgName string, msgCrc string) (uint16, error)
+
+ // SendMsg sends a binary-encoded message to VPP.
+ SendMsg(clientID uint32, data []byte) error
+
+ // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP.
+ SetMsgCallback(func(context uint32, msgId uint16, data []byte))
+}
diff --git a/adapter/doc.go b/adapter/doc.go
new file mode 100644
index 0000000..5b0ac8a
--- /dev/null
+++ b/adapter/doc.go
@@ -0,0 +1,9 @@
+// Package adapter provides an interface between govpp core and the VPP. It is responsible for sending
+// and receiving binary-encoded data to/from VPP via shared memory.
+//
+// The default adapter being used for connection with real VPP is called vppapiclient. It is based on the
+// communication with the vppapiclient VPP library written in C via CGO.
+//
+// Apart from the vppapiclient adapter, mock adapter is provided for unit/integration testing where the actual
+// communication with VPP is not demanded.
+package adapter
diff --git a/adapter/mock/binapi_reflect/binapi_reflect.go b/adapter/mock/binapi_reflect/binapi_reflect.go
new file mode 100644
index 0000000..f860150
--- /dev/null
+++ b/adapter/mock/binapi_reflect/binapi_reflect.go
@@ -0,0 +1,74 @@
+// 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 binapi_reflect is a helper package for generic handling of VPP binary API messages
+// in the mock adapter and integration tests.
+package binapi_reflect
+
+import (
+ "reflect"
+)
+
+const SwIfIndex = "SwIfIndex"
+const Retval = "Retval"
+const Reply = "_reply"
+
+// TODO comment
+func FindFieldOfType(reply reflect.Type, fieldName string) (reflect.StructField, bool) {
+ if reply.Kind() == reflect.Struct {
+ field, found := reply.FieldByName(fieldName)
+ return field, found
+ } else if reply.Kind() == reflect.Ptr && reply.Elem().Kind() == reflect.Struct {
+ field, found := reply.Elem().FieldByName(fieldName)
+ return field, found
+ }
+ return reflect.StructField{}, false
+}
+
+// TODO comment
+func FindFieldOfValue(reply reflect.Value, fieldName string) (reflect.Value, bool) {
+ if reply.Kind() == reflect.Struct {
+ field := reply.FieldByName(fieldName)
+ return field, field.IsValid()
+ } else if reply.Kind() == reflect.Ptr && reply.Elem().Kind() == reflect.Struct {
+ field := reply.Elem().FieldByName(fieldName)
+ return field, field.IsValid()
+ }
+ return reflect.Value{}, false
+}
+
+// TODO comment
+func IsReplySwIfIdx(reply reflect.Type) bool {
+ _, found := FindFieldOfType(reply, SwIfIndex)
+ return found
+}
+
+// TODO comment
+func SetSwIfIdx(reply reflect.Value, swIfIndex uint32) {
+ if field, found := FindFieldOfValue(reply, SwIfIndex); found {
+ field.Set(reflect.ValueOf(swIfIndex))
+ }
+}
+
+// TODO comment
+func SetRetVal(reply reflect.Value, retVal int32) {
+ if field, found := FindFieldOfValue(reply, Retval); found {
+ field.Set(reflect.ValueOf(retVal))
+ }
+}
+
+// TODO comment
+func ReplyNameFor(request string) (string, bool) {
+ return request + Reply, true
+}
diff --git a/adapter/mock/mock_adapter.go b/adapter/mock/mock_adapter.go
new file mode 100644
index 0000000..cc3be07
--- /dev/null
+++ b/adapter/mock/mock_adapter.go
@@ -0,0 +1,302 @@
+// 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 mock is an alternative VPP adapter aimed for unit/integration testing where the
+// actual communication with VPP is not demanded.
+package mock
+
+import (
+ "bytes"
+ "log"
+ "reflect"
+ "sync"
+
+ "github.com/lunixbochs/struc"
+
+ "gerrit.fd.io/r/govpp/adapter"
+ "gerrit.fd.io/r/govpp/adapter/mock/binapi_reflect"
+ "gerrit.fd.io/r/govpp/api"
+)
+
+// VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
+type VppAdapter struct {
+ callback func(context uint32, msgId uint16, data []byte)
+
+ msgNameToIds *map[string]uint16
+ msgIdsToName *map[uint16]string
+ msgIdSeq uint16
+ binApiTypes map[string]reflect.Type
+ //TODO lock
+}
+
+// replyHeader represents a common header of each VPP request message.
+type requestHeader struct {
+ VlMsgID uint16
+ ClientIndex uint32
+ Context uint32
+}
+
+// replyHeader represents a common header of each VPP reply message.
+type replyHeader struct {
+ VlMsgID uint16
+ Context uint32
+}
+
+// replyHeader represents a common header of each VPP reply message.
+type vppOtherHeader struct {
+ VlMsgID uint16
+}
+
+// defaultReply is a default reply message that mock adapter returns for a request.
+type defaultReply struct {
+ Retval int32
+}
+
+// MessageDTO is a structure used for propageating informations to ReplyHandlers
+type MessageDTO struct {
+ MsgID uint16
+ MsgName string
+ ClientID uint32
+ Data []byte
+}
+
+// ReplyHandler is a type that allows to extend the behaviour of VPP mock.
+// Return value prepared is used to signalize that mock reply is calculated.
+type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, prepared bool)
+
+const (
+ //defaultMsgID = 1 // default message ID to be returned from GetMsgId
+ defaultReplyMsgID = 2 // default message ID for the reply to be sent back via callback
+)
+
+var replies []api.Message // FIFO queue of messages
+var replyHandlers []ReplyHandler // callbacks that are able to calculate mock responses
+var repliesLock sync.Mutex // mutex for the queue
+var mode = 0
+
+const useRepliesQueue = 1 // use replies in the queue instead of the default one
+const useReplyHandlers = 2 //use ReplyHandler
+
+// NewVppAdapter returns a new mock adapter.
+func NewVppAdapter() adapter.VppAdapter {
+ return &VppAdapter{}
+}
+
+// Connect emulates connecting the process to VPP.
+func (a *VppAdapter) Connect() error {
+ return nil
+}
+
+// Disconnect emulates disconnecting the process from VPP.
+func (a *VppAdapter) Disconnect() {
+ // no op
+}
+
+func (a *VppAdapter) GetMsgNameByID(msgId uint16) (string, bool) {
+ a.initMaps()
+
+ switch msgId {
+ case 100:
+ return "control_ping", true
+ case 101:
+ return "control_ping_reply", true
+ case 200:
+ return "sw_interface_dump", true
+ case 201:
+ return "sw_interface_details", true
+ }
+
+ msgName, found := (*a.msgIdsToName)[msgId]
+
+ return msgName, found
+}
+
+func (a *VppAdapter) RegisterBinApiTypes(binApiTypes map[string]reflect.Type) {
+ a.initMaps()
+ for _, v := range binApiTypes {
+ if msg, ok := reflect.New(v).Interface().(api.Message); ok {
+ a.binApiTypes[msg.GetMessageName()] = v
+ }
+ }
+}
+
+func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
+ replyName, foundName := binapi_reflect.ReplyNameFor(requestMsgName)
+ if foundName {
+ if reply, found := a.binApiTypes[replyName]; found {
+ msgID, err := a.GetMsgID(replyName, "")
+ if err == nil {
+ return reply, msgID, found
+ }
+ }
+ }
+
+ return nil, 0, false
+}
+
+func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
+ replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
+ if foundReplType {
+ msgVal := reflect.New(replType)
+ if msg, ok := msgVal.Interface().(api.Message); ok {
+ log.Println("FFF ", replType, msgID, foundReplType)
+ return msg, msgID, true
+ }
+ }
+
+ return nil, 0, false
+}
+
+func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
+ replyMsgId, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
+ if err != nil {
+ log.Println("ReplyBytesE ", replyMsgId, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
+ " ", err)
+ return nil, err
+ }
+ log.Println("ReplyBytes ", replyMsgId, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
+
+ buf := new(bytes.Buffer)
+ struc.Pack(buf, &replyHeader{VlMsgID: replyMsgId, Context: request.ClientID})
+ struc.Pack(buf, reply)
+
+ return buf.Bytes(), nil
+}
+
+// GetMsgID returns mocked message ID for the given message name and CRC.
+func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
+ switch msgName {
+ case "control_ping":
+ return 100, nil
+ case "control_ping_reply":
+ return 101, nil
+ case "sw_interface_dump":
+ return 200, nil
+ case "sw_interface_details":
+ return 201, nil
+ }
+
+ a.initMaps()
+
+ if msgId, found := (*a.msgNameToIds)[msgName]; found {
+ return msgId, nil
+ } else {
+ a.msgIdSeq++
+ msgId = a.msgIdSeq
+ (*a.msgNameToIds)[msgName] = msgId
+ (*a.msgIdsToName)[msgId] = msgName
+
+ log.Println("VPP GetMessageId ", msgId, " name:", msgName, " crc:", msgCrc)
+
+ return msgId, nil
+ }
+}
+
+func (a *VppAdapter) initMaps() {
+ if a.msgIdsToName == nil {
+ a.msgIdsToName = &map[uint16]string{}
+ a.msgNameToIds = &map[string]uint16{}
+ a.msgIdSeq = 1000
+ }
+
+ if a.binApiTypes == nil {
+ a.binApiTypes = map[string]reflect.Type{}
+ }
+}
+
+// SendMsg emulates sending a binary-encoded message to VPP.
+func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
+ switch mode {
+ case useReplyHandlers:
+ for i := len(replyHandlers) - 1; i >= 0; i-- {
+ replyHandler := replyHandlers[i]
+
+ buf := bytes.NewReader(data)
+ reqHeader := requestHeader{}
+ struc.Unpack(buf, &reqHeader)
+
+ a.initMaps()
+ reqMsgName, _ := (*a.msgIdsToName)[reqHeader.VlMsgID]
+
+ reply, msgID, finished := replyHandler(MessageDTO{reqHeader.VlMsgID, reqMsgName,
+ clientID, data})
+ if finished {
+ a.callback(clientID, msgID, reply)
+ return nil
+ }
+ }
+ fallthrough
+ case useRepliesQueue:
+ repliesLock.Lock()
+ defer repliesLock.Unlock()
+
+ // pop all replies from queue
+ for i, reply := range replies {
+ if i > 0 && reply.GetMessageName() == "control_ping_reply" {
+ // hack - do not send control_ping_reply immediately, leave it for the the next callback
+ replies = []api.Message{}
+ replies = append(replies, reply)
+ return nil
+ }
+ msgID, _ := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
+ buf := new(bytes.Buffer)
+ if reply.GetMessageType() == api.ReplyMessage {
+ struc.Pack(buf, &replyHeader{VlMsgID: msgID, Context: clientID})
+ } else {
+ struc.Pack(buf, &requestHeader{VlMsgID: msgID, Context: clientID})
+ }
+ struc.Pack(buf, reply)
+ a.callback(clientID, msgID, buf.Bytes())
+ }
+ if len(replies) > 0 {
+ replies = []api.Message{}
+ return nil
+ }
+
+ //fallthrough
+ default:
+ // return default reply
+ buf := new(bytes.Buffer)
+ msgID := uint16(defaultReplyMsgID)
+ struc.Pack(buf, &replyHeader{VlMsgID: msgID, Context: clientID})
+ struc.Pack(buf, &defaultReply{})
+ a.callback(clientID, msgID, buf.Bytes())
+ }
+ return nil
+}
+
+// SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
+func (a *VppAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
+ a.callback = cb
+}
+
+// MockReply stores a message to be returned when the next request comes. It is a FIFO queue - multiple replies
+// can be pushed into it, the first one will be popped when some request comes.
+//
+// It is able to also receive callback that calculates the reply
+func (a *VppAdapter) MockReply(msg api.Message) {
+ repliesLock.Lock()
+ defer repliesLock.Unlock()
+
+ replies = append(replies, msg)
+ mode = useRepliesQueue
+}
+
+func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
+ repliesLock.Lock()
+ defer repliesLock.Unlock()
+
+ replyHandlers = append(replyHandlers, replyHandler)
+ mode = useReplyHandlers
+}
diff --git a/adapter/vppapiclient/empty_adapter.go b/adapter/vppapiclient/empty_adapter.go
new file mode 100644
index 0000000..2701c87
--- /dev/null
+++ b/adapter/vppapiclient/empty_adapter.go
@@ -0,0 +1,52 @@
+// 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.
+
+// +build windows darwin
+
+/*
+ This is just an empty adapter that does nothing. It builds only on Windows and OSX, where the real
+ VPP API client adapter does not build. Its sole purpose is to make the compiler happy on Windows and OSX.
+*/
+
+package vppapiclient
+
+import (
+ "gerrit.fd.io/r/govpp/adapter"
+)
+
+type vppAPIClientAdapter struct{}
+
+func NewVppAdapter() adapter.VppAdapter {
+ return &vppAPIClientAdapter{}
+}
+
+func (a *vppAPIClientAdapter) Connect() error {
+ return nil
+}
+
+func (a *vppAPIClientAdapter) Disconnect() {
+ // no op
+}
+
+func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
+ return 0, nil
+}
+
+func (a *vppAPIClientAdapter) SendMsg(clientID uint32, data []byte) error {
+ return nil
+}
+
+func (a *vppAPIClientAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
+ // no op
+}
diff --git a/adapter/vppapiclient/vppapiclient_adapter.go b/adapter/vppapiclient/vppapiclient_adapter.go
new file mode 100644
index 0000000..9340f58
--- /dev/null
+++ b/adapter/vppapiclient/vppapiclient_adapter.go
@@ -0,0 +1,149 @@
+// 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.
+
+// +build !windows,!darwin
+
+// Package vppapiclient is the default VPP adapter being used for the connection with VPP via shared memory.
+// It is based on the communication with the vppapiclient VPP library written in C via CGO.
+package vppapiclient
+
+/*
+#cgo CFLAGS: -DPNG_DEBUG=1
+#cgo LDFLAGS: -lvppapiclient
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <vpp-api/client/vppapiclient.h>
+
+extern void go_msg_callback(uint16_t, uint32_t, void*, size_t);
+
+typedef struct __attribute__((__packed__)) _req_header {
+ uint16_t msg_id;
+ uint32_t client_index;
+ uint32_t context;
+} req_header_t;
+
+typedef struct __attribute__((__packed__)) _reply_header {
+ uint16_t msg_id;
+ uint32_t context;
+} reply_header_t;
+
+static void
+govpp_msg_callback (unsigned char *data, int size)
+{
+ reply_header_t *header = ((reply_header_t *)data);
+ go_msg_callback(ntohs(header->msg_id), ntohl(header->context), data, size);
+}
+
+static int
+govpp_connect()
+{
+ return vac_connect("govpp", NULL, govpp_msg_callback, 32);
+}
+
+static int
+govvp_disconnect()
+{
+ return vac_disconnect();
+}
+
+static int
+govpp_send(uint32_t context, void *data, size_t size)
+{
+ req_header_t *header = ((req_header_t *)data);
+ header->context = htonl(context);
+ return vac_write(data, size);
+}
+
+static uint32_t
+govpp_get_msg_index(char *name_and_crc)
+{
+ return vac_get_msg_index(name_and_crc);
+}
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "unsafe"
+
+ "gerrit.fd.io/r/govpp/adapter"
+)
+
+// vppAPIClientAdapter is the opaque context of the adapter.
+type vppAPIClientAdapter struct {
+ callback func(context uint32, msgId uint16, data []byte)
+}
+
+var vppClient *vppAPIClientAdapter // global vpp API client adapter context
+
+// NewVppAdapter returns a new vpp API client adapter.
+func NewVppAdapter() adapter.VppAdapter {
+ return &vppAPIClientAdapter{}
+}
+
+// Connect connects the process to VPP.
+func (a *vppAPIClientAdapter) Connect() error {
+ vppClient = a
+ rc := C.govpp_connect()
+ if rc != 0 {
+ return fmt.Errorf("unable to connect to VPP (error=%d)", rc)
+ }
+ return nil
+}
+
+// Disconnect disconnects the process from VPP.
+func (a *vppAPIClientAdapter) Disconnect() {
+ C.govvp_disconnect()
+}
+
+// GetMsgID returns a runtime message ID for the given message name and CRC.
+func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
+ nameAndCrc := C.CString(fmt.Sprintf("%s_%s", msgName, msgCrc))
+ defer C.free(unsafe.Pointer(nameAndCrc))
+
+ msgID := uint16(C.govpp_get_msg_index(nameAndCrc))
+ if msgID == ^uint16(0) {
+ return msgID, errors.New("unkonwn message")
+ }
+
+ return msgID, nil
+}
+
+// SendMsg sends a binary-encoded message to VPP.
+func (a *vppAPIClientAdapter) SendMsg(clientID uint32, data []byte) error {
+ rc := C.govpp_send(C.uint32_t(clientID), unsafe.Pointer(&data[0]), C.size_t(len(data)))
+ if rc != 0 {
+ return fmt.Errorf("unable to send the message (error=%d)", rc)
+ }
+ return nil
+}
+
+// SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP.
+func (a *vppAPIClientAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
+ a.callback = cb
+}
+
+//export go_msg_callback
+func go_msg_callback(msgID C.uint16_t, context C.uint32_t, data unsafe.Pointer, size C.size_t) {
+ // convert unsafe.Pointer to byte slice
+ slice := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)}
+ byteArr := *(*[]byte)(unsafe.Pointer(slice))
+
+ vppClient.callback(uint32(context), uint16(msgID), byteArr)
+}