blob: 8be866aba94788b3fa1e5eec4a061cc10fd8231f [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"
Andrea Campanella18448bc2021-07-08 18:47:22 +020027 "github.com/opencord/voltha-lib-go/v5/pkg/log"
Maninder12b909f2020-10-23 14:23:36 +053028 "github.com/opencord/voltha-protos/v4/go/voltha"
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
Jonathan Hart4b110f62020-03-13 17:36:19 -0700115 PacketOutChannel chan *voltha.PacketOut
116 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 {
222 connection.events <- ofcEventStop
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800223 }
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800224}
225
226func (ofc *OFClient) Run(ctx context.Context) {
Jonathan Hart4b110f62020-03-13 17:36:19 -0700227 for _, endpoint := range ofc.OFControllerEndPoints {
228 connection := &OFConnection{
229 OFControllerEndPoint: endpoint,
230 DeviceID: ofc.DeviceID,
231 VolthaClient: ofc.VolthaClient,
232 PacketOutChannel: ofc.PacketOutChannel,
233 ConnectionMaxRetries: ofc.ConnectionMaxRetries,
234 ConnectionRetryDelay: ofc.ConnectionRetryDelay,
235 role: ofcRoleNone,
236 roleManager: ofc,
237 events: make(chan ofcEvent, 10),
238 sendChannel: make(chan Message, 100),
Matteo Scandolo256266d2020-06-01 13:44:07 -0700239 flowsChunkSize: ofc.flowsChunkSize,
240 portsChunkSize: ofc.portsChunkSize,
241 portsDescChunkSize: ofc.portsDescChunkSize,
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800242 }
Jonathan Hart4b110f62020-03-13 17:36:19 -0700243
244 ofc.connections[endpoint] = connection
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800245 }
246
Jonathan Hart4b110f62020-03-13 17:36:19 -0700247 for _, connection := range ofc.connections {
248 go connection.Run(ctx)
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800249 }
250}
251
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000252func (ofc *OFClient) SendMessage(ctx context.Context, message Message) error {
Matteo Scandolo65e96762020-09-18 14:24:57 -0700253
254 var toEqual bool
255 var msgType string
256
257 switch message.(type) {
258 case *ofp.PortStatus:
259 msgType = "PortStatus"
260 toEqual = true
261 case *ofp.PacketIn:
262 msgType = "PacketIn"
263 toEqual = false
Manindere2af7e42020-12-04 11:46:26 +0530264 case *ofp.ErrorMsg:
265 msgType = "Error"
266 toEqual = false
Matteo Scandolo65e96762020-09-18 14:24:57 -0700267 default:
268 toEqual = true
269 }
270
271 for endpoint, connection := range ofc.connections {
272 if connection.role == ofcRoleMaster || (connection.role == ofcRoleEqual && toEqual) {
273 logger.Debugw(ctx, "sending-message", log.Fields{
274 "endpoint": endpoint,
275 "toEqual": toEqual,
276 "msgType": msgType,
277 })
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000278 err := connection.SendMessage(ctx, message)
Jonathan Hart4b110f62020-03-13 17:36:19 -0700279 if err != nil {
280 return err
281 }
282 }
283 }
David K. Bainbridge157bdab2020-01-16 14:38:05 -0800284 return nil
285}