VOL-639 - Use a more scalable scheme of numbering for the NNI and UNI
          logical port numbering

Change-Id: If94f45dd6af8c1f22d2f58ec361aea1738ef546b
diff --git a/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py b/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
index cf0cf60..746f0ed 100644
--- a/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
+++ b/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
@@ -20,6 +20,7 @@
 
 import arrow
 from twisted.internet.defer import inlineCallbacks
+from itertools import count, ifilterfalse
 from voltha.protos.events_pb2 import KpiEvent, MetricValuePairs
 from voltha.protos.events_pb2 import KpiEventType
 from voltha.protos.device_pb2 import PmConfigs, PmConfig,PmGroupConfig
@@ -27,11 +28,9 @@
 from voltha.protos.events_pb2 import AlarmEvent, AlarmEventType, \
     AlarmEventSeverity, AlarmEventState, AlarmEventCategory
 from scapy.layers.l2 import Ether, Dot1Q
-from uuid import uuid4
 from common.frameio.frameio import BpfProgramFilter
 from common.utils.asleep import asleep
 from twisted.internet import reactor
-from common.frameio.frameio import hexify
 from scapy.packet import Packet
 import voltha.core.flow_decomposer as fd
 from voltha.protos.common_pb2 import OperStatus, ConnectStatus
@@ -45,6 +44,7 @@
 from voltha.core.logical_device_agent import mac_str_to_tuple
 from voltha.adapters.asfvolt16_olt.bal import Bal
 from voltha.adapters.device_handler import OltDeviceHandler
+from voltha.adapters.asfvolt16_olt.asfvolt16_device_info import Asfvolt16DeviceInfo
 from voltha.protos.bbf_fiber_base_pb2 import \
     ChannelgroupConfig, ChannelpartitionConfig, ChannelpairConfig, \
     ChannelterminationConfig, OntaniConfig, VOntaniConfig, VEnetConfig
@@ -52,21 +52,30 @@
     TrafficDescriptorProfileData
 from voltha.protos.bbf_fiber_tcont_body_pb2 import TcontsConfigData
 from voltha.protos.bbf_fiber_gemport_body_pb2 import GemportsConfigData
-from voltha.protos.bbf_fiber_multicast_gemport_body_pb2 import \
-    MulticastGemportsConfigData
-from voltha.protos.bbf_fiber_multicast_distribution_set_body_pb2 import \
-    MulticastDistributionSetData
-import time
 import binascii
 from argparse import ArgumentParser, ArgumentError
 import shlex
 
-ASFVOLT_NNI_PORT = 129
-# ASFVOLT_NNI_PORT needs to be other than pon port value.
-# Edgecore OLT assigns PONport between 0 to 15, hence
-# having a value 129 for NNI port to avoid collision.
+# The current scheme of device port numbering is below.
+# 0 to 127 is reserved for OLT PON PORTS - Unique per OLT
+# 128 to 255 is reserved for OLT NNI PORTS - Unique per OLT
+# 256 is reserved for ONU PON PORT (assume one PON port for the ONU) - Same for all ONU
+# 257 and above is reserved for ONU UNI port numbering. - Unique per OLT
 
-ASFVOLT16_MAX_PON_PORT_ID = 15
+MIN_ASFVOLT_PON_PORT_NUM = 0
+MAX_ASFVOLT_PON_PORT_NUM = MIN_ASFVOLT_PON_PORT_NUM + 127
+NUM_OF_ASFVOLT_PON_PORTS = MAX_ASFVOLT_PON_PORT_NUM - \
+                           MIN_ASFVOLT_PON_PORT_NUM + 1
+
+MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM = MAX_ASFVOLT_PON_PORT_NUM + 1
+MAX_ASFVOLT_NNI_LOGICAL_PORT_NUM = MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM + 127
+NUM_OF_ASFVOLT_NNI_LOGICAL_PORTS = MAX_ASFVOLT_NNI_LOGICAL_PORT_NUM - \
+                                   MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM + 1
+
+ONU_PON_PORT_ID = MAX_ASFVOLT_NNI_LOGICAL_PORT_NUM + 1
+NUM_OF_ONU_PON_PORTS = 1
+
+ONU_UNI_PORT_START_ID = ONU_PON_PORT_ID + 1
 
 # TODO: VLAN ID needs to come from some sort of configuration.
 ASFVOLT16_DEFAULT_VLAN = 4091
@@ -106,6 +115,7 @@
         self.action = dict()
         self.traffic_class = None
 
+
 class VEnetHandler(object):
 
     def __init__(self):
@@ -113,12 +123,14 @@
         self.gem_ports = dict()
         self.pending_flows = []
 
+
 class VOntAniHandler(object):
 
     def __init__(self):
         self.v_ont_ani = VOntaniConfig()
         self.tconts = dict()
 
+
 class Asfvolt16OltPmMetrics:
     class Metrics:
         def __init__(self, config, value=0):
@@ -168,12 +180,14 @@
             freq_override = False)
         return pm_config
 
+
 class MyArgumentParser(ArgumentParser):
     # Must override the exit command to prevent it from
     # calling sys.exit().  Return exception instead.
     def exit(self, status=0, message=None):
         raise Exception(message)
 
+
 class Asfvolt16Handler(OltDeviceHandler):
 
     def __init__(self, adapter, device_id):
@@ -192,7 +206,6 @@
         self.traffic_descriptors = dict()
         self.pon_id_gem_port_to_v_enet_name = dict()
         self.adapter_name = adapter.name
-        self.uni_port_num = 20
         self.pm_metrics = None
         self.heartbeat_count = 0
         self.heartbeat_miss = 0
@@ -201,7 +214,9 @@
         self.heartbeat_interval = 5
         self.heartbeat_failed_limit = 1
         self.is_heartbeat_started = 0
-        self.transceiver_type = bal_model_types_pb2.BAL_TRX_TYPE_XGPON_LTH_7226_PC;
+        self.transceiver_type = bal_model_types_pb2.BAL_TRX_TYPE_XGPON_LTH_7226_PC
+        self.asfvolt_device_info = Asfvolt16DeviceInfo(self.bal,
+                                                       self.log, self.device_id)
 
     def __del__(self):
         super(Asfvolt16Handler, self).__del__()
@@ -210,8 +225,24 @@
         return "Asfvolt16Handler: {}".format(self.host_and_port)
 
     def _get_next_uni_port(self):
-        self.uni_port_num += 1
-        return self.uni_port_num
+        uni_ports = self.adapter_agent.get_ports(self.device_id,
+                                                 Port.ETHERNET_UNI)
+        uni_port_nums = set([uni_port.port_no for uni_port in uni_ports])
+        # We need to start allocating port numbers from ONU_UNI_PORT_START_ID.
+        # Find the first unused port number.
+        next_port_num = next(ifilterfalse(uni_port_nums.__contains__,
+                                          count(ONU_UNI_PORT_START_ID)))
+        if next_port_num <= 65535:
+            return next_port_num
+        else:
+            raise ValueError("invalid-port-number-{}".format(next_port_num))
+
+    def _valid_nni_port(self, port):
+        if port < self.asfvolt_device_info.asfvolt16_device_topology.num_of_pon_ports or \
+            port >= (self.asfvolt_device_info.asfvolt16_device_topology.num_of_pon_ports +
+                     self.asfvolt_device_info.asfvolt16_device_topology.num_of_nni_ports):
+            return False
+        return True
 
     def get_venet(self, **kwargs):
         name = kwargs.pop('name', None)
@@ -257,7 +288,7 @@
         return None
 
     def get_traffic_profile(self, name):
-        for key, traffic_profile in self.traffic_descriptors():
+        for key, traffic_profile in self.traffic_descriptors.items():
             if name is not None:
                 if name == traffic_profile.name:
                     return traffic_profile
@@ -370,7 +401,6 @@
         return None
 
     def activate(self, device):
-
         self.log.info('activating-asfvolt16-olt', device=device)
 
         if not device.host_and_port:
@@ -400,27 +430,17 @@
             device.model = 'ASFvOLT16'
             device.serial_number = device.host_and_port
             self.adapter_agent.update_device(device)
-
-            self.add_port(port_no=ASFVOLT_NNI_PORT,
-                          port_type=Port.ETHERNET_NNI,
-                          label='NNI facing Ethernet port')
             self.add_logical_device(device_id=device.id)
-            self.add_logical_port(port_no=ASFVOLT_NNI_PORT,
-                                  port_type=Port.ETHERNET_NNI,
-                                  device_id=device.id,
-                                  logical_device_id=self.logical_device_id)
             reactor.callInThread(self.bal.get_indication_info, self.device_id)
 
         self.bal.activate_olt()
 
-        device = self.adapter_agent.get_device(device.id)
         device.parent_id = self.logical_device_id
         device.connect_status = ConnectStatus.REACHABLE
         device.oper_status = OperStatus.ACTIVATING
         self.adapter_agent.update_device(device)
 
     def reconcile(self, device):
-
         self.log.info('reconciling-asfvolt16-starts',device=device)
 
         if not device.host_and_port:
@@ -479,8 +499,12 @@
     def heartbeat(self, state = 'run'):
         device = self.adapter_agent.get_device(self.device_id)
 
-        self.log.debug('olt-heartbeat', device=device, state=state,
-                       count=self.heartbeat_count)
+        # Commenting this unnecessary debug. Debug prints only in case of
+        # heartbeat miss/recovery should be good enough.
+        # The status of the device can anyway be queried from the CLI if
+        # necessary.
+        # self.log.debug('olt-heartbeat', device=device, state=state,
+        #               count=self.heartbeat_count)
         self.is_heartbeat_started = 1
 
         def heartbeat_alarm(device, status, heartbeat_misses=0):
@@ -525,11 +549,21 @@
                 if d.is_reboot == bal_pb2.BAL_OLT_UP_AFTER_REBOOT:
                     self.log.info('activating-olt-again-after-reboot')
 
-                    # Since OLT is reachable after reboot, OLT should configurable with
-                    # all the old existing flows. NNI port should be mark it as down for
-                    # ONOS to push the old flows
-                    self.update_logical_port(ASFVOLT_NNI_PORT, Port.ETHERNET_NNI,
-                                             OFPPS_LINK_DOWN)
+                    for port in self.asfvolt_device_info.sfp_device_presence_map.iterkeys():
+                        # ignore any sfp other than the NNI.
+                        if not self._valid_nni_port(port):
+                            continue
+
+                        # Since OLT is reachable after reboot, OLT should configurable with
+                        # all the old existing flows. NNI ports should be mark it as down for
+                        # ONOS to push the old flows
+                        nni_intf_id = (port -
+                                       self.asfvolt_device_info.
+                                       asfvolt16_device_topology.num_of_pon_ports)
+                        self.update_logical_port(nni_intf_id + MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM,
+                                                 Port.ETHERNET_NNI,
+                                                 OFPPS_LINK_DOWN)
+
                     for key, v_ont_ani in self.v_ont_anis.items():
                         child_device = self.adapter_agent.get_child_device(
                            self.device_id, onu_id=v_ont_ani.v_ont_ani.data.onu_id)
@@ -547,8 +581,8 @@
                     device.reason = ''
                     self.adapter_agent.update_device(device)
                     # Update the device control block with the latest update
-                    self.log.info("all-fine-no-heartbeat-miss",device=device)
-                self.log.info('clearing-hearbeat-alarm')
+                    self.log.debug("all-fine-no-heartbeat-miss", device=device)
+                self.log.info('clearing-heartbeat-alarm')
                 heartbeat_alarm(device, 0)
 
         if (self.heartbeat_miss >= self.heartbeat_failed_limit) and \
@@ -569,28 +603,30 @@
         self.log.info('Reboot-Status', err_status = err_status)
 
     def _handle_pm_counter_req_towards_device(self, device):
-        # Currently ASFVOLT16 supports only one NNI and its zero
-        #  ToDo A mechanism to fetch NNT_INT_ID needs to included
-        NNI_INT_ID = 0
-        self._handle_nni_pm_counter_req_towards_device(device, NNI_INT_ID)
-        if len(self.channel_terminations) > 0:
-           for value in self.channel_terminations.values():
-                self._handle_pon_pm_counter_req_towards_device(device, value.data.xgs_ponid)
+        for port in self.asfvolt_device_info.sfp_device_presence_map.iterkeys():
+            if not self._valid_nni_port(port):
+                continue
+            nni_intf_id = (port -
+                            self.asfvolt_device_info.
+                            asfvolt16_device_topology.num_of_pon_ports)
+            self._handle_nni_pm_counter_req_towards_device(device, nni_intf_id)
+        for value in self.channel_terminations.itervalues():
+            self._handle_pon_pm_counter_req_towards_device(device,
+                                                           value.data.xgs_ponid)
         reactor.callLater(self.pm_metrics.pm_default_freq / 10,
-                                  self._handle_pm_counter_req_towards_device, device)
+                          self._handle_pm_counter_req_towards_device,
+                          device)
 
     @inlineCallbacks
     def _handle_nni_pm_counter_req_towards_device(self, device, intf_id):
         interface_type = bal_model_types_pb2.BAL_INTF_TYPE_NNI
         yield self._req_pm_counter_from_device(device, interface_type, intf_id)
 
-
     @inlineCallbacks
     def _handle_pon_pm_counter_req_towards_device(self, device, intf_id):
         interface_type = bal_model_types_pb2.BAL_INTF_TYPE_PON
         yield self._req_pm_counter_from_device(device, interface_type, intf_id)
 
-
     @inlineCallbacks
     def _req_pm_counter_from_device(self, device, interface_type, intf_id):
         # NNI port is hardcoded to 0
@@ -598,51 +634,55 @@
         if device.connect_status == ConnectStatus.UNREACHABLE:
            self.log.info('Device-is-not-Reachable')
         else:
-           try:
-              stats_info = yield self.bal.get_bal_interface_stats(intf_id, interface_type)
-              kpi_status = 0
-              self.log.info('stats_info',stats_info=stats_info)
-           except Exception as e:
-              kpi_status = -1
+            try:
+                stats_info = yield self.bal.get_bal_interface_stats(intf_id, interface_type)
+                kpi_status = 0
+                # Commenting this unnecessary debug log. The statistics information
+                # is also available from kafka_proxy.send_message debug log.
+                # self.log.debug('stats_info', stats_info=stats_info)
+            except Exception as e:
+                kpi_status = -1
 
-        if kpi_status == 0 and stats_info!=None:
-           pm_data = { }
-           pm_data["rx_bytes"]= stats_info.data.rx_bytes
-           pm_data["rx_packets"]= stats_info.data.rx_packets
-           pm_data["rx_ucast_packets"]= stats_info.data.rx_ucast_packets
-           pm_data["rx_mcast_packets"]= stats_info.data.rx_mcast_packets
-           pm_data["rx_bcast_packets"]= stats_info.data.rx_bcast_packets
-           pm_data["rx_error_packets"]= stats_info.data.rx_error_packets
-           pm_data["rx_unknown_protos"]= stats_info.data.rx_unknown_protos
-           pm_data["tx_bytes"]= stats_info.data.tx_bytes
-           pm_data["tx_packets"]= stats_info.data.tx_packets
-           pm_data["tx_ucast_packets"]= stats_info.data.tx_ucast_packets
-           pm_data["tx_mcast_packets"]= stats_info.data.tx_mcast_packets
-           pm_data["tx_bcast_packets"]= stats_info.data.tx_bcast_packets
-           pm_data["tx_error_packets"]= stats_info.data.tx_error_packets
-           pm_data["rx_crc_errors"]= stats_info.data.rx_crc_errors
-           pm_data["bip_errors"]= stats_info.data.bip_errors
+        if kpi_status == 0 and stats_info != None:
+            pm_data = {}
+            pm_data["rx_bytes"] = stats_info.data.rx_bytes
+            pm_data["rx_packets"] = stats_info.data.rx_packets
+            pm_data["rx_ucast_packets"] = stats_info.data.rx_ucast_packets
+            pm_data["rx_mcast_packets"] = stats_info.data.rx_mcast_packets
+            pm_data["rx_bcast_packets"] = stats_info.data.rx_bcast_packets
+            pm_data["rx_error_packets"] = stats_info.data.rx_error_packets
+            pm_data["rx_unknown_protos"] = stats_info.data.rx_unknown_protos
+            pm_data["tx_bytes"] = stats_info.data.tx_bytes
+            pm_data["tx_packets"] = stats_info.data.tx_packets
+            pm_data["tx_ucast_packets"] = stats_info.data.tx_ucast_packets
+            pm_data["tx_mcast_packets"] = stats_info.data.tx_mcast_packets
+            pm_data["tx_bcast_packets"] = stats_info.data.tx_bcast_packets
+            pm_data["tx_error_packets"] = stats_info.data.tx_error_packets
+            pm_data["rx_crc_errors"] = stats_info.data.rx_crc_errors
+            pm_data["bip_errors"] = stats_info.data.bip_errors
 
-           self.log.info('KPI stats', pm_data = pm_data)
-           name = 'asfvolt16_olt'
-           prefix = 'voltha.{}.{}'.format(name, self.device_id)
-           ts = arrow.utcnow().timestamp
-           if stats_info.key.intf_type == bal_model_types_pb2.BAL_INTF_TYPE_NNI:
-              prefixes = {
-                      prefix + '.nni': MetricValuePairs(metrics = pm_data)
-              }
-           elif stats_info.key.intf_type == bal_model_types_pb2.BAL_INTF_TYPE_PON:
-              prefixes = {
-                      prefix + '.pon': MetricValuePairs(metrics = pm_data)
-              }
+            # Commenting this unnecessary debug log. The statistics information
+            # is also available from kafka_proxy.send_message debug log.
+            # self.log.debug('KPI stats', pm_data=pm_data)
+            name = 'asfvolt16_olt'
+            prefix = 'voltha.{}.{}'.format(name, self.device_id)
+            ts = arrow.utcnow().timestamp
+            if stats_info.key.intf_type == bal_model_types_pb2.BAL_INTF_TYPE_NNI:
+                prefixes = {
+                    prefix + '.nni': MetricValuePairs(metrics=pm_data)
+                }
+            elif stats_info.key.intf_type == bal_model_types_pb2.BAL_INTF_TYPE_PON:
+                prefixes = {
+                    prefix + '.pon': MetricValuePairs(metrics=pm_data)
+                }
 
-           kpi_event = KpiEvent(
-                 type=KpiEventType.slice,
-                 ts=ts,
-                 prefixes=prefixes)
-           self.adapter_agent.submit_kpis(kpi_event)
+            kpi_event = KpiEvent(
+                type=KpiEventType.slice,
+                ts=ts,
+                prefixes=prefixes)
+            self.adapter_agent.submit_kpis(kpi_event)
         else:
-           self.log.info('Lost-Connectivity-to-OLT')
+            self.log.info('Lost-Connectivity-to-OLT')
 
     def update_pm_config(self, device, pm_config):
         self.log.info("update-pm-config", device=device, pm_config=pm_config)
@@ -850,7 +890,8 @@
         self.logical_device_id = ld_initialized.id
 
     def add_logical_port(self, port_no, port_type,
-                         device_id, logical_device_id):
+                         device_id, logical_device_id,
+                         port_state=OFPPS_LINK_DOWN):
         self.log.info('adding-logical-port', port_no=port_no,
                       port_type=port_type, device_id=device_id)
         if port_type is Port.ETHERNET_NNI:
@@ -864,11 +905,10 @@
 
         ofp = ofp_port(
             port_no=port_no,  # is 0 OK?
-            hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % 129),
+            hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % (port_no + 1)),
             name=label,
             config=0,
-            #state=OFPPS_LIVE,
-            state=OFPPS_LINK_DOWN,
+            state=port_state,
             curr=cap,
             advertised=cap,
             peer=cap,
@@ -900,20 +940,39 @@
         self.adapter_agent.update_logical_port(self.logical_device_id,
                                                logical_port)
 
-    def handle_access_term_ind(self, ind_info, intf_id):
+    def handle_access_term_ind(self, ind_info, access_term_ind):
         device = self.adapter_agent.get_device(self.device_id)
         if ind_info['activation_successful'] is True:
             self.log.info('successful-access-terminal-Indication',
                           olt_id=self.olt_id)
-            device.connect_status = ConnectStatus.REACHABLE
-            device.oper_status = OperStatus.ACTIVE
-            device.reason = 'OLT activated successfully'
-            self.adapter_agent.update_device(device)
-            self.update_logical_port(ASFVOLT_NNI_PORT, Port.ETHERNET_NNI,
-                                     OFPPS_LIVE)
+
+            self.asfvolt_device_info.update_device_topology(access_term_ind)
+            self.asfvolt_device_info.update_device_software_info(access_term_ind)
+            self.asfvolt_device_info.read_and_build_device_sfp_presence_map()
+
+            # For all the detected NNI ports, create corresponding physical and
+            # logical ports on the device and logical device.
+            for port in self.asfvolt_device_info.sfp_device_presence_map.iterkeys():
+                if not self._valid_nni_port(port):
+                    continue
+
+                nni_intf_id = (port -
+                                self.asfvolt_device_info.
+                                asfvolt16_device_topology.num_of_pon_ports)
+                self.add_port(port_no=nni_intf_id +
+                              MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM,
+                              port_type=Port.ETHERNET_NNI,
+                              label='NNI facing Ethernet port')
+                self.add_logical_port(port_no=nni_intf_id + \
+                                      MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM,
+                                      port_type=Port.ETHERNET_NNI,
+                                      device_id=device.id,
+                                      logical_device_id=self.logical_device_id,
+                                      port_state=OFPPS_LIVE)
+
             self.log.info('OLT-activation-complete')
 
-            #heart beat - To health checkup of OLT
+            # heart beat - To health checkup of OLT
             if self.is_heartbeat_started == 0:
                 self.log.info('Heart-beat-is-not-yet-started-starting-now')
                 self.start_heartbeat()
@@ -930,6 +989,11 @@
                 # intf_id:nni_port
                 self._handle_pm_counter_req_towards_device(device)
 
+            device.connect_status = ConnectStatus.REACHABLE
+            device.oper_status = OperStatus.ACTIVE
+            device.reason = 'OLT activated successfully'
+            self.adapter_agent.update_device(device)
+
         else:
             device.oper_status = OperStatus.FAILED
             device.reason = 'Failed to Intialize OLT'
@@ -1180,10 +1244,12 @@
                     channel_pair_config.CopyFrom(data)
                     self.channel_pairs[data.name] = channel_pair_config
             if isinstance(data, ChannelterminationConfig):
-                if data.data.xgs_ponid > ASFVOLT16_MAX_PON_PORT_ID:
+                max_pon_ports = self.asfvolt_device_info.\
+                        asfvolt16_device_topology.num_of_pon_ports
+                if data.data.xgs_ponid > max_pon_ports:
                     raise ValueError\
                         ("pon_id-%u-is-greater-than-%u"
-                         % (data.data.xgs_ponid, ASFVOLT16_MAX_PON_PORT_ID))
+                         % (data.data.xgs_ponid, max_pon_ports))
 
                 self.log.info('Activating-PON-port-at-OLT',
                               pon_id=data.data.xgs_ponid)
@@ -1212,13 +1278,24 @@
                     v_ont_ani_config.v_ont_ani.CopyFrom(data)
                     self.v_ont_anis[data.name] = v_ont_ani_config
             if isinstance(data, VEnetConfig):
-                self.adapter_agent.add_port(self.device_id, Port(
-                                            port_no=self._get_next_uni_port(),
-                                            label=data.interface.name,
-                                            type=Port.ETHERNET_UNI,
-                                            admin_state=AdminState.ENABLED,
-                                            oper_status=OperStatus.ACTIVE
-                                           ))
+                uni_ports = self.adapter_agent.get_ports(self.device_id,
+                                                         Port.ETHERNET_UNI)
+                uni_port_labels = [uni_port.label for uni_port in uni_ports]
+                if data.interface.name not in uni_port_labels:
+                    # Add the port only if it didnt already exist. Each port
+                    # has a unique label
+                    self.adapter_agent.add_port(self.device_id, Port(
+                                                port_no=self._get_next_uni_port(),
+                                                label=data.interface.name,
+                                                type=Port.ETHERNET_UNI,
+                                                admin_state=AdminState.ENABLED,
+                                                oper_status=OperStatus.ACTIVE
+                                                ))
+                else:
+                    # Usually happens during xpon replay after voltha reboot.
+                    # The port already exists in vcore. We will not create again.
+                    # Throw a message and proceed ahead.
+                    self.log.info("port-already-exists", port_label=data.interface.name)
 
                 if data.name in self.v_enets:
                     self.log.info('v_enet-already-present',
@@ -1420,10 +1497,11 @@
                       packet=str(payload).encode("HEX"))
         send_pkt = binascii.unhexlify(str(payload).encode("HEX"))
 
-        if egress_port == ASFVOLT_NNI_PORT:
-            port_id = 'nni'
+        if egress_port >= MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM and \
+           egress_port <= MAX_ASFVOLT_NNI_LOGICAL_PORT_NUM:
             pkt_info['dest_type'] = 'nni'
-            pkt_info['intf_id'] = 0
+            # BAL expects NNI intf_id to be between 0 to 15.
+            pkt_info['intf_id'] = egress_port - MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM
             self.log.info('packet-out-is-for-olt-nni')
             send_pkt = binascii.unhexlify(str(pkt).encode("HEX"))
         else:
diff --git a/voltha/adapters/asfvolt16_olt/asfvolt16_device_info.py b/voltha/adapters/asfvolt16_olt/asfvolt16_device_info.py
new file mode 100755
index 0000000..719f62e
--- /dev/null
+++ b/voltha/adapters/asfvolt16_olt/asfvolt16_device_info.py
@@ -0,0 +1,90 @@
+#

+# Copyright 2017 the original author or authors.

+#

+# Licensed under the Apache License, Version 2.0 (the "License");

+# you may not use this file except in compliance with the License.

+# You may obtain a copy of the License at

+#

+#      http://www.apache.org/licenses/LICENSE-2.0

+#

+# Unless required by applicable law or agreed to in writing, software

+# distributed under the License is distributed on an "AS IS" BASIS,

+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+# See the License for the specific language governing permissions and

+# limitations under the License.

+#

+

+from twisted.internet.defer import inlineCallbacks

+

+"""

+Asfvolt16 Device specific information handler

+"""

+

+

+class Asfvolt16DeviceTopology(object):

+    def __init__(self, nnis=4, pons=16, mac_devs=8, mac_per_dev=2,

+                 pon_sub_family="BAL_PON_SUB_FAMILY_XGPON"):

+        self.num_of_nni_ports = nnis

+        self.num_of_pon_ports = pons

+        self.num_of_mac_devs = mac_devs

+        self.num_of_pons_per_mac_dev = mac_per_dev

+        self.pon_sub_family = pon_sub_family

+

+

+class Asfvolt16DeviceSoftwareInfo(object):

+    def __init__(self, ver_type="BAL_VERSION_TYPE_RELEASE", maj_ver=2,

+                 min_ver=4, om_ver=1):

+        self.version_type = ver_type

+        self.major_version = maj_ver

+        self.minor_version = min_ver

+        self.om_version = om_ver

+

+

+class Asfvolt16DeviceInfo(object):

+    def __init__(self, bal, log, device_id):

+        self.bal = bal

+        self.log = log

+        self.device_id = device_id

+        self.asfvolt16_device_topology = Asfvolt16DeviceTopology()

+        self.asfvolt16_device_sw_info = Asfvolt16DeviceSoftwareInfo()

+        self.sfp_device_presence_map = dict()

+

+    def update_device_topology(self, access_term_ind):

+        try:

+            self.asfvolt16_device_topology.num_of_nni_ports = \

+                access_term_ind.data.topology.num_of_nni_ports

+            self.asfvolt16_device_topology.num_of_pon_ports = \

+                access_term_ind.data.topology.num_of_pon_ports

+            self.asfvolt16_device_topology.num_of_mac_devs = \

+                access_term_ind.data.topology.num_of_mac_devs

+            self.asfvolt16_device_topology.num_of_pons_per_mac_dev = \

+                access_term_ind.data.topology.num_of_pons_per_mac_dev

+            self.asfvolt16_device_topology.pon_sub_family = \

+                access_term_ind.data.topology.pon_sub_family

+        except Exception as e:

+            self.log.error("error-reading-topology", e=e)

+

+    def update_device_software_info(self, access_term_ind):

+        try:

+            self.asfvolt16_device_sw_info.version_type = \

+                access_term_ind.data.sw_version.version_type

+            self.asfvolt16_device_sw_info.major_rev = \

+                access_term_ind.data.sw_version.major_rev

+            self.asfvolt16_device_sw_info.minor_rev = \

+                access_term_ind.data.sw_version.minor_rev

+            self.asfvolt16_device_sw_info.om_version = \

+                access_term_ind.data.sw_version.om_version

+        except Exception as e:

+            self.log.error("error-reading-software-info", e=e)

+

+    @inlineCallbacks

+    def read_and_build_device_sfp_presence_map(self):

+        try:

+            sfp_presence_bitmap = \

+                yield self.bal.get_asfvolt_sfp_presence_map(self.device_id)

+            for i in range(self.asfvolt16_device_topology.num_of_pon_ports + \

+                           self.asfvolt16_device_topology.num_of_nni_ports):

+                if (1 << i) & int(sfp_presence_bitmap):

+                    self.sfp_device_presence_map[i] = True

+        except Exception as e:

+            self.log.error("read_and_build_device_sfp_presence_map", e=e)

diff --git a/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py b/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py
index 826ff4f..0ba2a21 100644
--- a/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py
+++ b/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py
@@ -53,7 +53,7 @@
         reactor.callLater(0,
                           device_handler.handle_access_term_ind,
                           ind_info,
-                          indication.access_term_ind.key.access_term_id)
+                          indication.access_term_ind)
         bal_err = bal_pb2.BalErr()
         bal_err.err = bal_errno_pb2.BAL_ERR_OK
         return bal_err
diff --git a/voltha/adapters/asfvolt16_olt/bal.py b/voltha/adapters/asfvolt16_olt/bal.py
index e0cab9f..7d93b66 100644
--- a/voltha/adapters/asfvolt16_olt/bal.py
+++ b/voltha/adapters/asfvolt16_olt/bal.py
@@ -469,14 +469,18 @@
 
     @inlineCallbacks
     def get_bal_interface_stats(self, intf_id, interface_type):
-        self.log.info('Fetching-Statistics')
+        # This happens too often and the below log is unnecessary.
+        # The status are anyway available on kafka
+        # self.log.debug('Fetching-Statistics')
         try:
             obj = bal_model_types_pb2.BalInterfaceKey()
             obj.intf_id = intf_id
             obj.intf_type = interface_type
             stats = yield self.stub.BalCfgStatGet(obj, timeout=GRPC_TIMEOUT)
-            self.log.info('Fetching-statistics-success',
-                          stats_data=stats.data)
+            # This happens too often and the below log is unnecessary.
+            # The status are anyway available on kafka
+            # self.log.debug('Fetching-statistics-success',
+            #               stats_data=stats.data)
             returnValue(stats)
         except Exception as e:
             self.log.info('Fetching-statistics-failed', exc=str(e))
@@ -521,6 +525,22 @@
             self.log.error('get-asfvolt-system-info-failed', exc=str(e))
             returnValue(None)
 
+    @inlineCallbacks
+    def get_asfvolt_sfp_presence_map(self, device_id):
+        self.log.info('get-asfvolt-sfp-presence-map')
+        sfp_presence_bitmap = None
+        try:
+            obj = bal_pb2.BalDefault()
+            obj.device_id = device_id
+            sfp_presence_bitmap = \
+                 yield self.asfvolt_stub.AsfvoltGetSfpPresenceBitmap(obj, timeout=GRPC_TIMEOUT)
+            self.log.debug('asf-volt-sfp-presence-bit-map',
+                           sfp_presence_bitmap=sfp_presence_bitmap.bitmap)
+            returnValue(sfp_presence_bitmap.bitmap)
+        except Exception as e:
+            self.log.error('get-asfvolt-sfp-presence-map-failed', exc=str(e))
+            returnValue(sfp_presence_bitmap)
+
     def get_indication_info(self, device_id):
         while self.olt.running:
             try:
diff --git a/voltha/adapters/asfvolt16_olt/protos/asfvolt.proto b/voltha/adapters/asfvolt16_olt/protos/asfvolt.proto
index 71869b6..89af47f 100644
--- a/voltha/adapters/asfvolt16_olt/protos/asfvolt.proto
+++ b/voltha/adapters/asfvolt16_olt/protos/asfvolt.proto
@@ -34,8 +34,20 @@
     string onie_version      = 13;
 }
 
+message AsfSfpPresenceBitmap {
+    // This is a bit map of SFP presence.
+    // 0th bit is PON0
+    // 15th bit is PON15
+    // 16th bit is NNI0
+    // 19th bit is NNI3
+    // In total there are 16 PON ports and 4 NNI ports
+    uint32 bitmap = 1;
+}
+
 service Asfvolt {
     /* The grpc-c doesn't accept empty parameters in the RPC request.
        Hence filling in some default message in the RPC request */
     rpc AsfvoltGetSystemInfo(BalDefault) returns(AsfSystemInfo) {};
+    rpc AsfvoltGetSfpPresenceBitmap(BalDefault) returns(AsfSfpPresenceBitmap) {};
+
 }