blob: a4f8a8da7ead5162c24a6da885b50354fb922d6e [file] [log] [blame]
/*
* Copyright 2022-present Open Networking Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controller
import (
"context"
"time"
infraerror "voltha-go-controller/internal/pkg/errorcodes"
infraerrorcode "voltha-go-controller/internal/pkg/errorcodes/service"
"voltha-go-controller/internal/pkg/of"
"voltha-go-controller/log"
)
const (
// MaxRetryCount - Maximum retry attempts on failure
MaxRetryCount int = 1
)
// AddFlowsTask structure
type AddFlowsTask struct {
ctx context.Context
flow *of.VoltFlow
device *Device
timestamp string
taskID uint8
}
// NewAddFlowsTask is constructor for AddFlowsTask
func NewAddFlowsTask(ctx context.Context, flow *of.VoltFlow, device *Device) *AddFlowsTask {
var aft AddFlowsTask
aft.device = device
aft.flow = flow
aft.ctx = ctx
tstamp := (time.Now()).Format(time.RFC3339Nano)
aft.timestamp = tstamp
return &aft
}
// Name to add flow task
func (aft *AddFlowsTask) Name() string {
for _, flow := range aft.flow.SubFlows {
logger.Debugw(ctx, "Flow Cookies", log.Fields{"Cookie": flow.Cookie})
}
return "Add Flows Task"
}
// TaskID to return task ID
func (aft *AddFlowsTask) TaskID() uint8 {
return aft.taskID
}
// Timestamp to return timestamp
func (aft *AddFlowsTask) Timestamp() string {
return aft.timestamp
}
// Stop to stop the add flow task
func (aft *AddFlowsTask) Stop() {
}
// Start to start adding flow task
func (aft *AddFlowsTask) Start(ctx context.Context, taskID uint8) error {
var err error
aft.taskID = taskID
aft.ctx = ctx
flowsToProcess := make(map[uint64]*of.VoltSubFlow)
flowsPresent := 0
// First add/delete the flows first locally before passing them to actual device
for _, flow := range aft.flow.SubFlows {
logger.Debugw(ctx, "Flow Mod Request", log.Fields{"Cookie": flow.Cookie, "Oper": aft.flow.Command, "Port": aft.flow.PortID})
if aft.flow.Command == of.CommandAdd {
flow.State = of.FlowAddPending
if err = aft.device.AddFlow(ctx, flow); err != nil {
logger.Warnw(ctx, "Add Flow Error", log.Fields{"Cookie": flow.Cookie, "Reason": err.Error()})
// If flow already exists in cache, check for flow state
// If Success: Trigger success FLow Indication
// if Failure: Continue process, so add-retry happens
if err.Error() == ErrDuplicateFlow {
dbFlow, _ := aft.device.GetFlow(flow.Cookie)
if dbFlow.State == of.FlowAddSuccess {
aft.device.triggerFlowNotification(ctx, flow.Cookie, aft.flow.Command, of.BwAvailDetails{}, nil)
flowsPresent++
continue
}
}
}
flowsToProcess[flow.Cookie] = flow
} else {
dbFlow, ok := aft.device.GetFlow(flow.Cookie)
if !ok {
logger.Warnw(ctx, "Delete Flow Error: Flow Does not Exist", log.Fields{"Cookie": flow.Cookie, "Device": aft.device.ID})
} else {
// dbFlow.State = of.FlowDelPending
// aft.device.AddFlowToDb(dbFlow)
flowsToProcess[flow.Cookie] = dbFlow
}
// Below call will delete flow from DB and will not allow to maintain flow count and state. Hence commenting the below call.
//aft.device.triggerFlowNotification(ctx, flow.Cookie, aft.flow.Command, of.BwAvailDetails{}, nil, false)
}
}
if flowsPresent == len(aft.flow.SubFlows) {
logger.Warn(ctx, "All Flows already present in database. Skipping Flow Push to SB")
return nil
}
// PortName and PortID are used for validation of PortID, whether it is still valid and associated with old PortName or
// PortID got assigned to another PortName. If the condition met, skip these flow update to voltha core
if aft.flow.PortName != "" && aft.flow.PortID != 0 {
portName, _ := aft.device.GetPortName(aft.flow.PortID)
if aft.flow.PortName != portName && portName != "" {
for _, flow := range aft.flow.SubFlows {
logger.Warnw(ctx, "Skip Flow Update", log.Fields{"Reason": "Port Deleted", "PortName": aft.flow.PortName, "PortNo": aft.flow.PortID, "Cookie": flow.Cookie, "Operation": aft.flow.Command})
if aft.flow.Command == of.CommandDel {
aft.device.triggerFlowNotification(ctx, flow.Cookie, aft.flow.Command, of.BwAvailDetails{}, nil)
}
}
return nil
}
}
if !aft.device.isSBOperAllowed(aft.flow.ForceAction) {
for _, flow := range aft.flow.SubFlows {
logger.Warnw(ctx, "Skipping Flow Table Update", log.Fields{"Reason": "Device State not UP", "State": aft.device.State, "Cookie": flow.Cookie, "Operation": aft.flow.Command})
}
return nil
}
flows := of.ProcessVoltFlow(aft.device.ID, aft.flow.Command, flowsToProcess)
for _, flow := range flows {
attempt := 0
if vc := aft.device.VolthaClient(); vc != nil {
for {
if _, err = vc.UpdateLogicalDeviceFlowTable(aft.ctx, flow); err != nil {
logger.Errorw(ctx, "Update Flow Table Failed", log.Fields{"Cookie": flow.GetFlowMod().Cookie, "Reason": err.Error(), "Operation": aft.flow.Command})
statusCode, _ := infraerror.GetErrorInfo(err)
// Retry on flow delete failure once.
// Do NOT retry incase of failure with reason: Entry Not Found
if aft.flow.Command == of.CommandDel && statusCode != uint32(infraerrorcode.ErrNotExists) {
if attempt != MaxRetryCount {
logger.Warnw(ctx, "Retrying Flow Delete", log.Fields{"Cookie": flow.GetFlowMod().Cookie, "Attempt": attempt})
attempt++
continue
}
logger.Errorw(ctx, "Flow Delete failed even aft max retries", log.Fields{"Flow": flow, "Attempt": attempt})
}
}
break
}
aft.device.triggerFlowNotification(ctx, flow.FlowMod.Cookie, aft.flow.Command, of.BwAvailDetails{}, err)
} else {
logger.Errorw(ctx, "Update Flow Table Failed: Voltha Client Unavailable", log.Fields{"Flow": flow})
}
}
return nil
}
func isFlowOperSuccess(statusCode uint32, oper of.Command) bool {
volthaErrorCode := infraerrorcode.ErrorCode(statusCode)
if volthaErrorCode == infraerrorcode.ErrOk {
return true
}
if oper == of.CommandAdd && volthaErrorCode == infraerrorcode.ErrAlreadyExists {
return true
} else if oper == of.CommandDel && volthaErrorCode == infraerrorcode.ErrNotExists {
return true
}
return false
}
// func getBwAvailInfo(bwAvailInfo []*voltha.ResponseMsg) of.BwAvailDetails {
// var bwInfo of.BwAvailDetails
// // convert the bw details sent from olt to a struct
// // received msg format:
// // additional_data[Data{ResponseMsg
// //{"key":"prevBW","value":"111111"},
// //{"key":"presentBW","value":"10000"}]
// if len(bwAvailInfo) > 1 {
// prevBwResp := bwAvailInfo[0]
// if prevBwResp.Key == of.PrevBwInfo {
// _, err := strconv.Atoi(prevBwResp.Val)
// if err == nil {
// bwInfo.PrevBw = prevBwResp.Val
// }
// }
// presentBwResp := bwAvailInfo[1]
// if presentBwResp.Key == of.PresentBwInfo {
// _, err := strconv.Atoi(prevBwResp.Val)
// if err == nil {
// bwInfo.PresentBw = presentBwResp.Val
// }
// }
// if bwInfo.PresentBw == bwInfo.PrevBw {
// return of.BwAvailDetails{}
// }
// logger.Infow(ctx, "Bandwidth-consumed-info", log.Fields{"BwConsumed": bwInfo})
// }
// return bwInfo
// }