blob: 97df5d27f83d8d4fae132d1bf84543caf766aa5f [file] [log] [blame]
David K. Bainbridge157bdab2020-01-16 14:38:05 -08001/*
2 Copyright 2020 the original author or authors.
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15*/
16
17package openflow
18
19import (
David K. Bainbridge55376262020-01-22 23:28:27 -080020 "bufio"
David K. Bainbridge157bdab2020-01-16 14:38:05 -080021 "context"
22 "encoding/binary"
23 "encoding/json"
24 "errors"
25 "github.com/donNewtonAlpha/goloxi"
26 ofp "github.com/donNewtonAlpha/goloxi/of13"
David K. Bainbridgeaea73cd2020-01-27 10:44:50 -080027 "github.com/opencord/voltha-lib-go/v3/pkg/log"
28 "github.com/opencord/voltha-protos/v3/go/voltha"
David K. Bainbridge157bdab2020-01-16 14:38:05 -080029 "io"
30 "net"
31 "time"
32)
33
34var logger, _ = log.AddPackage(log.JSON, log.DebugLevel, nil)
David K. Bainbridge9cb404e2020-01-28 14:32:29 -080035var NoVolthaConnectionError = errors.New("no-voltha-connection")
David K. Bainbridge157bdab2020-01-16 14:38:05 -080036
37type ofcEvent byte
38type ofcState byte
39
40const (
41 ofcEventStart = ofcEvent(iota)
David K. Bainbridge55376262020-01-22 23:28:27 -080042 ofcEventConnect
43 ofcEventDisconnect
44 ofcEventStop
David K. Bainbridge157bdab2020-01-16 14:38:05 -080045
David K. Bainbridge55376262020-01-22 23:28:27 -080046 ofcStateCreated = ofcState(iota)
47 ofcStateStarted
48 ofcStateConnected
David K. Bainbridge157bdab2020-01-16 14:38:05 -080049 ofcStateDisconnected
David K. Bainbridge55376262020-01-22 23:28:27 -080050 ofcStateStopped
David K. Bainbridge157bdab2020-01-16 14:38:05 -080051)
52
David K. Bainbridge55376262020-01-22 23:28:27 -080053func (e ofcEvent) String() string {
54 switch e {
55 case ofcEventStart:
56 return "ofc-event-start"
57 case ofcEventConnect:
58 return "ofc-event-connected"
59 case ofcEventDisconnect:
60 return "ofc-event-disconnected"
61 case ofcEventStop:
62 return "ofc-event-stop"
63 default:
64 return "ofc-event-unknown"
65 }
66}
67
68func (s ofcState) String() string {
69 switch s {
70 case ofcStateCreated:
71 return "ofc-state-created"
72 case ofcStateStarted:
73 return "ofc-state-started"
74 case ofcStateConnected:
75 return "ofc-state-connected"
76 case ofcStateDisconnected:
77 return "ofc-state-disconnected"
78 case ofcStateStopped:
79 return "ofc-state-stopped"
80 default:
81 return "ofc-state-unknown"
82 }
83}
84
85// OFClient the configuration and operational state of a connection to an
86// openflow controller
David K. Bainbridge157bdab2020-01-16 14:38:05 -080087type OFClient struct {
88 OFControllerEndPoint string
89 Port uint16
90 DeviceID string
David K. Bainbridge157bdab2020-01-16 14:38:05 -080091 VolthaClient voltha.VolthaServiceClient
92 PacketOutChannel chan *voltha.PacketOut
93 ConnectionMaxRetries int
94 ConnectionRetryDelay time.Duration
95 conn net.Conn
96
97 // expirimental
98 events chan ofcEvent
99 sendChannel chan Message
100 lastUnsentMessage Message
101}
102
David K. Bainbridge55376262020-01-22 23:28:27 -0800103// NewClient returns an initialized OFClient instance based on the configuration
104// specified
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800105func NewOFClient(config *OFClient) *OFClient {
106
107 ofc := OFClient{
108 DeviceID: config.DeviceID,
109 OFControllerEndPoint: config.OFControllerEndPoint,
110 VolthaClient: config.VolthaClient,
111 PacketOutChannel: config.PacketOutChannel,
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800112 ConnectionMaxRetries: config.ConnectionMaxRetries,
113 ConnectionRetryDelay: config.ConnectionRetryDelay,
114 events: make(chan ofcEvent, 10),
115 sendChannel: make(chan Message, 100),
116 }
117
118 if ofc.ConnectionRetryDelay <= 0 {
119 logger.Warnw("connection retry delay not valid, setting to default",
120 log.Fields{
121 "device-id": ofc.DeviceID,
122 "value": ofc.ConnectionRetryDelay.String(),
123 "default": (3 * time.Second).String()})
124 ofc.ConnectionRetryDelay = 3 * time.Second
125 }
126 return &ofc
127}
128
David K. Bainbridge55376262020-01-22 23:28:27 -0800129// Stop initiates a shutdown of the OFClient
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800130func (ofc *OFClient) Stop() {
David K. Bainbridge55376262020-01-22 23:28:27 -0800131 ofc.events <- ofcEventStop
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800132}
133
134func (ofc *OFClient) peekAtOFHeader(buf []byte) (ofp.IHeader, error) {
135 header := ofp.Header{}
136 header.Version = uint8(buf[0])
137 header.Type = uint8(buf[1])
138 header.Length = binary.BigEndian.Uint16(buf[2:4])
139 header.Xid = binary.BigEndian.Uint32(buf[4:8])
140
141 // TODO: add minimal validation of version and type
142
143 return &header, nil
144}
145
146func (ofc *OFClient) establishConnectionToController() error {
147 if ofc.conn != nil {
David K. Bainbridge55376262020-01-22 23:28:27 -0800148 logger.Debugw("closing-of-connection-to-reconnect",
149 log.Fields{"device-id": ofc.DeviceID})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800150 ofc.conn.Close()
151 ofc.conn = nil
152 }
153 try := 1
154 for ofc.ConnectionMaxRetries == 0 || try < ofc.ConnectionMaxRetries {
155 if raddr, err := net.ResolveTCPAddr("tcp", ofc.OFControllerEndPoint); err != nil {
156 logger.Debugw("openflow-client unable to resolve endpoint",
157 log.Fields{
158 "device-id": ofc.DeviceID,
159 "endpoint": ofc.OFControllerEndPoint})
160 } else {
161 if connection, err := net.DialTCP("tcp", nil, raddr); err == nil {
162 ofc.conn = connection
163 ofc.sayHello()
David K. Bainbridge55376262020-01-22 23:28:27 -0800164 ofc.events <- ofcEventConnect
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800165 return nil
166 } else {
167 logger.Warnw("openflow-client-connect-error",
168 log.Fields{
169 "device-id": ofc.DeviceID,
170 "endpoint": ofc.OFControllerEndPoint})
171 }
172 }
173 if ofc.ConnectionMaxRetries == 0 || try < ofc.ConnectionMaxRetries {
174 if ofc.ConnectionMaxRetries != 0 {
175 try += 1
176 }
177 time.Sleep(ofc.ConnectionRetryDelay)
178 }
179 }
180 return errors.New("failed-to-connect-to-of-controller")
181}
182
David K. Bainbridge55376262020-01-22 23:28:27 -0800183// Run implementes the state machine for the OF client reacting to state change
184// events and invoking actions as a reaction to those state changes
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800185func (ofc *OFClient) Run(ctx context.Context) {
186
187 var ofCtx context.Context
188 var ofDone func()
David K. Bainbridge55376262020-01-22 23:28:27 -0800189 state := ofcStateCreated
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800190 ofc.events <- ofcEventStart
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800191top:
192 for {
193 select {
194 case <-ctx.Done():
David K. Bainbridge55376262020-01-22 23:28:27 -0800195 state = ofcStateStopped
196 logger.Debugw("state-transition-context-done",
197 log.Fields{"device-id": ofc.DeviceID})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800198 break top
199 case event := <-ofc.events:
David K. Bainbridge55376262020-01-22 23:28:27 -0800200 previous := state
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800201 switch event {
202 case ofcEventStart:
David K. Bainbridge55376262020-01-22 23:28:27 -0800203 logger.Debugw("ofc-event-start",
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800204 log.Fields{"device-id": ofc.DeviceID})
David K. Bainbridge55376262020-01-22 23:28:27 -0800205 if state == ofcStateCreated {
206 state = ofcStateStarted
207 logger.Debug("STARTED MORE THAN ONCE")
208 go ofc.establishConnectionToController()
209 } else {
210 logger.Errorw("illegal-state-transition",
211 log.Fields{
212 "device-id": ofc.DeviceID,
213 "current-state": state.String(),
214 "event": event.String()})
215 }
216 case ofcEventConnect:
217 logger.Debugw("ofc-event-connected",
218 log.Fields{"device-id": ofc.DeviceID})
219 if state == ofcStateStarted || state == ofcStateDisconnected {
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800220 state = ofcStateConnected
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800221 ofCtx, ofDone = context.WithCancel(context.Background())
222 go ofc.messageSender(ofCtx)
223 go ofc.processOFStream(ofCtx)
David K. Bainbridge55376262020-01-22 23:28:27 -0800224 } else {
225 logger.Errorw("illegal-state-transition",
226 log.Fields{
227 "device-id": ofc.DeviceID,
228 "current-state": state.String(),
229 "event": event.String()})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800230 }
David K. Bainbridge55376262020-01-22 23:28:27 -0800231 case ofcEventDisconnect:
232 logger.Debugw("ofc-event-disconnected",
233 log.Fields{
234 "device-id": ofc.DeviceID,
235 "state": state.String()})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800236 if state == ofcStateConnected {
237 state = ofcStateDisconnected
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800238 if ofDone != nil {
239 ofDone()
240 ofDone = nil
241 }
242 go ofc.establishConnectionToController()
David K. Bainbridge55376262020-01-22 23:28:27 -0800243 } else {
244 logger.Errorw("illegal-state-transition",
245 log.Fields{
246 "device-id": ofc.DeviceID,
247 "current-state": state.String(),
248 "event": event.String()})
249 }
250 case ofcEventStop:
251 logger.Debugw("ofc-event-stop",
252 log.Fields{"device-id": ofc.DeviceID})
253 if state == ofcStateCreated || state == ofcStateConnected || state == ofcStateDisconnected {
254 state = ofcStateStopped
255 break top
256 } else {
257 logger.Errorw("illegal-state-transition",
258 log.Fields{
259 "device-id": ofc.DeviceID,
260 "current-state": state.String(),
261 "event": event.String()})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800262 }
263 }
David K. Bainbridge55376262020-01-22 23:28:27 -0800264 logger.Debugw("state-transition",
265 log.Fields{
266 "device-id": ofc.DeviceID,
267 "previous-state": previous.String(),
268 "current-state": state.String(),
269 "event": event.String()})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800270 }
271 }
272
David K. Bainbridge55376262020-01-22 23:28:27 -0800273 // If the child context exists, then cancel it
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800274 if ofDone != nil {
David K. Bainbridge55376262020-01-22 23:28:27 -0800275 log.Debugw("closing-child-processes",
276 log.Fields{"device-id": ofc.DeviceID})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800277 ofDone()
278 ofDone = nil
279 }
280
David K. Bainbridge55376262020-01-22 23:28:27 -0800281 // If the connection is open, then close it
282 if ofc.conn != nil {
283 log.Debugw("closing-of-connection",
284 log.Fields{"device-id": ofc.DeviceID})
285 ofc.conn.Close()
286 ofc.conn = nil
287 }
288 log.Debugw("state-machine-finished",
289 log.Fields{"device-id": ofc.DeviceID})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800290}
291
David K. Bainbridge55376262020-01-22 23:28:27 -0800292// processOFStream processes the OF connection from the controller and invokes
293// the appropriate handler methods for each message.
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800294func (ofc *OFClient) processOFStream(ctx context.Context) {
David K. Bainbridge55376262020-01-22 23:28:27 -0800295 fromController := bufio.NewReader(ofc.conn)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800296
297 /*
David K. Bainbridge55376262020-01-22 23:28:27 -0800298 * We have a read buffer of a max size of 4096, so if we ever have
299 * a message larger than this then we will have issues
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800300 */
David K. Bainbridgee6d95f62020-01-28 11:11:47 -0800301 headerBuf := make([]byte, 8)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800302
303top:
304 // Continue until we are told to stop
David K. Bainbridge55376262020-01-22 23:28:27 -0800305 for {
306 select {
307 case <-ctx.Done():
308 logger.Error("of-loop-ending-context-done")
309 break top
310 default:
311 // Read 8 bytes, the standard OF header
David K. Bainbridgee6d95f62020-01-28 11:11:47 -0800312 read, err := io.ReadFull(fromController, headerBuf)
David K. Bainbridge55376262020-01-22 23:28:27 -0800313 if err != nil {
314 logger.Errorw("bad-of-header",
315 log.Fields{
316 "byte-count": read,
317 "device-id": ofc.DeviceID,
318 "error": err})
319 break top
320 }
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800321
David K. Bainbridge55376262020-01-22 23:28:27 -0800322 // Decode the header
David K. Bainbridgee6d95f62020-01-28 11:11:47 -0800323 peek, err := ofc.peekAtOFHeader(headerBuf)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800324 if err != nil {
325 /*
326 * Header is bad, assume stream is corrupted
327 * and needs to be restarted
328 */
329 logger.Errorw("bad-of-packet",
330 log.Fields{
331 "device-id": ofc.DeviceID,
332 "error": err})
333 break top
334 }
335
David K. Bainbridge55376262020-01-22 23:28:27 -0800336 // Calculate the size of the rest of the packet and read it
337 need := int(peek.GetLength())
David K. Bainbridgee6d95f62020-01-28 11:11:47 -0800338 messageBuf := make([]byte, need)
339 copy(messageBuf, headerBuf)
340 read, err = io.ReadFull(fromController, messageBuf[8:])
David K. Bainbridge55376262020-01-22 23:28:27 -0800341 if err != nil {
342 logger.Errorw("bad-of-packet",
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800343 log.Fields{
David K. Bainbridge55376262020-01-22 23:28:27 -0800344 "byte-count": read,
345 "device-id": ofc.DeviceID,
346 "error": err})
347 break top
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800348 }
349
350 // Decode and process the packet
David K. Bainbridgee6d95f62020-01-28 11:11:47 -0800351 decoder := goloxi.NewDecoder(messageBuf)
David K. Bainbridge0b3f6482020-01-27 19:32:39 -0800352 msg, err := ofp.DecodeHeader(decoder)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800353 if err != nil {
354 js, _ := json.Marshal(decoder)
355 logger.Errorw("failed-to-decode",
356 log.Fields{
357 "device-id": ofc.DeviceID,
358 "decoder": js,
359 "error": err})
360 break top
361 }
362 if logger.V(log.DebugLevel) {
David K. Bainbridge0b3f6482020-01-27 19:32:39 -0800363 js, _ := json.Marshal(msg)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800364 logger.Debugw("packet-header",
365 log.Fields{
366 "device-id": ofc.DeviceID,
367 "header": js})
368 }
David K. Bainbridge0b3f6482020-01-27 19:32:39 -0800369 ofc.parseHeader(msg)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800370 }
371 }
David K. Bainbridge55376262020-01-22 23:28:27 -0800372 logger.Debugw("end-of-stream",
373 log.Fields{"device-id": ofc.DeviceID})
374 ofc.events <- ofcEventDisconnect
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800375}
376
377func (ofc *OFClient) sayHello() {
378 hello := ofp.NewHello()
379 hello.Xid = uint32(GetXid())
380 elem := ofp.NewHelloElemVersionbitmap()
381 elem.SetType(ofp.OFPHETVersionbitmap)
382 elem.SetLength(8)
383 elem.SetBitmaps([]*ofp.Uint32{&ofp.Uint32{Value: 16}})
384 hello.SetElements([]ofp.IHelloElem{elem})
385 if logger.V(log.DebugLevel) {
386 js, _ := json.Marshal(hello)
387 logger.Debugw("sayHello Called",
388 log.Fields{
389 "device-id": ofc.DeviceID,
390 "hello-message": js})
391 }
392 if err := ofc.SendMessage(hello); err != nil {
393 logger.Fatalw("Failed saying hello to Openflow Server, unable to proceed",
394 log.Fields{
395 "device-id": ofc.DeviceID,
396 "error": err})
397 }
398}
399
400func (ofc *OFClient) parseHeader(header ofp.IHeader) {
401 switch header.GetType() {
402 case ofp.OFPTHello:
403 //x := header.(*ofp.Hello)
404 case ofp.OFPTError:
405 go ofc.handleErrMsg(header.(*ofp.ErrorMsg))
406 case ofp.OFPTEchoRequest:
407 go ofc.handleEchoRequest(header.(*ofp.EchoRequest))
408 case ofp.OFPTEchoReply:
409 case ofp.OFPTExperimenter:
410 case ofp.OFPTFeaturesRequest:
411 go ofc.handleFeatureRequest(header.(*ofp.FeaturesRequest))
412 case ofp.OFPTFeaturesReply:
413 case ofp.OFPTGetConfigRequest:
414 go ofc.handleGetConfigRequest(header.(*ofp.GetConfigRequest))
415 case ofp.OFPTGetConfigReply:
416 case ofp.OFPTSetConfig:
417 go ofc.handleSetConfig(header.(*ofp.SetConfig))
418 case ofp.OFPTPacketIn:
419 case ofp.OFPTFlowRemoved:
420 case ofp.OFPTPortStatus:
421 case ofp.OFPTPacketOut:
422 go ofc.handlePacketOut(header.(*ofp.PacketOut))
423 case ofp.OFPTFlowMod:
424 /*
425 * Not using go routine to handle flow* messages or barrier requests
426 * onos typically issues barrier requests just before a flow* message.
427 * by handling in this thread I ensure all flow* are handled when barrier
428 * request is issued.
429 */
430 switch header.(ofp.IFlowMod).GetCommand() {
431 case ofp.OFPFCAdd:
432 ofc.handleFlowAdd(header.(*ofp.FlowAdd))
433 case ofp.OFPFCModify:
434 ofc.handleFlowMod(header.(*ofp.FlowMod))
435 case ofp.OFPFCModifyStrict:
436 ofc.handleFlowModStrict(header.(*ofp.FlowModifyStrict))
437 case ofp.OFPFCDelete:
438 ofc.handleFlowDelete(header.(*ofp.FlowDelete))
439 case ofp.OFPFCDeleteStrict:
440 ofc.handleFlowDeleteStrict(header.(*ofp.FlowDeleteStrict))
441 }
442 case ofp.OFPTStatsRequest:
443 go ofc.handleStatsRequest(header, header.(ofp.IStatsRequest).GetStatsType())
444 case ofp.OFPTBarrierRequest:
445 /* See note above at case ofp.OFPTFlowMod:*/
446 ofc.handleBarrierRequest(header.(*ofp.BarrierRequest))
447 case ofp.OFPTRoleRequest:
448 go ofc.handleRoleRequest(header.(*ofp.RoleRequest))
449 case ofp.OFPTMeterMod:
Don Newtonac0455d2020-01-23 11:52:26 -0500450 ofc.handleMeterModRequest(header.(*ofp.MeterMod))
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800451 }
452}
453
David K. Bainbridge55376262020-01-22 23:28:27 -0800454// Message interface that represents an open flow message and enables for a
455// unified implementation of SendMessage
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800456type Message interface {
457 Serialize(encoder *goloxi.Encoder) error
458}
459
460func (ofc *OFClient) doSend(msg Message) error {
461 if ofc.conn == nil {
462 return errors.New("no-connection")
463 }
464 enc := goloxi.NewEncoder()
465 msg.Serialize(enc)
466 bytes := enc.Bytes()
467 if _, err := ofc.conn.Write(bytes); err != nil {
David K. Bainbridge55376262020-01-22 23:28:27 -0800468 logger.Errorw("unable-to-send-message-to-controller",
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800469 log.Fields{
470 "device-id": ofc.DeviceID,
471 "message": msg,
472 "error": err})
473 return err
474 }
475 return nil
476}
477
478func (ofc *OFClient) messageSender(ctx context.Context) {
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800479 // first process last fail if it exists
480 if ofc.lastUnsentMessage != nil {
481 if err := ofc.doSend(ofc.lastUnsentMessage); err != nil {
David K. Bainbridge55376262020-01-22 23:28:27 -0800482 ofc.events <- ofcEventDisconnect
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800483 return
484 }
485 ofc.lastUnsentMessage = nil
486 }
487top:
488 for {
489 select {
490 case <-ctx.Done():
491 break top
492 case msg := <-ofc.sendChannel:
David K. Bainbridge55376262020-01-22 23:28:27 -0800493 if err := ofc.doSend(msg); err != nil {
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800494 ofc.lastUnsentMessage = msg
David K. Bainbridge55376262020-01-22 23:28:27 -0800495 ofc.events <- ofcEventDisconnect
David K. Bainbridge9cb404e2020-01-28 14:32:29 -0800496 logger.Debugw("message-sender-error",
497 log.Fields{
498 "device-id": ofc.DeviceID,
499 "error": err.Error()})
500 break top
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800501 }
David K. Bainbridge9cb404e2020-01-28 14:32:29 -0800502 logger.Debugw("message-sender-send",
503 log.Fields{
504 "device-id": ofc.DeviceID})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800505 ofc.lastUnsentMessage = nil
506 }
507 }
David K. Bainbridge9cb404e2020-01-28 14:32:29 -0800508
509 logger.Debugw("message-sender-finished",
510 log.Fields{
511 "device-id": ofc.DeviceID})
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800512}
513
David K. Bainbridge55376262020-01-22 23:28:27 -0800514// SendMessage queues a message to be sent to the openflow controller
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800515func (ofc *OFClient) SendMessage(message Message) error {
David K. Bainbridge9cb404e2020-01-28 14:32:29 -0800516 logger.Debug("queuing-message")
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800517 ofc.sendChannel <- message
518 return nil
519}