SEBA-958 send periodic port stats

Change-Id: I981e6c70b214845d87e8ae96b370fcdf58ccfef3
diff --git a/cmd/bbr/bbr.go b/cmd/bbr/bbr.go
index 52ae622..441ddb0 100644
--- a/cmd/bbr/bbr.go
+++ b/cmd/bbr/bbr.go
@@ -70,18 +70,7 @@
 
 	// create the OLT device
 	olt := devices.CreateOLT(
-		options.Olt.ID,
-		int(options.Olt.NniPorts),
-		int(options.Olt.PonPorts),
-		int(options.Olt.OnusPonPort),
-		options.BBSim.STag,
-		options.BBSim.CTagInit,
-		true, // this parameter is not important in the BBR Case
-		true, // this parameter is not important in the BBR Case
-		0,    // this parameter does not matter in the BBR case
-		options.BBSim.ControlledActivation,
-		false, // this parameter is not important in the BBR Case
-		false,
+		*options.BBSimYamlConfig,
 		true,
 	)
 
diff --git a/cmd/bbsim/bbsim.go b/cmd/bbsim/bbsim.go
index 2de8906..8a1b088 100644
--- a/cmd/bbsim/bbsim.go
+++ b/cmd/bbsim/bbsim.go
@@ -163,18 +163,7 @@
 	apiDoneChannel := make(chan bool)
 
 	olt := devices.CreateOLT(
-		options.Olt.ID,
-		int(options.Olt.NniPorts),
-		int(options.Olt.PonPorts),
-		int(options.Olt.OnusPonPort),
-		options.BBSim.STag,
-		options.BBSim.CTagInit,
-		options.BBSim.EnableAuth,
-		options.BBSim.EnableDhcp,
-		options.BBSim.Delay,
-		options.BBSim.ControlledActivation,
-		options.BBSim.EnablePerf,
-		options.BBSim.Events,
+		*options,
 		false,
 	)
 
diff --git a/configs/bbsim.yaml b/configs/bbsim.yaml
index dcba903..23a6cc9 100644
--- a/configs/bbsim.yaml
+++ b/configs/bbsim.yaml
@@ -37,6 +37,7 @@
   reboot_delay: 10      # reboot delay in seconds
   # firmware_version: ""
   # device_id: 0a:0a:0a:0a:0a:<id>
+  port_stats_interval: 20 # in seconds
 
 # BBR settings
 bbr:
diff --git a/internal/bbsim/alarmsim/alarmsim.go b/internal/bbsim/alarmsim/alarmsim.go
index 266282a..aa80212 100644
--- a/internal/bbsim/alarmsim/alarmsim.go
+++ b/internal/bbsim/alarmsim/alarmsim.go
@@ -263,22 +263,6 @@
 	return nil
 }
 
-// InterfaceIDToPortNo converts InterfaceID to voltha PortID
-// Refer openolt adapter code(master) voltha-openolt-adapter/adaptercore/olt_platform.go: IntfIDToPortNo()
-func InterfaceIDToPortNo(req *bbsim.OLTAlarmRequest) uint32 {
-	// Converts interface-id to port-numbers that can be understood by the VOLTHA
-	if req.InterfaceType == "nni" || req.InterfaceType == "PonLossOfSignal" {
-		// nni at voltha starts with 1,048,576
-		// nni = 1,048,576 + InterfaceID
-		return 0x1<<20 + req.InterfaceID
-	} else if req.InterfaceType == "pon" || req.InterfaceType == "NniLossOfSignal" {
-		// pon = 536,870,912 + InterfaceID
-		return (0x2 << 28) + req.InterfaceID
-		// In bbsim, pon starts from 1
-	}
-	return 0
-}
-
 // IsPonPortPresentInOlt verifies if given Pon port is present in olt
 func IsPonPortPresentInOlt(PonPort uint32) bool {
 	o := devices.GetOLT()
@@ -321,7 +305,7 @@
 	alarmIndication = &openolt.AlarmIndication{
 		Data: &openolt.AlarmIndication_LosInd{&openolt.LosIndication{
 			Status: req.Status,
-			IntfId: InterfaceIDToPortNo(req),
+			IntfId: devices.InterfaceIDToPortNo(req.InterfaceID, req.InterfaceType),
 		}},
 	}
 
diff --git a/internal/bbsim/devices/helpers.go b/internal/bbsim/devices/helpers.go
index c4bab28..e72093b 100644
--- a/internal/bbsim/devices/helpers.go
+++ b/internal/bbsim/devices/helpers.go
@@ -17,6 +17,7 @@
 package devices
 
 import (
+	"math/rand"
 	"strconv"
 	"time"
 
@@ -85,3 +86,46 @@
 		olt.EventChannel <- event
 	}
 }
+
+func getPortStats(packetCount uint64, incrementStat bool) (*openolt.PortStatistics, uint64) {
+	// increment current packet count by random number
+	if incrementStat {
+		packetCount = packetCount + uint64(rand.Intn(50)+1*10)
+	}
+
+	// fill all other stats based on packet count
+	portStats := &openolt.PortStatistics{
+		RxBytes:        packetCount * 64,
+		RxPackets:      packetCount,
+		RxUcastPackets: packetCount * 40 / 100,
+		RxMcastPackets: packetCount * 30 / 100,
+		RxBcastPackets: packetCount * 30 / 100,
+		RxErrorPackets: 0,
+		TxBytes:        packetCount * 64,
+		TxPackets:      packetCount,
+		TxUcastPackets: packetCount * 40 / 100,
+		TxMcastPackets: packetCount * 30 / 100,
+		TxBcastPackets: packetCount * 30 / 100,
+		TxErrorPackets: 0,
+		RxCrcErrors:    0,
+		BipErrors:      0,
+		Timestamp:      uint32(time.Now().Unix()),
+	}
+
+	return portStats, packetCount
+}
+
+// InterfaceIDToPortNo converts InterfaceID to voltha PortID
+// Refer openolt adapter code(master) voltha-openolt-adapter/adaptercore/olt_platform.go: IntfIDToPortNo()
+func InterfaceIDToPortNo(intfID uint32, intfType string) uint32 {
+	// Converts interface-id to port-numbers that can be understood by the VOLTHA
+	if intfType == "nni" {
+		// nni at voltha starts with 1,048,576
+		// nni = 1,048,576 + InterfaceID
+		return 0x1<<20 + intfID
+	} else if intfType == "pon" {
+		// pon = 536,870,912 + InterfaceID
+		return (0x2 << 28) + intfID
+	}
+	return 0
+}
diff --git a/internal/bbsim/devices/nni.go b/internal/bbsim/devices/nni.go
index 0d658e9..fabe69c 100644
--- a/internal/bbsim/devices/nni.go
+++ b/internal/bbsim/devices/nni.go
@@ -54,6 +54,7 @@
 	ID           uint32
 	nniVeth      string
 	upstreamVeth string
+	PacketCount  uint64
 
 	// PON Attributes
 	OperState *fsm.FSM
diff --git a/internal/bbsim/devices/olt.go b/internal/bbsim/devices/olt.go
index 4aafaba..0209b88 100644
--- a/internal/bbsim/devices/olt.go
+++ b/internal/bbsim/devices/olt.go
@@ -63,6 +63,7 @@
 	ControlledActivation mode
 	EventChannel         chan common.Event
 	PublishEvents        bool
+	PortStatsInterval    int
 
 	Pons []*PonPort
 	Nnis []*NniPort
@@ -84,32 +85,33 @@
 	return &olt
 }
 
-func CreateOLT(oltId int, nni int, pon int, onuPerPon int, sTag int, cTagInit int, auth bool, dhcp bool, delay int, ca string, enablePerf bool, event bool, isMock bool) *OltDevice {
+func CreateOLT(options common.BBSimYamlConfig, isMock bool) *OltDevice {
 	oltLogger.WithFields(log.Fields{
-		"ID":           oltId,
-		"NumNni":       nni,
-		"NumPon":       pon,
-		"NumOnuPerPon": onuPerPon,
+		"ID":           options.Olt.ID,
+		"NumNni":       options.Olt.NniPorts,
+		"NumPon":       options.Olt.PonPorts,
+		"NumOnuPerPon": options.Olt.OnusPonPort,
 	}).Debug("CreateOLT")
 
 	olt = OltDevice{
-		ID:           oltId,
-		SerialNumber: fmt.Sprintf("BBSIM_OLT_%d", oltId),
+		ID:           options.Olt.ID,
+		SerialNumber: fmt.Sprintf("BBSIM_OLT_%d", options.Olt.ID),
 		OperState: getOperStateFSM(func(e *fsm.Event) {
 			oltLogger.Debugf("Changing OLT OperState from %s to %s", e.Src, e.Dst)
 		}),
-		NumNni:        nni,
-		NumPon:        pon,
-		NumOnuPerPon:  onuPerPon,
-		Pons:          []*PonPort{},
-		Nnis:          []*NniPort{},
-		Delay:         delay,
-		Flows:         make(map[FlowKey]openolt.Flow),
-		enablePerf:    enablePerf,
-		PublishEvents: event,
+		NumNni:            int(options.Olt.NniPorts),
+		NumPon:            int(options.Olt.PonPorts),
+		NumOnuPerPon:      int(options.Olt.OnusPonPort),
+		Pons:              []*PonPort{},
+		Nnis:              []*NniPort{},
+		Delay:             options.BBSim.Delay,
+		Flows:             make(map[FlowKey]openolt.Flow),
+		enablePerf:        options.BBSim.EnablePerf,
+		PublishEvents:     options.BBSim.Events,
+		PortStatsInterval: options.Olt.PortStatsInterval,
 	}
 
-	if val, ok := ControlledActivationModes[ca]; ok {
+	if val, ok := ControlledActivationModes[options.BBSim.ControlledActivation]; ok {
 		olt.ControlledActivation = val
 	} else {
 		oltLogger.Warn("Unknown ControlledActivation Mode given, running in Default mode")
@@ -146,14 +148,14 @@
 	}
 
 	// create PON ports
-	availableCTag := cTagInit
-	for i := 0; i < pon; i++ {
+	availableCTag := options.BBSim.CTagInit
+	for i := 0; i < olt.NumPon; i++ {
 		p := CreatePonPort(&olt, uint32(i))
 
 		// create ONU devices
-		for j := 0; j < onuPerPon; j++ {
+		for j := 0; j < olt.NumOnuPerPon; j++ {
 			delay := time.Duration(olt.Delay*j) * time.Millisecond
-			o := CreateONU(&olt, *p, uint32(j+1), sTag, availableCTag, auth, dhcp, delay, isMock)
+			o := CreateONU(&olt, *p, uint32(j+1), options.BBSim.STag, availableCTag, options.BBSim.EnableAuth, options.BBSim.EnableDhcp, delay, isMock)
 			p.Onus = append(p.Onus, o)
 			availableCTag = availableCTag + 1
 		}
@@ -391,6 +393,12 @@
 	}
 
 	oltLogger.Debug("Enable OLT Done")
+
+	if !o.enablePerf {
+		// Start a go routine to send periodic port stats to openolt adapter
+		go o.periodicPortStats(o.enableContext)
+	}
+
 	wg.Wait()
 	return nil
 }
@@ -432,6 +440,39 @@
 	wg.Done()
 }
 
+func (o *OltDevice) periodicPortStats(ctx context.Context) {
+	var portStats *openolt.PortStatistics
+	for {
+		select {
+		case <-time.After(time.Duration(o.PortStatsInterval) * time.Second):
+			// send NNI port stats
+			for _, port := range o.Nnis {
+				incrementStat := true
+				if port.OperState.Current() == "down" {
+					incrementStat = false
+				}
+				portStats, port.PacketCount = getPortStats(port.PacketCount, incrementStat)
+				o.sendPortStatsIndication(portStats, port.ID, port.Type)
+			}
+
+			// send PON port stats
+			for _, port := range o.Pons {
+				incrementStat := true
+				// do not increment port stats if PON port is down or no ONU is activated on PON port
+				if port.OperState.Current() == "down" || port.GetNumOfActiveOnus() < 1 {
+					incrementStat = false
+				}
+				portStats, port.PacketCount = getPortStats(port.PacketCount, incrementStat)
+				o.sendPortStatsIndication(portStats, port.ID, port.Type)
+			}
+		case <-ctx.Done():
+			log.Debug("Stop sending port stats")
+			return
+		}
+
+	}
+}
+
 // Helpers method
 
 func (o OltDevice) GetPonById(id uint32) (*PonPort, error) {
@@ -553,6 +594,22 @@
 	}).Debug("Sent Indication_IntfOperInd for PON")
 }
 
+func (o *OltDevice) sendPortStatsIndication(stats *openolt.PortStatistics, portID uint32, portType string) {
+	oltLogger.WithFields(log.Fields{
+		"Type":   portType,
+		"IntfId": portID,
+	}).Trace("Sending port stats")
+	stats.IntfId = InterfaceIDToPortNo(portID, portType)
+	data := &openolt.Indication_PortStats{
+		PortStats: stats,
+	}
+	stream := *o.OpenoltStream
+	if err := stream.Send(&openolt.Indication{Data: data}); err != nil {
+		oltLogger.Errorf("Failed to send PortStats: %v", err)
+		return
+	}
+}
+
 // processOltMessages handles messages received over the OpenOLT interface
 func (o *OltDevice) processOltMessages(ctx context.Context, stream openolt.Openolt_EnableIndicationServer, wg *sync.WaitGroup) {
 	oltLogger.Debug("Starting OLT Indication Channel")
diff --git a/internal/bbsim/devices/pon.go b/internal/bbsim/devices/pon.go
index 130c74f..c2847b1 100644
--- a/internal/bbsim/devices/pon.go
+++ b/internal/bbsim/devices/pon.go
@@ -28,17 +28,16 @@
 
 type PonPort struct {
 	// BBSIM Internals
-	ID     uint32
-	NumOnu int
-	Onus   []*Onu
-	Olt    *OltDevice
+	ID            uint32
+	NumOnu        int
+	Onus          []*Onu
+	Olt           *OltDevice
+	PacketCount   uint64
+	InternalState *fsm.FSM
 
 	// PON Attributes
 	OperState *fsm.FSM
 	Type      string
-
-	// NOTE do we need a state machine for the PON Ports?
-	InternalState *fsm.FSM
 }
 
 // CreatePonPort creates pon port object
@@ -148,3 +147,15 @@
 	}
 	return nil, errors.New(fmt.Sprintf("Cannot find Onu with id %d in PonPort %d", id, p.ID))
 }
+
+// GetNumOfActiveOnus returns number of active ONUs for PON port
+func (p PonPort) GetNumOfActiveOnus() uint32 {
+	var count uint32 = 0
+	for _, onu := range p.Onus {
+		if onu.InternalState.Current() == "initialized" || onu.InternalState.Current() == "created" || onu.InternalState.Current() == "disabled" {
+			continue
+		}
+		count++
+	}
+	return count
+}
diff --git a/internal/bbsimctl/commands/oltalarms.go b/internal/bbsimctl/commands/oltalarms.go
index 645aade..f251b83 100755
--- a/internal/bbsimctl/commands/oltalarms.go
+++ b/internal/bbsimctl/commands/oltalarms.go
@@ -75,10 +75,18 @@
 	ctx, cancel := context.WithTimeout(context.Background(), config.GlobalConfig.Grpc.Timeout)
 	defer cancel()
 
-	req := pb.OLTAlarmRequest{InterfaceType: string(o.Args.Name),
+	req := pb.OLTAlarmRequest{
 		InterfaceID: uint32(o.Args.IntfID),
 		Status:      "on"}
 
+	if string(o.Args.Name) == "PonLossOfSignal" {
+		req.InterfaceType = "pon"
+	} else if string(o.Args.Name) == "NniLossOfSignal" {
+		req.InterfaceType = "nni"
+	} else {
+		return fmt.Errorf("Unknown alarm type")
+	}
+
 	res, err := client.SetOltAlarmIndication(ctx, &req)
 	if err != nil {
 		log.Fatalf("Cannot raise OLT alarm: %v", err)
@@ -97,10 +105,18 @@
 	ctx, cancel := context.WithTimeout(context.Background(), config.GlobalConfig.Grpc.Timeout)
 	defer cancel()
 
-	req := pb.OLTAlarmRequest{InterfaceType: string(o.Args.Name),
+	req := pb.OLTAlarmRequest{
 		InterfaceID: uint32(o.Args.IntfID),
 		Status:      "off"}
 
+	if string(o.Args.Name) == "PonLossOfSignal" {
+		req.InterfaceType = "pon"
+	} else if string(o.Args.Name) == "NniLossOfSignal" {
+		req.InterfaceType = "nni"
+	} else {
+		return fmt.Errorf("Unknown alarm type")
+	}
+
 	res, err := client.SetOltAlarmIndication(ctx, &req)
 
 	if err != nil {
diff --git a/internal/common/options.go b/internal/common/options.go
index 6724d1f..d7bc9eb 100644
--- a/internal/common/options.go
+++ b/internal/common/options.go
@@ -52,6 +52,7 @@
 	Technology         string `yaml:"technology"`
 	ID                 int    `yaml:"id"`
 	OltRebootDelay     int    `yaml:"reboot_delay"`
+	PortStatsInterval  int    `yaml: "port_stats_interval"`
 }
 
 type BBSimConfig struct {
@@ -124,6 +125,7 @@
 			Technology:         "XGS-PON",
 			ID:                 0,
 			OltRebootDelay:     10,
+			PortStatsInterval:  20,
 		},
 		BBRConfig{
 			LogLevel:  "debug",