Merge "Mock Adapter: Switch back to handlers once the queue is empty."
[govpp.git] / adapter / mock / mock_adapter.go
1 // Copyright (c) 2017 Cisco and/or its affiliates.
2 //
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:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 // Package mock is an alternative VPP adapter aimed for unit/integration testing where the
16 // actual communication with VPP is not demanded.
17 package mock
18
19 import (
20         "bytes"
21         "log"
22         "reflect"
23         "sync"
24
25         "git.fd.io/govpp.git/core"
26         "github.com/lunixbochs/struc"
27
28         "git.fd.io/govpp.git/adapter"
29         "git.fd.io/govpp.git/adapter/mock/binapi"
30         "git.fd.io/govpp.git/api"
31 )
32
33 // VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
34 type VppAdapter struct {
35         callback func(context uint32, msgId uint16, data []byte)
36
37         msgNameToIds map[string]uint16
38         msgIDsToName map[uint16]string
39         msgIDSeq     uint16
40         binAPITypes  map[string]reflect.Type
41         access       sync.RWMutex
42 }
43
44 // defaultReply is a default reply message that mock adapter returns for a request.
45 type defaultReply struct {
46         Retval int32
47 }
48
49 // MessageDTO is a structure used for propagating information to ReplyHandlers.
50 type MessageDTO struct {
51         MsgID    uint16
52         MsgName  string
53         ClientID uint32
54         Data     []byte
55 }
56
57 // ReplyHandler is a type that allows to extend the behaviour of VPP mock.
58 // Return value ok is used to signalize that mock reply is calculated and ready to be used.
59 type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, ok bool)
60
61 const (
62         defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
63 )
64
65 var replies []api.Message        // FIFO queue of messages
66 var replyHandlers []ReplyHandler // callbacks that are able to calculate mock responses
67 var repliesLock sync.Mutex       // mutex for the queue
68 var mode = 0                     // mode in which the mock operates
69
70 const useRepliesQueue = 1  // use replies in the queue instead of the default one
71 const useReplyHandlers = 2 //use ReplyHandler
72
73 // NewVppAdapter returns a new mock adapter.
74 func NewVppAdapter() adapter.VppAdapter {
75         return &VppAdapter{}
76 }
77
78 // Connect emulates connecting the process to VPP.
79 func (a *VppAdapter) Connect() error {
80         return nil
81 }
82
83 // Disconnect emulates disconnecting the process from VPP.
84 func (a *VppAdapter) Disconnect() {
85         // no op
86 }
87
88 // GetMsgNameByID returns message name for specified message ID.
89 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
90         switch msgID {
91         case 100:
92                 return "control_ping", true
93         case 101:
94                 return "control_ping_reply", true
95         case 200:
96                 return "sw_interface_dump", true
97         case 201:
98                 return "sw_interface_details", true
99         }
100
101         a.access.Lock()
102         defer a.access.Unlock()
103         a.initMaps()
104         msgName, found := a.msgIDsToName[msgID]
105
106         return msgName, found
107 }
108
109 // RegisterBinAPITypes registers binary API message types in the mock adapter.
110 func (a *VppAdapter) RegisterBinAPITypes(binAPITypes map[string]reflect.Type) {
111         a.access.Lock()
112         defer a.access.Unlock()
113         a.initMaps()
114         for _, v := range binAPITypes {
115                 if msg, ok := reflect.New(v).Interface().(api.Message); ok {
116                         a.binAPITypes[msg.GetMessageName()] = v
117                 }
118         }
119 }
120
121 // ReplyTypeFor returns reply message type for given request message name.
122 func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
123         replyName, foundName := binapi.ReplyNameFor(requestMsgName)
124         if foundName {
125                 if reply, found := a.binAPITypes[replyName]; found {
126                         msgID, err := a.GetMsgID(replyName, "")
127                         if err == nil {
128                                 return reply, msgID, found
129                         }
130                 }
131         }
132
133         return nil, 0, false
134 }
135
136 // ReplyFor returns reply message for given request message name.
137 func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
138         replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
139         if foundReplType {
140                 msgVal := reflect.New(replType)
141                 if msg, ok := msgVal.Interface().(api.Message); ok {
142                         log.Println("FFF ", replType, msgID, foundReplType)
143                         return msg, msgID, true
144                 }
145         }
146
147         return nil, 0, false
148 }
149
150 // ReplyBytes encodes the mocked reply into binary format.
151 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
152         replyMsgID, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
153         if err != nil {
154                 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
155                         " ", err)
156                 return nil, err
157         }
158         log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
159
160         buf := new(bytes.Buffer)
161         struc.Pack(buf, &core.VppReplyHeader{VlMsgID: replyMsgID, Context: request.ClientID})
162         struc.Pack(buf, reply)
163
164         return buf.Bytes(), nil
165 }
166
167 // GetMsgID returns mocked message ID for the given message name and CRC.
168 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
169         switch msgName {
170         case "control_ping":
171                 return 100, nil
172         case "control_ping_reply":
173                 return 101, nil
174         case "sw_interface_dump":
175                 return 200, nil
176         case "sw_interface_details":
177                 return 201, nil
178         }
179
180         a.access.Lock()
181         defer a.access.Unlock()
182         a.initMaps()
183
184         msgID, found := a.msgNameToIds[msgName]
185         if found {
186                 return msgID, nil
187         }
188
189         a.msgIDSeq++
190         msgID = a.msgIDSeq
191         a.msgNameToIds[msgName] = msgID
192         a.msgIDsToName[msgID] = msgName
193
194         log.Println("VPP GetMessageId ", msgID, " name:", msgName, " crc:", msgCrc)
195
196         return msgID, nil
197 }
198
199 // initMaps initializes internal maps (if not already initialized).
200 func (a *VppAdapter) initMaps() {
201         if a.msgIDsToName == nil {
202                 a.msgIDsToName = map[uint16]string{}
203                 a.msgNameToIds = map[string]uint16{}
204                 a.msgIDSeq = 1000
205         }
206
207         if a.binAPITypes == nil {
208                 a.binAPITypes = map[string]reflect.Type{}
209         }
210 }
211
212 // SendMsg emulates sending a binary-encoded message to VPP.
213 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
214         switch mode {
215         case useReplyHandlers:
216                 a.initMaps()
217                 for i := len(replyHandlers) - 1; i >= 0; i-- {
218                         replyHandler := replyHandlers[i]
219
220                         buf := bytes.NewReader(data)
221                         reqHeader := core.VppRequestHeader{}
222                         struc.Unpack(buf, &reqHeader)
223
224                         a.access.Lock()
225                         reqMsgName, _ := a.msgIDsToName[reqHeader.VlMsgID]
226                         a.access.Unlock()
227
228                         reply, msgID, finished := replyHandler(MessageDTO{reqHeader.VlMsgID, reqMsgName,
229                                 clientID, data})
230                         if finished {
231                                 a.callback(clientID, msgID, reply)
232                                 return nil
233                         }
234                 }
235                 fallthrough
236         case useRepliesQueue:
237                 repliesLock.Lock()
238                 defer repliesLock.Unlock()
239
240                 // pop all replies from queue
241                 for i, reply := range replies {
242                         if i > 0 && reply.GetMessageName() == "control_ping_reply" {
243                                 // hack - do not send control_ping_reply immediately, leave it for the the next callback
244                                 replies = []api.Message{}
245                                 replies = append(replies, reply)
246                                 return nil
247                         }
248                         msgID, _ := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
249                         buf := new(bytes.Buffer)
250                         if reply.GetMessageType() == api.ReplyMessage {
251                                 struc.Pack(buf, &core.VppReplyHeader{VlMsgID: msgID, Context: clientID})
252                         } else if reply.GetMessageType() == api.EventMessage {
253                                 struc.Pack(buf, &core.VppEventHeader{VlMsgID: msgID, Context: clientID})
254                         } else if reply.GetMessageType() == api.RequestMessage {
255                                 struc.Pack(buf, &core.VppRequestHeader{VlMsgID: msgID, Context: clientID})
256                         } else {
257                                 struc.Pack(buf, &core.VppOtherHeader{VlMsgID: msgID})
258                         }
259                         struc.Pack(buf, reply)
260                         a.callback(clientID, msgID, buf.Bytes())
261                 }
262                 if len(replies) > 0 {
263                         replies = []api.Message{}
264                         if len(replyHandlers) > 0 {
265                                 // Switch back to handlers once the queue is empty to revert back
266                                 // the fallthrough effect.
267                                 mode = useReplyHandlers
268                         }
269                         return nil
270                 }
271
272                 //fallthrough
273         default:
274                 // return default reply
275                 buf := new(bytes.Buffer)
276                 msgID := uint16(defaultReplyMsgID)
277                 struc.Pack(buf, &core.VppReplyHeader{VlMsgID: msgID, Context: clientID})
278                 struc.Pack(buf, &defaultReply{})
279                 a.callback(clientID, msgID, buf.Bytes())
280         }
281         return nil
282 }
283
284 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
285 func (a *VppAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
286         a.callback = cb
287 }
288
289 // WaitReady mocks waiting for VPP
290 func (a *VppAdapter) WaitReady() error {
291         return nil
292 }
293
294 // MockReply stores a message to be returned when the next request comes. It is a FIFO queue - multiple replies
295 // can be pushed into it, the first one will be popped when some request comes.
296 // Using of this method automatically switches the mock into th useRepliesQueue mode.
297 func (a *VppAdapter) MockReply(msg api.Message) {
298         repliesLock.Lock()
299         defer repliesLock.Unlock()
300
301         replies = append(replies, msg)
302         mode = useRepliesQueue
303 }
304
305 // MockReplyHandler registers a handler function that is supposed to generate mock responses to incoming requests.
306 // Using of this method automatically switches the mock into th useReplyHandlers mode.
307 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
308         repliesLock.Lock()
309         defer repliesLock.Unlock()
310
311         replyHandlers = append(replyHandlers, replyHandler)
312         mode = useReplyHandlers
313 }