VOL-291 : PON simulator refactoring for cluster integration

- Added ponsim build target in Makefile
- Added new option to vcore to select comm type with ponsim
- Modified all proto files to include destination go package

Amendments:

- Clean up based on review comments
- Properly close GRPC connections in ponsim_olt adapter
- Added voltha namespace to some k8s templates

Change-Id: I2f349fa7b3550a8a8cc8fc676cc896f33fbb9372
diff --git a/ponsim/v2/core/ponsim_alarm.go b/ponsim/v2/core/ponsim_alarm.go
new file mode 100644
index 0000000..e99b84d
--- /dev/null
+++ b/ponsim/v2/core/ponsim_alarm.go
@@ -0,0 +1,175 @@
+package core
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/protos/go/voltha"
+	"github.com/sirupsen/logrus"
+	"math/rand"
+	"net"
+	"time"
+)
+
+// TODO: user-defined values? min/max intervals, vlan?
+
+const (
+	minInterval = 20
+	maxInterval = 60
+	vlandId     = 4000
+	localhost   = "127.0.0.1"
+	ttl         = 64
+	ipVersion   = 4
+)
+
+type Alarm struct {
+	Severity    int    `json:"severity"`
+	Type        int    `json:"type"`
+	Category    int    `json:"category"`
+	State       int    `json:"state"`
+	TimeStamp   int    `json:"ts"`
+	Description string `json:"description"`
+}
+
+/*
+PonSimAlarm is the structure responsible for the handling of alarms
+*/
+type PonSimAlarm struct {
+	forwardFunction func(int, gopacket.Packet)
+	dstInterface    string
+	dstEndpoint     string
+}
+
+/*
+NewPonSimAlarm instantiates a new alarm handling structure
+*/
+func NewPonSimAlarm(dstInterface string, dstEndpoint string, function func(int, gopacket.Packet)) *PonSimAlarm {
+	psa := &PonSimAlarm{dstInterface: dstInterface, dstEndpoint: dstEndpoint, forwardFunction: function}
+	return psa
+}
+
+/*
+prepareAlarm constructs an alarm object with random field values.
+*/
+func (a *PonSimAlarm) prepareAlarm() *Alarm {
+	alarm_severity := rand.Intn(len(voltha.AlarmEventSeverity_AlarmEventSeverity_value))
+	alarm_type := rand.Intn(len(voltha.AlarmEventType_AlarmEventType_value))
+	alarm_category := rand.Intn(len(voltha.AlarmEventCategory_AlarmEventCategory_value))
+	alarm_state := int(voltha.AlarmEventState_RAISED)
+	alarm_ts := time.Now().UTC().Second()
+	alarm_description := fmt.Sprintf("%s.%s alarm",
+		voltha.AlarmEventType_AlarmEventType_name[int32(alarm_type)],
+		voltha.AlarmEventCategory_AlarmEventCategory_name[int32(alarm_category)],
+	)
+
+	alarm := &Alarm{
+		Severity:    alarm_severity,
+		Type:        alarm_type,
+		Category:    alarm_category,
+		State:       alarm_state,
+		TimeStamp:   alarm_ts,
+		Description: alarm_description,
+	}
+
+	return alarm
+}
+
+/*
+sendAlarm constructs and forwards the alarm to the network
+*/
+func (a *PonSimAlarm) sendAlarm(alarm *Alarm) {
+	// Ethernet layer is configured as a broadcast packet
+	ethLayer := &layers.Ethernet{
+		SrcMAC:       common.GetMacAddress(a.dstInterface),
+		DstMAC:       layers.EthernetBroadcast,
+		EthernetType: layers.EthernetTypeDot1Q,
+	}
+
+	// Need to encapsulate in VLAN so that voltha captures the packet
+	dot1qLayer := &layers.Dot1Q{
+		Type:           layers.EthernetTypeIPv4,
+		VLANIdentifier: vlandId,
+	}
+
+	common.Logger().WithFields(logrus.Fields{
+		"Alarm": a,
+		"srcIp": common.GetInterfaceIP(a.dstInterface),
+		"dstIp": common.GetHostIP(a.dstEndpoint),
+	}).Info("SRC/DST IP addresses")
+
+	// IP layer needs the following attributes at a minimum in order to have
+	// a properly formed packet
+	ipLayer := &layers.IPv4{
+		SrcIP: net.ParseIP(common.GetInterfaceIP(a.dstInterface)),
+		DstIP: net.ParseIP(common.GetHostIP(a.dstEndpoint)),
+		//SrcIP:    net.ParseIP(localhost),
+		//DstIP:    net.ParseIP(localhost),
+		Version:  ipVersion,
+		TTL:      ttl,
+		Protocol: layers.IPProtocolTCP,
+	}
+
+	// TCP layer does not require anything special
+	// except than providing the IP layer so that the checksum can be
+	// properly calculated
+	tcpLayer := &layers.TCP{}
+	tcpLayer.SetNetworkLayerForChecksum(ipLayer)
+
+	// Convert the alarm to bytes to include it as the packet payload
+	rawData, _ := json.Marshal(alarm)
+
+	// Construct the packet
+	buffer := gopacket.NewSerializeBuffer()
+	options := gopacket.SerializeOptions{
+		FixLengths:       true,
+		ComputeChecksums: true,
+	}
+	gopacket.SerializeLayers(buffer, options,
+		ethLayer,
+		dot1qLayer,
+		ipLayer,
+		tcpLayer,
+		gopacket.Payload(rawData),
+	)
+	frame := gopacket.NewPacket(
+		buffer.Bytes(),
+		layers.LayerTypeEthernet,
+		gopacket.Default,
+	)
+
+	// Forward the packetized alarm to the network
+	a.forwardFunction(0, frame)
+
+	common.Logger().WithFields(logrus.Fields{
+		"Alarm": alarm,
+		"Frame": frame.Dump(),
+	}).Debug("Sent alarm")
+}
+
+/*
+raiseAlarm submits an alarm object with a RAISED state
+*/
+func (a *PonSimAlarm) raiseAlarm(alarm *Alarm) {
+	alarm.State = int(voltha.AlarmEventState_RAISED)
+	a.sendAlarm(alarm)
+}
+
+/*
+clearAlarm submits an alarm object with a CLEARED state
+*/
+func (a *PonSimAlarm) clearAlarm(alarm *Alarm) {
+	alarm.State = int(voltha.AlarmEventState_CLEARED)
+	a.sendAlarm(alarm)
+}
+
+/*
+GenerateAlarm simulates RAISE and CLEAR alarm events with a random delay in between each state.
+*/
+func (a *PonSimAlarm) GenerateAlarm() {
+	alarm := a.prepareAlarm()
+	a.raiseAlarm(alarm)
+	time.Sleep(time.Duration(rand.Intn(maxInterval-minInterval)+minInterval) * time.Second)
+	a.clearAlarm(alarm)
+}
diff --git a/ponsim/v2/core/ponsim_api_type.go b/ponsim/v2/core/ponsim_api_type.go
new file mode 100644
index 0000000..f178c9b
--- /dev/null
+++ b/ponsim/v2/core/ponsim_api_type.go
@@ -0,0 +1,17 @@
+package core
+
+type PonSimApiType uint8
+
+const (
+	PONSIM PonSimApiType = iota
+	BAL
+)
+
+var enum_ponsim_api_types = []string{
+	"PONSIM",
+	"BAL",
+}
+
+func (t PonSimApiType) String() string {
+	return enum_ponsim_api_types[t]
+}
diff --git a/ponsim/v2/core/ponsim_device.go b/ponsim/v2/core/ponsim_device.go
new file mode 100644
index 0000000..c13d003
--- /dev/null
+++ b/ponsim/v2/core/ponsim_device.go
@@ -0,0 +1,796 @@
+package core
+
+import (
+	"context"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/google/gopacket/pcap"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/protos/go/openflow_13"
+	"github.com/sirupsen/logrus"
+	"net"
+	"sort"
+)
+
+// TODO: Pass-in the certificate information as a structure parameter
+// TODO: Add certification information
+
+type PonSimDevice struct {
+	Name        string               `json:name`
+	Port        int32                `json:port`
+	Address     string               `json:address`
+	ExternalIf  string               `json:external_if`
+	InternalIf  string               `json:internal_if`
+	Promiscuous bool                 `json:promiscuous`
+	SnapshotLen int32                `json:snapshot_len`
+	AlarmsOn    bool                 `json:alarm_on`
+	AlarmsFreq  int                  `json:alarm_freq`
+	Counter     *PonSimMetricCounter `json:counter`
+
+	//*grpc.GrpcSecurity
+
+	flows          []*openflow_13.OfpFlowStats `json:-`
+	ingressHandler *pcap.Handle                `json:-`
+	egressHandler  *pcap.Handle                `json:-`
+	links          map[int]map[int]interface{} `json:-`
+}
+
+const (
+	UDP_DST  = 1
+	UDP_SRC  = 2
+	IPV4_DST = 4
+	VLAN_PCP = 8
+	VLAN_VID = 16
+	IP_PROTO = 32
+	ETH_TYPE = 64
+	IN_PORT  = 128
+)
+
+/*
+Start performs common setup operations for a ponsim device
+*/
+func (o *PonSimDevice) Start(ctx context.Context) {
+}
+
+/*
+Stop performs common cleanup operations for a ponsim device
+*/
+func (o *PonSimDevice) Stop(ctx context.Context) {
+}
+
+/*
+GetAddress returns the IP/FQDN for the device
+*/
+func (o *PonSimDevice) GetAddress() string {
+	return o.Address
+}
+
+/*
+GetPort return the port assigned to the device
+*/
+func (o *PonSimDevice) GetPort() int32 {
+	return o.Port
+}
+
+/*
+Forward is responsible of processing incoming data, filtering it and redirecting to the
+intended destination
+*/
+func (o *PonSimDevice) Forward(
+	ctx context.Context,
+	port int,
+	frame gopacket.Packet,
+) error {
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+		"port":   port,
+		"frame":  frame,
+	}).Debug("Forwarding packet")
+
+	var err error
+
+	o.Counter.CountRxFrame(port, len(common.GetEthernetLayer(frame).Payload))
+
+	if egressPort, egressFrame := o.processFrame(ctx, port, frame); egressFrame != nil {
+		forwarded := 0
+		links := o.links[int(egressPort)]
+
+		o.Counter.CountTxFrame(int(egressPort), len(common.GetEthernetLayer(egressFrame).Payload))
+
+		for _, link := range links {
+			forwarded += 1
+
+			common.Logger().WithFields(logrus.Fields{
+				"device":      o,
+				"egressPort":  port,
+				"egressFrame": egressFrame,
+			}).Debug("Forwarding packet to link")
+
+			link.(func(int, gopacket.Packet))(int(egressPort), egressFrame)
+		}
+		if forwarded == 0 {
+			common.Logger().WithFields(logrus.Fields{
+				"device": o,
+				"port":   port,
+				"frame":  frame,
+			}).Warn("Nothing was forwarded")
+		}
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"device": o,
+			"port":   egressPort,
+			"frame":  egressFrame,
+		}).Error("Failed to properly process frame")
+	}
+
+	return err
+}
+
+/*
+connectNetworkInterfaces opens network interfaces for reading and/or writing packets
+*/
+func (o *PonSimDevice) connectNetworkInterfaces() {
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+	}).Debug("Opening network interfaces")
+
+	var err error
+	if o.ingressHandler, err = pcap.OpenLive(
+		o.ExternalIf, o.SnapshotLen, o.Promiscuous, pcap.BlockForever,
+	); err != nil {
+		common.Logger().WithFields(logrus.Fields{
+			"device":    o,
+			"interface": o.ExternalIf,
+			"error":     err.Error(),
+		}).Fatal("Unable to open Ingress interface")
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"device":    o,
+			"interface": o.ExternalIf,
+		}).Info("Opened Ingress interface")
+	}
+
+	if o.egressHandler, err = pcap.OpenLive(
+		o.InternalIf, o.SnapshotLen, o.Promiscuous, pcap.BlockForever,
+	); err != nil {
+		common.Logger().WithFields(logrus.Fields{
+			"device":    o,
+			"interface": o.InternalIf,
+			"error":     err.Error(),
+		}).Fatal("Unable to open egress interface")
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"device":    o,
+			"interface": o.InternalIf,
+		}).Info("Opened egress interface")
+	}
+}
+
+/*
+AddLink assigns a functional operation to a device endpoint
+
+The functional operation is called whenever a packet has been processed
+and the endpoint has been identified as the outgoing interface
+*/
+func (o *PonSimDevice) AddLink(
+	port int,
+	index int,
+	function interface{},
+) error {
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+		"port":   port,
+		"index":  index,
+	}).Debug("Linking port to functional operation")
+
+	if o.links == nil {
+		o.links = make(map[int]map[int]interface{})
+	}
+	if _, ok := o.links[port]; !ok {
+		o.links[port] = make(map[int]interface{})
+	}
+	o.links[port][index] = function
+
+	return nil
+}
+
+/*
+RemoveLink will remove reference a functional operation for a given port and index
+*/
+func (o *PonSimDevice) RemoveLink(
+	port int,
+	index int,
+) error {
+	if _, hasPort := o.links[port]; hasPort {
+		if _, hasIndex := o.links[port][index]; hasIndex {
+			common.Logger().WithFields(logrus.Fields{
+				"device": o,
+				"port":   port,
+				"index":  index,
+			}).Debug("Removing link functional operation")
+
+			delete(o.links[port], index)
+
+		} else {
+			common.Logger().WithFields(logrus.Fields{
+				"device": o,
+				"port":   port,
+				"index":  index,
+			}).Warn("No such index for link functional operation")
+
+		}
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"device": o,
+			"port":   port,
+			"index":  index,
+		}).Warn("No such port for functional operation")
+	}
+
+	return nil
+}
+
+/*
+InstallFlows assigns flows to the device in order of priority
+*/
+func (o *PonSimDevice) InstallFlows(
+	ctx context.Context,
+	flows []*openflow_13.OfpFlowStats,
+) error {
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+		"flows":  flows,
+	}).Debug("Installing flows")
+
+	o.flows = flows
+	sort.Sort(common.SortByPriority(o.flows))
+
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+	}).Debug("Installed sorted flows")
+
+	return nil
+}
+
+/*
+processFrame is responsible for matching or discarding a frame based on the configured flows
+*/
+func (o *PonSimDevice) processFrame(
+	ctx context.Context,
+	port int,
+	frame gopacket.Packet,
+) (uint32, gopacket.Packet) {
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+		"port":   port,
+		"frame":  frame,
+	}).Debug("Processing frame")
+
+	var err error
+	var matchedMask int = 0
+	var currentMask int
+	var highestPriority uint32 = 0
+	var matchedFlow *openflow_13.OfpFlowStats = nil
+
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+	}).Debug("Looping through flows")
+
+	for _, flow := range o.flows {
+		common.Logger().WithFields(logrus.Fields{
+			"device": o,
+			"flow":   flow,
+		}).Debug("Checking flow")
+
+		if matchedFlow != nil && flow.Priority < highestPriority {
+			common.Logger().WithFields(logrus.Fields{
+				"device":      o,
+				"matchedFlow": matchedFlow,
+				"priority":    highestPriority,
+			}).Debug("Flow has already been matched")
+			break
+		} else {
+			common.Logger().WithFields(logrus.Fields{
+				"device":          o,
+				"matchedFlow":     matchedFlow,
+				"priority":        flow.Priority,
+				"highestPriority": highestPriority,
+			}).Debug("Flow OR Priority requirements not met")
+		}
+
+		highestPriority = flow.Priority
+		if currentMask, err = o.isMatch(ctx, flow, port, frame); err != nil {
+			common.Logger().WithFields(logrus.Fields{
+				"device": o,
+				"flow":   flow,
+				"port":   port,
+				"frame":  frame,
+				"error":  err.Error(),
+			}).Error("Problem while matching flow")
+
+		} else if currentMask > matchedMask {
+			matchedMask = currentMask
+			matchedFlow = flow
+
+			common.Logger().WithFields(logrus.Fields{
+				"device":      o,
+				"matchedFlow": flow,
+				"port":        port,
+				"frame":       frame,
+				"matchedMask": matchedMask,
+			}).Debug("Flow matches")
+		}
+	}
+
+	if matchedFlow != nil {
+		egressPort, egressFrame := o.processActions(ctx, matchedFlow, frame)
+
+		common.Logger().WithFields(logrus.Fields{
+			"device":      o,
+			"port":        port,
+			"egressPort":  egressPort,
+			"egressFrame": egressFrame,
+		}).Debug("Processed actions to matched flow")
+
+		return egressPort, egressFrame
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"device":      o,
+			"port":        port,
+			"frame":       frame,
+			"matchedMask": matchedMask,
+		}).Warn("Flow was not successfully matched")
+	}
+
+	return 0, nil
+}
+
+/*
+isMatch traverses the criteria of a flow and identify all matching elements of a frame (if any)
+*/
+func (o *PonSimDevice) isMatch(
+	ctx context.Context,
+	flow *openflow_13.OfpFlowStats,
+	port int,
+	frame gopacket.Packet,
+) (int, error) {
+	matchedMask := 0
+
+	for _, ofbfield := range flow.Match.OxmFields {
+		if ofbfield.GetOxmClass() == openflow_13.OfpOxmClass_OFPXMC_OPENFLOW_BASIC {
+			switch ofbfield.GetOfbField().Type {
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_IN_PORT:
+				if ofbfield.GetOfbField().GetPort() != uint32(port) {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetPort(),
+						"actual":   port,
+					}).Warn("Port does not match")
+					return 0, nil
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetPort(),
+						"actual":   port,
+					}).Debug("Port matches")
+				}
+				matchedMask |= IN_PORT
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_ETH_TYPE:
+				cmpType := uint32(common.GetEthernetLayer(frame).EthernetType)
+				if dot1q := common.GetDot1QLayer(frame); dot1q != nil {
+					cmpType = uint32(dot1q.Type)
+				}
+				if ofbfield.GetOfbField().GetEthType() != cmpType {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": layers.EthernetType(ofbfield.GetOfbField().GetEthType()),
+						"actual":   common.GetEthernetLayer(frame).EthernetType,
+					}).Warn("Frame type does not match")
+					return 0, nil
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": layers.EthernetType(ofbfield.GetOfbField().GetEthType()),
+						"actual":   common.GetEthernetLayer(frame).EthernetType,
+					}).Debug("Frame type matches")
+				}
+				matchedMask |= ETH_TYPE
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_IP_PROTO:
+				if ofbfield.GetOfbField().GetIpProto() != uint32(common.GetIpLayer(frame).Protocol) {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetIpProto(),
+						"actual":   common.GetIpLayer(frame).Protocol,
+					}).Warn("IP protocol does not match")
+					return 0, nil
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetIpProto(),
+						"actual":   common.GetIpLayer(frame).Protocol,
+					}).Debug("IP protocol matches")
+				}
+				matchedMask |= IP_PROTO
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID:
+				expectedVlan := ofbfield.GetOfbField().GetVlanVid()
+				dot1q := common.GetDot1QLayer(frame)
+
+				if (expectedVlan&4096 == 0) != (dot1q == nil) {
+					common.Logger().WithFields(logrus.Fields{
+						"device":       o,
+						"flow":         flow,
+						"expectedVlan": expectedVlan,
+						"vlanBitwise":  expectedVlan & 4096,
+						"dot1q":        dot1q,
+					}).Warn("VLAN condition not met")
+					return 0, nil
+				}
+				if dot1q != nil {
+					if uint32(dot1q.VLANIdentifier) != (expectedVlan & 4095) {
+						common.Logger().WithFields(logrus.Fields{
+							"device":   o,
+							"flow":     flow,
+							"expected": expectedVlan,
+							"actual":   uint32(dot1q.VLANIdentifier),
+						}).Warn("VLAN VID does not match")
+						return 0, nil
+					} else {
+						common.Logger().WithFields(logrus.Fields{
+							"device":   o,
+							"flow":     flow,
+							"expected": expectedVlan,
+							"actual":   uint32(dot1q.VLANIdentifier),
+						}).Debug("VLAN VID matches")
+					}
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device": o,
+						"flow":   flow,
+					}).Warn("VLAN VID missing. Not dot1q encapsulation")
+				}
+				matchedMask |= VLAN_VID
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP:
+				if ofbfield.GetOfbField().GetVlanPcp() != uint32(common.GetDot1QLayer(frame).Priority) {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetVlanPcp(),
+						"actual":   uint32(common.GetDot1QLayer(frame).Priority),
+					}).Warn("VLAN priority does not match")
+					return 0, nil
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetVlanPcp(),
+						"actual":   uint32(common.GetDot1QLayer(frame).Priority),
+					}).Debug("VLAN priority matches")
+				}
+				matchedMask |= VLAN_PCP
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_DST:
+				dstIpRaw := ofbfield.GetOfbField().GetIpv4Dst()
+				dstIp := net.IPv4(
+					byte((dstIpRaw>>24)&0xFF),
+					byte((dstIpRaw>>16)&0xFF),
+					byte((dstIpRaw>>8)&0xFF),
+					byte(dstIpRaw&0xFF))
+
+				if !dstIp.Equal(common.GetIpLayer(frame).DstIP) {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": dstIp,
+						"actual":   common.GetIpLayer(frame).DstIP,
+					}).Warn("IPv4 destination does not match")
+					return 0, nil
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": dstIp,
+						"actual":   common.GetIpLayer(frame).DstIP,
+					}).Debug("IPv4 destination matches")
+
+				}
+				matchedMask |= IPV4_DST
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_UDP_SRC:
+				if ofbfield.GetOfbField().GetUdpSrc() != uint32(common.GetUdpLayer(frame).SrcPort) {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetUdpSrc(),
+						"actual":   common.GetUdpLayer(frame).SrcPort,
+					}).Warn("UDP source port does not match")
+					return 0, nil
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetUdpSrc(),
+						"actual":   common.GetUdpLayer(frame).SrcPort,
+					}).Debug("UDP source port matches")
+				}
+				matchedMask |= UDP_SRC
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_UDP_DST:
+				if ofbfield.GetOfbField().GetUdpDst() != uint32(common.GetUdpLayer(frame).DstPort) {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetUdpDst(),
+						"actual":   common.GetUdpLayer(frame).DstPort,
+					}).Warn("UDP destination port does not match")
+					return 0, nil
+				} else {
+					common.Logger().WithFields(logrus.Fields{
+						"device":   o,
+						"flow":     flow,
+						"expected": ofbfield.GetOfbField().GetUdpDst(),
+						"actual":   common.GetUdpLayer(frame).DstPort,
+					}).Debug("UDP destination port does matches")
+				}
+				matchedMask |= UDP_DST
+
+			case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_METADATA:
+				common.Logger().WithFields(logrus.Fields{
+					"device": o,
+					"flow":   flow,
+				}).Warn("Skipping metadata")
+				continue
+
+			default:
+				common.Logger().WithFields(logrus.Fields{
+					"device": o,
+					"flow":   flow,
+					"type":   ofbfield.GetOfbField().Type,
+				}).Warn("Field type not implemented")
+			}
+		}
+	}
+	return matchedMask, nil
+}
+
+/*
+processActions applies transformation instructions to a frame that met all the flow criteria
+*/
+func (o *PonSimDevice) processActions(
+	ctx context.Context,
+	flow *openflow_13.OfpFlowStats,
+	frame gopacket.Packet,
+) (uint32, gopacket.Packet) {
+	var egressPort uint32
+	var retFrame gopacket.Packet = frame
+
+	common.Logger().WithFields(logrus.Fields{
+		"device": o,
+		"flow":   flow,
+		"frame":  retFrame,
+	}).Info("Processing actions")
+
+	for _, instruction := range flow.Instructions {
+		common.Logger().WithFields(logrus.Fields{
+			"device":      o,
+			"flow":        flow,
+			"frame":       retFrame,
+			"instruction": instruction,
+		}).Debug("Processing actions - Instruction entry")
+		if instruction.Type == uint32(openflow_13.OfpInstructionType_OFPIT_APPLY_ACTIONS) {
+			for _, action := range instruction.GetActions().GetActions() {
+				common.Logger().WithFields(logrus.Fields{
+					"device":     o,
+					"flow":       flow,
+					"frame":      retFrame,
+					"action":     action,
+					"actionType": action.Type,
+				}).Debug("Processing actions - Action entry")
+
+				switch action.Type {
+				case openflow_13.OfpActionType_OFPAT_OUTPUT:
+					common.Logger().WithFields(logrus.Fields{
+						"device": o,
+						"flow":   flow,
+						"frame":  retFrame,
+					}).Debug("Processing action OFPAT output")
+					egressPort = action.GetOutput().Port
+
+				case openflow_13.OfpActionType_OFPAT_POP_VLAN:
+					common.Logger().WithFields(logrus.Fields{
+						"device": o,
+						"flow":   flow,
+						"frame":  retFrame,
+					}).Debug("Processing action OFPAT POP VLAN")
+					if shim := common.GetDot1QLayer(retFrame); shim != nil {
+						if eth := common.GetEthernetLayer(retFrame); eth != nil {
+							ethernetLayer := &layers.Ethernet{
+								SrcMAC:       eth.SrcMAC,
+								DstMAC:       eth.DstMAC,
+								EthernetType: shim.Type,
+							}
+							buffer := gopacket.NewSerializeBuffer()
+							gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{},
+								ethernetLayer,
+								gopacket.Payload(shim.Payload),
+							)
+							retFrame = gopacket.NewPacket(
+								buffer.Bytes(),
+								layers.LayerTypeEthernet,
+								gopacket.Default,
+							)
+						} else {
+							common.Logger().WithFields(logrus.Fields{
+								"device": o,
+								"flow":   flow,
+								"frame":  retFrame,
+							}).Warn("No ETH found while processing POP VLAN action")
+						}
+					} else {
+						common.Logger().WithFields(logrus.Fields{
+							"device": o,
+							"flow":   flow,
+							"frame":  retFrame,
+						}).Warn("No DOT1Q found while processing POP VLAN action")
+					}
+				case openflow_13.OfpActionType_OFPAT_PUSH_VLAN:
+					if eth := common.GetEthernetLayer(retFrame); eth != nil {
+						ethernetLayer := &layers.Ethernet{
+							SrcMAC:       eth.SrcMAC,
+							DstMAC:       eth.DstMAC,
+							EthernetType: layers.EthernetType(action.GetPush().GetEthertype()),
+						}
+						dot1qLayer := &layers.Dot1Q{
+							Type: eth.EthernetType,
+						}
+
+						buffer := gopacket.NewSerializeBuffer()
+						gopacket.SerializeLayers(
+							buffer,
+							gopacket.SerializeOptions{
+								FixLengths: false,
+							},
+							ethernetLayer,
+							dot1qLayer,
+							gopacket.Payload(eth.Payload),
+						)
+						retFrame = gopacket.NewPacket(
+							buffer.Bytes(),
+							layers.LayerTypeEthernet,
+							gopacket.Default,
+						)
+					} else {
+						common.Logger().WithFields(logrus.Fields{
+							"device": o,
+							"flow":   flow,
+							"frame":  retFrame,
+						}).Warn("No ETH found while processing PUSH VLAN action")
+					}
+				case openflow_13.OfpActionType_OFPAT_SET_FIELD:
+					common.Logger().WithFields(logrus.Fields{
+						"device": o,
+						"flow":   flow,
+						"frame":  retFrame,
+					}).Debug("Processing action OFPAT SET FIELD")
+					if action.GetSetField().GetField().GetOxmClass() ==
+						openflow_13.OfpOxmClass_OFPXMC_OPENFLOW_BASIC {
+						field := action.GetSetField().GetField().GetOfbField()
+
+						switch field.Type {
+						case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID:
+							common.Logger().WithFields(logrus.Fields{
+								"device": o,
+								"flow":   flow,
+								"frame":  retFrame,
+							}).Debug("Processing action OFPAT SET FIELD - VLAN VID")
+							if shim := common.GetDot1QLayer(retFrame); shim != nil {
+								eth := common.GetEthernetLayer(retFrame)
+								buffer := gopacket.NewSerializeBuffer()
+
+								var dot1qLayer *layers.Dot1Q
+								var ethernetLayer *layers.Ethernet
+								ethernetLayer = &layers.Ethernet{
+									SrcMAC:       eth.SrcMAC,
+									DstMAC:       eth.DstMAC,
+									EthernetType: eth.EthernetType,
+								}
+
+								dot1qLayer = &layers.Dot1Q{
+									Type:           shim.Type,
+									VLANIdentifier: uint16(field.GetVlanVid() & 4095),
+								}
+
+								gopacket.SerializeLayers(
+									buffer,
+									gopacket.SerializeOptions{},
+									ethernetLayer,
+									dot1qLayer,
+									gopacket.Payload(shim.LayerPayload()),
+								)
+								retFrame = gopacket.NewPacket(
+									buffer.Bytes(),
+									layers.LayerTypeEthernet,
+									gopacket.Default,
+								)
+
+								common.Logger().WithFields(logrus.Fields{
+									"device":    o,
+									"flow":      flow,
+									"frame":     retFrame,
+									"frameDump": retFrame.Dump(),
+									"vlanVid":   shim.VLANIdentifier,
+								}).Info("Setting DOT1Q VLAN VID")
+							} else {
+								common.Logger().WithFields(logrus.Fields{
+									"device": o,
+									"flow":   flow,
+									"frame":  retFrame,
+								}).Warn("No DOT1Q found while setting VLAN VID")
+							}
+
+						case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP:
+							common.Logger().WithFields(logrus.Fields{
+								"device": o,
+								"flow":   flow,
+								"frame":  retFrame,
+							}).Debug("Processing action OFPAT SET FIELD - VLAN PCP")
+							if shim := common.GetDot1QLayer(retFrame); shim != nil {
+								shim.Priority = uint8(field.GetVlanPcp())
+								common.Logger().WithFields(logrus.Fields{
+									"device":   o,
+									"flow":     flow,
+									"frame":    retFrame,
+									"priority": shim.Priority,
+								}).Info("Setting DOT1Q VLAN PCP")
+							} else {
+								common.Logger().WithFields(logrus.Fields{
+									"device": o,
+									"flow":   flow,
+									"frame":  retFrame,
+								}).Warn("No DOT1Q found while setting VLAN PCP")
+							}
+						default:
+							common.Logger().WithFields(logrus.Fields{
+								"device": o,
+								"flow":   flow,
+								"frame":  retFrame,
+								"type":   field.Type,
+							}).Warn("Set field not implemented for this type")
+						}
+					} else {
+						common.Logger().WithFields(logrus.Fields{
+							"device": o,
+							"flow":   flow,
+							"frame":  retFrame,
+						}).Warn("Field not of type OF-BASIC")
+					}
+				default:
+					common.Logger().WithFields(logrus.Fields{
+						"device": o,
+						"flow":   flow,
+						"frame":  retFrame,
+						"type":   action.Type,
+					}).Warn("Action type not implemented")
+				}
+			}
+		}
+	}
+
+	common.Logger().WithFields(logrus.Fields{
+		"device":     o,
+		"flow":       flow,
+		"egressPort": egressPort,
+		"retFrame":   retFrame,
+	}).Debug("Processed actions")
+
+	return egressPort, retFrame
+}
diff --git a/ponsim/v2/core/ponsim_device_state.go b/ponsim/v2/core/ponsim_device_state.go
new file mode 100644
index 0000000..a5805a2
--- /dev/null
+++ b/ponsim/v2/core/ponsim_device_state.go
@@ -0,0 +1,22 @@
+package core
+
+type PonSimDeviceState uint8
+
+const (
+	DISCONNECTED_FROM_PON PonSimDeviceState = iota
+	CONNECTED_TO_PON
+	REGISTERED_WITH_OLT
+	CONNECTED_IO_INTERFACE
+)
+
+// Execute state string equivalents
+var PonSimDeviceStateEnum = []string{
+	"DISCONNECTED_FROM_PON",
+	"CONNECTED_TO_PON",
+	"REGISTERED_WITH_OLT",
+	"CONNECTED_IO_INTERFACE",
+}
+
+func (s PonSimDeviceState) String() string {
+	return PonSimDeviceStateEnum[s]
+}
diff --git a/ponsim/v2/core/ponsim_device_type.go b/ponsim/v2/core/ponsim_device_type.go
new file mode 100644
index 0000000..306440c
--- /dev/null
+++ b/ponsim/v2/core/ponsim_device_type.go
@@ -0,0 +1,17 @@
+package core
+
+type PonSimDeviceType uint8
+
+const (
+	OLT PonSimDeviceType = iota
+	ONU
+)
+
+var enum_ponsim_device_types = []string{
+	"OLT",
+	"ONU",
+}
+
+func (t PonSimDeviceType) String() string {
+	return enum_ponsim_device_types[t]
+}
diff --git a/ponsim/v2/core/ponsim_interface.go b/ponsim/v2/core/ponsim_interface.go
new file mode 100644
index 0000000..d73d2ca
--- /dev/null
+++ b/ponsim/v2/core/ponsim_interface.go
@@ -0,0 +1,18 @@
+package core
+
+import (
+	"context"
+	"github.com/google/gopacket"
+)
+
+type PonSimInterface interface {
+	Start(context.Context)
+
+	Stop(context.Context)
+
+	GetAddress() string
+
+	GetPort() int32
+
+	Forward(context.Context, int, gopacket.Packet) error
+}
diff --git a/ponsim/v2/core/ponsim_metric.go b/ponsim/v2/core/ponsim_metric.go
new file mode 100644
index 0000000..d28c420
--- /dev/null
+++ b/ponsim/v2/core/ponsim_metric.go
@@ -0,0 +1,220 @@
+package core
+
+import (
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/protos/go/voltha"
+	"github.com/sirupsen/logrus"
+)
+
+/*
+metricCounter holds details for a specific metric
+*/
+type metricCounter struct {
+	Name  string
+	Value [2]int // [PON,NNI] values
+	Min   int
+	Max   int
+}
+
+/*
+Create a new MetricCounter instance for TX packets
+*/
+func newTxMetricCounter(name txMetricCounterType, min int, max int) *metricCounter {
+	return &metricCounter{Name: name.String(), Min: min, Max: max}
+}
+
+/*
+Create a new MetricCounter instance for RX packets
+*/
+func newRxMetricCounter(name rxMetricCounterType, min int, max int) *metricCounter {
+	return &metricCounter{Name: name.String(), Min: min, Max: max}
+}
+
+/*
+Define TX constants
+*/
+type txMetricCounterType uint8
+
+const (
+	tx_64_pkts txMetricCounterType = iota
+	tx_65_127_pkts
+	tx_128_255_pkts
+	tx_256_511_pkts
+	tx_512_1023_pkts
+	tx_1024_1518_pkts
+	tx_1519_9k_pkts
+)
+
+/*
+TX packet constants string equivalents
+*/
+var txMetricCounterEnum = []string{
+	"tx_64_pkts",
+	"tx_65_127_pkts",
+	"tx_128_255_pkts",
+	"tx_256_511_pkts",
+	"tx_512_1023_pkts",
+	"tx_1024_1518_pkts",
+	"tx_1519_9k_pkts",
+}
+
+func (t txMetricCounterType) String() string {
+	return txMetricCounterEnum[t]
+}
+
+/*
+Define RX constants
+*/
+type rxMetricCounterType uint8
+
+const (
+	rx_64_pkts rxMetricCounterType = iota
+	rx_65_127_pkts
+	rx_128_255_pkts
+	rx_256_511_pkts
+	rx_512_1023_pkts
+	rx_1024_1518_pkts
+	rx_1519_9k_pkts
+)
+
+/*
+RX packet constants string equivalents
+*/
+var rxMetricCounterEnum = []string{
+	"rx_64_pkts",
+	"rx_65_127_pkts",
+	"rx_128_255_pkts",
+	"rx_256_511_pkts",
+	"rx_512_1023_pkts",
+	"rx_1024_1518_pkts",
+	"rx_1519_9k_pkts",
+}
+
+func (t rxMetricCounterType) String() string {
+	return rxMetricCounterEnum[t]
+}
+
+/*
+
+ */
+type PonSimMetricCounter struct {
+	Name       string
+	TxCounters map[txMetricCounterType]*metricCounter
+	RxCounters map[rxMetricCounterType]*metricCounter
+}
+
+/*
+NewPonSimMetricCounter instantiates new metric counters for a PON device
+*/
+func NewPonSimMetricCounter(name string) *PonSimMetricCounter {
+	counter := &PonSimMetricCounter{Name: name}
+
+	counter.TxCounters = map[txMetricCounterType]*metricCounter{
+		tx_64_pkts:        newTxMetricCounter(tx_64_pkts, 1, 64),
+		tx_65_127_pkts:    newTxMetricCounter(tx_65_127_pkts, 65, 127),
+		tx_128_255_pkts:   newTxMetricCounter(tx_128_255_pkts, 128, 255),
+		tx_256_511_pkts:   newTxMetricCounter(tx_256_511_pkts, 256, 511),
+		tx_512_1023_pkts:  newTxMetricCounter(tx_512_1023_pkts, 512, 1023),
+		tx_1024_1518_pkts: newTxMetricCounter(tx_1024_1518_pkts, 1024, 1518),
+		tx_1519_9k_pkts:   newTxMetricCounter(tx_1519_9k_pkts, 1519, 9216),
+	}
+	counter.RxCounters = map[rxMetricCounterType]*metricCounter{
+		rx_64_pkts:        newRxMetricCounter(rx_64_pkts, 1, 64),
+		rx_65_127_pkts:    newRxMetricCounter(rx_65_127_pkts, 65, 127),
+		rx_128_255_pkts:   newRxMetricCounter(rx_128_255_pkts, 128, 255),
+		rx_256_511_pkts:   newRxMetricCounter(rx_256_511_pkts, 256, 511),
+		rx_512_1023_pkts:  newRxMetricCounter(rx_512_1023_pkts, 512, 1023),
+		rx_1024_1518_pkts: newRxMetricCounter(rx_1024_1518_pkts, 1024, 1518),
+		rx_1519_9k_pkts:   newRxMetricCounter(rx_1519_9k_pkts, 1519, 9216),
+	}
+
+	return counter
+}
+
+/*
+CountRxFrame increments the receive count for a specific packet size metric
+*/
+func (mc *PonSimMetricCounter) CountRxFrame(port int, size int) {
+	for k, v := range mc.RxCounters {
+		if size >= v.Min && size <= v.Max {
+			mc.RxCounters[k].Value[port-1] += 1
+		}
+	}
+}
+
+/*
+CountTxFrame increments the transmit count for a specific packet size metric
+*/
+func (mc *PonSimMetricCounter) CountTxFrame(port int, size int) {
+	for k, v := range mc.TxCounters {
+		if size >= v.Min && size <= v.Max {
+			mc.TxCounters[k].Value[port-1] += 1
+		}
+	}
+}
+
+/*
+LogCounts logs the current counts for all RX/TX packets
+*/
+func (mc *PonSimMetricCounter) LogCounts() {
+	common.Logger().WithFields(logrus.Fields{
+		"counters": mc.RxCounters,
+	}).Info("RX Metrics")
+	common.Logger().WithFields(logrus.Fields{
+		"counters": mc.TxCounters,
+	}).Info("TX Metrics")
+}
+
+/*
+MakeProto collects all RX/TX metrics with which it constructs a GRPC proto metrics structure
+*/
+func (mc *PonSimMetricCounter) MakeProto() *voltha.PonSimMetrics {
+	simMetrics := &voltha.PonSimMetrics{Device: mc.Name}
+	ponMetrics := &voltha.PonSimPortMetrics{PortName: "pon"}
+	nniMetrics := &voltha.PonSimPortMetrics{PortName: "nni"}
+
+	// Collect RX metrics
+	for _, c := range mc.RxCounters {
+		// PON values
+		ponMetrics.Packets = append(
+			ponMetrics.Packets,
+			&voltha.PonSimPacketCounter{
+				Name:  c.Name,
+				Value: int64(c.Value[0]),
+			},
+		)
+		// NNI values
+		nniMetrics.Packets = append(
+			nniMetrics.Packets,
+			&voltha.PonSimPacketCounter{
+				Name:  c.Name,
+				Value: int64(c.Value[1]),
+			},
+		)
+	}
+	// Collect TX metrics
+	for _, c := range mc.TxCounters {
+		// PON values
+		ponMetrics.Packets = append(
+			ponMetrics.Packets,
+			&voltha.PonSimPacketCounter{
+				Name:  c.Name,
+				Value: int64(c.Value[0]),
+			},
+		)
+		// NNI values
+		nniMetrics.Packets = append(
+			nniMetrics.Packets,
+			&voltha.PonSimPacketCounter{
+				Name:  c.Name,
+				Value: int64(c.Value[1]),
+			},
+		)
+	}
+
+	// Populate GRPC proto structure
+	simMetrics.Metrics = append(simMetrics.Metrics, ponMetrics)
+	simMetrics.Metrics = append(simMetrics.Metrics, nniMetrics)
+
+	return simMetrics
+}
diff --git a/ponsim/v2/core/ponsim_olt.go b/ponsim/v2/core/ponsim_olt.go
new file mode 100644
index 0000000..2485a67
--- /dev/null
+++ b/ponsim/v2/core/ponsim_olt.go
@@ -0,0 +1,403 @@
+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
+		}
+	}
+}
diff --git a/ponsim/v2/core/ponsim_onu.go b/ponsim/v2/core/ponsim_onu.go
new file mode 100644
index 0000000..f58d473
--- /dev/null
+++ b/ponsim/v2/core/ponsim_onu.go
@@ -0,0 +1,388 @@
+package core
+
+import (
+	"context"
+	"crypto/tls"
+	"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"
+	"google.golang.org/grpc/credentials"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+// 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
+	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,
+			}
+			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)),
+	}, ":")
+
+	// TODO: make it secure
+	// GRPC communication needs to be secured
+	ta := credentials.NewTLS(&tls.Config{
+		//Certificates:       []tls.Certificate{peerCert},
+		//RootCAs:            caCertPool,
+		InsecureSkipVerify: true,
+	})
+
+	if o.Conn, err = grpc.DialContext(
+		context.Background(), host, grpc.WithTransportCredentials(ta), 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
+}
diff --git a/ponsim/v2/core/xponsim_device.go b/ponsim/v2/core/xponsim_device.go
new file mode 100644
index 0000000..aa7d64a
--- /dev/null
+++ b/ponsim/v2/core/xponsim_device.go
@@ -0,0 +1,215 @@
+package core
+
+import (
+	"context"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/protos/go/bbf_fiber"
+	"github.com/opencord/voltha/protos/go/voltha"
+	"github.com/sirupsen/logrus"
+)
+
+type XPonSimDevice struct {
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) Start(ctx context.Context) {
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) Stop(ctx context.Context) {
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) CreateInterface(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("create-interface-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) UpdateInterface(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("update-interface-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) RemoveInterface(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("remove-interface-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) CreateTcont(ctx context.Context,
+	config *bbf_fiber.TcontsConfigData,
+	profile *bbf_fiber.TrafficDescriptorProfileData,
+) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":                                ctx,
+		"device":                                 d,
+		"tcont_config_data":                      config,
+		"traffic_descriptor_profile_config_data": profile,
+	}).Info("create-tcont-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) UpdateTcont(
+	ctx context.Context,
+	config *bbf_fiber.TcontsConfigData,
+	profile *bbf_fiber.TrafficDescriptorProfileData,
+) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":                                ctx,
+		"device":                                 d,
+		"tcont_config_data":                      config,
+		"traffic_descriptor_profile_config_data": profile,
+	}).Info("update-tcont-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) RemoveTcont(
+	ctx context.Context,
+	config *bbf_fiber.TcontsConfigData,
+	profile *bbf_fiber.TrafficDescriptorProfileData,
+) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":                                ctx,
+		"device":                                 d,
+		"tcont_config_data":                      config,
+		"traffic_descriptor_profile_config_data": profile,
+	}).Info("remove-tcont-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) CreateGemport(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("create-gemport-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) UpdateGemport(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("update-gemport-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) RemoveGemport(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("remove-gemport-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) CreateMulticastGemport(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("create-multicast-gemport-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) UpdateMulticastGemport(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("update-multicast-gemport-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) RemoveMulticastGemport(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("remove-multicast-gemport-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) CreateMulticastDistributionSet(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("create-multicast-distribution-set-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) UpdateMulticastDistributionSet(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("update-multicast-distribution-set-request")
+}
+
+/*
+
+ */
+func (d *XPonSimDevice) RemoveMulticastDistributionSet(ctx context.Context, config *voltha.InterfaceConfig) {
+	common.Logger().WithFields(logrus.Fields{
+		"context":        ctx,
+		"device":         d,
+		"interface_type": config.GetInterfaceType(),
+		"data":           config,
+	}).Info("remove-multicast-distribution-set-request")
+}