make api.Channel as interface
[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/adapter"
26         "git.fd.io/govpp.git/adapter/mock/binapi"
27         "git.fd.io/govpp.git/api"
28
29         "git.fd.io/govpp.git/codec"
30         "github.com/lunixbochs/struc"
31 )
32
33 type replyMode int
34
35 const (
36         _                replyMode = 0
37         useRepliesQueue            = 1 // use replies in the queue
38         useReplyHandlers           = 2 // use reply handler
39 )
40
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)
44
45         msgNameToIds map[string]uint16
46         msgIDsToName map[uint16]string
47         msgIDSeq     uint16
48         binAPITypes  map[string]reflect.Type
49         access       sync.RWMutex
50
51         replies       []reply        // 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
55 }
56
57 // defaultReply is a default reply message that mock adapter returns for a request.
58 type defaultReply struct {
59         Retval int32
60 }
61
62 // MessageDTO is a structure used for propagating information to ReplyHandlers.
63 type MessageDTO struct {
64         MsgID    uint16
65         MsgName  string
66         ClientID uint32
67         Data     []byte
68 }
69
70 // reply for one request (can be multipart, contain replies to previously timeouted requests, etc.)
71 type reply struct {
72         msgs []MsgWithContext
73 }
74
75 // MsgWithContext encapsulates reply message with possibly sequence number and is-multipart flag.
76 type MsgWithContext struct {
77         Msg       api.Message
78         SeqNum    uint16
79         Multipart bool
80
81         /* set by mock adapter */
82         hasCtx bool
83 }
84
85 // ReplyHandler is a type that allows to extend the behaviour of VPP mock.
86 // Return value ok is used to signalize that mock reply is calculated and ready to be used.
87 type ReplyHandler func(request MessageDTO) (reply []byte, msgID uint16, ok bool)
88
89 const (
90         defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
91 )
92
93 // NewVppAdapter returns a new mock adapter.
94 func NewVppAdapter() adapter.VppAdapter {
95         return &VppAdapter{}
96 }
97
98 // Connect emulates connecting the process to VPP.
99 func (a *VppAdapter) Connect() error {
100         return nil
101 }
102
103 // Disconnect emulates disconnecting the process from VPP.
104 func (a *VppAdapter) Disconnect() {
105         // no op
106 }
107
108 // GetMsgNameByID returns message name for specified message ID.
109 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
110         switch msgID {
111         case 100:
112                 return "control_ping", true
113         case 101:
114                 return "control_ping_reply", true
115         case 200:
116                 return "sw_interface_dump", true
117         case 201:
118                 return "sw_interface_details", true
119         }
120
121         a.access.Lock()
122         defer a.access.Unlock()
123         a.initMaps()
124         msgName, found := a.msgIDsToName[msgID]
125
126         return msgName, found
127 }
128
129 // RegisterBinAPITypes registers binary API message types in the mock adapter.
130 func (a *VppAdapter) RegisterBinAPITypes(binAPITypes map[string]reflect.Type) {
131         a.access.Lock()
132         defer a.access.Unlock()
133         a.initMaps()
134         for _, v := range binAPITypes {
135                 if msg, ok := reflect.New(v).Interface().(api.Message); ok {
136                         a.binAPITypes[msg.GetMessageName()] = v
137                 }
138         }
139 }
140
141 // ReplyTypeFor returns reply message type for given request message name.
142 func (a *VppAdapter) ReplyTypeFor(requestMsgName string) (reflect.Type, uint16, bool) {
143         replyName, foundName := binapi.ReplyNameFor(requestMsgName)
144         if foundName {
145                 if reply, found := a.binAPITypes[replyName]; found {
146                         msgID, err := a.GetMsgID(replyName, "")
147                         if err == nil {
148                                 return reply, msgID, found
149                         }
150                 }
151         }
152
153         return nil, 0, false
154 }
155
156 // ReplyFor returns reply message for given request message name.
157 func (a *VppAdapter) ReplyFor(requestMsgName string) (api.Message, uint16, bool) {
158         replType, msgID, foundReplType := a.ReplyTypeFor(requestMsgName)
159         if foundReplType {
160                 msgVal := reflect.New(replType)
161                 if msg, ok := msgVal.Interface().(api.Message); ok {
162                         log.Println("FFF ", replType, msgID, foundReplType)
163                         return msg, msgID, true
164                 }
165         }
166
167         return nil, 0, false
168 }
169
170 // ReplyBytes encodes the mocked reply into binary format.
171 func (a *VppAdapter) ReplyBytes(request MessageDTO, reply api.Message) ([]byte, error) {
172         replyMsgID, err := a.GetMsgID(reply.GetMessageName(), reply.GetCrcString())
173         if err != nil {
174                 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
175                         " ", err)
176                 return nil, err
177         }
178         log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
179
180         buf := new(bytes.Buffer)
181         struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: replyMsgID, Context: request.ClientID})
182         struc.Pack(buf, reply)
183
184         return buf.Bytes(), nil
185 }
186
187 // GetMsgID returns mocked message ID for the given message name and CRC.
188 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
189         switch msgName {
190         case "control_ping":
191                 return 100, nil
192         case "control_ping_reply":
193                 return 101, nil
194         case "sw_interface_dump":
195                 return 200, nil
196         case "sw_interface_details":
197                 return 201, nil
198         }
199
200         a.access.Lock()
201         defer a.access.Unlock()
202         a.initMaps()
203
204         msgID, found := a.msgNameToIds[msgName]
205         if found {
206                 return msgID, nil
207         }
208
209         a.msgIDSeq++
210         msgID = a.msgIDSeq
211         a.msgNameToIds[msgName] = msgID
212         a.msgIDsToName[msgID] = msgName
213
214         log.Println("VPP GetMessageId ", msgID, " name:", msgName, " crc:", msgCrc)
215
216         return msgID, nil
217 }
218
219 // initMaps initializes internal maps (if not already initialized).
220 func (a *VppAdapter) initMaps() {
221         if a.msgIDsToName == nil {
222                 a.msgIDsToName = map[uint16]string{}
223                 a.msgNameToIds = map[string]uint16{}
224                 a.msgIDSeq = 1000
225         }
226
227         if a.binAPITypes == nil {
228                 a.binAPITypes = map[string]reflect.Type{}
229         }
230 }
231
232 // SendMsg emulates sending a binary-encoded message to VPP.
233 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
234         switch a.mode {
235         case useReplyHandlers:
236                 a.initMaps()
237                 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
238                         replyHandler := a.replyHandlers[i]
239
240                         buf := bytes.NewReader(data)
241                         reqHeader := codec.VppRequestHeader{}
242                         struc.Unpack(buf, &reqHeader)
243
244                         a.access.Lock()
245                         reqMsgName := a.msgIDsToName[reqHeader.VlMsgID]
246                         a.access.Unlock()
247
248                         reply, msgID, finished := replyHandler(MessageDTO{
249                                 MsgID:    reqHeader.VlMsgID,
250                                 MsgName:  reqMsgName,
251                                 ClientID: clientID,
252                                 Data:     data,
253                         })
254                         if finished {
255                                 a.callback(clientID, msgID, reply)
256                                 return nil
257                         }
258                 }
259                 fallthrough
260         case useRepliesQueue:
261                 a.repliesLock.Lock()
262                 defer a.repliesLock.Unlock()
263
264                 // pop the first reply
265                 if len(a.replies) > 0 {
266                         reply := a.replies[0]
267                         for _, msg := range reply.msgs {
268                                 msgID, _ := a.GetMsgID(msg.Msg.GetMessageName(), msg.Msg.GetCrcString())
269                                 buf := new(bytes.Buffer)
270                                 context := clientID
271                                 if msg.hasCtx {
272                                         context = setMultipart(context, msg.Multipart)
273                                         context = setSeqNum(context, msg.SeqNum)
274                                 }
275                                 if msg.Msg.GetMessageType() == api.ReplyMessage {
276                                         struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: msgID, Context: context})
277                                 } else if msg.Msg.GetMessageType() == api.EventMessage {
278                                         struc.Pack(buf, &codec.VppEventHeader{VlMsgID: msgID, Context: context})
279                                 } else if msg.Msg.GetMessageType() == api.RequestMessage {
280                                         struc.Pack(buf, &codec.VppRequestHeader{VlMsgID: msgID, Context: context})
281                                 } else {
282                                         struc.Pack(buf, &codec.VppOtherHeader{VlMsgID: msgID})
283                                 }
284                                 struc.Pack(buf, msg.Msg)
285                                 a.callback(context, msgID, buf.Bytes())
286                         }
287
288                         a.replies = a.replies[1:]
289                         if len(a.replies) == 0 && len(a.replyHandlers) > 0 {
290                                 // Switch back to handlers once the queue is empty to revert back
291                                 // the fallthrough effect.
292                                 a.mode = useReplyHandlers
293                         }
294                         return nil
295                 }
296
297                 //fallthrough
298         default:
299                 // return default reply
300                 buf := new(bytes.Buffer)
301                 msgID := uint16(defaultReplyMsgID)
302                 struc.Pack(buf, &codec.VppReplyHeader{VlMsgID: msgID, Context: clientID})
303                 struc.Pack(buf, &defaultReply{})
304                 a.callback(clientID, msgID, buf.Bytes())
305         }
306         return nil
307 }
308
309 // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from the mock.
310 func (a *VppAdapter) SetMsgCallback(cb func(context uint32, msgID uint16, data []byte)) {
311         a.callback = cb
312 }
313
314 // WaitReady mocks waiting for VPP
315 func (a *VppAdapter) WaitReady() error {
316         return nil
317 }
318
319 // MockReply stores a message or a list of multipart messages to be returned when
320 // the next request comes. It is a FIFO queue - multiple replies can be pushed into it,
321 // the first message or the first set of multi-part messages will be popped when
322 // some request comes.
323 // Using of this method automatically switches the mock into the useRepliesQueue mode.
324 //
325 // Note: multipart requests are implemented using two requests actually - the multipart
326 // request itself followed by control ping used to tell which multipart message
327 // is the last one. A mock reply to a multipart request has to thus consist of
328 // exactly two calls of this method.
329 // For example:
330 //
331 //    mockVpp.MockReply(  // push multipart messages all at once
332 //                      &interfaces.SwInterfaceDetails{SwIfIndex:1},
333 //                      &interfaces.SwInterfaceDetails{SwIfIndex:2},
334 //                      &interfaces.SwInterfaceDetails{SwIfIndex:3},
335 //    )
336 //    mockVpp.MockReply(&vpe.ControlPingReply{})
337 //
338 // Even if the multipart request has no replies, MockReply has to be called twice:
339 //
340 //    mockVpp.MockReply()  // zero multipart messages
341 //    mockVpp.MockReply(&vpe.ControlPingReply{})
342 func (a *VppAdapter) MockReply(msgs ...api.Message) {
343         a.repliesLock.Lock()
344         defer a.repliesLock.Unlock()
345
346         r := reply{}
347         for _, msg := range msgs {
348                 r.msgs = append(r.msgs, MsgWithContext{Msg: msg, hasCtx: false})
349         }
350         a.replies = append(a.replies, r)
351         a.mode = useRepliesQueue
352 }
353
354 // MockReplyWithContext queues next reply like MockReply() does, except that the
355 // sequence number and multipart flag (= context minus channel ID) can be customized
356 // and not necessarily match with the request.
357 // The purpose of this function is to test handling of sequence numbers and as such
358 // it is not really meant to be used outside the govpp UTs.
359 func (a *VppAdapter) MockReplyWithContext(msgs ...MsgWithContext) {
360         a.repliesLock.Lock()
361         defer a.repliesLock.Unlock()
362
363         r := reply{}
364         for _, msg := range msgs {
365                 r.msgs = append(r.msgs,
366                         MsgWithContext{Msg: msg.Msg, SeqNum: msg.SeqNum, Multipart: msg.Multipart, hasCtx: true})
367         }
368         a.replies = append(a.replies, r)
369         a.mode = useRepliesQueue
370 }
371
372 // MockReplyHandler registers a handler function that is supposed to generate mock responses to incoming requests.
373 // Using of this method automatically switches the mock into th useReplyHandlers mode.
374 func (a *VppAdapter) MockReplyHandler(replyHandler ReplyHandler) {
375         a.repliesLock.Lock()
376         defer a.repliesLock.Unlock()
377
378         a.replyHandlers = append(a.replyHandlers, replyHandler)
379         a.mode = useReplyHandlers
380 }
381
382 func setSeqNum(context uint32, seqNum uint16) (newContext uint32) {
383         context &= 0xffff0000
384         context |= uint32(seqNum)
385         return context
386 }
387
388 func setMultipart(context uint32, isMultipart bool) (newContext uint32) {
389         context &= 0xfffeffff
390         if isMultipart {
391                 context |= 1 << 16
392         }
393         return context
394 }