blob: 1785d355834bceb3ac55f52c1df47de12e9a6b1d [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 (
20 "context"
David K. Bainbridge157bdab2020-01-16 14:38:05 -080021 "errors"
Jonathan Hart4b110f62020-03-13 17:36:19 -070022 "sync"
David K. Bainbridge157bdab2020-01-16 14:38:05 -080023 "time"
David Bainbridgef8ce7d22020-04-08 12:49:41 -070024
Jonathan Hart828908c2020-04-15 14:23:45 -070025 ofp "github.com/opencord/goloxi/of13"
David Bainbridgef8ce7d22020-04-08 12:49:41 -070026 "github.com/opencord/ofagent-go/internal/pkg/holder"
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000027 "github.com/opencord/voltha-lib-go/v7/pkg/log"
khenaidoofcf0b8d2021-10-19 17:57:30 -040028 "github.com/opencord/voltha-protos/v5/go/openflow_13"
David K. Bainbridge157bdab2020-01-16 14:38:05 -080029)
30
David K. Bainbridge9cb404e2020-01-28 14:32:29 -080031var NoVolthaConnectionError = errors.New("no-voltha-connection")
David K. Bainbridge157bdab2020-01-16 14:38:05 -080032
33type ofcEvent byte
34type ofcState byte
Jonathan Hart4b110f62020-03-13 17:36:19 -070035type ofcRole byte
David K. Bainbridge157bdab2020-01-16 14:38:05 -080036
37const (
38 ofcEventStart = ofcEvent(iota)
David K. Bainbridge55376262020-01-22 23:28:27 -080039 ofcEventConnect
40 ofcEventDisconnect
41 ofcEventStop
David K. Bainbridge157bdab2020-01-16 14:38:05 -080042
David K. Bainbridge55376262020-01-22 23:28:27 -080043 ofcStateCreated = ofcState(iota)
44 ofcStateStarted
45 ofcStateConnected
David K. Bainbridge157bdab2020-01-16 14:38:05 -080046 ofcStateDisconnected
David K. Bainbridge55376262020-01-22 23:28:27 -080047 ofcStateStopped
Jonathan Hart4b110f62020-03-13 17:36:19 -070048
49 ofcRoleNone = ofcRole(iota)
50 ofcRoleEqual
51 ofcRoleMaster
52 ofcRoleSlave
Matteo Scandolo256266d2020-06-01 13:44:07 -070053
54 // according to testing this is the maximum content of an
55 // openflow message to remain under 64KB
Matteo Scandoloe8d2e9c2020-09-21 15:52:14 -070056 ofcFlowsChunkSize = 350 // this amount of flows is around 40KB for DT, 47KB ATT and 61KB for TT
Matteo Scandolo256266d2020-06-01 13:44:07 -070057 ofcPortsChunkSize = 550 // this amount of port stats is around 61KB
58 ofcPortsDescChunkSize = 900 // this amount of port desc is around 57KB
David K. Bainbridge157bdab2020-01-16 14:38:05 -080059)
60
David K. Bainbridge55376262020-01-22 23:28:27 -080061func (e ofcEvent) String() string {
62 switch e {
63 case ofcEventStart:
64 return "ofc-event-start"
65 case ofcEventConnect:
66 return "ofc-event-connected"
67 case ofcEventDisconnect:
68 return "ofc-event-disconnected"
69 case ofcEventStop:
70 return "ofc-event-stop"
71 default:
72 return "ofc-event-unknown"
73 }
74}
75
76func (s ofcState) String() string {
77 switch s {
78 case ofcStateCreated:
79 return "ofc-state-created"
80 case ofcStateStarted:
81 return "ofc-state-started"
82 case ofcStateConnected:
83 return "ofc-state-connected"
84 case ofcStateDisconnected:
85 return "ofc-state-disconnected"
86 case ofcStateStopped:
87 return "ofc-state-stopped"
88 default:
89 return "ofc-state-unknown"
90 }
91}
92
Matteo Scandolo65e96762020-09-18 14:24:57 -070093func (r ofcRole) String() string {
94 switch r {
95 case ofcRoleNone:
96 return "ofcRoleNone"
97 case ofcRoleEqual:
98 return "ofcRoleEqual"
99 case ofcRoleMaster:
100 return "ofcRoleMaster"
101 case ofcRoleSlave:
102 return "ofcRoleSlave"
103 default:
104 return "ofc-role-unknown"
105
106 }
107}
108
David K. Bainbridge55376262020-01-22 23:28:27 -0800109// OFClient the configuration and operational state of a connection to an
110// openflow controller
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800111type OFClient struct {
Jonathan Hart4b110f62020-03-13 17:36:19 -0700112 OFControllerEndPoints []string
113 DeviceID string
David Bainbridgef8ce7d22020-04-08 12:49:41 -0700114 VolthaClient *holder.VolthaServiceClientHolder
khenaidoofcf0b8d2021-10-19 17:57:30 -0400115 PacketOutChannel chan *openflow_13.PacketOut
Jonathan Hart4b110f62020-03-13 17:36:19 -0700116 ConnectionMaxRetries int
117 ConnectionRetryDelay time.Duration
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800118
Jonathan Hart4b110f62020-03-13 17:36:19 -0700119 // map of endpoint to OF connection
120 connections map[string]*OFConnection
121
122 // global role state for device
123 generationIsDefined bool
124 generationID uint64
125 roleLock sync.Mutex
Matteo Scandolo256266d2020-06-01 13:44:07 -0700126
127 flowsChunkSize int
128 portsChunkSize int
129 portsDescChunkSize int
Jonathan Hart4b110f62020-03-13 17:36:19 -0700130}
131
132type RoleManager interface {
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000133 UpdateRoles(ctx context.Context, from string, request *ofp.RoleRequest) bool
Jonathan Hart4b110f62020-03-13 17:36:19 -0700134}
135
136func distance(a uint64, b uint64) int64 {
137 return (int64)(a - b)
138}
139
140// UpdateRoles validates a role request and updates role state for connections where it changed
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000141func (ofc *OFClient) UpdateRoles(ctx context.Context, from string, request *ofp.RoleRequest) bool {
Matteo Scandolo65e96762020-09-18 14:24:57 -0700142 logger.Debugw(ctx, "updating-role", log.Fields{
Jonathan Hart4b110f62020-03-13 17:36:19 -0700143 "from": from,
144 "to": request.Role,
145 "id": request.GenerationId})
146
147 ofc.roleLock.Lock()
148 defer ofc.roleLock.Unlock()
149
150 if request.Role == ofp.OFPCRRoleEqual {
151 // equal request doesn't care about generation ID and always succeeds
152 connection := ofc.connections[from]
153 connection.role = ofcRoleEqual
154 return true
155 }
156
157 if ofc.generationIsDefined && distance(request.GenerationId, ofc.generationID) < 0 {
158 // generation ID is not valid
159 return false
160 } else {
161 ofc.generationID = request.GenerationId
162 ofc.generationIsDefined = true
163
164 if request.Role == ofp.OFPCRRoleMaster {
165 // master is potentially changing, find the existing master and set it to slave
166 for endpoint, connection := range ofc.connections {
167 if endpoint == from {
168 connection.role = ofcRoleMaster
Matteo Scandolo65e96762020-09-18 14:24:57 -0700169 logger.Infow(ctx, "updating-master", log.Fields{
170 "endpoint": endpoint,
171 })
Jonathan Hart4b110f62020-03-13 17:36:19 -0700172 } else if connection.role == ofcRoleMaster {
173 // the old master should be set to slave
174 connection.role = ofcRoleSlave
Matteo Scandolo65e96762020-09-18 14:24:57 -0700175 logger.Debugw(ctx, "updating-slave", log.Fields{
176 "endpoint": endpoint,
177 })
Jonathan Hart4b110f62020-03-13 17:36:19 -0700178 }
179 }
180 return true
181 } else if request.Role == ofp.OFPCRRoleSlave {
182 connection := ofc.connections[from]
183 connection.role = ofcRoleSlave
184 return true
185 }
186 }
187
188 return false
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800189}
190
David K. Bainbridge55376262020-01-22 23:28:27 -0800191// NewClient returns an initialized OFClient instance based on the configuration
192// specified
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000193func NewOFClient(ctx context.Context, config *OFClient) *OFClient {
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800194
195 ofc := OFClient{
Jonathan Hart4b110f62020-03-13 17:36:19 -0700196 DeviceID: config.DeviceID,
197 OFControllerEndPoints: config.OFControllerEndPoints,
198 VolthaClient: config.VolthaClient,
199 PacketOutChannel: config.PacketOutChannel,
200 ConnectionMaxRetries: config.ConnectionMaxRetries,
201 ConnectionRetryDelay: config.ConnectionRetryDelay,
202 connections: make(map[string]*OFConnection),
Matteo Scandolo256266d2020-06-01 13:44:07 -0700203 flowsChunkSize: ofcFlowsChunkSize,
204 portsChunkSize: ofcPortsChunkSize,
205 portsDescChunkSize: ofcPortsDescChunkSize,
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800206 }
207
208 if ofc.ConnectionRetryDelay <= 0 {
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000209 logger.Warnw(ctx, "connection retry delay not valid, setting to default",
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800210 log.Fields{
211 "device-id": ofc.DeviceID,
212 "value": ofc.ConnectionRetryDelay.String(),
213 "default": (3 * time.Second).String()})
214 ofc.ConnectionRetryDelay = 3 * time.Second
215 }
216 return &ofc
217}
218
David K. Bainbridge55376262020-01-22 23:28:27 -0800219// Stop initiates a shutdown of the OFClient
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800220func (ofc *OFClient) Stop() {
Jonathan Hart4b110f62020-03-13 17:36:19 -0700221 for _, connection := range ofc.connections {
Andrea Campanelladec47f92021-05-27 15:32:45 +0200222 for len(connection.sendChannel) > 0 || connection.lastUnsentMessage != nil {
223 logger.Debugw(context.Background(), "waiting for channel to be empty before closing", log.Fields{
224 "len": connection.sendChannel})
225 //do nothing, waiting for the channel to send the messages
226 }
Jonathan Hart4b110f62020-03-13 17:36:19 -0700227 connection.events <- ofcEventStop
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800228 }
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800229}
230
231func (ofc *OFClient) Run(ctx context.Context) {
Jonathan Hart4b110f62020-03-13 17:36:19 -0700232 for _, endpoint := range ofc.OFControllerEndPoints {
233 connection := &OFConnection{
234 OFControllerEndPoint: endpoint,
235 DeviceID: ofc.DeviceID,
236 VolthaClient: ofc.VolthaClient,
237 PacketOutChannel: ofc.PacketOutChannel,
238 ConnectionMaxRetries: ofc.ConnectionMaxRetries,
239 ConnectionRetryDelay: ofc.ConnectionRetryDelay,
240 role: ofcRoleNone,
241 roleManager: ofc,
242 events: make(chan ofcEvent, 10),
243 sendChannel: make(chan Message, 100),
Matteo Scandolo256266d2020-06-01 13:44:07 -0700244 flowsChunkSize: ofc.flowsChunkSize,
245 portsChunkSize: ofc.portsChunkSize,
246 portsDescChunkSize: ofc.portsDescChunkSize,
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800247 }
Jonathan Hart4b110f62020-03-13 17:36:19 -0700248
249 ofc.connections[endpoint] = connection
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800250 }
251
Jonathan Hart4b110f62020-03-13 17:36:19 -0700252 for _, connection := range ofc.connections {
253 go connection.Run(ctx)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800254 }
255}
256
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000257func (ofc *OFClient) SendMessage(ctx context.Context, message Message) error {
Matteo Scandolo65e96762020-09-18 14:24:57 -0700258
259 var toEqual bool
260 var msgType string
261
262 switch message.(type) {
263 case *ofp.PortStatus:
264 msgType = "PortStatus"
265 toEqual = true
266 case *ofp.PacketIn:
267 msgType = "PacketIn"
268 toEqual = false
Manindere2af7e42020-12-04 11:46:26 +0530269 case *ofp.ErrorMsg:
270 msgType = "Error"
271 toEqual = false
Matteo Scandolo65e96762020-09-18 14:24:57 -0700272 default:
273 toEqual = true
274 }
275
276 for endpoint, connection := range ofc.connections {
277 if connection.role == ofcRoleMaster || (connection.role == ofcRoleEqual && toEqual) {
278 logger.Debugw(ctx, "sending-message", log.Fields{
279 "endpoint": endpoint,
280 "toEqual": toEqual,
Andrea Campanella0f751d52021-07-27 10:54:08 +0200281 "role": connection.role,
Matteo Scandolo65e96762020-09-18 14:24:57 -0700282 "msgType": msgType,
283 })
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000284 err := connection.SendMessage(ctx, message)
Jonathan Hart4b110f62020-03-13 17:36:19 -0700285 if err != nil {
286 return err
287 }
288 }
289 }
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800290 return nil
291}