Add API to set ControlPing msg and fail connect on unknown ID
[govpp.git] / core / request_handler.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 core
16
17 import (
18         "errors"
19         "fmt"
20         "sync/atomic"
21
22         logger "github.com/sirupsen/logrus"
23
24         "git.fd.io/govpp.git/api"
25 )
26
27 // watchRequests watches for requests on the request API channel and forwards them as messages to VPP.
28 func (c *Connection) watchRequests(ch *api.Channel, chMeta *channelMetadata) {
29         for {
30                 select {
31                 case req, ok := <-ch.ReqChan:
32                         // new request on the request channel
33                         if !ok {
34                                 // after closing the request channel, release API channel and return
35                                 c.releaseAPIChannel(ch, chMeta)
36                                 return
37                         }
38                         c.processRequest(ch, chMeta, req)
39
40                 case req := <-ch.NotifSubsChan:
41                         // new request on the notification subscribe channel
42                         c.processNotifSubscribeRequest(ch, req)
43                 }
44         }
45 }
46
47 // processRequest processes a single request received on the request channel.
48 func (c *Connection) processRequest(ch *api.Channel, chMeta *channelMetadata, req *api.VppRequest) error {
49         // check whether we are connected to VPP
50         if atomic.LoadUint32(&c.connected) == 0 {
51                 error := errors.New("not connected to VPP, ignoring the request")
52                 log.Error(error)
53                 sendReply(ch, &api.VppReply{Error: error})
54                 return error
55         }
56
57         // retrieve message ID
58         msgID, err := c.GetMessageID(req.Message)
59         if err != nil {
60                 error := fmt.Errorf("unable to retrieve message ID: %v", err)
61                 log.WithFields(logger.Fields{
62                         "msg_name": req.Message.GetMessageName(),
63                         "msg_crc":  req.Message.GetCrcString(),
64                 }).Error(error)
65                 sendReply(ch, &api.VppReply{Error: error})
66                 return error
67         }
68
69         // encode the message into binary
70         data, err := c.codec.EncodeMsg(req.Message, msgID)
71         if err != nil {
72                 error := fmt.Errorf("unable to encode the messge: %v", err)
73                 log.WithFields(logger.Fields{
74                         "context": chMeta.id,
75                         "msg_id":  msgID,
76                 }).Error(error)
77                 sendReply(ch, &api.VppReply{Error: error})
78                 return error
79         }
80
81         if log.Level == logger.DebugLevel { // for performance reasons - logrus does some processing even if debugs are disabled
82                 log.WithFields(logger.Fields{
83                         "context":  chMeta.id,
84                         "msg_id":   msgID,
85                         "msg_size": len(data),
86                 }).Debug("Sending a message to VPP.")
87         }
88
89         // send the message
90         if req.Multipart {
91                 // expect multipart response
92                 atomic.StoreUint32(&chMeta.multipart, 1)
93         }
94
95         // send the request to VPP
96         c.vpp.SendMsg(chMeta.id, data)
97
98         if req.Multipart {
99                 // send a control ping to determine end of the multipart response
100                 pingData, _ := c.codec.EncodeMsg(msgControlPing, c.pingReqID)
101
102                 log.WithFields(logger.Fields{
103                         "context":  chMeta.id,
104                         "msg_id":   c.pingReqID,
105                         "msg_size": len(pingData),
106                 }).Debug("Sending a control ping to VPP.")
107
108                 c.vpp.SendMsg(chMeta.id, pingData)
109         }
110
111         return nil
112 }
113
114 // msgCallback is called whenever any binary API message comes from VPP.
115 func msgCallback(context uint32, msgID uint16, data []byte) {
116         connLock.RLock()
117         defer connLock.RUnlock()
118
119         if conn == nil {
120                 log.Warn("Already disconnected, ignoring the message.")
121                 return
122         }
123
124         if log.Level == logger.DebugLevel { // for performance reasons - logrus does some processing even if debugs are disabled
125                 log.WithFields(logger.Fields{
126                         "context":  context,
127                         "msg_id":   msgID,
128                         "msg_size": len(data),
129                 }).Debug("Received a message from VPP.")
130         }
131
132         if context == 0 || conn.isNotificationMessage(msgID) {
133                 // process the message as a notification
134                 conn.sendNotifications(msgID, data)
135                 return
136         }
137
138         // match ch according to the context
139         conn.channelsLock.RLock()
140         ch, ok := conn.channels[context]
141         conn.channelsLock.RUnlock()
142
143         if !ok {
144                 log.WithFields(logger.Fields{
145                         "context": context,
146                         "msg_id":  msgID,
147                 }).Error("Context ID not known, ignoring the message.")
148                 return
149         }
150
151         chMeta := ch.Metadata().(*channelMetadata)
152         lastReplyReceived := false
153         // if this is a control ping reply and multipart request is being processed, treat this as a last part of the reply
154         if msgID == conn.pingReplyID && atomic.CompareAndSwapUint32(&chMeta.multipart, 1, 0) {
155                 lastReplyReceived = true
156         }
157
158         // send the data to the channel
159         sendReply(ch, &api.VppReply{
160                 MessageID:         msgID,
161                 Data:              data,
162                 LastReplyReceived: lastReplyReceived,
163         })
164 }
165
166 // sendReply sends the reply into the go channel, if it cannot be completed without blocking, otherwise
167 // it logs the error and do not send the message.
168 func sendReply(ch *api.Channel, reply *api.VppReply) {
169         select {
170         case ch.ReplyChan <- reply:
171                 // reply sent successfully
172         default:
173                 // unable to write into the channel without blocking
174                 log.WithFields(logger.Fields{
175                         "channel": ch,
176                         "msg_id":  reply.MessageID,
177                 }).Warn("Unable to send the reply, reciever end not ready.")
178         }
179 }
180
181 // GetMessageID returns message identifier of given API message.
182 func (c *Connection) GetMessageID(msg api.Message) (uint16, error) {
183         if c == nil {
184                 return 0, errors.New("nil connection passed in")
185         }
186         return c.messageNameToID(msg.GetMessageName(), msg.GetCrcString())
187 }
188
189 // messageNameToID returns message ID of a message identified by its name and CRC.
190 func (c *Connection) messageNameToID(msgName string, msgCrc string) (uint16, error) {
191         // try to get the ID from the map
192         c.msgIDsLock.RLock()
193         id, ok := c.msgIDs[msgName+msgCrc]
194         c.msgIDsLock.RUnlock()
195         if ok {
196                 return id, nil
197         }
198
199         // get the ID using VPP API
200         id, err := c.vpp.GetMsgID(msgName, msgCrc)
201         if err != nil {
202                 error := fmt.Errorf("unable to retrieve message ID: %v", err)
203                 log.WithFields(logger.Fields{
204                         "msg_name": msgName,
205                         "msg_crc":  msgCrc,
206                 }).Error(error)
207                 return id, error
208         }
209
210         c.msgIDsLock.Lock()
211         c.msgIDs[msgName+msgCrc] = id
212         c.msgIDsLock.Unlock()
213
214         return id, nil
215 }