blob: abf31088f073e5cb296be771c6be529527e1e451 [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"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
infraerror "voltha-go-controller/internal/pkg/errorcodes"
"voltha-go-controller/database"
"voltha-go-controller/internal/pkg/holder"
"voltha-go-controller/internal/pkg/intf"
"voltha-go-controller/internal/pkg/of"
//"voltha-go-controller/internal/pkg/vpagent"
"voltha-go-controller/internal/pkg/tasks"
"voltha-go-controller/internal/pkg/util"
"voltha-go-controller/log"
ofp "github.com/opencord/voltha-protos/v5/go/openflow_13"
"github.com/opencord/voltha-protos/v5/go/voltha"
)
// PortState type
type PortState string
const (
// PortStateDown constant
PortStateDown PortState = "DOWN"
// PortStateUp constant
PortStateUp PortState = "UP"
// DefaultMaxFlowQueues constant
DefaultMaxFlowQueues = 67
//ErrDuplicateFlow - indicates flow already exists in DB
ErrDuplicateFlow string = "duplicate flow"
//Unknown_Port_ID - indicates that the port id is unknown
Unknown_Port_ID = "unknown port id"
//Duplicate_Port - indicates the port is already exist in controller
Duplicate_Port = "duplicate port"
)
// DevicePort structure
type DevicePort struct {
Name string
State PortState
Version string
HwAddr string
tasks.Tasks
CurrSpeed uint32
MaxSpeed uint32
ID uint32
}
// NewDevicePort is the constructor for DevicePort
func NewDevicePort(mp *ofp.OfpPort) *DevicePort {
var port DevicePort
port.ID = mp.PortNo
port.Name = mp.Name
//port.HwAddr = strings.Trim(strings.Join(strings.Fields(fmt.Sprint("%02x", mp.HwAddr)), ":"), "[]")
port.HwAddr = strings.Trim(strings.ReplaceAll(fmt.Sprintf("%02x", mp.HwAddr), " ", ":"), "[]")
port.CurrSpeed = mp.CurrSpeed
port.MaxSpeed = mp.MaxSpeed
port.State = PortStateDown
return &port
}
// UniIDFlowQueue structure which maintains flows in queue.
type UniIDFlowQueue struct {
tasks.Tasks
ID uint32
}
// NewUniIDFlowQueue is the constructor for UniIDFlowQueue.
func NewUniIDFlowQueue(id uint32) *UniIDFlowQueue {
var flowQueue UniIDFlowQueue
flowQueue.ID = id
return &flowQueue
}
// DeviceState type
type DeviceState string
const (
// DeviceStateUNKNOWN constant
DeviceStateUNKNOWN DeviceState = "UNKNOWN"
// DeviceStateINIT constant
DeviceStateINIT DeviceState = "INIT"
// DeviceStateUP constant
DeviceStateUP DeviceState = "UP"
// DeviceStateDOWN constant
DeviceStateDOWN DeviceState = "DOWN"
// DeviceStateREBOOTED constant
DeviceStateREBOOTED DeviceState = "REBOOTED"
// DeviceStateDISABLED constant
DeviceStateDISABLED DeviceState = "DISABLED"
// DeviceStateDELETED constant
DeviceStateDELETED DeviceState = "DELETED"
)
type DeviceInterface interface {
SetFlowHash(cntx context.Context, hash uint32)
}
// Device structure
type Device struct {
ctx context.Context
cancel context.CancelFunc
vclientHolder *holder.VolthaServiceClientHolder
packetOutChannel chan *ofp.PacketOut
PortsByName map[string]*DevicePort
flows map[uint64]*of.VoltSubFlow
PortsByID map[uint32]*DevicePort
meters map[uint32]*of.Meter
flowQueue map[uint32]*UniIDFlowQueue // key is hash ID generated and value is UniIDFlowQueue.
SouthBoundID string
MfrDesc string
HwDesc string
SwDesc string
ID string
SerialNum string
State DeviceState
TimeStamp time.Time
groups sync.Map //map[uint32]*of.Group -> [GroupId : Group]
tasks.Tasks
portLock sync.RWMutex
flowLock sync.RWMutex
meterLock sync.RWMutex
flowQueueLock sync.RWMutex
flowHash uint32
auditInProgress bool
deviceAuditInProgress bool
}
// NewDevice is the constructor for Device
func NewDevice(cntx context.Context, id string, slno string, vclientHldr *holder.VolthaServiceClientHolder, southBoundID, mfr, hwDesc, swDesc string) *Device {
var device Device
device.ID = id
device.SerialNum = slno
device.State = DeviceStateDOWN
device.PortsByID = make(map[uint32]*DevicePort)
device.PortsByName = make(map[string]*DevicePort)
device.vclientHolder = vclientHldr
device.flows = make(map[uint64]*of.VoltSubFlow)
device.meters = make(map[uint32]*of.Meter)
device.flowQueue = make(map[uint32]*UniIDFlowQueue)
// Get the flowhash from db and update the flowhash variable in the device.
device.SouthBoundID = southBoundID
device.MfrDesc = mfr
device.HwDesc = hwDesc
device.SwDesc = swDesc
device.TimeStamp = time.Now()
flowHash, err := db.GetFlowHash(cntx, id)
if err != nil {
device.flowHash = DefaultMaxFlowQueues
} else {
var hash uint32
err = json.Unmarshal([]byte(flowHash), &hash)
if err != nil {
logger.Errorw(ctx, "Failed to unmarshall flowhash", log.Fields{"data": flowHash})
} else {
device.flowHash = hash
}
}
logger.Infow(ctx, "Flow hash for device", log.Fields{"Deviceid": id, "hash": device.flowHash})
return &device
}
// ResetCache to reset cache
func (d *Device) ResetCache() {
logger.Warnw(ctx, "Resetting flows, meters and groups cache", log.Fields{"Device": d.ID})
d.flows = make(map[uint64]*of.VoltSubFlow)
d.meters = make(map[uint32]*of.Meter)
d.groups = sync.Map{}
}
// GetFlow - Get the flow from device obj
func (d *Device) GetFlow(cookie uint64) (*of.VoltSubFlow, bool) {
d.flowLock.RLock()
defer d.flowLock.RUnlock()
flow, ok := d.flows[cookie]
logger.Debugw(ctx, "Get Flow", log.Fields{"Cookie": cookie})
return flow, ok
}
// GetAllFlows - Get the flow from device obj
func (d *Device) GetAllFlows() []*of.VoltSubFlow {
d.flowLock.RLock()
defer d.flowLock.RUnlock()
var flows []*of.VoltSubFlow
logger.Debugw(ctx, "Get All Flows", log.Fields{"deviceID": d.ID})
for _, f := range d.flows {
flows = append(flows, f)
}
return flows
}
// GetAllPendingFlows - Get the flow from device obj
func (d *Device) GetAllPendingFlows() []*of.VoltSubFlow {
d.flowLock.RLock()
defer d.flowLock.RUnlock()
var flows []*of.VoltSubFlow
logger.Debugw(ctx, "Get All Pending Flows", log.Fields{"deviceID": d.ID})
for _, f := range d.flows {
if f.State == of.FlowAddPending {
flows = append(flows, f)
}
}
return flows
}
// AddFlow - Adds the flow to the device and also to the database
func (d *Device) AddFlow(cntx context.Context, flow *of.VoltSubFlow) error {
d.flowLock.Lock()
defer d.flowLock.Unlock()
logger.Debugw(ctx, "AddFlow to device", log.Fields{"Cookie": flow.Cookie})
if dbFlow, ok := d.flows[flow.Cookie]; ok {
// In case of ONU reboot after flow delete failure, try to install flow in the device by checking for previous flow state
if dbFlow.State != of.FlowDelFailure {
return errors.New(ErrDuplicateFlow)
}
}
d.flows[flow.Cookie] = flow
d.AddFlowToDb(cntx, flow)
return nil
}
// AddFlowToDb is the utility to add the flow to the device
func (d *Device) AddFlowToDb(cntx context.Context, flow *of.VoltSubFlow) {
if b, err := json.Marshal(flow); err == nil {
if err = db.PutFlow(cntx, d.ID, flow.Cookie, string(b)); err != nil {
logger.Errorw(ctx, "Write Flow to DB failed", log.Fields{"device": d.ID, "cookie": flow.Cookie, "Reason": err})
}
}
}
// DelFlow - Deletes the flow from the device and the database
func (d *Device) DelFlow(cntx context.Context, flow *of.VoltSubFlow) error {
d.flowLock.Lock()
defer d.flowLock.Unlock()
if _, ok := d.flows[flow.Cookie]; ok {
delete(d.flows, flow.Cookie)
d.DelFlowFromDb(cntx, flow.Cookie)
return nil
}
return errors.New("flow does not exist")
}
// DelFlowFromDb is utility to delete the flow from the device
func (d *Device) DelFlowFromDb(cntx context.Context, flowID uint64) {
_ = db.DelFlow(cntx, d.ID, flowID)
}
// IsFlowPresentWithOldCookie is to check whether there is any flow with old cookie.
func (d *Device) IsFlowPresentWithOldCookie(flow *of.VoltSubFlow) bool {
d.flowLock.RLock()
defer d.flowLock.RUnlock()
if _, ok := d.flows[flow.Cookie]; ok {
return false
} else if flow.OldCookie != 0 && flow.Cookie != flow.OldCookie {
if _, ok := d.flows[flow.OldCookie]; ok {
logger.Warnw(ctx, "Flow present with old cookie", log.Fields{"OldCookie": flow.OldCookie})
return true
}
}
return false
}
// DelFlowWithOldCookie is to delete flow with old cookie.
func (d *Device) DelFlowWithOldCookie(cntx context.Context, flow *of.VoltSubFlow) error {
d.flowLock.Lock()
defer d.flowLock.Unlock()
if _, ok := d.flows[flow.OldCookie]; ok {
logger.Debugw(ctx, "Flow was added before vgc upgrade. Trying to delete with old cookie",
log.Fields{"OldCookie": flow.OldCookie})
delete(d.flows, flow.OldCookie)
d.DelFlowFromDb(cntx, flow.OldCookie)
return nil
}
return errors.New("flow does not exist")
}
// RestoreFlowsFromDb to restore flows from database
func (d *Device) RestoreFlowsFromDb(cntx context.Context) {
flows, _ := db.GetFlows(cntx, d.ID)
for _, flow := range flows {
b, ok := flow.Value.([]byte)
if !ok {
logger.Warn(ctx, "The value type is not []byte")
continue
}
d.CreateFlowFromString(b)
}
}
// CreateFlowFromString to create flow from string
func (d *Device) CreateFlowFromString(b []byte) {
var flow of.VoltSubFlow
if err := json.Unmarshal(b, &flow); err == nil {
if _, ok := d.flows[flow.Cookie]; !ok {
logger.Debugw(ctx, "Adding Flow From Db", log.Fields{"Cookie": flow.Cookie})
d.flows[flow.Cookie] = &flow
} else {
logger.Warnw(ctx, "Duplicate Flow", log.Fields{"Cookie": flow.Cookie})
}
} else {
logger.Warn(ctx, "Unmarshal failed")
}
}
// ----------------------------------------------------------
// Database related functionality
// Group operations at the device which include update and delete
// UpdateGroupEntry - Adds/Updates the group to the device and also to the database
func (d *Device) UpdateGroupEntry(cntx context.Context, group *of.Group) {
logger.Debugw(ctx, "Update Group to device", log.Fields{"ID": group.GroupID})
d.groups.Store(group.GroupID, group)
d.AddGroupToDb(cntx, group)
}
// AddGroupToDb - Utility to add the group to the device DB
func (d *Device) AddGroupToDb(cntx context.Context, group *of.Group) {
if b, err := json.Marshal(group); err == nil {
logger.Debugw(ctx, "Adding Group to DB", log.Fields{"grp": group, "Json": string(b)})
if err = db.PutGroup(cntx, d.ID, group.GroupID, string(b)); err != nil {
logger.Errorw(ctx, "Write Group to DB failed", log.Fields{"device": d.ID, "groupID": group.GroupID, "Reason": err})
}
}
}
// DelGroupEntry - Deletes the group from the device and the database
func (d *Device) DelGroupEntry(cntx context.Context, group *of.Group) {
if _, ok := d.groups.Load(group.GroupID); ok {
d.groups.Delete(group.GroupID)
d.DelGroupFromDb(cntx, group.GroupID)
}
}
// DelGroupFromDb - Utility to delete the Group from the device
func (d *Device) DelGroupFromDb(cntx context.Context, groupID uint32) {
_ = db.DelGroup(cntx, d.ID, groupID)
}
// RestoreGroupsFromDb - restores all groups from DB
func (d *Device) RestoreGroupsFromDb(cntx context.Context) {
logger.Info(ctx, "Restoring Groups")
groups, _ := db.GetGroups(cntx, d.ID)
for _, group := range groups {
b, ok := group.Value.([]byte)
if !ok {
logger.Warn(ctx, "The value type is not []byte")
continue
}
d.CreateGroupFromString(b)
}
}
// CreateGroupFromString - Forms group struct from json string
func (d *Device) CreateGroupFromString(b []byte) {
var group of.Group
if err := json.Unmarshal(b, &group); err == nil {
if _, ok := d.groups.Load(group.GroupID); !ok {
logger.Debugw(ctx, "Adding Group From Db", log.Fields{"GroupId": group.GroupID})
d.groups.Store(group.GroupID, &group)
} else {
logger.Warnw(ctx, "Duplicate Group", log.Fields{"GroupId": group.GroupID})
}
} else {
logger.Warn(ctx, "Unmarshal failed")
}
}
// AddMeter to add meter
func (d *Device) AddMeter(cntx context.Context, meter *of.Meter) error {
d.meterLock.Lock()
defer d.meterLock.Unlock()
if _, ok := d.meters[meter.ID]; ok {
return errors.New("duplicate meter")
}
d.meters[meter.ID] = meter
go d.AddMeterToDb(cntx, meter)
return nil
}
// UpdateMeter to update meter
func (d *Device) UpdateMeter(cntx context.Context, meter *of.Meter) error {
d.meterLock.Lock()
defer d.meterLock.Unlock()
if _, ok := d.meters[meter.ID]; ok {
d.meters[meter.ID] = meter
d.AddMeterToDb(cntx, meter)
} else {
return errors.New("meter not found for updation")
}
return nil
}
// GetMeter to get meter
func (d *Device) GetMeter(id uint32) (*of.Meter, error) {
d.meterLock.RLock()
defer d.meterLock.RUnlock()
if m, ok := d.meters[id]; ok {
return m, nil
}
return nil, errors.New("meter not found")
}
// DelMeter to delete meter
func (d *Device) DelMeter(cntx context.Context, meter *of.Meter) bool {
d.meterLock.Lock()
defer d.meterLock.Unlock()
if _, ok := d.meters[meter.ID]; ok {
delete(d.meters, meter.ID)
go d.DelMeterFromDb(cntx, meter.ID)
return true
}
return false
}
// AddMeterToDb is utility to add the Group to the device
func (d *Device) AddMeterToDb(cntx context.Context, meter *of.Meter) {
if b, err := json.Marshal(meter); err == nil {
if err = db.PutDeviceMeter(cntx, d.ID, meter.ID, string(b)); err != nil {
logger.Errorw(ctx, "Write Meter to DB failed", log.Fields{"device": d.ID, "meterID": meter.ID, "Reason": err})
}
}
}
// DelMeterFromDb to delete meter from db
func (d *Device) DelMeterFromDb(cntx context.Context, id uint32) {
_ = db.DelDeviceMeter(cntx, d.ID, id)
}
// RestoreMetersFromDb to restore meters from db
func (d *Device) RestoreMetersFromDb(cntx context.Context) {
meters, _ := db.GetDeviceMeters(cntx, d.ID)
for _, meter := range meters {
b, ok := meter.Value.([]byte)
if !ok {
logger.Warn(ctx, "The value type is not []byte")
continue
}
d.CreateMeterFromString(b)
}
}
// CreateMeterFromString to create meter from string
func (d *Device) CreateMeterFromString(b []byte) {
var meter of.Meter
if err := json.Unmarshal(b, &meter); err == nil {
if _, ok := d.meters[meter.ID]; !ok {
logger.Debugw(ctx, "Adding Meter From Db", log.Fields{"ID": meter.ID})
d.meters[meter.ID] = &meter
} else {
logger.Warnw(ctx, "Duplicate Meter", log.Fields{"ID": meter.ID})
}
} else {
logger.Warnw(ctx, "Unmarshal failed", log.Fields{"error": err, "meter": string(b)})
}
}
// VolthaClient to get voltha client
func (d *Device) VolthaClient() voltha.VolthaServiceClient {
return d.vclientHolder.Get()
}
// AddPort to add the port as requested by the device/VOLTHA
// Inform the application if the port is successfully added
func (d *Device) AddPort(cntx context.Context, mp *ofp.OfpPort) error {
d.portLock.Lock()
id := mp.PortNo
name := mp.Name
if _, ok := d.PortsByID[id]; ok {
d.portLock.Unlock()
return errors.New(Duplicate_Port)
}
if _, ok := d.PortsByName[name]; ok {
d.portLock.Unlock()
return errors.New(Duplicate_Port)
}
p := NewDevicePort(mp)
d.PortsByID[id] = p
d.PortsByName[name] = p
d.WritePortToDb(cntx, p)
d.portLock.Unlock()
GetController().PortAddInd(cntx, d.ID, p.ID, p.Name)
logger.Infow(ctx, "Added Port", log.Fields{"Device": d.ID, "Port": id})
return nil
}
// DelPort to delete the port as requested by the device/VOLTHA
// Inform the application if the port is successfully deleted
func (d *Device) DelPort(cntx context.Context, id uint32, portName string) error {
p := d.GetPortByID(id)
if p == nil {
p = d.GetPortByName(portName)
if p == nil {
return errors.New("unknown port")
} else {
logger.Infow(ctx, "Found port by name", log.Fields{"PortName": p.Name, "PortID": p.ID})
}
}
if p.State == PortStateUp {
GetController().PortDownInd(cntx, d.ID, p.Name)
}
GetController().PortDelInd(cntx, d.ID, p.Name)
d.portLock.Lock()
defer d.portLock.Unlock()
delete(d.PortsByID, p.ID)
delete(d.PortsByName, p.Name)
d.DelPortFromDb(cntx, p.ID)
logger.Infow(ctx, "Deleted Port", log.Fields{"Device": d.ID, "Port": id})
return nil
}
// CheckAndDeletePort deletes the port if the port name matches with VGC and one sent from voltha in OFPPR_DELETE
func (d *Device) CheckAndDeletePort(cntx context.Context, portNo uint32, portName string) {
if p := d.GetPortByID(portNo); p != nil {
if p.Name != portName {
logger.Warnw(ctx, "Dropping Del Port event: Port name mismatch", log.Fields{"vgcPortName": p.Name, "ofpPortName": portName, "ID": p.ID})
return
}
if err := d.DelPort(cntx, portNo, portName); err != nil {
logger.Warnw(ctx, "DelPort Failed", log.Fields{"Port No": portNo, "Error": err})
}
}
}
// UpdatePortByName is utility to update the port by Name
func (d *Device) UpdatePortByName(cntx context.Context, name string, port uint32) {
d.portLock.Lock()
defer d.portLock.Unlock()
p, ok := d.PortsByName[name]
if !ok {
return
}
delete(d.PortsByID, p.ID)
p.ID = port
d.PortsByID[port] = p
d.WritePortToDb(cntx, p)
GetController().PortUpdateInd(d.ID, p.Name, p.ID)
logger.Infow(ctx, "Updated Port", log.Fields{"Device": d.ID, "Port": p.ID, "PortName": name})
}
// GetPortName to get the name of the port by its id
func (d *Device) GetPortName(id uint32) (string, error) {
d.portLock.RLock()
defer d.portLock.RUnlock()
if p, ok := d.PortsByID[id]; ok {
return p.Name, nil
}
logger.Errorw(ctx, "Port not found", log.Fields{"port": id})
return "", errors.New(Unknown_Port_ID)
}
// GetPortByID is utility to retrieve the port by ID
func (d *Device) GetPortByID(id uint32) *DevicePort {
d.portLock.RLock()
defer d.portLock.RUnlock()
p, ok := d.PortsByID[id]
if ok {
return p
}
return nil
}
// GetPortByName is utility to retrieve the port by Name
func (d *Device) GetPortByName(name string) *DevicePort {
d.portLock.RLock()
defer d.portLock.RUnlock()
p, ok := d.PortsByName[name]
if ok {
return p
}
return nil
}
// GetPortState to get the state of the port by name
func (d *Device) GetPortState(name string) (PortState, error) {
d.portLock.RLock()
defer d.portLock.RUnlock()
if p, ok := d.PortsByName[name]; ok {
return p.State, nil
}
return PortStateDown, errors.New(Unknown_Port_ID)
}
// GetPortID to get the port-id by the port name
func (d *Device) GetPortID(name string) (uint32, error) {
d.portLock.RLock()
defer d.portLock.RUnlock()
if p, ok := d.PortsByName[name]; ok {
return p.ID, nil
}
return 0, errors.New(Unknown_Port_ID)
}
// WritePortToDb to add the port to the database
func (d *Device) WritePortToDb(ctx context.Context, port *DevicePort) {
port.Version = database.PresentVersionMap[database.DevicePortPath]
if b, err := json.Marshal(port); err == nil {
if err = db.PutPort(ctx, d.ID, port.ID, string(b)); err != nil {
logger.Errorw(ctx, "Write port to DB failed", log.Fields{"device": d.ID, "port": port.ID, "Reason": err})
}
}
}
// DelPortFromDb to delete port from database
func (d *Device) DelPortFromDb(cntx context.Context, id uint32) {
_ = db.DelPort(cntx, d.ID, id)
}
// RestorePortsFromDb to restore ports from database
func (d *Device) RestorePortsFromDb(cntx context.Context) {
ports, _ := db.GetPorts(cntx, d.ID)
for _, port := range ports {
b, ok := port.Value.([]byte)
if !ok {
logger.Warn(ctx, "The value type is not []byte")
continue
}
d.CreatePortFromString(cntx, b)
}
}
// CreatePortFromString to create port from string
func (d *Device) CreatePortFromString(cntx context.Context, b []byte) {
var port DevicePort
if err := json.Unmarshal(b, &port); err == nil {
if _, ok := d.PortsByID[port.ID]; !ok {
logger.Debugw(ctx, "Adding Port From Db", log.Fields{"ID": port.ID})
d.PortsByID[port.ID] = &port
d.PortsByName[port.Name] = &port
GetController().PortAddInd(cntx, d.ID, port.ID, port.Name)
} else {
logger.Warnw(ctx, Duplicate_Port, log.Fields{"ID": port.ID})
}
} else {
logger.Warnw(ctx, "Unmarshal failed", log.Fields{"port": string(b)})
}
}
// Delete : OLT Delete functionality yet to be implemented. IDeally all of the
// resources should have been removed by this time. It is an error
// scenario if the OLT has resources associated with it.
func (d *Device) Delete() {
d.StopAll()
}
// Stop to stop the task
func (d *Device) Stop() {
}
// ConnectInd is called when the connection between VGC and the VOLTHA is
// restored. This will perform audit of the device post reconnection
func (d *Device) ConnectInd(ctx context.Context, discType intf.DiscoveryType) {
logger.Warnw(ctx, "Audit Upon Connection Establishment", log.Fields{"Device": d.ID, "State": d.State})
ctx1, cancel := context.WithCancel(ctx)
d.cancel = cancel
d.ctx = ctx1
d.Tasks.Initialize(ctx1)
logger.Debugw(ctx, "Device State change Ind: UP", log.Fields{"Device": d.ID})
d.State = DeviceStateUP
d.TimeStamp = time.Now()
GetController().DeviceUpInd(d.ID)
logger.Debugw(ctx, "Device State change Ind: UP, trigger Audit Tasks", log.Fields{"Device": d.ID})
t := NewAuditDevice(d, AuditEventDeviceDisc)
d.Tasks.AddTask(t)
t1 := NewAuditTablesTask(d)
d.Tasks.AddTask(t1)
t2 := NewPendingProfilesTask(d)
d.Tasks.AddTask(t2)
go d.synchronizeDeviceTables()
}
func (d *Device) synchronizeDeviceTables() {
tick := time.NewTicker(GetController().GetDeviceTableSyncDuration())
loop:
for {
select {
case <-d.ctx.Done():
logger.Warnw(d.ctx, "Context Done. Canceling Periodic Audit", log.Fields{"Context": ctx, "Device": d.ID, "DeviceSerialNum": d.SerialNum})
break loop
case <-tick.C:
t1 := NewAuditTablesTask(d)
d.Tasks.AddTask(t1)
}
}
tick.Stop()
}
// DeviceUpInd is called when the logical device state changes to UP. This will perform audit of the device post reconnection
func (d *Device) DeviceUpInd() {
logger.Warnw(ctx, "Device State change Ind: UP", log.Fields{"Device": d.ID})
d.State = DeviceStateUP
d.TimeStamp = time.Now()
GetController().DeviceUpInd(d.ID)
logger.Warnw(ctx, "Device State change Ind: UP, trigger Audit Tasks", log.Fields{"Device": d.ID})
t := NewAuditDevice(d, AuditEventDeviceDisc)
d.Tasks.AddTask(t)
t1 := NewAuditTablesTask(d)
d.Tasks.AddTask(t1)
t2 := NewPendingProfilesTask(d)
d.Tasks.AddTask(t2)
}
// DeviceDownInd is called when the logical device state changes to Down.
func (d *Device) DeviceDownInd() {
logger.Warnw(ctx, "Device State change Ind: Down", log.Fields{"Device": d.ID})
d.State = DeviceStateDOWN
d.TimeStamp = time.Now()
GetController().DeviceDownInd(d.ID)
}
// DeviceRebootInd is called when the logical device is rebooted.
func (d *Device) DeviceRebootInd(cntx context.Context) {
logger.Warnw(ctx, "Device State change Ind: Rebooted", log.Fields{"Device": d.ID})
if d.State == DeviceStateREBOOTED {
d.State = DeviceStateREBOOTED
logger.Warnw(ctx, "Ignoring Device State change Ind: REBOOT, Device Already in REBOOT state", log.Fields{"Device": d.ID, "SeralNo": d.SerialNum})
return
}
d.State = DeviceStateREBOOTED
d.TimeStamp = time.Now()
GetController().SetRebootInProgressForDevice(d.ID)
GetController().DeviceRebootInd(cntx, d.ID, d.SerialNum, d.SouthBoundID)
d.ReSetAllPortStates(cntx)
}
// DeviceDisabledInd is called when the logical device is disabled
func (d *Device) DeviceDisabledInd(cntx context.Context) {
logger.Warnw(ctx, "Device State change Ind: Disabled", log.Fields{"Device": d.ID})
d.State = DeviceStateDISABLED
d.TimeStamp = time.Now()
GetController().DeviceDisableInd(cntx, d.ID)
}
// ReSetAllPortStates - Set all logical device port status to DOWN
func (d *Device) ReSetAllPortStates(cntx context.Context) {
logger.Warnw(ctx, "Resetting all Ports State to DOWN", log.Fields{"Device": d.ID, "State": d.State})
d.portLock.Lock()
defer d.portLock.Unlock()
for _, port := range d.PortsByID {
if port.State != PortStateDown {
logger.Infow(ctx, "Resetting Port State to DOWN", log.Fields{"Device": d.ID, "Port": port})
GetController().PortDownInd(cntx, d.ID, port.Name)
port.State = PortStateDown
d.WritePortToDb(cntx, port)
}
}
}
// ReSetAllPortStatesInDb - Set all logical device port status to DOWN in DB and skip indication to application
func (d *Device) ReSetAllPortStatesInDb(cntx context.Context) {
logger.Warnw(ctx, "Resetting all Ports State to DOWN In DB", log.Fields{"Device": d.ID, "State": d.State})
d.portLock.Lock()
defer d.portLock.Unlock()
for _, port := range d.PortsByID {
if port.State != PortStateDown {
logger.Debugw(ctx, "Resetting Port State to DOWN and Write to DB", log.Fields{"Device": d.ID, "Port": port})
port.State = PortStateDown
d.WritePortToDb(cntx, port)
}
}
}
// ProcessPortUpdate deals with the change in port id (ONU movement) and taking action
// to update only when the port state is DOWN
func (d *Device) ProcessPortUpdate(cntx context.Context, portName string, port uint32, state uint32) {
if p := d.GetPortByName(portName); p != nil {
if p.ID != port {
logger.Warnw(ctx, "Port update indication received with mismatching ID", log.Fields{"Port": p.Name, "Old PortID": p.ID, "New Port ID": port})
return
//Do not process port update received from change event, as we will only handle port updates during polling
}
d.ProcessPortState(cntx, port, state, portName)
}
}
// ***Operations Performed on Port state Transitions***
//
// |-----------------------------------------------------------------------------|
// | State | Action |
// |--------------------|--------------------------------------------------------|
// | UP | UNI - Trigger Flow addition for service configured |
// | | NNI - Trigger Flow addition for vnets & mvlan profiles |
// | | |
// | DOWN | UNI - Trigger Flow deletion for service configured |
// | | NNI - Trigger Flow deletion for vnets & mvlan profiles |
// | | |
// |-----------------------------------------------------------------------------|
//
// ProcessPortState deals with the change in port status and taking action
// based on the new state and the old state
func (d *Device) ProcessPortState(cntx context.Context, port uint32, state uint32, portName string) {
if d.State != DeviceStateUP && !util.IsNniPort(port) {
logger.Warnw(ctx, "Ignore Port State Processing - Device not UP", log.Fields{"Device": d.ID, "Port": port, "DeviceState": d.State})
return
}
if p := d.GetPortByID(port); p != nil {
logger.Infow(ctx, "Port State Processing", log.Fields{"Received": state, "Current": p.State})
if p.Name != portName {
logger.Warnw(ctx, "Dropping Port State processing: Port name does not match", log.Fields{"vgcPort": p.Name, "ofpPort": portName, "ID": port})
return
}
// Avoid blind initialization as the current tasks in the queue will be lost
// Eg: Service Del followed by Port Down - The flows will be dangling
// Eg: NNI Down followed by NNI UP - Mcast data flows will be dangling
p.Tasks.CheckAndInitialize(d.ctx)
if state == uint32(ofp.OfpPortState_OFPPS_LIVE) && p.State == PortStateDown {
// Transition from DOWN to UP
logger.Infow(ctx, "Port State Change to UP", log.Fields{"Device": d.ID, "Port": port})
GetController().PortUpInd(cntx, d.ID, p.Name)
p.State = PortStateUp
d.WritePortToDb(cntx, p)
} else if (state != uint32(ofp.OfpPortState_OFPPS_LIVE)) && (p.State != PortStateDown) {
// Transition from UP to Down
logger.Infow(ctx, "Port State Change to Down", log.Fields{"Device": d.ID, "Port": port})
GetController().PortDownInd(cntx, d.ID, p.Name)
p.State = PortStateDown
d.WritePortToDb(cntx, p)
} else {
logger.Warnw(ctx, "Dropping Port Ind: No Change in Port State", log.Fields{"PortName": p.Name, "ID": port, "Device": d.ID, "PortState": p.State, "IncomingState": state})
}
}
}
// ProcessPortStateAfterReboot - triggers the port state indication to sort out configu mismatch due to reboot
func (d *Device) ProcessPortStateAfterReboot(cntx context.Context, port uint32, state uint32) {
if d.State != DeviceStateUP && !util.IsNniPort(port) {
logger.Warnw(ctx, "Ignore Port State Processing - Device not UP", log.Fields{"Device": d.ID, "Port": port, "DeviceState": d.State})
return
}
if p := d.GetPortByID(port); p != nil {
logger.Infow(ctx, "Port State Processing after Reboot", log.Fields{"Received": state, "Current": p.State})
p.Tasks.Initialize(d.ctx)
if p.State == PortStateUp {
logger.Infow(ctx, "Port State: UP", log.Fields{"Device": d.ID, "Port": port})
GetController().PortUpInd(cntx, d.ID, p.Name)
} else if p.State == PortStateDown {
logger.Infow(ctx, "Port State: Down", log.Fields{"Device": d.ID, "Port": port})
GetController().PortDownInd(cntx, d.ID, p.Name)
}
}
}
// ChangeEvent : Change event brings in ports related changes such as addition/deletion
// or modification where the port status change up/down is indicated to the
// controller
func (d *Device) ChangeEvent(event *ofp.ChangeEvent) error {
cet := NewChangeEventTask(d.ctx, event, d)
d.AddTask(cet)
return nil
}
// PacketIn handle the incoming packet-in and deliver to the application for the
// actual processing
func (d *Device) PacketIn(cntx context.Context, pkt *ofp.PacketIn) {
logger.Debugw(ctx, "Received a Packet-In", log.Fields{"Device": d.ID})
if pkt.PacketIn.Reason != ofp.OfpPacketInReason_OFPR_ACTION {
logger.Warnw(ctx, "Unsupported PacketIn Reason", log.Fields{"Reason": pkt.PacketIn.Reason})
return
}
data := pkt.PacketIn.Data
port := PacketInGetPort(pkt.PacketIn)
if pName, err := d.GetPortName(port); err == nil {
GetController().PacketInInd(cntx, d.ID, pName, data)
} else {
logger.Warnw(ctx, "Unknown Port", log.Fields{"Reason": err.Error()})
}
}
// PacketInGetPort to get the port on which the packet-in is reported
func PacketInGetPort(pkt *ofp.OfpPacketIn) uint32 {
for _, field := range pkt.Match.OxmFields {
if field.OxmClass == ofp.OfpOxmClass_OFPXMC_OPENFLOW_BASIC {
if ofbField, ok := field.Field.(*ofp.OfpOxmField_OfbField); ok {
if ofbField.OfbField.Type == ofp.OxmOfbFieldTypes_OFPXMT_OFB_IN_PORT {
if port, ok := ofbField.OfbField.Value.(*ofp.OfpOxmOfbField_Port); ok {
return port.Port
}
}
}
}
}
return 0
}
// PacketOutReq receives the packet out request from the application via the
// controller. The interface from the application uses name as the identity.
func (d *Device) PacketOutReq(outport string, inport string, data []byte, isCustomPkt bool) error {
inp, err := d.GetPortID(inport)
if err != nil {
return errors.New("unknown inport")
}
outp, err1 := d.GetPortID(outport)
if err1 != nil {
return errors.New("unknown outport")
}
logger.Debugw(ctx, "Sending packet out", log.Fields{"Device": d.ID, "Inport": inport, "Outport": outport})
return d.SendPacketOut(outp, inp, data, isCustomPkt)
}
// SendPacketOut is responsible for building the OF structure and send the
// packet-out to the VOLTHA
func (d *Device) SendPacketOut(outport uint32, inport uint32, data []byte, isCustomPkt bool) error {
pout := &ofp.PacketOut{}
pout.Id = d.ID
opout := &ofp.OfpPacketOut{}
pout.PacketOut = opout
opout.InPort = inport
opout.Data = data
opout.Actions = []*ofp.OfpAction{
{
Type: ofp.OfpActionType_OFPAT_OUTPUT,
Action: &ofp.OfpAction_Output{
Output: &ofp.OfpActionOutput{
Port: outport,
MaxLen: 65535,
},
},
},
}
d.packetOutChannel <- pout
return nil
}
// UpdateFlows receives the flows in the form that is implemented
// in the VGC and transforms them to the OF format. This is handled
// as a port of the task that is enqueued to do the same.
func (d *Device) UpdateFlows(flow *of.VoltFlow, devPort *DevicePort) {
t := NewAddFlowsTask(d.ctx, flow, d)
logger.Debugw(ctx, "Port Context", log.Fields{"Ctx": devPort.GetContext()})
// check if port isNni , if yes flows will be added to device port queues.
if util.IsNniPort(devPort.ID) {
// Adding the flows to device port queues.
devPort.AddTask(t)
return
}
// If the flowHash is enabled then add the flows to the flowhash generated queues.
flowQueue := d.getAndAddFlowQueueForUniID(uint32(devPort.ID))
if flowQueue != nil {
logger.Debugw(ctx, "flowHashQId", log.Fields{"uniid": devPort.ID, "flowhash": flowQueue.ID})
flowQueue.AddTask(t)
logger.Debugw(ctx, "Tasks Info", log.Fields{"uniid": devPort.ID, "flowhash": flowQueue.ID, "Total": flowQueue.TotalTasks(), "Pending": flowQueue.NumPendingTasks()})
} else {
//FlowThrotling disabled, add to the device port queue
devPort.AddTask(t)
return
}
}
// UpdateGroup to update group info
func (d *Device) UpdateGroup(group *of.Group, devPort *DevicePort) {
task := NewModGroupTask(d.ctx, group, d)
logger.Debugw(ctx, "NNI Port Context", log.Fields{"Ctx": devPort.GetContext()})
devPort.AddTask(task)
}
// ModMeter for mod meter task
func (d *Device) ModMeter(command of.MeterCommand, meter *of.Meter, devPort *DevicePort) {
if command == of.MeterCommandAdd {
if _, err := d.GetMeter(meter.ID); err == nil {
logger.Debugw(ctx, "Meter already added", log.Fields{"ID": meter.ID})
return
}
}
t := NewModMeterTask(d.ctx, command, meter, d)
devPort.AddTask(t)
}
func (d *Device) getAndAddFlowQueueForUniID(id uint32) *UniIDFlowQueue {
d.flowQueueLock.RLock()
// If flowhash is 0 that means flowhash throttling is disabled, return nil
if d.flowHash == 0 {
d.flowQueueLock.RUnlock()
return nil
}
flowHashID := id % uint32(d.flowHash)
if value, found := d.flowQueue[uint32(flowHashID)]; found {
d.flowQueueLock.RUnlock()
return value
}
d.flowQueueLock.RUnlock()
logger.Debugw(ctx, "Flow queue not found creating one", log.Fields{"uniid": id, "hash": flowHashID})
return d.addFlowQueueForUniID(id)
}
func (d *Device) addFlowQueueForUniID(id uint32) *UniIDFlowQueue {
d.flowQueueLock.Lock()
defer d.flowQueueLock.Unlock()
flowHashID := id % uint32(d.flowHash)
flowQueue := NewUniIDFlowQueue(uint32(flowHashID))
flowQueue.Tasks.Initialize(d.ctx)
d.flowQueue[flowHashID] = flowQueue
return flowQueue
}
// SetFlowHash sets the device flow hash and writes to the DB.
func (d *Device) SetFlowHash(cntx context.Context, hash uint32) {
d.flowQueueLock.Lock()
defer d.flowQueueLock.Unlock()
d.flowHash = hash
d.writeFlowHashToDB(cntx)
}
func (d *Device) writeFlowHashToDB(cntx context.Context) {
hash, err := json.Marshal(d.flowHash)
if err != nil {
logger.Errorw(ctx, "failed to marshal flow hash", log.Fields{"hash": d.flowHash, "error": err})
return
}
if err := db.PutFlowHash(cntx, d.ID, string(hash)); err != nil {
logger.Errorw(ctx, "Failed to add flow hash to DB", log.Fields{"device": d.ID, "hash": d.flowHash, "error": err})
}
}
// isSBOperAllowed - determines if the SB operation is allowed based on device state & force flag
func (d *Device) isSBOperAllowed(forceAction bool) bool {
if d.State == DeviceStateUP {
return true
}
if d.State == DeviceStateDISABLED && forceAction {
return true
}
return false
}
// IsFlowDelThresholdReached - check if the attempts for flow delete has reached threshold or not
func (d *Device) IsFlowDelThresholdReached(flowCount uint32, cookie uint64) bool {
logger.Debugw(ctx, "Check flow delete threshold", log.Fields{"Cookie": cookie, "FlowCount": flowCount})
return flowCount >= GetController().GetMaxFlowRetryAttempt()
}
// IsFlowAddThresholdReached - check if the attempts for flow add has reached threshold or not
func (d *Device) IsFlowAddThresholdReached(flowCount uint32, cookie uint64) bool {
logger.Debugw(ctx, "Check flow add threshold", log.Fields{"Cookie": cookie, "FlowCount": flowCount})
return flowCount >= GetController().GetMaxFlowRetryAttempt()
}
func (d *Device) UpdateFlowCount(cntx context.Context, cookie uint64) {
if dbFlow, ok := d.flows[cookie]; ok {
dbFlow.FlowCount++
d.AddFlowToDb(cntx, dbFlow)
}
}
func (d *Device) triggerFlowNotification(cntx context.Context, cookie uint64, oper of.Command, bwDetails of.BwAvailDetails, err error) {
flow, _ := d.GetFlow(cookie)
d.triggerFlowResultNotification(cntx, cookie, flow, oper, bwDetails, err)
}
func (d *Device) triggerFlowResultNotification(cntx context.Context, cookie uint64, flow *of.VoltSubFlow, oper of.Command, bwDetails of.BwAvailDetails, err error) {
statusCode, statusMsg := infraerror.GetErrorInfo(err)
success := isFlowOperSuccess(statusCode, oper)
updateFlowStatus := func(cookie uint64, state int, reason string) {
d.flowLock.Lock()
defer d.flowLock.Unlock()
if dbFlow, ok := d.flows[cookie]; ok {
dbFlow.State = uint8(state)
dbFlow.ErrorReason = reason
if state == of.FlowAddSuccess {
dbFlow.FlowCount = 0
}
d.AddFlowToDb(cntx, dbFlow)
}
}
// Update flow results
// Add - Update Success or Failure status with reason
// Del - Delete entry from DB on success else update error reason
if oper == of.CommandAdd {
state := of.FlowAddSuccess
reason := ""
if !success {
state = of.FlowAddFailure
reason = statusMsg
}
updateFlowStatus(cookie, state, reason)
logger.Debugw(ctx, "Add flow updated to DB", log.Fields{"Cookie": cookie, "State": state})
} else {
if success && flow != nil {
logger.Debugw(ctx, "Deleted flow from device and DB", log.Fields{"Cookie": cookie})
if err := d.DelFlow(cntx, flow); err != nil {
logger.Warnw(ctx, "Delete Flow Error", log.Fields{"Cookie": flow.Cookie, "Reason": err.Error()})
}
} else if !success {
if d.IsFlowDelThresholdReached(flow.FlowCount, flow.Cookie) {
logger.Debugw(ctx, "Deleted flow from device and DB after delete threshold reached", log.Fields{"Cookie": cookie})
if err := d.DelFlow(cntx, flow); err != nil {
logger.Warnw(ctx, "Delete Flow Error", log.Fields{"Cookie": flow.Cookie, "Reason": err.Error()})
}
} else {
updateFlowStatus(cookie, of.FlowDelFailure, statusMsg)
logger.Debugw(ctx, "Delete flow updated to DB", log.Fields{"Cookie": cookie})
}
}
}
flowResult := intf.FlowStatus{
Cookie: strconv.FormatUint(cookie, 10),
Device: d.ID,
FlowModType: oper,
Flow: flow,
Status: statusCode,
Reason: statusMsg,
AdditionalData: bwDetails,
}
logger.Debugw(ctx, "Sending Flow Notification", log.Fields{"Cookie": cookie, "Error Code": statusCode, "FlowOp": oper})
GetController().ProcessFlowModResultIndication(cntx, flowResult)
}