blob: 53f81abafe2682dc2b0e76997f197c531486a9d5 [file] [log] [blame]
/*
* Copyright 2019-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 core provides the utility for olt devices, flows and statistics
package core
import (
"context"
"fmt"
"strconv"
"sync"
"time"
"github.com/opencord/voltha-lib-go/v3/pkg/log"
"github.com/opencord/voltha-openolt-adapter/internal/pkg/olterrors"
"github.com/opencord/voltha-protos/v3/go/openolt"
"github.com/opencord/voltha-protos/v3/go/voltha"
)
var mutex = &sync.Mutex{}
// PonPort representation
type PonPort struct {
/*
This is a highly reduced version taken from the adtran pon_port.
TODO: Extend for use in the openolt adapter set.
*/
/* MAX_ONUS_SUPPORTED = 256
DEFAULT_ENABLED = False
MAX_DEPLOYMENT_RANGE = 25000 # Meters (OLT-PB maximum)
_MCAST_ONU_ID = 253
_MCAST_ALLOC_BASE = 0x500
_SUPPORTED_ACTIVATION_METHODS = ['autodiscovery'] # , 'autoactivate']
_SUPPORTED_AUTHENTICATION_METHODS = ['serial-number']
*/
PONID uint32
DeviceID string
IntfID uint32
PortNum uint32
PortID uint32
Label string
ONUs map[uint32]interface{}
ONUsByID map[uint32]interface{}
RxBytes uint64
RxPackets uint64
RxUcastPackets uint64
RxMcastPackets uint64
RxBcastPackets uint64
RxErrorPackets uint64
TxBytes uint64
TxPackets uint64
TxUcastPackets uint64
TxMcastPackets uint64
TxBcastPackets uint64
TxErrorPackets uint64
RxCrcErrors uint64
BipErrors uint64
}
// NewPONPort returns a new instance of PonPort initialized with given PONID, DeviceID, IntfID and PortNum
func NewPONPort(PONID uint32, DeviceID string, IntfID uint32, PortNum uint32) *PonPort {
var PON PonPort
PON.PONID = PONID
PON.DeviceID = DeviceID
PON.IntfID = IntfID
PON.PortNum = PortNum
PON.PortID = 0
PON.Label = fmt.Sprintf("%s%d", "pon-", PONID)
PON.ONUs = make(map[uint32]interface{})
PON.ONUsByID = make(map[uint32]interface{})
/*
Statistics taken from nni_port
self.intf_id = 0 #handled by getter
self.port_no = 0 #handled by getter
self.port_id = 0 #handled by getter
Note: In the current implementation of the kpis coming from the BAL the stats are the
samne model for NNI and PON.
TODO: Integrate additional kpis for the PON and other southbound port objecgts.
*/
PON.RxBytes = 0
PON.RxPackets = 0
PON.RxUcastPackets = 0
PON.RxMcastPackets = 0
PON.RxBcastPackets = 0
PON.RxErrorPackets = 0
PON.TxBytes = 0
PON.TxPackets = 0
PON.TxUcastPackets = 0
PON.TxMcastPackets = 0
PON.TxBcastPackets = 0
PON.TxErrorPackets = 0
PON.RxCrcErrors = 0
PON.BipErrors = 0
/* def __str__(self):
return "PonPort-{}: Admin: {}, Oper: {}, OLT: {}".format(self._label,
self._admin_state,
self._oper_status,
self.olt)
*/
return &PON
}
// NniPort representation
type NniPort struct {
/*
Northbound network port, often Ethernet-based
This is a highly reduced version taken from the adtran nni_port code set
TODO: add functions to allow for port specific values and operations
*/
PortNum uint32
Name string
LogicalPort uint32
IntfID uint32
RxBytes uint64
RxPackets uint64
RxUcastPackets uint64
RxMcastPackets uint64
RxBcastPackets uint64
RxErrorPackets uint64
TxBytes uint64
TxPackets uint64
TxUcastPackets uint64
TxMcastPackets uint64
TxBcastPackets uint64
TxErrorPackets uint64
RxCrcErrors uint64
BipErrors uint64
}
// NewNniPort returns a new instance of NniPort initialized with the given PortNum and IntfID
func NewNniPort(PortNum uint32, IntfID uint32) *NniPort {
var NNI NniPort
NNI.PortNum = PortNum
NNI.Name = fmt.Sprintf("%s%d", "nni-", PortNum)
NNI.IntfID = IntfID
NNI.RxBytes = 0
NNI.RxPackets = 0
NNI.RxUcastPackets = 0
NNI.RxMcastPackets = 0
NNI.RxBcastPackets = 0
NNI.RxErrorPackets = 0
NNI.TxBytes = 0
NNI.TxPackets = 0
NNI.TxUcastPackets = 0
NNI.TxMcastPackets = 0
NNI.TxBcastPackets = 0
NNI.TxErrorPackets = 0
NNI.RxCrcErrors = 0
NNI.BipErrors = 0
return &NNI
}
// OpenOltStatisticsMgr structure
type OpenOltStatisticsMgr struct {
Device *DeviceHandler
NorthBoundPort map[uint32]*NniPort
SouthBoundPort map[uint32]*PonPort
// TODO PMMetrics Metrics
}
// NewOpenOltStatsMgr returns a new instance of the OpenOltStatisticsMgr
func NewOpenOltStatsMgr(ctx context.Context, Dev *DeviceHandler) *OpenOltStatisticsMgr {
var StatMgr OpenOltStatisticsMgr
StatMgr.Device = Dev
// TODO call metric PMMetric =
// Northbound and Southbound ports
// added to initialize the pm_metrics
var Ports interface{}
Ports, _ = InitPorts(ctx, "nni", Dev.device.Id, 1)
StatMgr.NorthBoundPort, _ = Ports.(map[uint32]*NniPort)
NumPonPorts := Dev.resourceMgr.DevInfo.GetPonPorts()
Ports, _ = InitPorts(ctx, "pon", Dev.device.Id, NumPonPorts)
StatMgr.SouthBoundPort, _ = Ports.(map[uint32]*PonPort)
return &StatMgr
}
// InitPorts collects the port objects: nni and pon that are updated with the current data from the OLT
func InitPorts(ctx context.Context, Intftype string, DeviceID string, numOfPorts uint32) (interface{}, error) {
/*
This method collects the port objects: nni and pon that are updated with the
current data from the OLT
Both the northbound (nni) and southbound ports are indexed by the interface id (intf_id)
and NOT the port number. When the port object is instantiated it will contain the intf_id and
port_no values
:param type:
:return:
*/
var i uint32
if Intftype == "nni" {
NniPorts := make(map[uint32]*NniPort)
for i = 0; i < numOfPorts; i++ {
Port := BuildPortObject(ctx, i, "nni", DeviceID).(*NniPort)
NniPorts[Port.IntfID] = Port
}
return NniPorts, nil
} else if Intftype == "pon" {
PONPorts := make(map[uint32]*PonPort)
for i = 0; i < numOfPorts; i++ {
PONPort := BuildPortObject(ctx, i, "pon", DeviceID).(*PonPort)
PONPorts[PortNoToIntfID(PONPort.IntfID, voltha.Port_PON_OLT)] = PONPort
}
return PONPorts, nil
} else {
logger.Errorw(ctx, "invalid-type-of-interface", log.Fields{"interface-type": Intftype})
return nil, olterrors.NewErrInvalidValue(log.Fields{"interface-type": Intftype}, nil)
}
}
// BuildPortObject allows for updating north and southbound ports, newly discovered ports, and devices
func BuildPortObject(ctx context.Context, PortNum uint32, IntfType string, DeviceID string) interface{} {
/*
Separate method to allow for updating north and southbound ports
newly discovered ports and devices
:param port_num:
:param type:
:return:
*/
//This builds a port object which is added to the
//appropriate northbound or southbound values
if IntfType == "nni" {
IntfID := IntfIDToPortNo(PortNum, voltha.Port_ETHERNET_NNI)
nniID := PortNoToIntfID(IntfID, voltha.Port_ETHERNET_NNI)
logger.Debugw(ctx, "interface-type-nni",
log.Fields{
"nni-id": nniID,
"intf-type": IntfType})
return NewNniPort(PortNum, nniID)
} else if IntfType == "pon" {
// PON ports require a different configuration
// intf_id and pon_id are currently equal.
IntfID := IntfIDToPortNo(PortNum, voltha.Port_PON_OLT)
PONID := PortNoToIntfID(IntfID, voltha.Port_PON_OLT)
logger.Debugw(ctx, "interface-type-pon",
log.Fields{
"pon-id": PONID,
"intf-type": IntfType})
return NewPONPort(PONID, DeviceID, IntfID, PortNum)
} else {
logger.Errorw(ctx, "invalid-type-of-interface", log.Fields{"intf-type": IntfType})
return nil
}
}
// collectNNIMetrics will collect the nni port metrics
func (StatMgr *OpenOltStatisticsMgr) collectNNIMetrics(nniID uint32) map[string]float32 {
nnival := make(map[string]float32)
mutex.Lock()
cm := StatMgr.Device.portStats.NorthBoundPort[nniID]
mutex.Unlock()
metricNames := StatMgr.Device.metrics.GetSubscriberMetrics()
var metrics []string
for metric := range metricNames {
if metricNames[metric].Enabled {
metrics = append(metrics, metric)
}
}
for _, mName := range metrics {
switch mName {
case "rx_bytes":
nnival["RxBytes"] = float32(cm.RxBytes)
case "rx_packets":
nnival["RxPackets"] = float32(cm.RxPackets)
case "rx_ucast_packets":
nnival["RxUcastPackets"] = float32(cm.RxUcastPackets)
case "rx_mcast_packets":
nnival["RxMcastPackets"] = float32(cm.RxMcastPackets)
case "rx_bcast_packets":
nnival["RxBcastPackets"] = float32(cm.RxBcastPackets)
case "tx_bytes":
nnival["TxBytes"] = float32(cm.TxBytes)
case "tx_packets":
nnival["TxPackets"] = float32(cm.TxPackets)
case "tx_mcast_packets":
nnival["TxMcastPackets"] = float32(cm.TxMcastPackets)
case "tx_bcast_packets":
nnival["TxBcastPackets"] = float32(cm.TxBcastPackets)
}
}
return nnival
}
// collectPONMetrics will collect the pon port metrics
func (StatMgr *OpenOltStatisticsMgr) collectPONMetrics(pID uint32) map[string]float32 {
ponval := make(map[string]float32)
mutex.Lock()
cm := StatMgr.Device.portStats.SouthBoundPort[pID]
mutex.Unlock()
metricNames := StatMgr.Device.metrics.GetSubscriberMetrics()
var metrics []string
for metric := range metricNames {
if metricNames[metric].Enabled {
metrics = append(metrics, metric)
}
}
for _, mName := range metrics {
switch mName {
case "rx_bytes":
ponval["RxBytes"] = float32(cm.RxBytes)
case "rx_packets":
ponval["RxPackets"] = float32(cm.RxPackets)
case "rx_ucast_packets":
ponval["RxUcastPackets"] = float32(cm.RxUcastPackets)
case "rx_mcast_packets":
ponval["RxMcastPackets"] = float32(cm.RxMcastPackets)
case "rx_bcast_packets":
ponval["RxBcastPackets"] = float32(cm.RxBcastPackets)
case "tx_bytes":
ponval["TxBytes"] = float32(cm.TxBytes)
case "tx_packets":
ponval["TxPackets"] = float32(cm.TxPackets)
case "tx_mcast_packets":
ponval["TxMcastPackets"] = float32(cm.TxMcastPackets)
case "tx_bcast_packets":
ponval["TxBcastPackets"] = float32(cm.TxBcastPackets)
}
}
return ponval
}
// publishMatrics will publish the pon port metrics
func (StatMgr OpenOltStatisticsMgr) publishMetrics(ctx context.Context, val map[string]float32,
port *voltha.Port, devID string, devType string) {
logger.Debugw(ctx, "publish-metrics",
log.Fields{
"port": port.Label,
"metrics": val})
var metricInfo voltha.MetricInformation
var ke voltha.KpiEvent2
var volthaEventSubCatgry voltha.EventSubCategory_Types
metricsContext := make(map[string]string)
metricsContext["oltid"] = devID
metricsContext["devicetype"] = devType
metricsContext["portlabel"] = port.Label
metricsContext["portno"] = strconv.Itoa(int(port.PortNo))
if port.Type == voltha.Port_ETHERNET_NNI {
volthaEventSubCatgry = voltha.EventSubCategory_NNI
} else {
volthaEventSubCatgry = voltha.EventSubCategory_PON
}
raisedTs := time.Now().UnixNano()
mmd := voltha.MetricMetaData{
Title: port.Type.String(),
Ts: float64(raisedTs),
Context: metricsContext,
DeviceId: devID,
}
metricInfo.Metadata = &mmd
metricInfo.Metrics = val
ke.SliceData = []*voltha.MetricInformation{&metricInfo}
ke.Type = voltha.KpiEventType_slice
ke.Ts = float64(time.Now().UnixNano())
if err := StatMgr.Device.EventProxy.SendKpiEvent(ctx, "STATS_EVENT", &ke, voltha.EventCategory_EQUIPMENT, volthaEventSubCatgry, raisedTs); err != nil {
logger.Errorw(ctx, "failed-to-send-pon-stats", log.Fields{"err": err})
}
}
// PortStatisticsIndication handles the port statistics indication
func (StatMgr *OpenOltStatisticsMgr) PortStatisticsIndication(ctx context.Context, PortStats *openolt.PortStatistics, NumPonPorts uint32) {
StatMgr.PortsStatisticsKpis(ctx, PortStats, NumPonPorts)
logger.Debugw(ctx, "received-port-stats-indication", log.Fields{"port-stats": PortStats})
// TODO send stats to core topic to the voltha kafka or a different kafka ?
}
// FlowStatisticsIndication to be implemented
func FlowStatisticsIndication(ctx context.Context, self, FlowStats *openolt.FlowStatistics) {
logger.Debugw(ctx, "flow-stats-collected", log.Fields{"flow-stats": FlowStats})
//TODO send to kafka ?
}
// PortsStatisticsKpis map the port stats values into a dictionary, creates the kpiEvent and then publish to Kafka
func (StatMgr *OpenOltStatisticsMgr) PortsStatisticsKpis(ctx context.Context, PortStats *openolt.PortStatistics, NumPonPorts uint32) {
/*map the port stats values into a dictionary
Create a kpoEvent and publish to Kafka
:param port_stats:
:return:
*/
//var err error
IntfID := PortStats.IntfId
if (IntfIDToPortNo(1, voltha.Port_ETHERNET_NNI) < IntfID) &&
(IntfID < IntfIDToPortNo(4, voltha.Port_ETHERNET_NNI)) {
/*
for this release we are only interested in the first NNI for
Northbound.
we are not using the other 3
*/
return
} else if IntfIDToPortNo(0, voltha.Port_ETHERNET_NNI) == IntfID {
var portNNIStat NniPort
portNNIStat.IntfID = IntfID
portNNIStat.PortNum = uint32(0)
portNNIStat.RxBytes = PortStats.RxBytes
portNNIStat.RxPackets = PortStats.RxPackets
portNNIStat.RxUcastPackets = PortStats.RxUcastPackets
portNNIStat.RxMcastPackets = PortStats.RxMcastPackets
portNNIStat.RxBcastPackets = PortStats.RxBcastPackets
portNNIStat.TxBytes = PortStats.TxBytes
portNNIStat.TxPackets = PortStats.TxPackets
portNNIStat.TxUcastPackets = PortStats.TxUcastPackets
portNNIStat.TxMcastPackets = PortStats.TxMcastPackets
portNNIStat.TxBcastPackets = PortStats.TxBcastPackets
mutex.Lock()
StatMgr.NorthBoundPort[0] = &portNNIStat
mutex.Unlock()
logger.Debugw(ctx, "received-nni-stats", log.Fields{"nni-stats": StatMgr.NorthBoundPort})
}
for i := uint32(0); i < NumPonPorts; i++ {
if IntfIDToPortNo(i, voltha.Port_PON_OLT) == IntfID {
var portPonStat PonPort
portPonStat.IntfID = IntfID
portPonStat.PortNum = i
portPonStat.PONID = i
portPonStat.RxBytes = PortStats.RxBytes
portPonStat.RxPackets = PortStats.RxPackets
portPonStat.RxUcastPackets = PortStats.RxUcastPackets
portPonStat.RxMcastPackets = PortStats.RxMcastPackets
portPonStat.RxBcastPackets = PortStats.RxBcastPackets
portPonStat.TxBytes = PortStats.TxBytes
portPonStat.TxPackets = PortStats.TxPackets
portPonStat.TxUcastPackets = PortStats.TxUcastPackets
portPonStat.TxMcastPackets = PortStats.TxMcastPackets
portPonStat.TxBcastPackets = PortStats.TxBcastPackets
mutex.Lock()
StatMgr.SouthBoundPort[i] = &portPonStat
mutex.Unlock()
logger.Debugw(ctx, "received-pon-stats-for-port", log.Fields{"port-pon-stats": portPonStat})
}
}
/*
Based upon the intf_id map to an nni port or a pon port
the intf_id is the key to the north or south bound collections
Based upon the intf_id the port object (nni_port or pon_port) will
have its data attr. updated by the current dataset collected.
For prefixing the rule is currently to use the port number and not the intf_id
*/
//FIXME : Just use first NNI for now
/* TODO should the data be marshaled before sending it ?
if IntfID == IntfIdToPortNo(0, voltha.Port_ETHERNET_NNI) {
//NNI port (just the first one)
err = UpdatePortObjectKpiData(StatMgr.NorthBoundPorts[PortStats.IntfID], PMData)
} else {
//PON ports
err = UpdatePortObjectKpiData(SouthboundPorts[PortStats.IntfID], PMData)
}
if (err != nil) {
logger.Error(ctx, "Error publishing statistics data")
}
*/
}