blob: 508f45569287e9718837c60edb36aae304797577 [file] [log] [blame]
/*
* Copyright 2017-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
import (
"context"
"crypto/tls"
"github.com/golang/protobuf/ptypes/empty"
"github.com/google/gopacket"
"github.com/opencord/voltha/ponsim/v2/common"
"github.com/opencord/voltha/protos/go/ponsim"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
"strconv"
"strings"
"time"
)
// TODO: Pass-in the certificate information as a structure parameter
/*
PonSimOltDevice is the structure responsible for the handling of an OLT device
*/
type PonSimOltDevice struct {
PonSimDevice `json:pon_device`
VCoreEndpoint string `json:vcore_ep`
MaxOnuCount int `json:max_onu`
Onus map[int32]*OnuRegistree `json:onu_registrees`
outgoing chan []byte
counterLoop *common.IntervalHandler
alarmLoop *common.IntervalHandler
}
/*
*/
type OnuRegistree struct {
Device *PonSimOnuDevice `json:onu_device`
Conn *grpc.ClientConn `json:grpc_conn`
Client ponsim.PonSimCommonClient `json:client`
Stream ponsim.PonSimCommon_ProcessDataClient `json:stream`
}
const (
BASE_PORT_NUMBER = 128
)
/*
NewPonSimOltDevice instantiates a new OLT device structure
*/
func NewPonSimOltDevice(device PonSimDevice) *PonSimOltDevice {
olt := &PonSimOltDevice{PonSimDevice: device}
return olt
}
/*
forwardToONU defines a EGRESS function to forward a packet to a specific ONU
*/
func (o *PonSimOltDevice) forwardToONU(onuPort int32) func(int, gopacket.Packet) {
return func(port int, frame gopacket.Packet) {
ipAddress := common.GetInterfaceIP(o.ExternalIf)
incoming := &ponsim.IncomingData{
Id: "EGRESS.OLT." + ipAddress,
Address: ipAddress,
Port: int32(port),
Payload: frame.Data(),
}
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
}).Debug("Forwarding to ONU")
// Forward packet to ONU
if err := o.GetOnu(onuPort).Stream.Send(incoming); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"frameDump": frame.Dump(),
"incoming": incoming,
"error": err.Error(),
}).Error("A problem occurred while forwarding to ONU")
}
}
}
/*
forwardToLAN defines an INGRESS function to forward a packet to VOLTHA
*/
func (o *PonSimOltDevice) forwardToLAN() func(int, gopacket.Packet) {
return func(port int, frame gopacket.Packet) {
common.Logger().WithFields(logrus.Fields{
"frame": frame.Dump(),
}).Info("Sending packet")
select {
case o.outgoing <- frame.Data():
common.Logger().WithFields(logrus.Fields{
"frame": frame.Dump(),
}).Info("Sent packet")
default:
common.Logger().WithFields(logrus.Fields{
"frame": frame.Dump(),
}).Warn("Unable to send packet")
}
}
}
/*
Start performs setup operations for an OLT device
*/
func (o *PonSimOltDevice) Start(ctx context.Context) {
common.Logger().Info("Starting OLT device...")
o.PonSimDevice.Start(ctx)
// Open network interfaces for listening
o.connectNetworkInterfaces()
o.outgoing = make(chan []byte, 1)
// Add INGRESS operation
o.AddLink(2, 0, o.forwardToLAN())
// Start PM counter logging
o.counterLoop = common.NewIntervalHandler(90, o.Counter.LogCounts)
o.counterLoop.Start()
// Start alarm simulation
if o.AlarmsOn {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("Starting alarm simulation")
alarms := NewPonSimAlarm(o.InternalIf, o.VCoreEndpoint, o.forwardToLAN())
o.alarmLoop = common.NewIntervalHandler(o.AlarmsFreq, alarms.GenerateAlarm)
o.alarmLoop.Start()
}
}
/*
Stop performs cleanup operations for an OLT device
*/
func (o *PonSimOltDevice) Stop(ctx context.Context) {
common.Logger().Info("Stopping OLT device...")
// Stop PM counters loop
o.counterLoop.Stop()
o.counterLoop = nil
// Stop alarm simulation
if o.AlarmsOn {
o.alarmLoop.Stop()
}
o.alarmLoop = nil
o.ingressHandler.Close()
o.egressHandler.Close()
o.PonSimDevice.Stop(ctx)
}
/*
ConnectToRemoteOnu establishes communication to a remote ONU device
*/
func (o *PonSimOltDevice) ConnectToRemoteOnu(onu *OnuRegistree) error {
var err error
host := strings.Join([]string{
onu.Device.Address,
strconv.Itoa(int(onu.Device.Port)),
}, ":")
common.Logger().WithFields(logrus.Fields{
"device": o,
"host": host,
}).Debug("Formatting host address")
// TODO: make it secure
ta := credentials.NewTLS(&tls.Config{
//Certificates: []tls.Certificate{peerCert},
//RootCAs: caCertPool,
InsecureSkipVerify: true,
})
// GRPC communication needs to be secured
if onu.Conn, err = grpc.DialContext(
context.Background(),
host,
grpc.WithTransportCredentials(ta),
); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"error": err.Error(),
}).Error("Problem with client connection")
}
return err
}
/*
Listen waits for incoming EGRESS data on the internal interface
*/
func (o *PonSimOltDevice) Listen(ctx context.Context, port int32) {
var reply *empty.Empty
var err error
// Establish a GRPC connection with the ONU
onu := o.GetOnu(port)
common.Logger().WithFields(logrus.Fields{
"onu": onu,
}).Debug("Connecting to remote ONU")
if onu.Client = ponsim.NewPonSimCommonClient(onu.Conn); onu.Client == nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Error("Problem establishing client connection to ONU")
o.RemoveOnu(ctx, port)
return
}
// Prepare stream to ONU to forward incoming data as needed
if onu.Stream, err = onu.Client.ProcessData(ctx); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Error("Problem establishing stream to ONU")
o.RemoveOnu(ctx, port)
return
}
defer o.egressHandler.Close()
packetSource := gopacket.NewPacketSource(o.egressHandler, o.egressHandler.LinkType())
common.Logger().WithFields(logrus.Fields{
"device": o,
"interface": o.InternalIf,
}).Debug("Listening to incoming EGRESS data")
// Wait for incoming EGRESS data
for packet := range packetSource.Packets() {
if dot1q := common.GetDot1QLayer(packet); dot1q != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"packet": packet,
}).Debug("Received EGRESS packet")
o.Forward(ctx, 2, packet)
}
}
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("No more packets to process")
if reply, err = onu.Stream.CloseAndRecv(); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"error": err.Error(),
}).Error("A problem occurred while closing client stream")
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"reply": reply,
}).Warn("Client stream closed")
}
}
/*
GetOnus returns the list of registered ONU devices
*/
func (o *PonSimOltDevice) GetOnus() map[int32]*OnuRegistree {
if o.Onus == nil {
o.Onus = make(map[int32]*OnuRegistree)
}
return o.Onus
}
/*
GetOnu return a specific registered ONU
*/
func (o *PonSimOltDevice) GetOnu(index int32) *OnuRegistree {
var onu *OnuRegistree
var ok bool
if onu, ok = (o.GetOnus())[index]; ok {
return onu
}
return nil
}
func (o *PonSimOltDevice) GetOutgoing() chan []byte {
return o.outgoing
}
/*
nextAvailablePort returns a port that is not already used by a registered ONU
*/
func (o *PonSimOltDevice) nextAvailablePort() int32 {
var port int32 = BASE_PORT_NUMBER
if len(o.GetOnus()) < o.MaxOnuCount {
for {
if o.GetOnu(port) != nil {
// port is already used
port += 1
} else {
// port is available... use it
return port
}
}
} else {
// OLT has reached its max number of ONUs
return -1
}
}
/*
AddOnu registers an ONU device and sets up all required monitoring and connections
*/
func (o *PonSimOltDevice) AddOnu(onu *PonSimOnuDevice) (int32, error) {
var portNum int32
ctx := context.Background()
if portNum = o.nextAvailablePort(); portNum != -1 {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": portNum,
"onu": onu,
}).Info("Adding ONU")
registree := &OnuRegistree{Device: onu}
// Setup GRPC communication and check if it succeeded
if err := o.ConnectToRemoteOnu(registree); err == nil {
o.GetOnus()[portNum] = registree
o.AddLink(1, int(portNum), o.forwardToONU(portNum))
go o.MonitorOnu(ctx, portNum)
go o.Listen(ctx, portNum)
}
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Warn("ONU Map is full")
}
return int32(portNum), nil
}
/*
RemoveOnu removes the reference to a registered ONU
*/
func (o *PonSimOltDevice) RemoveOnu(ctx context.Context, onuIndex int32) error {
onu := o.GetOnu(onuIndex)
if err := onu.Conn.Close(); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"onu": onu.Device,
"onuIndex": onuIndex,
}).Error("Problem closing connection to ONU")
}
common.Logger().WithFields(logrus.Fields{
"device": o,
"onu": onu,
"onuIndex": onuIndex,
}).Info("Removing ONU")
delete(o.Onus, onuIndex)
// Remove link entries for this ONU
o.RemoveLink(1, int(onuIndex))
return nil
}
/*
MonitorOnu verifies the connection status of a specific ONU and cleans up as necessary
*/
func (o *PonSimOltDevice) MonitorOnu(ctx context.Context, onuIndex int32) {
for {
if o.GetOnu(onuIndex) != nil {
if conn := o.GetOnu(onuIndex).Conn; conn.GetState() == connectivity.Ready {
// Wait for any change to occur
conn.WaitForStateChange(ctx, conn.GetState())
// We lost communication with the ONU ... remove it
o.RemoveOnu(ctx, onuIndex)
return
}
common.Logger().WithFields(logrus.Fields{
"device": o,
"ctx": ctx,
"onuIndex": onuIndex,
}).Debug("ONU is not ready")
time.Sleep(1 * time.Second)
} else {
return
}
}
}