1 // Copyright (c) 2017 Cisco and/or its affiliates.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // Package mock is an alternative VPP adapter aimed for unit/integration testing where the
16 // actual communication with VPP is not demanded.
25 "git.fd.io/govpp.git/adapter"
26 "git.fd.io/govpp.git/adapter/mock/binapi"
27 "git.fd.io/govpp.git/api"
28 "git.fd.io/govpp.git/core"
30 "github.com/lunixbochs/struc"
37 useRepliesQueue = 1 // use replies in the queue
38 useReplyHandlers = 2 // use reply handler
41 // VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
42 type VppAdapter struct {
43 callback func(context uint32, msgId uint16, data []byte)
45 msgNameToIds map[string]uint16
46 msgIDsToName map[uint16]string
48 binAPITypes map[string]reflect.Type
51 replies []api.Message // FIFO queue of messages
52 replyHandlers []ReplyHandler // callbacks that are able to calculate mock responses
53 repliesLock sync.Mutex // mutex for the queue
54 mode ReplyMode // mode in which the mock operates
57 // defaultReply is a default reply message that mock adapter returns for a request.
58 type defaultReply struct {
62 // MessageDTO is a structure used for propagating information to ReplyHandlers.
63 type MessageDTO struct {
70 // ReplyHandler is a type that allows to extend the behaviour of VPP mock.
71 // Return value ok is used to signalize that mock reply is calculated and ready to be used.
72 type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, ok bool)
75 defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
78 // NewVppAdapter returns a new mock adapter.
79 func NewVppAdapter() adapter.VppAdapter {
83 // Connect emulates connecting the process to VPP.
84 func (a *VppAdapter) Connect() error {
88 // Disconnect emulates disconnecting the process from VPP.
89 func (a *VppAdapter) Disconnect() {
93 // GetMsgNameByID returns message name for specified message ID.
94 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
97 return "control_ping", true
99 return "control_ping_reply", true
101 return "sw_interface_dump", true
103 return "sw_interface_details", true
107 defer a.access.Unlock()
109 msgName, found := a.msgIDsToName[msgID]
111 return msgName, found
114 // RegisterBinAPITypes registers binary API message types in the mock adapter.
115 func (a *VppAdapter) RegisterBinAPITypes(binAPITypes map[string]reflect.Type) {
117 defer a.access.Unlock()
119 for _, v := range binAPITypes {
120 if msg, ok := reflect.New(v).Interface().(api.Message); ok {
121 a.binAPITypes[msg.GetMessageName()] = v
126 // ReplyTypeFor returns reply message type for given request message name.
127 func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
128 replyName, foundName := binapi.ReplyNameFor(requestMsgName)
130 if reply, found := a.binAPITypes[replyName]; found {
131 msgID, err := a.GetMsgID(replyName, "")
133 return reply, msgID, found
141 // ReplyFor returns reply message for given request message name.
142 func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
143 replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
145 msgVal := reflect.New(replType)
146 if msg, ok := msgVal.Interface().(api.Message); ok {
147 log.Println("FFF ", replType, msgID, foundReplType)
148 return msg, msgID, true
155 // ReplyBytes encodes the mocked reply into binary format.
156 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
157 replyMsgID, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
159 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
163 log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
165 buf := new(bytes.Buffer)
166 struc.Pack(buf, &core.VppReplyHeader{VlMsgID: replyMsgID, Context: request.ClientID})
167 struc.Pack(buf, reply)
169 return buf.Bytes(), nil
172 // GetMsgID returns mocked message ID for the given message name and CRC.
173 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
177 case "control_ping_reply":
179 case "sw_interface_dump":
181 case "sw_interface_details":
186 defer a.access.Unlock()
189 msgID, found := a.msgNameToIds[msgName]
196 a.msgNameToIds[msgName] = msgID
197 a.msgIDsToName[msgID] = msgName
199 log.Println("VPP GetMessageId ", msgID, " name:", msgName, " crc:", msgCrc)
204 // initMaps initializes internal maps (if not already initialized).
205 func (a *VppAdapter) initMaps() {
206 if a.msgIDsToName == nil {
207 a.msgIDsToName = map[uint16]string{}
208 a.msgNameToIds = map[string]uint16{}
212 if a.binAPITypes == nil {
213 a.binAPITypes = map[string]reflect.Type{}
217 // SendMsg emulates sending a binary-encoded message to VPP.
218 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
220 case useReplyHandlers:
222 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
223 replyHandler := a.replyHandlers[i]
225 buf := bytes.NewReader(data)
226 reqHeader := core.VppRequestHeader{}
227 struc.Unpack(buf, &reqHeader)
230 reqMsgName := a.msgIDsToName[reqHeader.VlMsgID]
233 reply, msgID, finished := replyHandler(MessageDTO{
234 MsgID: reqHeader.VlMsgID,
240 a.callback(clientID, msgID, reply)
245 case useRepliesQueue:
247 defer a.repliesLock.Unlock()
249 // pop all replies from queue
250 for i, reply := range a.replies {
251 if i > 0 && reply.GetMessageName() == "control_ping_reply" {
252 // hack - do not send control_ping_reply immediately, leave it for the the next callback
253 a.replies = []api.Message{}
254 a.replies = append(a.replies, reply)
257 msgID, _ := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
258 buf := new(bytes.Buffer)
259 if reply.GetMessageType() == api.ReplyMessage {
260 struc.Pack(buf, &core.VppReplyHeader{VlMsgID: msgID, Context: clientID})
261 } else if reply.GetMessageType() == api.EventMessage {
262 struc.Pack(buf, &core.VppEventHeader{VlMsgID: msgID, Context: clientID})
263 } else if reply.GetMessageType() == api.RequestMessage {
264 struc.Pack(buf, &core.VppRequestHeader{VlMsgID: msgID, Context: clientID})
266 struc.Pack(buf, &core.VppOtherHeader{VlMsgID: msgID})
268 struc.Pack(buf, reply)
269 a.callback(clientID, msgID, buf.Bytes())
271 if len(a.replies) > 0 {
272 a.replies = []api.Message{}
273 if len(a.replyHandlers) > 0 {
274 // Switch back to handlers once the queue is empty to revert back
275 // the fallthrough effect.
276 a.mode = useReplyHandlers
283 // return default reply
284 buf := new(bytes.Buffer)
285 msgID := uint16(defaultReplyMsgID)
286 struc.Pack(buf, &core.VppReplyHeader{VlMsgID: msgID, Context: clientID})
287 struc.Pack(buf, &defaultReply{})
288 a.callback(clientID, msgID, buf.Bytes())
293 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
294 func (a *VppAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
298 // WaitReady mocks waiting for VPP
299 func (a *VppAdapter) WaitReady() error {
303 // MockReply stores a message to be returned when the next request comes. It is a FIFO queue - multiple replies
304 // can be pushed into it, the first one will be popped when some request comes.
305 // Using of this method automatically switches the mock into th useRepliesQueue mode.
306 func (a *VppAdapter) MockReply(msg api.Message) {
308 defer a.repliesLock.Unlock()
310 a.replies = append(a.replies, msg)
311 a.mode = useRepliesQueue
314 // MockReplyHandler registers a handler function that is supposed to generate mock responses to incoming requests.
315 // Using of this method automatically switches the mock into th useReplyHandlers mode.
316 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
318 defer a.repliesLock.Unlock()
320 a.replyHandlers = append(a.replyHandlers, replyHandler)
321 a.mode = useReplyHandlers