From a101d966133a70b8a76526be25070436d14fcf9f Mon Sep 17 00:00:00 2001 From: Rastislav Szabo Date: Thu, 4 May 2017 11:09:03 +0200 Subject: initial commit Signed-off-by: Rastislav Szabo --- adapter/adapter.go | 33 +++ adapter/doc.go | 9 + adapter/mock/binapi_reflect/binapi_reflect.go | 74 +++++++ adapter/mock/mock_adapter.go | 302 ++++++++++++++++++++++++++ adapter/vppapiclient/empty_adapter.go | 52 +++++ adapter/vppapiclient/vppapiclient_adapter.go | 149 +++++++++++++ 6 files changed, 619 insertions(+) create mode 100644 adapter/adapter.go create mode 100644 adapter/doc.go create mode 100644 adapter/mock/binapi_reflect/binapi_reflect.go create mode 100644 adapter/mock/mock_adapter.go create mode 100644 adapter/vppapiclient/empty_adapter.go create mode 100644 adapter/vppapiclient/vppapiclient_adapter.go (limited to 'adapter') 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 +#include +#include +#include +#include + +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) +} -- cgit 1.2.3-korg