Make global vars part of mock adapter
[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         "git.fd.io/govpp.git/core"
29
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       []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
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 // 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)
73
74 const (
75         defaultReplyMsgID = 1 // default message ID for the reply to be sent back via callback
76 )
77
78 // NewVppAdapter returns a new mock adapter.
79 func NewVppAdapter() adapter.VppAdapter {
80         return &VppAdapter{}
81 }
82
83 // Connect emulates connecting the process to VPP.
84 func (a *VppAdapter) Connect() error {
85         return nil
86 }
87
88 // Disconnect emulates disconnecting the process from VPP.
89 func (a *VppAdapter) Disconnect() {
90         // no op
91 }
92
93 // GetMsgNameByID returns message name for specified message ID.
94 func (a *VppAdapter) GetMsgNameByID(msgID uint16) (string, bool) {
95         switch msgID {
96         case 100:
97                 return "control_ping", true
98         case 101:
99                 return "control_ping_reply", true
100         case 200:
101                 return "sw_interface_dump", true
102         case 201:
103                 return "sw_interface_details", true
104         }
105
106         a.access.Lock()
107         defer a.access.Unlock()
108         a.initMaps()
109         msgName, found := a.msgIDsToName[msgID]
110
111         return msgName, found
112 }
113
114 // RegisterBinAPITypes registers binary API message types in the mock adapter.
115 func (a *VppAdapter) RegisterBinAPITypes(binAPITypes map[string]reflect.Type) {
116         a.access.Lock()
117         defer a.access.Unlock()
118         a.initMaps()
119         for _, v := range binAPITypes {
120                 if msg, ok := reflect.New(v).Interface().(api.Message); ok {
121                         a.binAPITypes[msg.GetMessageName()] = v
122                 }
123         }
124 }
125
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)
129         if foundName {
130                 if reply, found := a.binAPITypes[replyName]; found {
131                         msgID, err := a.GetMsgID(replyName, "")
132                         if err == nil {
133                                 return reply, msgID, found
134                         }
135                 }
136         }
137
138         return nil, 0, false
139 }
140
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)
144         if foundReplType {
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
149                 }
150         }
151
152         return nil, 0, false
153 }
154
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())
158         if err != nil {
159                 log.Println("ReplyBytesE ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID,
160                         " ", err)
161                 return nil, err
162         }
163         log.Println("ReplyBytes ", replyMsgID, " ", reply.GetMessageName(), " clientId: ", request.ClientID)
164
165         buf := new(bytes.Buffer)
166         struc.Pack(buf, &core.VppReplyHeader{VlMsgID: replyMsgID, Context: request.ClientID})
167         struc.Pack(buf, reply)
168
169         return buf.Bytes(), nil
170 }
171
172 // GetMsgID returns mocked message ID for the given message name and CRC.
173 func (a *VppAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
174         switch msgName {
175         case "control_ping":
176                 return 100, nil
177         case "control_ping_reply":
178                 return 101, nil
179         case "sw_interface_dump":
180                 return 200, nil
181         case "sw_interface_details":
182                 return 201, nil
183         }
184
185         a.access.Lock()
186         defer a.access.Unlock()
187         a.initMaps()
188
189         msgID, found := a.msgNameToIds[msgName]
190         if found {
191                 return msgID, nil
192         }
193
194         a.msgIDSeq++
195         msgID = a.msgIDSeq
196         a.msgNameToIds[msgName] = msgID
197         a.msgIDsToName[msgID] = msgName
198
199         log.Println("VPP GetMessageId ", msgID, " name:", msgName, " crc:", msgCrc)
200
201         return msgID, nil
202 }
203
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{}
209                 a.msgIDSeq = 1000
210         }
211
212         if a.binAPITypes == nil {
213                 a.binAPITypes = map[string]reflect.Type{}
214         }
215 }
216
217 // SendMsg emulates sending a binary-encoded message to VPP.
218 func (a *VppAdapter) SendMsg(clientID uint32, data []byte) error {
219         switch a.mode {
220         case useReplyHandlers:
221                 a.initMaps()
222                 for i := len(a.replyHandlers) - 1; i >= 0; i-- {
223                         replyHandler := a.replyHandlers[i]
224
225                         buf := bytes.NewReader(data)
226                         reqHeader := core.VppRequestHeader{}
227                         struc.Unpack(buf, &reqHeader)
228
229                         a.access.Lock()
230                         reqMsgName := a.msgIDsToName[reqHeader.VlMsgID]
231                         a.access.Unlock()
232
233                         reply, msgID, finished := replyHandler(MessageDTO{
234                                 MsgID:    reqHeader.VlMsgID,
235                                 MsgName:  reqMsgName,
236                                 ClientID: clientID,
237                                 Data:     data,
238                         })
239                         if finished {
240                                 a.callback(clientID, msgID, reply)
241                                 return nil
242                         }
243                 }
244                 fallthrough
245         case useRepliesQueue:
246                 a.repliesLock.Lock()
247                 defer a.repliesLock.Unlock()
248
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)
255                                 return nil
256                         }
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})
265                         } else {
266                                 struc.Pack(buf, &core.VppOtherHeader{VlMsgID: msgID})
267                         }
268                         struc.Pack(buf, reply)
269                         a.callback(clientID, msgID, buf.Bytes())
270                 }
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
277                         }
278                         return nil
279                 }
280
281                 //fallthrough
282         default:
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())
289         }
290         return nil
291 }
292
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)) {
295         a.callback = cb
296 }
297
298 // WaitReady mocks waiting for VPP
299 func (a *VppAdapter) WaitReady() error {
300         return nil
301 }
302
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) {
307         a.repliesLock.Lock()
308         defer a.repliesLock.Unlock()
309
310         a.replies = append(a.replies, msg)
311         a.mode = useRepliesQueue
312 }
313
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) {
317         a.repliesLock.Lock()
318         defer a.repliesLock.Unlock()
319
320         a.replyHandlers = append(a.replyHandlers, replyHandler)
321         a.mode = useReplyHandlers
322 }