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