blob: ae403873afaadf25d1332592398b19e9037dd090 [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"
"strconv"
"strings"
"sync"
"time"
"github.com/golang/protobuf/ptypes/empty"
"github.com/google/gopacket"
"github.com/google/uuid"
"github.com/opencord/voltha/ponsim/v2/common"
"github.com/opencord/voltha/protos/go/ponsim"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
)
// TODO: Cleanup GRPC security config
// TODO: Pass-in the certificate information as a structure parameter
/*
PonSimOnuDevice is the structure responsible for the handling of an ONU device
*/
type PonSimOnuDevice struct {
PonSimDevice
ParentAddress string
ParentPort int32
AssignedPort int32
SerialNumber string
Conn *grpc.ClientConn
oltClient ponsim.PonSimCommonClient
stream ponsim.PonSimCommon_ProcessDataClient
monitor chan PonSimDeviceState
state PonSimDeviceState
}
/*
NewPonSimOnuDevice instantiates a new ONU device structure
*/
func NewPonSimOnuDevice(device PonSimDevice) *PonSimOnuDevice {
onu := &PonSimOnuDevice{PonSimDevice: device}
return onu
}
/*
forwardToOLT defines a INGRESS function to forward a packet to the parent OLT
*/
func (o *PonSimOnuDevice) forwardToOLT() func(int, gopacket.Packet) {
return func(port int, frame gopacket.Packet) {
ipAddress := common.GetInterfaceIP(o.InternalIf)
incoming := &ponsim.IncomingData{
Id: "INGRESS.ONU." + ipAddress,
Address: ipAddress,
Port: int32(port),
Payload: frame.Data(),
}
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
"frameDump": frame.Dump(),
"incoming": incoming,
}).Debug("Forwarding to OLT")
// Forward packet to OLT
if err := o.stream.Send(incoming); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frameDump": frame.Dump(),
"incoming": incoming,
}).Fatal("A problem occurred while forwarding to OLT")
}
}
}
/*
forwardToWAN defines a EGRESS function to forward a packet to the world
*/
func (o *PonSimOnuDevice) forwardToWAN() func(int, gopacket.Packet) {
return func(port int, frame gopacket.Packet) {
var err error
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
}).Debug("Forwarding packet to world")
if err = o.ingressHandler.WritePacketData(frame.Data()); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
}).Fatal("Problem while forwarding packet to world")
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
}).Debug("Forwarded packet to world")
}
}
}
/*
Start performs setup operations for an ONU device
*/
func (o *PonSimOnuDevice) Start(ctx context.Context) {
// Initialize the parent
o.PonSimDevice.Start(ctx)
// Setup flow behaviours
// ONU -> OLT
o.AddLink(1, 0, o.forwardToOLT())
// ONU -> World
o.AddLink(2, 0, o.forwardToWAN())
go o.MonitorConnection(ctx)
}
/*
Stop performs cleanup operations for an ONU device
*/
func (o *PonSimOnuDevice) Stop(ctx context.Context) {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("Stopping ONU")
o.RemoveLink(1, 0)
o.RemoveLink(2, 0)
o.PonSimDevice.Stop(ctx)
}
/*
Listen waits for incoming INGRESS data on the external interface
*/
func (o *PonSimOnuDevice) Listen(ctx context.Context) {
var reply *empty.Empty
var err error
if o.oltClient = ponsim.NewPonSimCommonClient(o.Conn); o.oltClient == nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Fatal("Problem establishing client connection to OLT")
panic("Problem establishing client connection to OLT")
}
// Establish GRPC connection with OLT
if o.stream, err = o.oltClient.ProcessData(ctx); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"error": err.Error(),
}).Fatal("Problem establishing stream")
panic(err)
}
defer o.ingressHandler.Close()
packetSource := gopacket.NewPacketSource(o.ingressHandler, o.ingressHandler.LinkType())
common.Logger().WithFields(logrus.Fields{
"device": o,
"interface": o.ExternalIf,
}).Debug("Listening to incoming ONU data")
for packet := range packetSource.Packets() {
common.Logger().WithFields(logrus.Fields{
"device": o,
"packet": packet,
}).Debug("Received INGRESS packet")
o.Forward(ctx, 2, packet)
}
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("No more packets to process")
if reply, err = o.stream.CloseAndRecv(); err != nil {
common.Logger().Fatal("A problem occurred while closing Ingress stream", err.Error())
} else {
common.Logger().Info("Ingress stream closed", reply)
}
}
/*
Register sends a registration request to the remote OLT
*/
func (o *PonSimOnuDevice) Register(ctx context.Context) error {
var err error
var rreq *ponsim.RegistrationRequest
var rrep *ponsim.RegistrationReply
var client ponsim.PonSimOltClient
if o.Conn != nil {
if client = ponsim.NewPonSimOltClient(o.Conn); client != nil {
rreq = &ponsim.RegistrationRequest{
Id: uuid.New().String(),
Address: common.GetInterfaceIP(o.InternalIf),
Port: o.Port,
SerialNumber: o.SerialNumber,
}
common.Logger().Printf("Request details %+v\n", rreq)
// TODO: Loop registration until an OLT becomes available??
rrep, err = client.Register(ctx, rreq)
if err != nil {
common.Logger().Printf("Problem with registration", err.Error())
} else {
// Save OLT address details
o.ParentAddress = rrep.GetParentAddress()
o.ParentPort = rrep.GetParentPort()
o.AssignedPort = rrep.GetAssignedPort()
common.Logger().Printf("Registration details - %+v\n", rrep)
o.monitor <- REGISTERED_WITH_OLT
}
} else {
common.Logger().Info("Client is NIL")
}
}
return err
}
/*
MonitorConnection verifies the communication with the OLT
*/
func (o *PonSimOnuDevice) MonitorConnection(ctx context.Context) {
for {
if o.state == DISCONNECTED_FROM_PON {
// Establish communication with OLT
o.Connect(ctx)
}
if o.state == CONNECTED_TO_PON {
// Just stay idle while the ONU-OLT connection is up
o.Conn.WaitForStateChange(ctx, o.Conn.GetState())
// The ONU-OLT connection was lost... need to cleanup
o.Disconnect(ctx)
}
time.Sleep(1 * time.Second)
}
}
/*
Connect sets up communication and monitoring with remote OLT
*/
func (o *PonSimOnuDevice) Connect(ctx context.Context) {
o.monitor = make(chan PonSimDeviceState, 1)
// Define a waitgroup to block the current routine until
// a CONNECTED state is reached
wg := sync.WaitGroup{}
wg.Add(1)
go o.MonitorState(ctx, &wg)
o.ConnectToRemoteOlt()
// Wait until we establish a connection to the remote PON
wg.Wait()
}
/*
Disconnect tears down communication and monitoring with remote OLT
*/
func (o *PonSimOnuDevice) Disconnect(ctx context.Context) {
if o.egressHandler != nil {
o.egressHandler.Close()
o.egressHandler = nil
}
if o.Conn != nil {
o.Conn.Close()
o.Conn = nil
}
if o.monitor != nil {
close(o.monitor)
o.monitor = nil
o.state = DISCONNECTED_FROM_PON
}
}
/*
MonitorState follows the progress of the OLT connection
*/
func (o *PonSimOnuDevice) MonitorState(ctx context.Context, wg *sync.WaitGroup) {
// Start a concurrent routine to handle ONU state changes
var ok bool
for {
select {
case o.state, ok = <-o.monitor:
if ok {
common.Logger().WithFields(logrus.Fields{
"device": o,
"state": o.state,
}).Info("Received monitoring state")
switch o.state {
case CONNECTED_TO_PON:
// We have successfully connected to the OLT
// proceed with registration
wg.Done()
if err := o.Register(ctx); err != nil {
o.Disconnect(ctx)
}
case DISCONNECTED_FROM_PON:
// Connection to remote OLT was lost... exit
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Warn("Exiting due to disconnection")
return
case REGISTERED_WITH_OLT:
// Start listening on network interfaces
o.connectNetworkInterfaces()
o.monitor <- CONNECTED_IO_INTERFACE
case CONNECTED_IO_INTERFACE:
// Start listening on local interfaces
go o.Listen(ctx)
}
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Warn("Monitoring channel has closed")
return
}
case <-ctx.Done():
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Warn("Received a cancellation notification")
return
}
}
}
/*
ConnectToRemoteOlt establishes GRPC communication with the remote OLT
*/
func (o *PonSimOnuDevice) ConnectToRemoteOlt() {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("Connecting to remote device")
var err error
host := strings.Join([]string{
o.ParentAddress,
strconv.Itoa(int(o.ParentPort)),
}, ":")
if o.Conn, err = grpc.DialContext(
context.Background(), host, grpc.WithInsecure(), grpc.WithBlock(),
); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"error": err.Error(),
}).Error("Problem establishing connection")
} else {
// We are now connected
// time to move on
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Info("Connected to OLT")
}
o.monitor <- CONNECTED_TO_PON
}