[VOL-1349] EPON OLT adapter (package B)
Change-Id: I634ef62c53813dcf4456f54948f13e06358e263c
diff --git a/internal/pkg/core/statsmanager.go b/internal/pkg/core/statsmanager.go
new file mode 100644
index 0000000..d8fa663
--- /dev/null
+++ b/internal/pkg/core/statsmanager.go
@@ -0,0 +1,497 @@
+/*
+ * 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
+}
+
+// NewOpenOltStatsMgr returns a new instance of the OpenOltStatisticsMgr
+func NewOpenOltStatsMgr(ctx context.Context, Dev *DeviceHandler) *OpenOltStatisticsMgr {
+
+ var StatMgr OpenOltStatisticsMgr
+
+ StatMgr.Device = Dev
+ 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:
+ */
+
+ 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})
+}
+
+// FlowStatisticsIndication to be implemented
+func FlowStatisticsIndication(ctx context.Context, self, FlowStats *openolt.FlowStatistics) {
+ logger.Debugw(ctx, "flow-stats-collected", log.Fields{"flow-stats": FlowStats})
+}
+
+// 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:
+ */
+ 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
+ */
+ /* 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")
+ }
+ */
+
+}