blob: eb48c2859463cfbf1e9f8a819dc2592844a59d2c [file] [log] [blame]
Naveen Sampath04696f72022-06-13 15:19:14 +05301/*
2* Copyright 2022-present Open Networking Foundation
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
16package controller
17
18import (
19 "context"
20 "errors"
21 "sync"
22 "time"
23
24 "encoding/hex"
25
26 "voltha-go-controller/database"
27 errorCodes "voltha-go-controller/internal/pkg/errorcodes"
28 "voltha-go-controller/internal/pkg/intf"
29 "voltha-go-controller/internal/pkg/of"
30 "voltha-go-controller/internal/pkg/tasks"
31 "voltha-go-controller/internal/pkg/util"
32 "voltha-go-controller/internal/pkg/vpagent"
33
Tinoj Joseph1d108322022-07-13 10:07:39 +053034 "voltha-go-controller/log"
Naveen Sampath04696f72022-06-13 15:19:14 +053035)
36
37var logger log.CLogger
38var ctx = context.TODO()
39
40func init() {
41 // Setup this package so that it's log level can be modified at run time
42 var err error
Tinoj Joseph1d108322022-07-13 10:07:39 +053043 logger, err = log.AddPackageWithDefaultParam()
Naveen Sampath04696f72022-06-13 15:19:14 +053044 if err != nil {
45 panic(err)
46 }
47}
48
49var db database.DBIntf
50
51var deviceTableSyncDuration = 15 * time.Minute
52
53//SetDeviceTableSyncDuration - sets interval between device table sync up activity
54// duration - in minutes
55func SetDeviceTableSyncDuration(duration int) {
56 deviceTableSyncDuration = time.Duration(duration) * time.Minute
57}
58
59// VoltController structure
60type VoltController struct {
61 rebootLock sync.Mutex
62 rebootInProgressDevices map[string]string
63 devices map[string]*Device
64 deviceLock sync.RWMutex
65 vagent map[string]*vpagent.VPAgent
66 ctx context.Context
67 app intf.App
68 RebootFlow bool
69 BlockedDeviceList *util.ConcurrentMap
70 deviceTaskQueue *util.ConcurrentMap
71}
72
73var vcontroller *VoltController
74
75// NewController is the constructor for VoltController
76func NewController(ctx context.Context, app intf.App) intf.IVPClientAgent {
77 var controller VoltController
78
79 controller.rebootInProgressDevices = make(map[string]string)
80 controller.devices = make(map[string]*Device)
81 controller.deviceLock = sync.RWMutex{}
82 controller.ctx = ctx
83 controller.app = app
84 controller.BlockedDeviceList = util.NewConcurrentMap()
85 controller.deviceTaskQueue = util.NewConcurrentMap()
86 db = database.GetDatabase()
87 vcontroller = &controller
88 return &controller
89}
90
91// AddDevice to add device
Tinoj Joseph07cc5372022-07-18 22:53:51 +053092func (v *VoltController) AddDevice(cntx context.Context, config *intf.VPClientCfg) intf.IVPClient {
Naveen Sampath04696f72022-06-13 15:19:14 +053093
Tinoj Joseph07cc5372022-07-18 22:53:51 +053094 d := NewDevice(cntx, config.DeviceID, config.SerialNum, config.VolthaClient, config.SouthBoundID)
Naveen Sampath04696f72022-06-13 15:19:14 +053095 v.devices[config.DeviceID] = d
Tinoj Joseph07cc5372022-07-18 22:53:51 +053096 v.app.AddDevice(cntx, d.ID, d.SerialNum, config.SouthBoundID)
Naveen Sampath04696f72022-06-13 15:19:14 +053097
Tinoj Joseph07cc5372022-07-18 22:53:51 +053098 d.RestoreMetersFromDb(cntx)
99 d.RestoreGroupsFromDb(cntx)
100 d.RestoreFlowsFromDb(cntx)
101 d.RestorePortsFromDb(cntx)
Naveen Sampath04696f72022-06-13 15:19:14 +0530102 d.ConnectInd(context.TODO(), intf.DeviceDisc)
103 d.packetOutChannel = config.PacketOutChannel
104
105 logger.Warnw(ctx, "Added device", log.Fields{"Device": config.DeviceID, "SerialNo": d.SerialNum, "State": d.State})
106
107 return d
108}
109
110// DelDevice to delete device
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530111func (v *VoltController) DelDevice(cntx context.Context, id string) {
Naveen Sampath04696f72022-06-13 15:19:14 +0530112 d, ok := v.devices[id]
113 if ok {
114 delete(v.devices, id)
115 d.Delete()
116 }
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530117 v.app.DelDevice(cntx, id)
Naveen Sampath04696f72022-06-13 15:19:14 +0530118 d.cancel() // To stop the device tables sync routine
119 logger.Warnw(ctx, "Deleted device", log.Fields{"Device": id})
120}
121
122//AddControllerTask - add task to controller queue
123func (v *VoltController) AddControllerTask(device string, task tasks.Task) {
124 var taskQueueIntf interface{}
125 var taskQueue *tasks.Tasks
126 var found bool
127 if taskQueueIntf, found = v.deviceTaskQueue.Get(device); !found {
128 taskQueue = tasks.NewTasks(context.TODO())
129 v.deviceTaskQueue.Set(device, taskQueue)
130 } else {
131 taskQueue = taskQueueIntf.(*tasks.Tasks)
132 }
133 taskQueue.AddTask(task)
134 logger.Warnw(ctx, "Task Added to Controller Task List", log.Fields{"Len": taskQueue.NumPendingTasks(), "Total": taskQueue.TotalTasks()})
135}
136
137//AddNewDevice - called when new device is discovered. This will be
138//processed as part of controller queue
139func (v *VoltController) AddNewDevice(config *intf.VPClientCfg) {
140 adt := NewAddDeviceTask(config)
141 v.AddControllerTask(config.DeviceID, adt)
142}
143
144// GetDevice to get device info
145func (v *VoltController) GetDevice(id string) (*Device, error) {
146 d, ok := v.devices[id]
147 if ok {
148 return d, nil
149 }
150 return nil, errorCodes.ErrDeviceNotFound
151}
152
153// IsRebootInProgressForDevice to check if reboot is in progress for the device
154func (v *VoltController) IsRebootInProgressForDevice(device string) bool {
155 v.rebootLock.Lock()
156 defer v.rebootLock.Unlock()
157 _, ok := v.rebootInProgressDevices[device]
158 return ok
159}
160
161// SetRebootInProgressForDevice to set reboot in progress for the device
162func (v *VoltController) SetRebootInProgressForDevice(device string) bool {
163 v.rebootLock.Lock()
164 defer v.rebootLock.Unlock()
165 _, ok := v.rebootInProgressDevices[device]
166 if ok {
167 return true
168 }
169 v.rebootInProgressDevices[device] = device
170 logger.Warnw(ctx, "Setted Reboot-In-Progress flag", log.Fields{"Device": device})
171
172 d, err := v.GetDevice(device)
173 if err == nil {
174 d.ResetCache()
175 } else {
176 logger.Errorw(ctx, "Failed to get device", log.Fields{"Device": device, "Error": err})
177 }
178
179 return true
180}
181
182// ReSetRebootInProgressForDevice to reset reboot in progress for the device
183func (v *VoltController) ReSetRebootInProgressForDevice(device string) bool {
184 v.rebootLock.Lock()
185 defer v.rebootLock.Unlock()
186 _, ok := v.rebootInProgressDevices[device]
187 if !ok {
188 return true
189 }
190 delete(v.rebootInProgressDevices, device)
191 logger.Warnw(ctx, "Resetted Reboot-In-Progress flag", log.Fields{"Device": device})
192 return true
193}
194
195// DeviceRebootInd is device reboot indication
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530196func (v *VoltController) DeviceRebootInd(cntx context.Context, dID string, srNo string, sbID string) {
197 v.app.DeviceRebootInd(cntx, dID, srNo, sbID)
198 _ = db.DelAllRoutesForDevice(cntx, dID)
199 _ = db.DelAllGroup(cntx, dID)
200 _ = db.DelAllMeter(cntx, dID)
201 _ = db.DelAllPONCounters(cntx, dID)
Naveen Sampath04696f72022-06-13 15:19:14 +0530202}
203
204// DeviceDisableInd is device deactivation indication
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530205func (v *VoltController) DeviceDisableInd(cntx context.Context, dID string) {
206 v.app.DeviceDisableInd(cntx, dID)
Naveen Sampath04696f72022-06-13 15:19:14 +0530207}
208
209//TriggerPendingProfileDeleteReq - trigger pending profile delete requests
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530210func (v *VoltController) TriggerPendingProfileDeleteReq(cntx context.Context, device string) {
211 v.app.TriggerPendingProfileDeleteReq(cntx, device)
Naveen Sampath04696f72022-06-13 15:19:14 +0530212}
213
214//TriggerPendingMigrateServicesReq - trigger pending services migration requests
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530215func (v *VoltController) TriggerPendingMigrateServicesReq(cntx context.Context, device string) {
216 v.app.TriggerPendingMigrateServicesReq(cntx, device)
Naveen Sampath04696f72022-06-13 15:19:14 +0530217}
218
219// SetAuditFlags to set the audit flags
220func (v *VoltController) SetAuditFlags(device *Device) {
221 v.app.SetRebootFlag(true)
222 device.auditInProgress = true
223}
224
225// ResetAuditFlags to reset the audit flags
226func (v *VoltController) ResetAuditFlags(device *Device) {
227 v.app.SetRebootFlag(false)
228 device.auditInProgress = false
229}
230
231//ProcessFlowModResultIndication - send flow mod result notification
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530232func (v *VoltController) ProcessFlowModResultIndication(cntx context.Context, flowStatus intf.FlowStatus) {
233 v.app.ProcessFlowModResultIndication(cntx, flowStatus)
Naveen Sampath04696f72022-06-13 15:19:14 +0530234}
235
236// AddVPAgent to add the vpagent
237func (v *VoltController) AddVPAgent(vep string, vpa *vpagent.VPAgent) {
238 v.vagent[vep] = vpa
239}
240
241// VPAgent to get vpagent info
242func (v *VoltController) VPAgent(vep string) (*vpagent.VPAgent, error) {
243 vpa, ok := v.vagent[vep]
244 if ok {
245 return vpa, nil
246 }
247 return nil, errors.New("VPA Not Registered")
248}
249
250// PacketOutReq for packet out request
251func (v *VoltController) PacketOutReq(device string, inport string, outport string, pkt []byte, isCustomPkt bool) error {
252 logger.Debugw(ctx, "Packet Out Req", log.Fields{"Device": device, "OutPort": outport})
253 d, err := v.GetDevice(device)
254 if err != nil {
255 return err
256 }
257 logger.Debugw(ctx, "Packet Out Pkt", log.Fields{"Pkt": hex.EncodeToString(pkt)})
258 return d.PacketOutReq(inport, outport, pkt, isCustomPkt)
259}
260
261// AddFlows to add flows
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530262func (v *VoltController) AddFlows(cntx context.Context, port string, device string, flow *of.VoltFlow) error {
Naveen Sampath04696f72022-06-13 15:19:14 +0530263 d, err := v.GetDevice(device)
264 if err != nil {
265 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": device})
266 return err
267 }
268 devPort := d.GetPortByName(port)
269 if devPort == nil {
270 logger.Errorw(ctx, "Port Not Found", log.Fields{"Device": device})
271 return errorCodes.ErrPortNotFound
272 }
273 if d.ctx == nil {
274 //FIXME: Application should know the context before it could submit task. Handle at application level
275 logger.Errorw(ctx, "Context is missing. AddFlow Operation Not added to Task", log.Fields{"Device": device})
276 return errorCodes.ErrInvalidParamInRequest
277 }
278
279 var isMigrationRequired bool
280 if flow.MigrateCookie {
281 // flow migration to new cookie must be done only during the audit. Migration for all subflows must be done if
282 // atlease one subflow with old cookie found in the device.
283 for _, subFlow := range flow.SubFlows {
284 if isMigrationRequired = d.IsFlowPresentWithOldCookie(subFlow); isMigrationRequired {
285 break
286 }
287 }
288 }
289
290 if isMigrationRequired {
291 // In this case, the flow is updated in local cache and db here.
292 // Actual flow deletion and addition at voltha will happen during flow tables audit.
293 for _, subFlow := range flow.SubFlows {
294 logger.Debugw(ctx, "Cookie Migration Required", log.Fields{"OldCookie": subFlow.OldCookie, "NewCookie": subFlow.Cookie})
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530295 if err := d.DelFlowWithOldCookie(cntx, subFlow); err != nil {
Naveen Sampath04696f72022-06-13 15:19:14 +0530296 logger.Errorw(ctx, "Delete flow with old cookie failed", log.Fields{"Error": err, "OldCookie": subFlow.OldCookie})
297 }
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530298 if err := d.AddFlow(cntx, subFlow); err != nil {
Naveen Sampath04696f72022-06-13 15:19:14 +0530299 logger.Errorw(ctx, "Flow Add Failed", log.Fields{"Error": err, "Cookie": subFlow.Cookie})
300 }
301 }
302 } else {
303 flow.Command = of.CommandAdd
304 d.UpdateFlows(flow, devPort)
305 for cookie := range flow.SubFlows {
306 logger.Debugw(ctx, "Flow Add added to queue", log.Fields{"Cookie": cookie, "Device": device, "Port": port})
307 }
308 }
309 return nil
310}
311
312// DelFlows to delete flows
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530313func (v *VoltController) DelFlows(cntx context.Context, port string, device string, flow *of.VoltFlow) error {
Naveen Sampath04696f72022-06-13 15:19:14 +0530314 d, err := v.GetDevice(device)
315 if err != nil {
316 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": device})
317 return err
318 }
319 devPort := d.GetPortByName(port)
320 if devPort == nil {
321 logger.Errorw(ctx, "Port Not Found", log.Fields{"Device": device})
322 return errorCodes.ErrPortNotFound
323 }
324 if d.ctx == nil {
325 //FIXME: Application should know the context before it could submit task. Handle at application level
326 logger.Errorw(ctx, "Context is missing. DelFlow Operation Not added to Task", log.Fields{"Device": device})
327 return errorCodes.ErrInvalidParamInRequest
328 }
329
330 var isMigrationRequired bool
331 if flow.MigrateCookie {
332 // flow migration to new cookie must be done only during the audit. Migration for all subflows must be done if
333 // atlease one subflow with old cookie found in the device.
334 for _, subFlow := range flow.SubFlows {
335 if isMigrationRequired = d.IsFlowPresentWithOldCookie(subFlow); isMigrationRequired {
336 break
337 }
338 }
339 }
340
341 if isMigrationRequired {
342 // In this case, the flow is deleted from local cache and db here.
343 // Actual flow deletion at voltha will happen during flow tables audit.
344 for _, subFlow := range flow.SubFlows {
345 logger.Debugw(ctx, "Old Cookie delete Required", log.Fields{"OldCookie": subFlow.OldCookie})
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530346 if err := d.DelFlowWithOldCookie(cntx, subFlow); err != nil {
Naveen Sampath04696f72022-06-13 15:19:14 +0530347 logger.Errorw(ctx, "DelFlowWithOldCookie failed", log.Fields{"OldCookie": subFlow.OldCookie, "Error": err})
348 }
349 }
350 } else {
351 flow.Command = of.CommandDel
352 d.UpdateFlows(flow, devPort)
353 for cookie := range flow.SubFlows {
354 logger.Debugw(ctx, "Flow Del added to queue", log.Fields{"Cookie": cookie, "Device": device, "Port": port})
355 }
356 }
357 return nil
358}
359
360// GroupUpdate for group update
361func (v *VoltController) GroupUpdate(port string, device string, group *of.Group) error {
362 d, err := v.GetDevice(device)
363 if err != nil {
364 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": device})
365 return err
366 }
367
368 devPort := d.GetPortByName(port)
369 if devPort == nil {
370 logger.Errorw(ctx, "Port Not Found", log.Fields{"Device": device})
371 return errorCodes.ErrPortNotFound
372 }
373
374 if d.ctx == nil {
375 //FIXME: Application should know the context before it could submit task. Handle at application level
376 logger.Errorw(ctx, "Context is missing. GroupMod Operation Not added to task", log.Fields{"Device": device})
377 return errorCodes.ErrInvalidParamInRequest
378 }
379
380 d.UpdateGroup(group, devPort)
381 return nil
382}
383
384// ModMeter to get mod meter info
385func (v *VoltController) ModMeter(port string, device string, command of.MeterCommand, meter *of.Meter) error {
386 d, err := v.GetDevice(device)
387 if err != nil {
388 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": device})
389 return err
390 }
391
392 devPort := d.GetPortByName(port)
393 if devPort == nil {
394 logger.Errorw(ctx, "Port Not Found", log.Fields{"Device": device})
395 return errorCodes.ErrPortNotFound
396 }
397
398 d.ModMeter(command, meter, devPort)
399 return nil
400}
401
402// PortAddInd for port add indication
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530403func (v *VoltController) PortAddInd(cntx context.Context, device string, id uint32, name string) {
404 v.app.PortAddInd(cntx, device, id, name)
Naveen Sampath04696f72022-06-13 15:19:14 +0530405}
406
407// PortDelInd for port delete indication
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530408func (v *VoltController) PortDelInd(cntx context.Context, device string, port string) {
409 v.app.PortDelInd(cntx, device, port)
Naveen Sampath04696f72022-06-13 15:19:14 +0530410}
411
412// PortUpdateInd for port update indication
413func (v *VoltController) PortUpdateInd(device string, name string, id uint32) {
414 v.app.PortUpdateInd(device, name, id)
415}
416
417// PortUpInd for port up indication
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530418func (v *VoltController) PortUpInd(cntx context.Context, device string, port string) {
419 v.app.PortUpInd(cntx, device, port)
Naveen Sampath04696f72022-06-13 15:19:14 +0530420}
421
422// PortDownInd for port down indication
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530423func (v *VoltController) PortDownInd(cntx context.Context, device string, port string) {
424 v.app.PortDownInd(cntx, device, port)
Naveen Sampath04696f72022-06-13 15:19:14 +0530425}
426
427// DeviceUpInd for device up indication
428func (v *VoltController) DeviceUpInd(device string) {
429 v.app.DeviceUpInd(device)
430}
431
432// DeviceDownInd for device down indication
433func (v *VoltController) DeviceDownInd(device string) {
434 v.app.DeviceDownInd(device)
435}
436
437// PacketInInd for packet in indication
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530438func (v *VoltController) PacketInInd(cntx context.Context, device string, port string, data []byte) {
439 v.app.PacketInInd(cntx, device, port, data)
Naveen Sampath04696f72022-06-13 15:19:14 +0530440}
441
442// GetPortState to get port status
443func (v *VoltController) GetPortState(device string, name string) (PortState, error) {
444 d, err := v.GetDevice(device)
445 if err != nil {
446 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": device})
447 return PortStateDown, err
448 }
449 return d.GetPortState(name)
450}
451
452// UpdateMvlanProfiles for update mvlan profiles
Tinoj Joseph07cc5372022-07-18 22:53:51 +0530453func (v *VoltController) UpdateMvlanProfiles(cntx context.Context, device string) {
454 v.app.UpdateMvlanProfilesForDevice(cntx, device)
Naveen Sampath04696f72022-06-13 15:19:14 +0530455}
456
457// GetController to get controller
458func GetController() *VoltController {
459 return vcontroller
460}
461
462/*
463// PostIndication to post indication
464func (v *VoltController) PostIndication(device string, task interface{}) error {
465 var srvTask AddServiceIndTask
466 var portTask AddPortIndTask
467 var taskCommon tasks.Task
468 var isSvcTask bool
469
470 switch data := task.(type) {
471 case *AddServiceIndTask:
472 srvTask = *data
473 taskCommon = data
474 isSvcTask = true
475 case *AddPortIndTask:
476 portTask = *data
477 taskCommon = data
478 }
479
480 d, err := v.GetDevice(device)
481 if err != nil {
482 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": device})
483 //It means device itself it not present so just post the indication directly
484 if isSvcTask {
485 msgbus.PostAccessConfigInd(srvTask.result, d.SerialNum, srvTask.indicationType, srvTask.serviceName, 0, srvTask.reason, srvTask.trigger, srvTask.portState)
486 } else {
487 msgbus.ProcessPortInd(portTask.indicationType, d.SerialNum, portTask.portName, portTask.accessConfig, portTask.serviceList)
488 }
489 return err
490 }
491 if taskCommon != nil {
492 d.AddTask(taskCommon)
493 }
494 return nil
495}
496*/
497
498// GetTaskList to get the task list
499func (v *VoltController) GetTaskList(device string) []tasks.Task {
500 d, err := v.GetDevice(device)
501 if err != nil || d.ctx == nil {
502 logger.Errorw(ctx, "Device Not Connected/Found", log.Fields{"Device": device, "Dev Obj": d})
503 return []tasks.Task{}
504 }
505 return d.GetTaskList()
506
507}
508
509// AddBlockedDevices to add devices to blocked devices list
510func (v *VoltController) AddBlockedDevices(deviceSerialNumber string) {
511 v.BlockedDeviceList.Set(deviceSerialNumber, deviceSerialNumber)
512}
513
514// DelBlockedDevices to remove device from blocked device list
515func (v *VoltController) DelBlockedDevices(deviceSerialNumber string) {
516 v.BlockedDeviceList.Remove(deviceSerialNumber)
517}
518
519// IsBlockedDevice to check if device is blocked
520func (v *VoltController) IsBlockedDevice(deviceSerialNumber string) bool {
521 _, ifPresent := v.BlockedDeviceList.Get(deviceSerialNumber)
522 return ifPresent
523}
Tinoj Josephec742f62022-09-29 19:11:10 +0530524
525// GetFlows returns flow specific to device and flowID
526func (v *VoltController) GetFlow(deviceID string, cookie uint64) (*of.VoltSubFlow, error) {
527 d, err := v.GetDevice(deviceID)
528 if err != nil {
529 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": deviceID, "Error": err})
530 return nil, err
531 }
532 if flow, ok := d.GetFlow(cookie); ok {
533 return flow, nil
534 }
535 return nil, nil
536}
537
538// GetFlows returns list of flows for a particular device
539func (v *VoltController) GetFlows(deviceID string) ([]*of.VoltSubFlow, error) {
540 d, err := v.GetDevice(deviceID)
541 if err != nil {
542 logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": deviceID, "Error": err})
543 return nil, err
544 }
545 return d.GetAllFlows(), nil
546}
547
548// GetAllFlows returns list of all flows
549func (v *VoltController) GetAllFlows() ([]*of.VoltSubFlow, error) {
550 var flows []*of.VoltSubFlow
551 for _, d := range v.devices {
552 flows = append(flows, d.GetAllFlows()...)
553 }
554 return flows, nil
555}
556
557// GetAllPendingFlows returns list of all flows
558func (v *VoltController) GetAllPendingFlows() ([]*of.VoltSubFlow, error) {
559 var flows []*of.VoltSubFlow
560 for _, d := range v.devices {
561 flows = append(flows, d.GetAllPendingFlows()...)
562 }
563 return flows, nil
564}