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/README.md b/ponsim/v2/README.md
new file mode 100644
index 0000000..fe50243
--- /dev/null
+++ b/ponsim/v2/README.md
@@ -0,0 +1,318 @@
+# 1. Overview
+
+The PON simulator was re-written for the purpose of easily integrating it in a cluster environment.
+
+Here are some differences with the legacy PONSIM implementation:
+* The OLT and ONU instances are deployed as independent entities.
+* Both OLT and ONU are scalable containers.
+* OLT-ONU and VOLTHA-OLT communication is done via GRPC
+
+# 2. Directory structure
+
+```
+./common - Contains utilities used within the project
+./core - Contains the main component for handling the OLT/ONU services
+./grpc - Contains the GRPC server implementation along with the necessary NBI and SBI handlers
+./misc - Contains scripts and required protobuf files
+```
+
+# 3. Requirements
+
+# Golang Installation
+
+If you plan on running the simulator locally, i.e. not in a container, you will need to first 
+install setup Golang on your system.  Install using existing packages for your operating system 
+or issue the following commands (Linux).
+
+```
+cd /tmp
+wget https://storage.googleapis.com/golang/go1.9.3.linux-amd64.tar.gz
+tar -C /usr/local -xzf /tmp/go1.9.3.linux-amd64.tar.gz
+rm -f /tmp/go1.9.3.linux-amd64.tar.gz
+mkdir ~/go
+```
+
+Edit your profile (e.g. .bashrc) and add the following configuration
+
+```
+export GOROOT=/usr/local/go
+export GOPATH=~/go
+export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
+```
+
+# 4. Build the PON simulator
+
+## Container
+
+The PON simulator container can be built by issuing the following command.
+
+```
+make ponsim
+```
+
+## Local
+
+In order to run the PON simulator as a standalone application, you need to do some manual setups
+
+### Protos
+
+The required protobuf files are built as part of the docker build process.  If you intend to run 
+the simulator in a non-containerized way, you will need to build the protobuf files manually.
+
+```
+mkdir -p ponsim/v2/protos
+cp voltha/protos/*.proto ponsim/v2/protos
+cp voltha/adapters/asfvolt16_olt/protos/*.proto ponsim/v2/protos
+cp ponsim/v2/misc/protos/*.proto ponsim/v2/protos
+
+sh ponsim/v2/misc/scripts/build_protos.sh ponsim/v2/protos
+
+```
+
+### PON simulator executable (optional)
+
+You can optionally build the PON simulator and make it available through your GOPATH.
+
+```
+go get -u github.com/opencord/voltha/ponsim/v2
+
+go build -o $GOPATH/bin/ponsim $GOPATH/src/github.com/opencord/voltha/ponsim/v2/ponsim.go
+``` 
+
+
+# 5. PON Simulator Usage
+
+```
+Usage of ./ponsim:
+  -alarm_freq int
+    	Frequency of simulated alarms (in seconds) (default 60)
+  -alarm_sim
+    	Enable generation of simulated alarms
+  -api_type string
+    	Type of API used to communicate with devices (PONSIM or BAL) (default "PONSIM")
+  -device_type string
+    	Type of device to simulate (OLT or ONU) (default "OLT")
+  -external_if string
+    	External Communication Interface for read/write network traffic (default "eth2")
+  -grpc_addr string
+    	Address used to establish GRPC server connection
+  -grpc_port int
+    	Port used to establish GRPC server connection (default 50060)
+  -internal_if string
+    	Internal Communication Interface for read/write network traffic (default "eth1")
+  -name string
+    	Name of the PON device (default "PON")
+  -no_banner
+    	Omit startup banner log lines
+  -onus int
+    	Number of ONUs to simulate (default 1)
+  -parent_addr string
+    	Address of OLT to connect to (default "olt")
+  -parent_port int
+    	Port of OLT to connect to (default 50060)
+  -promiscuous
+    	Enable promiscuous mode on network interfaces
+  -quiet
+    	Suppress debug and info logs
+  -vcore_endpoint string
+    	Voltha core endpoint address (default "vcore")
+  -verbose
+    	Enable verbose logging
+```
+
+# 6. Run in local mode (no container)
+
+## Create the necessary docker networks
+
+```
+docker network create -o "com.docker.network.bridge.name"="ponsim_wan" \
+    --subnet=172.31.31.0/24 ponsim_wan
+    
+docker network create -o "com.docker.network.bridge.name"="ponsim_internal" \
+    --subnet=172.32.32.0/24 ponsim_internal
+```
+
+Allow multicast traffic to flow through the ponsim_wan network
+
+```
+echo 8 > /sys/class/net/ponsim_wan/bridge/group_fwd_mask
+```
+
+## Start VOLTHA
+
+```
+docker-compose -f compose/docker-compose-system-test.yml up -d
+docker-compose -f compose/docker-compose-auth-test.yml -p auth up -d
+```
+
+## OLT
+
+```
+ponsim -device_type OLT \
+    -internal_if <network to voltha> \
+    -external_if <internal network> \
+    -vcore_endpoint <ip of vcore instance> \
+    -onus 10
+```
+
+Example:
+
+```
+# Run as root
+sudo su
+
+ponsim -device_type OLT \
+    -internal_if ponmgmt \
+    -external_if ponsim_internal \
+    -vcore_endpoint 172.30.30.3 \
+    -onus 10
+```
+
+
+## ONU
+
+```
+ponsim -device_type ONU \
+    -external_if <network to world> \
+    -internal_if <internal network> \
+    -grpc_port 50061 \
+    -parent_addr localhost
+```
+
+Example:
+
+```
+# Run as root
+sudo su
+
+ponsim -device_type ONU \
+    -external_if ponsim_wan \
+    -internal_if ponsim_internal \
+    -grpc_port 50061 \
+    -parent_addr localhost
+```
+
+## Create PONSIM adapter
+
+Log into the VOLTHA CLI and provision an OLT instance.
+
+```
+ssh -p 5022 voltha@localhost
+
+preprovision_olt -t ponsim_olt -H 172.17.0.1:50060
+enable
+```
+
+## RG
+
+Run the RG tester
+
+```
+docker run --net=ponsim_wan --rm --name RG -it cord/tester bash
+```
+
+Execute the EAPOL authentication
+
+```
+/sbin/wpa_supplicant -Dwired -ieth0 -c /etc/wpa_supplicant/wpa_supplicant.conf
+```
+
+
+# 7. Run in a Kubernetes cluster
+
+Note: The following instructions are just a reference and may be incomplete.
+
+
+## Install networking components
+
+Install the Weave network package
+
+```
+kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
+```
+
+Install the CNI Genie package (Required to support multiple network interfaces in a container)
+
+```
+kubectl apply -f https://raw.githubusercontent.com/Huawei-PaaS/CNI-Genie/master/conf/1.8/genie.yaml
+```
+
+Configure PON management network template (on each host).  
+
+```
+# Run as root
+sudo su
+
+cat <<EOF >> /etc/cni/net.d/10-pon0.conf
+{
+    "name": "pon0",
+    "type": "bridge",
+    "bridge": "pon0",
+    "isGateway": true,
+    "ipMask": true,
+    "ipam": {
+      "type": "host-local",
+      "subnet": "10.22.0.0/16",
+      "routes": [
+        { "dst": "0.0.0.0/0" }
+      ]
+    }
+}
+EOF
+```
+
+## Start Voltha Components
+
+```
+cd k8s
+
+kubectl create configmap freeradius-config --from-file data/clients.conf --from-file data/users
+
+kubectl apply -f consul.yml
+kubectl apply -f zookeeper.yml
+kubectl apply -f kafka.yml
+kubectl apply -f envoy_for_consul.yml
+kubectl apply -f vcore_for_consul.yml
+kubectl apply -f ofagent.yml
+kubectl apply -f vcli.yml
+kubectl apply -f onos.yml
+kubectl apply -f freeradius.yml
+```
+
+## Start PONSIM
+
+From the main directory, execute the following command:
+
+```
+cd k8s
+
+kubectl apply -f olt.yml
+
+# The ONU configuration will setup a bridge on the host to ensure communication with the RG
+kubectl apply -f onu.yml
+
+# Setup bridge to allow multicast traffic (must be done on each host running an ONU)
+echo 8 > /sys/class/net/pon0/bridge/group_fwd_mask
+```
+
+## Create PONSIM adapter
+
+```
+ssh -p 5022 voltha@<ip of cli>
+
+preprovision_olt -t ponsim_olt -H olt:50060
+enable
+```
+
+## Start RG
+
+```
+kubectl apply -f rg.yml
+
+# Enter the RG container
+kubectl exec <rg container id> -ti bash
+
+# Execute some test (e.g. EAPOL authentication)
+wpa_supplicant -i eth0 -Dwired -c /etc/wpa_supplicant/wpa_supplicant.conf
+
+```
diff --git a/ponsim/v2/common/flow_sort.go b/ponsim/v2/common/flow_sort.go
new file mode 100644
index 0000000..f62f7ca
--- /dev/null
+++ b/ponsim/v2/common/flow_sort.go
@@ -0,0 +1,17 @@
+package common
+
+import (
+	"github.com/opencord/voltha/protos/go/openflow_13"
+)
+
+type SortByPriority []*openflow_13.OfpFlowStats
+
+func (s SortByPriority) Len() int {
+	return len(s)
+}
+func (s SortByPriority) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+func (s SortByPriority) Less(i, j int) bool {
+	return s[i].Priority < s[j].Priority
+}
diff --git a/ponsim/v2/common/interval_handler.go b/ponsim/v2/common/interval_handler.go
new file mode 100644
index 0000000..973865e
--- /dev/null
+++ b/ponsim/v2/common/interval_handler.go
@@ -0,0 +1,164 @@
+package common
+
+import (
+	"github.com/sirupsen/logrus"
+	"sync"
+	"time"
+)
+
+/*
+ IntervalHandler is used to run a routine at regular intervals and provide all the necessary
+ utilities to manage the execution.
+*/
+
+type IntervalHandler struct {
+	// Interval period in between each execution (in seconds?)
+	Interval int
+	// function to execute after each interval
+	function func()
+	// Channel listening to execution events
+	execute chan _ExecutionState
+	// Channel listening for a termination event
+	terminate chan struct{}
+	// Current execution state of the handler
+	state _ExecutionState
+	wg    sync.WaitGroup
+}
+
+// Define execution state constants
+type _ExecutionState uint8
+
+const (
+	STARTED _ExecutionState = iota
+	STOPPED
+	PAUSED
+	RESUMED
+)
+
+// Execute state string equivalents
+var _ExecutionStateEnum = []string{
+	"STARTED",
+	"STOPPED",
+	"PAUSED",
+	"RESUMED",
+}
+
+func (s _ExecutionState) String() string {
+	return _ExecutionStateEnum[s]
+}
+
+/*
+NewIntervalHandler instantiates a new interval based function execution handler
+*/
+func NewIntervalHandler(interval int, function func()) *IntervalHandler {
+	handler := &IntervalHandler{
+		Interval: interval,
+		function: function,
+	}
+
+	handler.execute = make(chan _ExecutionState)
+	handler.terminate = make(chan struct{})
+	handler.state = STOPPED
+
+	return handler
+}
+
+/*
+_Execute is a routine running concurrently and listening to execution events
+*/
+func (h *IntervalHandler) _Execute() {
+	defer h.wg.Done()
+	for {
+		select {
+		case h.state = <-h.execute:
+			Logger().WithFields(logrus.Fields{
+				"handler": h,
+			}).Debug("Processing execution state")
+			switch h.state {
+			case STARTED:
+			case PAUSED:
+			case RESUMED:
+				h.state = STARTED
+			case STOPPED:
+				fallthrough
+			default:
+				h.terminate <- struct{}{}
+			}
+
+		case <-h.terminate:
+			return
+
+		default:
+			if h.state == STARTED {
+				h.function()
+				time.Sleep(time.Duration(h.Interval) * time.Second)
+			} else {
+				// TODO: replace hardcoded delay with a configurable parameter
+				time.Sleep(1 * time.Second)
+			}
+		}
+	}
+}
+
+/*
+Start initiates the interval based function execution
+*/
+func (h *IntervalHandler) Start() {
+	Logger().WithFields(logrus.Fields{
+		"handler": h,
+	}).Info("Starting interval handler")
+
+	if h.execute == nil {
+		return
+	}
+	if h.state == STOPPED {
+		go h._Execute()
+		h.execute <- STARTED
+	}
+}
+
+/*
+Pause interrupts the interval based function execution
+*/
+func (h *IntervalHandler) Pause() {
+	Logger().WithFields(logrus.Fields{
+		"handler": h,
+	}).Info("Pausing interval handler")
+
+	if h.execute == nil || h.state == STOPPED {
+		return
+	}
+	if h.state == STARTED {
+		h.execute <- PAUSED
+	}
+}
+
+/*
+Resume continues the interval based function execution
+*/
+func (h *IntervalHandler) Resume() {
+	Logger().WithFields(logrus.Fields{
+		"handler": h,
+	}).Info("Resuming interval handler")
+
+	if h.execute == nil || h.state == STOPPED {
+		return
+	}
+	if h.state == PAUSED {
+		h.execute <- RESUMED
+	}
+}
+
+/*
+Stop terminates the interval based function execution
+*/
+func (h *IntervalHandler) Stop() {
+	Logger().WithFields(logrus.Fields{
+		"handler": h,
+	}).Info("Stopping interval handler")
+
+	if h.execute == nil || h.state == STOPPED {
+		return
+	}
+	h.execute <- STOPPED
+}
diff --git a/ponsim/v2/common/interval_handler_test.go b/ponsim/v2/common/interval_handler_test.go
new file mode 100644
index 0000000..e1f060f
--- /dev/null
+++ b/ponsim/v2/common/interval_handler_test.go
@@ -0,0 +1,72 @@
+package common
+
+import (
+	"fmt"
+	"testing"
+	"time"
+)
+
+var (
+	handler   *IntervalHandler
+	iteration int = 0
+	interval  int = 2
+)
+
+func RepeatMessage() {
+	fmt.Printf("Ran the function %d times\n", iteration)
+	iteration += 1
+}
+
+func TestNewIntervalHandler(t *testing.T) {
+	handler = NewIntervalHandler(interval, RepeatMessage)
+
+	if handler.state != STOPPED {
+		t.Error("The handler should be in STOPPED state", handler.state)
+	}
+	if handler.Interval != interval {
+		t.Error("The handler interval doesn't match the configured value", handler.Interval)
+	}
+	if handler.function == nil {
+		t.Error("The handler does not have function configured function", handler.function)
+	}
+}
+
+func TestIntervalHandler_Start(t *testing.T) {
+	handler.Start()
+
+	time.Sleep(5 * time.Second)
+
+	if handler.state != STARTED {
+		t.Error("The handler should be in STARTED state", handler.state)
+	}
+}
+
+func TestIntervalHandler_Pause(t *testing.T) {
+	handler.Pause()
+
+	if handler.state != PAUSED {
+		t.Error("The handler should be in PAUSED state", handler.state)
+	}
+
+	time.Sleep(5 * time.Second)
+}
+
+func TestIntervalHandler_Resume(t *testing.T) {
+	handler.Resume()
+
+	time.Sleep(5 * time.Second)
+
+	if handler.state != STARTED {
+		t.Error("The handler should be in STARTED state", handler.state)
+	}
+}
+
+func TestIntervalHandler_Stop(t *testing.T) {
+	handler.Stop()
+
+	if handler.state != STOPPED {
+		t.Error("The handler should be in STOPPED state", handler.state)
+	}
+
+	time.Sleep(5 * time.Second)
+}
diff --git a/ponsim/v2/common/logger.go b/ponsim/v2/common/logger.go
new file mode 100644
index 0000000..7749dd2
--- /dev/null
+++ b/ponsim/v2/common/logger.go
@@ -0,0 +1,81 @@
+package common
+
+import (
+	"github.com/evalphobia/logrus_fluent"
+	"github.com/sirupsen/logrus"
+	"net"
+	"strconv"
+	"sync"
+)
+
+type logManager struct {
+	*logrus.Logger
+}
+
+// Singleton instance
+var mgrInstance *logManager
+var once sync.Once
+
+func (mgr *logManager) SetFluentd(fluentdHost string) {
+	var hook *logrus_fluent.FluentHook
+	var err error
+	var host string
+	var portStr string
+	var port int
+
+	if host, portStr, err = net.SplitHostPort(fluentdHost); err != nil {
+		mgr.WithFields(logrus.Fields{
+			"error": err.Error(),
+		}).Error("Failed to retrieve host/port information")
+		return
+	}
+
+	if port, err = strconv.Atoi(portStr); err != nil {
+		mgr.WithFields(logrus.Fields{
+			"error": err.Error(),
+		}).Error("Failed to convert port to integer")
+		return
+	}
+
+	if hook, err = logrus_fluent.NewWithConfig(
+		logrus_fluent.Config{
+			Host: host,
+			Port: port,
+		}); err != nil {
+		mgr.WithFields(logrus.Fields{
+			"error": err.Error(),
+		}).Error("Failed to enable Fluentd hook")
+		return
+	}
+
+	hook.SetTag("ponsim")
+
+	hook.SetLevels([]logrus.Level{
+		logrus.DebugLevel,
+	})
+
+	mgr.AddHook(hook)
+
+	mgr.WithFields(logrus.Fields{
+		"hook": hook,
+	}).Info("Added fluentd hook")
+}
+
+/**
+ * Get instance
+ *
+ * It should get initialized only once
+ */
+func Logger() *logManager {
+	once.Do(func() {
+
+		_logger := logrus.New()
+		_logger.Formatter = new(logrus.JSONFormatter)
+		_logger.Level = logrus.DebugLevel
+		//_logger.Out =
+
+		mgrInstance = &logManager{_logger}
+	})
+
+	return mgrInstance
+}
diff --git a/ponsim/v2/common/net_utils.go b/ponsim/v2/common/net_utils.go
new file mode 100644
index 0000000..c37c4ef
--- /dev/null
+++ b/ponsim/v2/common/net_utils.go
@@ -0,0 +1,107 @@
+package common
+
+import (
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/sirupsen/logrus"
+	"net"
+)
+
+func GetInterfaceIP(ifName string) string {
+	var err error
+	var netIf *net.Interface
+	var netAddrs []net.Addr
+	var netIp net.IP
+	var ipAddr string
+
+	if netIf, err = net.InterfaceByName(ifName); err == nil {
+		if netAddrs, err = netIf.Addrs(); err == nil {
+			for _, addr := range netAddrs {
+				Logger().WithFields(logrus.Fields{
+					"type": addr.Network(),
+				}).Debug("Address network type")
+				switch v := addr.(type) {
+				case *net.IPNet:
+					netIp = v.IP
+				case *net.IPAddr:
+					netIp = v.IP
+				}
+				if netIp == nil || netIp.IsLoopback() {
+					continue
+				}
+				netIp = netIp.To4()
+				if netIp == nil {
+					continue // not an ipv4 address
+				}
+				ipAddr = netIp.String()
+				break
+			}
+		}
+	}
+
+	return ipAddr
+}
+func GetHostIP(hostName string) string {
+	var err error
+	var ipAddrs []string
+	var ipAddr string
+
+	if ipAddrs, err = net.LookupHost(hostName); err == nil {
+		for _, ip := range ipAddrs {
+			if addr := net.ParseIP(ip); err == nil {
+				Logger().WithFields(logrus.Fields{
+					"ip": addr,
+				}).Debug("Host address")
+				if addr == nil /*|| addr.IsLoopback()*/ {
+					continue
+				}
+				ipAddr = ip
+				break
+			}
+		}
+	}
+
+	return ipAddr
+}
+func GetMacAddress(ifName string) net.HardwareAddr {
+	var err error
+	var netIf *net.Interface
+	var hwAddr net.HardwareAddr
+
+	if netIf, err = net.InterfaceByName(ifName); err == nil {
+		hwAddr = netIf.HardwareAddr
+	}
+
+	return hwAddr
+}
+
+func GetEthernetLayer(frame gopacket.Packet) *layers.Ethernet {
+	eth := &layers.Ethernet{}
+	if ethLayer := frame.Layer(layers.LayerTypeEthernet); ethLayer != nil {
+		eth, _ = ethLayer.(*layers.Ethernet)
+	}
+	return eth
+}
+func GetDot1QLayer(frame gopacket.Packet) *layers.Dot1Q {
+	var dot1q *layers.Dot1Q
+	//dot1q := &layers.Dot1Q{}
+	if dot1qLayer := frame.Layer(layers.LayerTypeDot1Q); dot1qLayer != nil {
+		dot1q, _ = dot1qLayer.(*layers.Dot1Q)
+	}
+	return dot1q
+}
+
+func GetIpLayer(frame gopacket.Packet) *layers.IPv4 {
+	ip := &layers.IPv4{}
+	if ipLayer := frame.Layer(layers.LayerTypeIPv4); ipLayer != nil {
+		ip, _ = ipLayer.(*layers.IPv4)
+	}
+	return ip
+}
+func GetUdpLayer(frame gopacket.Packet) *layers.UDP {
+	udp := &layers.UDP{}
+	if udpLayer := frame.Layer(layers.LayerTypeUDP); udpLayer != nil {
+		udp, _ = udpLayer.(*layers.UDP)
+	}
+	return udp
+}
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")
+}
diff --git a/ponsim/v2/grpc/grpc_security.go b/ponsim/v2/grpc/grpc_security.go
new file mode 100644
index 0000000..6dab468
--- /dev/null
+++ b/ponsim/v2/grpc/grpc_security.go
@@ -0,0 +1,7 @@
+package grpc
+
+type GrpcSecurity struct {
+	KeyFile  string
+	CertFile string
+	CaFile   string
+}
diff --git a/ponsim/v2/grpc/grpc_server.go b/ponsim/v2/grpc/grpc_server.go
new file mode 100644
index 0000000..aaa8a4d
--- /dev/null
+++ b/ponsim/v2/grpc/grpc_server.go
@@ -0,0 +1,162 @@
+package grpc
+
+import (
+	"net"
+
+	"context"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/ponsim/v2/core"
+	"github.com/opencord/voltha/ponsim/v2/grpc/nbi"
+	"github.com/opencord/voltha/ponsim/v2/grpc/sbi"
+	"github.com/opencord/voltha/protos/go/bal"
+	"github.com/opencord/voltha/protos/go/ponsim"
+	"github.com/opencord/voltha/protos/go/voltha"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"strconv"
+	"strings"
+)
+
+type GrpcServer struct {
+	gs       *grpc.Server
+	address  string
+	port     int32
+	secure   bool
+	services []func(*grpc.Server)
+
+	*GrpcSecurity
+}
+
+/*
+Instantiate a GRPC server data structure
+*/
+func NewGrpcServer(
+	address string,
+	port int32,
+	certs *GrpcSecurity,
+	secure bool,
+) *GrpcServer {
+	server := &GrpcServer{
+		address:      address,
+		port:         port,
+		secure:       secure,
+		GrpcSecurity: certs,
+	}
+	return server
+}
+
+/*
+Start prepares the GRPC server and starts servicing requests
+*/
+func (s *GrpcServer) Start(ctx context.Context) {
+	host := strings.Join([]string{
+		s.address,
+		strconv.Itoa(int(s.port)),
+	}, ":")
+
+	lis, err := net.Listen("tcp", host)
+	if err != nil {
+		common.Logger().Fatalf("failed to listen: %v", err)
+	}
+
+	if s.secure {
+		creds, err := credentials.NewServerTLSFromFile(s.CertFile, s.KeyFile)
+		if err != nil {
+			common.Logger().Fatalf("could not load TLS keys: %s", err)
+		}
+		s.gs = grpc.NewServer(grpc.Creds(creds))
+
+	} else {
+		common.Logger().Println("In DEFAULT\n")
+		s.gs = grpc.NewServer()
+	}
+
+	// Register all required services
+	for _, service := range s.services {
+		service(s.gs)
+	}
+
+	if err := s.gs.Serve(lis); err != nil {
+		common.Logger().Fatalf("failed to serve: %v\n", err)
+	}
+}
+
+/*
+Stop servicing GRPC requests
+*/
+func (s *GrpcServer) Stop() {
+	s.gs.Stop()
+}
+
+/*
+AddService appends a generic service request function
+*/
+func (s *GrpcServer) AddService(
+	registerFunction func(*grpc.Server, interface{}),
+	handler interface{},
+) {
+	s.services = append(s.services, func(gs *grpc.Server) { registerFunction(gs, handler) })
+}
+
+/*
+AddPonSimService appends service request functions for PonSim devices
+*/
+func (s *GrpcServer) AddPonSimService(device core.PonSimInterface) {
+	s.services = append(
+		s.services,
+		func(gs *grpc.Server) {
+			voltha.RegisterPonSimServer(gs, nbi.NewPonSimHandler(device))
+		},
+	)
+}
+
+/*
+AddCommonService appends service request functions common to all PonSim devices
+*/
+func (s *GrpcServer) AddCommonService(device core.PonSimInterface) {
+	s.services = append(
+		s.services,
+		func(gs *grpc.Server) {
+			ponsim.RegisterPonSimCommonServer(gs, sbi.NewPonSimCommonHandler(device))
+		},
+	)
+}
+
+/*
+AddOltService appends service request functions specific to OLT devices
+*/
+func (s *GrpcServer) AddOltService(device core.PonSimInterface) {
+	s.services = append(
+		s.services,
+		func(gs *grpc.Server) {
+			ponsim.RegisterPonSimOltServer(
+				gs,
+				sbi.NewPonSimOltHandler(device.(*core.PonSimOltDevice)),
+			)
+		},
+	)
+}
+
+/*
+AddXPonService appends service request functions specific to XPonSim
+*/
+func (s *GrpcServer) AddXPonService() {
+	s.services = append(
+		s.services,
+		func(gs *grpc.Server) {
+			voltha.RegisterXPonSimServer(gs, nbi.NewXPonSimHandler())
+		},
+	)
+}
+
+/*
+AddBalService appends service request functions specific to BAL
+*/
+func (s *GrpcServer) AddBalService() {
+	s.services = append(
+		s.services,
+		func(gs *grpc.Server) {
+			bal.RegisterBalServer(gs, nbi.NewBalHandler())
+		},
+	)
+}
diff --git a/ponsim/v2/grpc/nbi/bal_handler.go b/ponsim/v2/grpc/nbi/bal_handler.go
new file mode 100644
index 0000000..5cbafb3
--- /dev/null
+++ b/ponsim/v2/grpc/nbi/bal_handler.go
@@ -0,0 +1,82 @@
+package nbi
+
+import (
+	"context"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/protos/go/bal"
+)
+
+// TODO: fix BAL function parameters and returns
+
+type BalHandler struct {
+}
+
+func NewBalHandler() *BalHandler {
+	var handler *BalHandler
+	handler = &BalHandler{}
+	return handler
+}
+
+func (handler *BalHandler) BalApiInit(
+	ctx context.Context,
+	request *bal.BalInit,
+) (*bal.BalErr, error) {
+	common.Logger().Info("BalApiInit Called", ctx, request)
+	return &bal.BalErr{Err: bal.BalErrno_BAL_ERR_OK}, nil
+}
+
+func (handler *BalHandler) BalApiFinish(
+	ctx context.Context,
+	request *bal.BalCfg,
+) (*bal.BalErr, error) {
+	common.Logger().Info("BalApiFinish Called", ctx, request)
+	return &bal.BalErr{Err: bal.BalErrno_BAL_ERR_OK}, nil
+}
+
+func (handler *BalHandler) BalCfgSet(
+	ctx context.Context,
+	request *bal.BalCfg,
+) (*bal.BalErr, error) {
+	common.Logger().Info("BalCfgSet Called", ctx, request)
+	return &bal.BalErr{Err: bal.BalErrno_BAL_ERR_OK}, nil
+}
+
+func (handler *BalHandler) BalCfgClear(
+	ctx context.Context,
+	request *bal.BalKey,
+) (*bal.BalErr, error) {
+	common.Logger().Info("BalCfgClear Called", ctx, request)
+	return &bal.BalErr{Err: bal.BalErrno_BAL_ERR_OK}, nil
+}
+
+func (handler *BalHandler) BalCfgGet(
+	ctx context.Context,
+	request *bal.BalKey,
+) (*bal.BalCfg, error) {
+	common.Logger().Info("BalCfgGet Called", ctx, request)
+	return &bal.BalCfg{}, nil
+}
+
+func (handler *BalHandler) BalApiReboot(
+	ctx context.Context,
+	request *bal.BalReboot,
+) (*bal.BalErr, error) {
+	common.Logger().Info("BalApiReboot Called", ctx, request)
+	return &bal.BalErr{Err: bal.BalErrno_BAL_ERR_OK}, nil
+}
+
+func (handler *BalHandler) BalApiHeartbeat(
+	ctx context.Context,
+	request *bal.BalHeartbeat,
+) (*bal.BalRebootState, error) {
+	common.Logger().Info("BalApiHeartbeat Called", ctx, request)
+	return &bal.BalRebootState{}, nil
+}
+
+func (handler *BalHandler) BalCfgStatGet(
+	ctx context.Context,
+	request *bal.BalInterfaceKey,
+) (*bal.BalInterfaceStat, error) {
+	common.Logger().Info("BalCfgStatGet Called", ctx, request)
+	return &bal.BalInterfaceStat{}, nil
+}
diff --git a/ponsim/v2/grpc/nbi/ponsim_handler.go b/ponsim/v2/grpc/nbi/ponsim_handler.go
new file mode 100644
index 0000000..763f4bc
--- /dev/null
+++ b/ponsim/v2/grpc/nbi/ponsim_handler.go
@@ -0,0 +1,327 @@
+package nbi
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/ponsim/v2/core"
+	"github.com/opencord/voltha/protos/go/voltha"
+	"github.com/sirupsen/logrus"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"strconv"
+	"strings"
+)
+
+// TODO: Cleanup GRPC security config
+// TODO: Pass-in the certificate information as a structure parameter
+
+type PonSimHandler struct {
+	device core.PonSimInterface
+}
+
+/*
+NewPonSimHandler instantiates a handler for a PonSim device
+*/
+func NewPonSimHandler(device core.PonSimInterface) *PonSimHandler {
+	var handler *PonSimHandler
+	handler = &PonSimHandler{device: device}
+	return handler
+}
+
+/*
+SendFrame handles and forwards EGRESS packets (i.e. VOLTHA to OLT)
+*/
+func (handler *PonSimHandler) SendFrame(ctx context.Context, data *voltha.PonSimFrame) (*empty.Empty, error) {
+	frame := gopacket.NewPacket(data.Payload, layers.LayerTypeEthernet, gopacket.Default)
+
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+		"frame":   frame.Dump(),
+	}).Info("Constructed frame")
+
+	handler.device.Forward(context.Background(), 2, frame)
+
+	out := new(empty.Empty)
+	return out, nil
+}
+
+/*
+ReceiveFrames handles a stream of INGRESS packets (i.e. OLT to VOLTHA)
+*/
+func (handler *PonSimHandler) ReceiveFrames(empty *empty.Empty, stream voltha.PonSim_ReceiveFramesServer) error {
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+	}).Info("start-receiving-frames")
+
+	if _, ok := (handler.device).(*core.PonSimOltDevice); ok {
+		var data []byte
+		var ok bool
+
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+			"device":  (handler.device).(*core.PonSimOltDevice),
+		}).Info("receiving-frames-from-olt-device")
+
+		for {
+			select {
+			case data, ok = <-(handler.device).(*core.PonSimOltDevice).GetOutgoing():
+				if ok {
+					frame := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default)
+					common.Logger().WithFields(logrus.Fields{
+						"handler": handler,
+						"frame":   frame,
+					}).Info("Received incoming data")
+
+					frameBytes := &voltha.PonSimFrame{Id: handler.device.GetAddress(), Payload: data}
+					if err := stream.Send(frameBytes); err != nil {
+						common.Logger().WithFields(logrus.Fields{
+							"handler": handler,
+							"frame":   frame,
+							"error":   err,
+						}).Error("Failed to send incoming data")
+						return err
+					}
+					common.Logger().WithFields(logrus.Fields{
+						"handler": handler,
+						"frame":   frame,
+					}).Info("Sent incoming data")
+
+				} else {
+					return errors.New("incoming data channel has closed")
+				}
+			}
+		}
+
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+		}).Error("Not handling an OLT device")
+	}
+
+	return nil
+}
+
+/*
+GetDeviceInfo returns information of a PonSim device (OLT or ONU)
+*/
+func (handler *PonSimHandler) GetDeviceInfo(
+	ctx context.Context,
+	empty *empty.Empty,
+) (*voltha.PonSimDeviceInfo, error) {
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+	}).Info("Getting device information")
+
+	var out *voltha.PonSimDeviceInfo
+
+	// Check which device type we're currently handling
+	if _, ok := (handler.device).(*core.PonSimOltDevice); ok {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+		}).Debug("Handling OLT device")
+		keys := make([]int32, 0, len((handler.device).(*core.PonSimOltDevice).GetOnus()))
+		for k := range (handler.device).(*core.PonSimOltDevice).GetOnus() {
+			keys = append(keys, k)
+		}
+		out = &voltha.PonSimDeviceInfo{NniPort: 0, UniPorts: []int32(keys)}
+
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+		}).Debug("Handling ONU/OTHER device")
+
+		out = &voltha.PonSimDeviceInfo{}
+	}
+
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+		"result":  out,
+	}).Info("Device information")
+
+	return out, nil
+}
+
+/*
+UpdateFlowTable populates and cleans up the flows for a PonSim device
+*/
+func (handler *PonSimHandler) UpdateFlowTable(
+	ctx context.Context,
+	table *voltha.FlowTable,
+) (*empty.Empty, error) {
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+		"table":   table,
+	}).Info("Updating flows")
+
+	if _, ok := (handler.device).(*core.PonSimOltDevice); ok {
+		if table.Port == 0 {
+			common.Logger().WithFields(logrus.Fields{
+				"handler": handler,
+				"port":    table.Port,
+			}).Debug("Updating OLT flows")
+
+			if err := (handler.device).(*core.PonSimOltDevice).InstallFlows(ctx, table.Flows); err != nil {
+				common.Logger().WithFields(logrus.Fields{
+					"handler": handler,
+					"error":   err.Error(),
+					"flows":   table.Flows,
+				}).Error("Problem updating flows on OLT")
+			} else {
+				common.Logger().WithFields(logrus.Fields{
+					"handler": handler,
+				}).Debug("Updated OLT flows")
+			}
+
+		} else {
+			common.Logger().WithFields(logrus.Fields{
+				"handler": handler,
+				"port":    table.Port,
+			}).Debug("Updating ONU flows")
+
+			if child, ok := (handler.device).(*core.PonSimOltDevice).GetOnus()[table.Port]; ok {
+				// TODO: make it secure
+				ta := credentials.NewTLS(&tls.Config{
+					InsecureSkipVerify: true,
+				})
+
+				host := strings.Join([]string{
+					child.Device.Address,
+					strconv.Itoa(int(child.Device.Port)),
+				}, ":")
+
+				conn, err := grpc.Dial(
+					host,
+					grpc.WithTransportCredentials(ta),
+				)
+				if err != nil {
+					common.Logger().WithFields(logrus.Fields{
+						"handler": handler,
+						"error":   err.Error(),
+					}).Error("GRPC Connection problem")
+				}
+				defer conn.Close()
+				client := voltha.NewPonSimClient(conn)
+
+				if _, err = client.UpdateFlowTable(ctx, table); err != nil {
+					common.Logger().WithFields(logrus.Fields{
+						"handler": handler,
+						"host":    host,
+						"error":   err.Error(),
+					}).Error("Problem forwarding update request to ONU")
+				}
+			} else {
+				common.Logger().WithFields(logrus.Fields{
+					"handler": handler,
+					"port":    table.Port,
+				}).Warn("Unable to find ONU")
+			}
+
+		}
+	} else if _, ok := (handler.device).(*core.PonSimOnuDevice); ok {
+		if err := (handler.device).(*core.PonSimOnuDevice).InstallFlows(ctx, table.Flows); err != nil {
+			common.Logger().WithFields(logrus.Fields{
+				"handler": handler,
+				"error":   err.Error(),
+				"flows":   table.Flows,
+			}).Error("Problem updating flows on ONU")
+		} else {
+			common.Logger().WithFields(logrus.Fields{
+				"handler": handler,
+			}).Debug("Updated ONU flows")
+		}
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+			"port":    table.Port,
+		}).Warn("Unknown device")
+	}
+
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+		"table":   table,
+	}).Info("Updated flows")
+
+	out := new(empty.Empty)
+	return out, nil
+}
+
+/*
+GetStats retrieves statistics for a PonSim device
+*/
+func (handler *PonSimHandler) GetStats(
+	ctx context.Context,
+	empty *empty.Empty,
+) (*voltha.PonSimMetrics, error) {
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+	}).Info("Retrieving stats")
+
+	var metrics *voltha.PonSimMetrics = new(voltha.PonSimMetrics)
+
+	if olt, ok := (handler.device).(*core.PonSimOltDevice); ok {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+			"olt":     olt,
+		}).Debug("Retrieving stats for OLT")
+
+		// Get stats for current device
+
+		// Loop through each onus to get stats from those as well?
+		// send grpc request to each onu
+		for _, child := range (handler.device).(*core.PonSimOltDevice).GetOnus() {
+			// TODO: make it secure
+			ta := credentials.NewTLS(&tls.Config{
+				InsecureSkipVerify: true,
+			})
+
+			host := strings.Join([]string{child.Device.Address, strconv.Itoa(int(child.Device.Port))}, ":")
+			conn, err := grpc.Dial(
+				host,
+				grpc.WithTransportCredentials(ta),
+			)
+			if err != nil {
+				common.Logger().WithFields(logrus.Fields{
+					"handler": handler,
+					"error":   err.Error(),
+				}).Error("GRPC Connection problem")
+			}
+			defer conn.Close()
+			client := voltha.NewPonSimClient(conn)
+
+			if _, err = client.GetStats(ctx, empty); err != nil {
+				common.Logger().WithFields(logrus.Fields{
+					"handler": handler,
+					"host":    host,
+					"error":   err.Error(),
+				}).Error("Problem forwarding stats request to ONU")
+			}
+		}
+		metrics = (handler.device).(*core.PonSimOltDevice).Counter.MakeProto()
+
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+			"metrics": metrics,
+		}).Debug("OLT Metrics")
+
+	} else if onu, ok := (handler.device).(*core.PonSimOnuDevice); ok {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+			"onu":     onu,
+		}).Debug("Retrieving stats for ONU")
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": handler,
+		}).Warn("Unknown device")
+	}
+
+	common.Logger().WithFields(logrus.Fields{
+		"handler": handler,
+	}).Info("Retrieved stats")
+
+	return metrics, nil
+}
diff --git a/ponsim/v2/grpc/nbi/xponsim_handler.go b/ponsim/v2/grpc/nbi/xponsim_handler.go
new file mode 100644
index 0000000..fbc63eb
--- /dev/null
+++ b/ponsim/v2/grpc/nbi/xponsim_handler.go
@@ -0,0 +1,124 @@
+package nbi
+
+import (
+	"context"
+	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/opencord/voltha/ponsim/v2/core"
+	"github.com/opencord/voltha/protos/go/voltha"
+)
+
+type XPonSimHandler struct {
+	device *core.XPonSimDevice
+}
+
+func NewXPonSimHandler() *XPonSimHandler {
+	var handler *XPonSimHandler
+	handler = &XPonSimHandler{}
+	return handler
+}
+
+func (handler *XPonSimHandler) CreateInterface(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.CreateInterface(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) UpdateInterface(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.UpdateInterface(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) RemoveInterface(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.RemoveInterface(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) CreateTcont(
+	ctx context.Context,
+	config *voltha.TcontInterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.CreateTcont(ctx, config.TcontsConfigData, config.TrafficDescriptorProfileConfigData)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) UpdateTcont(
+	ctx context.Context,
+	config *voltha.TcontInterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.UpdateTcont(ctx, config.TcontsConfigData, config.TrafficDescriptorProfileConfigData)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) RemoveTcont(
+	ctx context.Context,
+	config *voltha.TcontInterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.RemoveTcont(ctx, config.TcontsConfigData, config.TrafficDescriptorProfileConfigData)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) CreateGemport(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.CreateGemport(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) UpdateGemport(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.UpdateGemport(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) RemoveGemport(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.RemoveGemport(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) CreateMulticastGemport(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.CreateMulticastGemport(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) UpdateMulticastGemport(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.UpdateMulticastGemport(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) RemoveMulticastGemport(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.RemoveMulticastGemport(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) CreateMulticastDistributionSet(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.CreateMulticastDistributionSet(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) UpdateMulticastDistributionSet(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.UpdateMulticastDistributionSet(ctx, config)
+	return &empty.Empty{}, nil
+}
+func (handler *XPonSimHandler) RemoveMulticastDistributionSet(
+	ctx context.Context,
+	config *voltha.InterfaceConfig,
+) (*empty.Empty, error) {
+	handler.device.RemoveMulticastDistributionSet(ctx, config)
+	return &empty.Empty{}, nil
+}
diff --git a/ponsim/v2/grpc/sbi/common_handler.go b/ponsim/v2/grpc/sbi/common_handler.go
new file mode 100644
index 0000000..4c59af6
--- /dev/null
+++ b/ponsim/v2/grpc/sbi/common_handler.go
@@ -0,0 +1,72 @@
+package sbi
+
+import (
+	"context"
+	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/ponsim/v2/core"
+	"github.com/opencord/voltha/protos/go/ponsim"
+	"github.com/sirupsen/logrus"
+	"io"
+)
+
+type PonSimCommonHandler struct {
+	device core.PonSimInterface
+}
+
+/*
+NewPonSimCommonHandler instantiates a handler for common GRPC servicing methods
+*/
+func NewPonSimCommonHandler(device core.PonSimInterface) *PonSimCommonHandler {
+	var handler *PonSimCommonHandler
+
+	handler = &PonSimCommonHandler{device: device}
+
+	return handler
+}
+
+/*
+ProcessData handles and forwards streaming INGRESS/EGRESS packets
+*/
+func (h *PonSimCommonHandler) ProcessData(stream ponsim.PonSimCommon_ProcessDataServer) error {
+	common.Logger().WithFields(logrus.Fields{
+		"handler": h,
+	}).Debug("Processing data")
+
+	var err error
+	var data *ponsim.IncomingData
+
+	for {
+
+		if data, err = stream.Recv(); err == io.EOF {
+			common.Logger().WithFields(logrus.Fields{
+				"handler": h,
+			}).Warn("Streaming channel was closed")
+			return stream.SendAndClose(&empty.Empty{})
+		} else if err != nil {
+			common.Logger().WithFields(logrus.Fields{
+				"handler": h,
+				"error":   err.Error(),
+			}).Warn("Error occurred with stream")
+			return err
+		}
+
+		frame := gopacket.NewPacket(data.Payload, layers.LayerTypeEthernet, gopacket.Default)
+
+		h.device.Forward(
+			context.Background(),
+			int(data.Port),
+			frame,
+		)
+		common.Logger().WithFields(logrus.Fields{
+			"handler": h,
+			"frame":   frame,
+			"port":    data.Port,
+		}).Debug("Retrieved and forwarded packet")
+
+	}
+
+	return nil
+}
diff --git a/ponsim/v2/grpc/sbi/olt_handler.go b/ponsim/v2/grpc/sbi/olt_handler.go
new file mode 100644
index 0000000..bb058ca
--- /dev/null
+++ b/ponsim/v2/grpc/sbi/olt_handler.go
@@ -0,0 +1,62 @@
+package sbi
+
+import (
+	"context"
+	"github.com/google/uuid"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/ponsim/v2/core"
+	"github.com/opencord/voltha/protos/go/ponsim"
+	"github.com/sirupsen/logrus"
+)
+
+type PonSimOltHandler struct {
+	olt *core.PonSimOltDevice
+}
+
+func NewPonSimOltHandler(olt *core.PonSimOltDevice) *PonSimOltHandler {
+	var handler *PonSimOltHandler
+
+	handler = &PonSimOltHandler{olt: olt}
+
+	return handler
+}
+
+func (h *PonSimOltHandler) Register(
+	ctx context.Context,
+	request *ponsim.RegistrationRequest,
+) (*ponsim.RegistrationReply, error) {
+	common.Logger().WithFields(logrus.Fields{
+		"handler": h,
+	}).Info("Registering device")
+
+	onu := &core.PonSimOnuDevice{
+		PonSimDevice: core.PonSimDevice{
+			Address: request.Address, Port: request.Port, //GrpcSecurity: h.olt.GrpcSecurity,
+		}}
+
+	if assignedPort, err := h.olt.AddOnu(onu); assignedPort == -1 || err != nil {
+		return &ponsim.RegistrationReply{
+			Id:            uuid.New().String(),
+			Status:        ponsim.RegistrationReply_FAILED,
+			StatusMessage: "Failed to register ONU",
+			ParentAddress: common.GetInterfaceIP(h.olt.ExternalIf),
+			ParentPort:    h.olt.Port,
+			AssignedPort:  assignedPort,
+		}, err
+	} else {
+		common.Logger().WithFields(logrus.Fields{
+			"handler": h,
+			"onus":    h.olt.GetOnus(),
+		}).Debug("ONU Added")
+
+		return &ponsim.RegistrationReply{
+			Id:            uuid.New().String(),
+			Status:        ponsim.RegistrationReply_REGISTERED,
+			StatusMessage: "Successfully registered ONU",
+			ParentAddress: common.GetInterfaceIP(h.olt.ExternalIf),
+			ParentPort:    h.olt.Port,
+			AssignedPort:  assignedPort,
+		}, nil
+
+	}
+}
diff --git a/ponsim/v2/ponsim.go b/ponsim/v2/ponsim.go
new file mode 100644
index 0000000..fad0972
--- /dev/null
+++ b/ponsim/v2/ponsim.go
@@ -0,0 +1,289 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"github.com/opencord/voltha/ponsim/v2/common"
+	"github.com/opencord/voltha/ponsim/v2/core"
+	"github.com/opencord/voltha/ponsim/v2/grpc"
+	"log"
+	"os"
+	"os/signal"
+	"path"
+)
+
+// TODO: Cleanup logs
+
+const (
+	default_name           = "PON"
+	default_grpc_port      = 50060
+	default_grpc_addr      = ""
+	default_device_type    = "OLT"
+	default_api_type       = "PONSIM"
+	default_internal_if    = "eth0"
+	default_external_if    = "eth1"
+	default_onus           = 1
+	default_alarm_sim      = false
+	default_alarm_freq     = 60
+	default_quiet          = false
+	default_verbose        = false
+	default_no_banner      = false
+	default_parent_addr    = "olt"
+	default_parent_port    = 50060
+	default_vcore_endpoint = "vcore"
+	default_fluentd_host   = ""
+
+	default_snapshot_len = 65535
+	default_promiscuous  = false
+
+	default_voltha_key  = "pki/voltha.key"
+	default_voltha_cert = "pki/voltha.crt"
+	default_voltha_ca   = "pki/voltha-CA.pem"
+)
+
+var (
+	voltha_base = os.Getenv("VOLTHA_BASE")
+	certs       *grpc.GrpcSecurity
+
+	name           string = default_name + "_" + device_type
+	grpc_port      int    = default_grpc_port
+	grpc_addr      string = default_grpc_addr
+	device_type    string = default_device_type
+	api_type       string = default_api_type
+	internal_if    string = default_internal_if
+	external_if    string = default_external_if
+	onus           int    = default_onus
+	alarm_sim      bool   = default_alarm_sim
+	alarm_freq     int    = default_alarm_freq
+	quiet          bool   = default_quiet
+	verbose        bool   = default_verbose
+	no_banner      bool   = default_no_banner
+	voltha_key     string = default_voltha_key
+	voltha_cert    string = default_voltha_cert
+	voltha_ca      string = default_voltha_ca
+	parent_addr    string = default_parent_addr
+	parent_port    int    = default_parent_port
+	vcore_endpoint string = default_vcore_endpoint
+	fluentd_host   string = default_fluentd_host
+
+	snapshot_len int32 = default_snapshot_len
+	promiscuous  bool  = default_promiscuous
+)
+
+func init() {
+	parseArgs()
+
+	// Enable fluentd support
+	if fluentd_host != "" {
+		common.Logger().SetFluentd(fluentd_host)
+	}
+
+	// Print banner unless no_banner is specified
+	if !no_banner {
+		printBanner()
+	}
+}
+
+func parseArgs() {
+	var help string
+
+	help = fmt.Sprintf("Name of the PON device")
+	flag.StringVar(&grpc_addr, "name", default_name, help)
+
+	help = fmt.Sprintf("Address used to establish GRPC server connection")
+	flag.StringVar(&grpc_addr, "grpc_addr", default_grpc_addr, help)
+
+	help = fmt.Sprintf("Port used to establish GRPC server connection")
+	flag.IntVar(&grpc_port, "grpc_port", default_grpc_port, help)
+
+	help = fmt.Sprintf("Type of device to simulate (OLT or ONU)")
+	flag.StringVar(&device_type, "device_type", default_device_type, help)
+
+	help = fmt.Sprintf("Type of API used to communicate with devices (PONSIM or BAL)")
+	flag.StringVar(&api_type, "api_type", default_api_type, help)
+
+	help = fmt.Sprintf("Internal Communication Interface for read/write network traffic")
+	flag.StringVar(&internal_if, "internal_if", default_internal_if, help)
+
+	help = fmt.Sprintf("External Communication Interface for read/write network traffic")
+	flag.StringVar(&external_if, "external_if", default_external_if, help)
+
+	help = fmt.Sprintf("Enable promiscuous mode on network interfaces")
+	flag.BoolVar(&promiscuous, "promiscuous", default_promiscuous, help)
+
+	help = fmt.Sprintf("Number of ONUs to simulate")
+	flag.IntVar(&onus, "onus", default_onus, help)
+
+	help = fmt.Sprintf("Suppress debug and info logs")
+	flag.BoolVar(&quiet, "quiet", default_quiet, help)
+
+	help = fmt.Sprintf("Enable verbose logging")
+	flag.BoolVar(&verbose, "verbose", default_verbose, help)
+
+	help = fmt.Sprintf("Omit startup banner log lines")
+	flag.BoolVar(&no_banner, "no_banner", default_no_banner, help)
+
+	help = fmt.Sprintf("Enable generation of simulated alarms")
+	flag.BoolVar(&alarm_sim, "alarm_sim", default_alarm_sim, help)
+
+	help = fmt.Sprintf("Frequency of simulated alarms (in seconds)")
+	flag.IntVar(&alarm_freq, "alarm_freq", default_alarm_freq, help)
+
+	help = fmt.Sprintf("Address of OLT to connect to")
+	flag.StringVar(&parent_addr, "parent_addr", default_parent_addr, help)
+
+	help = fmt.Sprintf("Port of OLT to connect to")
+	flag.IntVar(&parent_port, "parent_port", default_parent_port, help)
+
+	help = fmt.Sprintf("Voltha core endpoint address")
+	flag.StringVar(&vcore_endpoint, "vcore_endpoint", default_vcore_endpoint, help)
+
+	help = fmt.Sprintf("Fluentd host address")
+	flag.StringVar(&fluentd_host, "fluentd", default_fluentd_host, help)
+
+	flag.Parse()
+}
+
+func printBanner() {
+	log.Println("    ____  ____  _   _______ ______  ___")
+	log.Println("   / __ \\/ __ \\/ | / / ___//  _/  |/  /")
+	log.Println("  / /_/ / / / /  |/ /\\__ \\ / // /|_/ / ")
+	log.Println(" / ____/ /_/ / /|  /___/ // // /  / /  ")
+	log.Println("/_/    \\____/_/ |_//____/___/_/  /_/  ")
+
+	switch device_type {
+	case "OLT":
+		printOltBanner()
+	case "ONU":
+		printOnuBanner()
+	}
+
+	log.Println("(to stop: press Ctrl-C)")
+}
+func printOltBanner() {
+	log.Println("   ____  __  ______")
+	log.Println("  / __ \\/ / /_  __/")
+	log.Println(" / / / / /   / /   ")
+	log.Println("/ /_/ / /___/ /    ")
+	log.Println("\\____/_____/_/     ")
+}
+func printOnuBanner() {
+	log.Println("   ____  _   ____  __")
+	log.Println("  / __ \\/ | / / / / /")
+	log.Println(" / / / /  |/ / / / / ")
+	log.Println("/ /_/ / /|  / /_/ /  ")
+	log.Println("\\____/_/ |_/\\____/   ")
+}
+
+/*
+-----------------------------------------------------------------
+*/
+type PonSimService struct {
+	device core.PonSimInterface
+	server *grpc.GrpcServer
+}
+
+func (s *PonSimService) Start(ctx context.Context) {
+	// GRPC server needs to be secure.
+	// Otherwise communication between adapter and simulator does not occur
+	s.server = grpc.NewGrpcServer(s.device.GetAddress(), s.device.GetPort(), certs, true)
+
+	// Add GRPC services
+	s.server.AddCommonService(s.device)
+	s.server.AddPonSimService(s.device)
+
+	// Add OLT specific services
+	if device_type == core.OLT.String() {
+		s.server.AddOltService(s.device)
+	}
+
+	// Add XPON services unless using BAL
+	if api_type == core.PONSIM.String() {
+		s.server.AddXPonService()
+	} else {
+		s.server.AddBalService()
+	}
+
+	// Start the GRPC server
+	go s.server.Start(ctx)
+
+	// Start the PON device
+	go s.device.Start(ctx)
+}
+
+func (s *PonSimService) Stop(ctx context.Context) {
+	// Stop PON device
+	s.device.Stop(ctx)
+
+	// Stop GRPC server
+	s.server.Stop()
+}
+
+func main() {
+	var device core.PonSimInterface
+
+	// Init based on type of device
+	// Construct OLT/ONU object and pass it down
+	certs = &grpc.GrpcSecurity{
+		CertFile: path.Join(voltha_base, voltha_cert),
+		KeyFile:  path.Join(voltha_base, voltha_key),
+		CaFile:   path.Join(voltha_base, voltha_ca),
+	}
+
+	// Initialize device with common parameters
+	pon := core.PonSimDevice{
+		Name:        name,
+		ExternalIf:  external_if,
+		InternalIf:  internal_if,
+		Promiscuous: promiscuous,
+		SnapshotLen: snapshot_len,
+		Address:     grpc_addr,
+		Port:        int32(grpc_port),
+		AlarmsOn:    alarm_sim,
+		AlarmsFreq:  alarm_freq,
+		Counter:     core.NewPonSimMetricCounter(name),
+
+		// TODO: pass certificates
+		//GrpcSecurity: certs,
+	}
+
+	switch device_type {
+	case core.OLT.String():
+		device = core.NewPonSimOltDevice(pon)
+		device.(*core.PonSimOltDevice).MaxOnuCount = onus
+		device.(*core.PonSimOltDevice).VCoreEndpoint = vcore_endpoint
+
+	case core.ONU.String():
+		device = core.NewPonSimOnuDevice(pon)
+		device.(*core.PonSimOnuDevice).ParentAddress = parent_addr
+		device.(*core.PonSimOnuDevice).ParentPort = int32(parent_port)
+
+	default:
+		log.Println("Unknown device type")
+	}
+
+	ps := PonSimService{device: device}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	ps.Start(ctx)
+
+	signals := make(chan os.Signal, 1)
+	signal.Notify(signals, os.Interrupt)
+
+	doneCh := make(chan struct{})
+
+	go func() {
+		for {
+			select {
+			case <-signals:
+				log.Println("Interrupt was detected")
+				doneCh <- struct{}{}
+			}
+		}
+	}()
+
+	<-doneCh
+}
diff --git a/ponsim/v2/protos/ponsim_common.proto b/ponsim/v2/protos/ponsim_common.proto
new file mode 100644
index 0000000..42f5994
--- /dev/null
+++ b/ponsim/v2/protos/ponsim_common.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+option go_package = "github.com/opencord/voltha/protos/go/ponsim";
+
+package ponsim;
+
+import "google/protobuf/empty.proto";
+
+service PonSimCommon {
+    rpc ProcessData (stream IncomingData) returns (google.protobuf.Empty) {}
+}
+
+message IncomingData {
+    string id = 1;
+    string address = 2;
+    int32 port = 3;
+    bytes payload = 4;
+
+}
\ No newline at end of file
diff --git a/ponsim/v2/protos/ponsim_olt.proto b/ponsim/v2/protos/ponsim_olt.proto
new file mode 100644
index 0000000..82214a7
--- /dev/null
+++ b/ponsim/v2/protos/ponsim_olt.proto
@@ -0,0 +1,32 @@
+syntax = "proto3";
+
+option go_package = "github.com/opencord/voltha/protos/go/ponsim";
+
+package ponsim;
+
+service PonSimOlt {
+    rpc Register (RegistrationRequest) returns (RegistrationReply) {}
+}
+
+message RegistrationRequest {
+    string id = 1;
+    string address = 2;
+    int32 port = 3;
+}
+
+message RegistrationReply {
+    string id = 1;
+
+    enum Status {
+        REGISTERED = 0;
+        FAILED = 1;
+        UNAVAILABLE = 2;
+    }
+
+    Status status = 2;
+    string status_message = 3;
+
+    string parent_address = 4;
+    int32 parent_port = 5;
+    int32 assigned_port = 6;
+}
diff --git a/ponsim/v2/scripts/build_protos.sh b/ponsim/v2/scripts/build_protos.sh
new file mode 100644
index 0000000..daccdbe
--- /dev/null
+++ b/ponsim/v2/scripts/build_protos.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+export SRC_DIR="$1"
+
+echo $SRC_DIR
+
+export MAPS=Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor
+export INCS="\
+    -I $SRC_DIR \
+    -I $GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis"
+
+export VOLTHA_PB="\
+    $SRC_DIR/adapter.proto \
+    $SRC_DIR/device.proto \
+    $SRC_DIR/events.proto \
+    $SRC_DIR/health.proto \
+    $SRC_DIR/logical_device.proto \
+    $SRC_DIR/ponsim.proto \
+    $SRC_DIR/voltha.proto"
+
+export COMMON_PB="\
+    $SRC_DIR/common.proto \
+    $SRC_DIR/meta.proto \
+    $SRC_DIR/yang_options.proto"
+
+export PONSIM_PB="$SRC_DIR/ponsim_common.proto $SRC_DIR/ponsim_olt.proto"
+export SCHEMA_PB="$SRC_DIR/schema.proto"
+export IETF_PB="$SRC_DIR/ietf_interfaces.proto"
+export OF_PB="$SRC_DIR/openflow_13.proto"
+export BAL_PB="$SRC_DIR/bal*.proto"
+export BBF_PB="$SRC_DIR/bbf*.proto"
+
+export PB_VARS="\
+    VOLTHA_PB \
+    COMMON_PB \
+    PONSIM_PB \
+    SCHEMA_PB \
+    IETF_PB \
+    OF_PB \
+    BAL_PB \
+    BBF_PB"
+
+for pb_var in $PB_VARS
+do
+    pbs="$(eval echo \$$pb_var)"
+    echo "Compiling $pbs"
+    protoc --go_out=$MAPS,plugins=grpc:$GOPATH/src $INCS $pbs
+done