VOL-638 - Support running data traffic through multiple ONUs on different PON ports of ASFvolt16 OLT

Change-Id: Idaa9940d8c34d2823db86f4e9a59db5dcb022e54
diff --git a/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py b/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
index c31bcfa..cf0cf60 100644
--- a/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
+++ b/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
@@ -65,6 +65,9 @@
 # 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.
+
+ASFVOLT16_MAX_PON_PORT_ID = 15
+
 # TODO: VLAN ID needs to come from some sort of configuration.
 ASFVOLT16_DEFAULT_VLAN = 4091
 PACKET_IN_VLAN = 4091
@@ -187,6 +190,7 @@
         self.ont_anis = dict()
         self.v_enets = dict()
         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
@@ -273,6 +277,46 @@
             return ports[0]
         return None
 
+    def create_pon_id_and_gem_port_to_uni_port_map(self,
+                                                   gem_port, v_enet
+                                                   ):
+        v_enet_name = v_enet.v_enet.name
+        v_ont_ani = self.v_ont_anis[v_enet.v_enet.data.v_ontani_ref]
+        pon_id = -1
+        # if the v_ont_ani and the channel_termination refer to the same
+        # channel_pair, we have the right channel_termination. We pick the
+        # xgs_ponid from this channel_termination.
+        for channel_termination in self.channel_terminations.itervalues():
+            if v_ont_ani.v_ont_ani.data.preferred_chanpair == \
+                   channel_termination.data.channelpair_ref:
+                pon_id = channel_termination.data.xgs_ponid
+                self.pon_id_gem_port_to_v_enet_name[(pon_id, gem_port)] = v_enet_name
+                self.log.debug("entry-created", pon_id=pon_id,
+                                gem_port=gem_port, v_enet_name=v_enet_name)
+                break
+        if pon_id < 0:
+            raise Exception("pon-id-gem-port-to-uni-port-map-creation-failed")
+
+    def delete_pon_id_and_gem_port_to_uni_port_map(self,
+                                                   gem_port, v_enet
+                                                   ):
+        v_enet_name = v_enet.v_enet.name
+        v_ont_ani = self.v_ont_anis[v_enet.v_enet.data.v_ontani_ref]
+        pon_id = -1
+        # if the v_ont_ani and the channel_termination refer to the same
+        # channel_pair, we have the right channel_termination. We pick the
+        # xgs_ponid from this channel_termination.
+        for channel_termination in self.channel_terminations.itervalues():
+            if v_ont_ani.v_ont_ani.data.preferred_chanpair == \
+                   channel_termination.data.channelpair_ref:
+                pon_id = channel_termination.data.xgs_ponid
+                del self.pon_id_gem_port_to_v_enet_name[(pon_id, gem_port)]
+                self.log.debug("entry-deleted", pon_id=pon_id,
+                                gem_port=gem_port, v_enet_name=v_enet_name)
+                break
+        if pon_id < 0:
+            raise Exception("pon-id-gem-port-to-uni-port-map-deletion-failed")
+
     def store_flows(self, uplink_classifier, uplink_action,
                     v_enet, traffic_class):
         flow = FlowInfo()
@@ -316,6 +360,15 @@
            logical_port = uni.port_no
         return logical_port
 
+    def get_logical_port_from_pon_id_and_gem_port(self, pon_id, gem_port):
+        v_enet_name = self.pon_id_gem_port_to_v_enet_name[(pon_id, gem_port)]
+        ports = self.adapter_agent.get_ports(self.device_id, Port.ETHERNET_UNI)
+        if ports is not None:
+            for port in ports:
+                if port.label == v_enet_name:
+                    return port.port_no
+        return None
+
     def activate(self, device):
 
         self.log.info('activating-asfvolt16-olt', device=device)
@@ -378,12 +431,8 @@
 
         try:
             # Establishing connection towards OLT
-            self.bal.connect_olt(device.host_and_port, self.device_id,is_init=False)
-            device.connect_status = ConnectStatus.REACHABLE
-            device.oper_status = OperStatus.ACTIVE
-            self.adapter_agent.update_device(device)
+            self.bal.connect_olt(device.host_and_port, self.device_id, is_init=False)
             reactor.callInThread(self.bal.get_indication_info, self.device_id)
-
         except Exception as e:
             self.log.exception('device-unreachable', error=e)
             device.connect_status = ConnectStatus.UNREACHABLE
@@ -401,11 +450,9 @@
             self.log.info("initial-pm-config", pm_config=pm_config)
             self.adapter_agent.update_device_pm_config(pm_config,init=True)
 
-
             # Apply the PM configuration
             self.update_pm_config(device, pm_config)
 
-
             # Request PM counters from OLT device.
             self._handle_pm_counter_req_towards_device(device)
 
@@ -421,8 +468,12 @@
         # Reconcile child devices
         self.log.info("reconcile-all-child-devices")
         self.adapter_agent.reconcile_child_devices(device.id)
-        self.log.info('reconciling-asfvolt16-device-ends',device=device)
 
+        device.connect_status = ConnectStatus.REACHABLE
+        device.oper_status = OperStatus.ACTIVE
+        self.adapter_agent.update_device(device)
+
+        self.log.info('reconciling-asfvolt16-device-ends',device=device)
 
     @inlineCallbacks
     def heartbeat(self, state = 'run'):
@@ -1044,7 +1095,7 @@
 
     def get_registration_id(self, data):
         if data.data.expected_registration_id is not None and \
-            len(data.data.expected_registration_id) > 0:
+           len(data.data.expected_registration_id) > 0:
             return self.hex_format(data.data.expected_registration_id)
         # default reg id
         return '202020202020202020202020202020202020202020202020202020202020202020202020'
@@ -1129,6 +1180,11 @@
                     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:
+                    raise ValueError\
+                        ("pon_id-%u-is-greater-than-%u"
+                         % (data.data.xgs_ponid, ASFVOLT16_MAX_PON_PORT_ID))
+
                 self.log.info('Activating-PON-port-at-OLT',
                               pon_id=data.data.xgs_ponid)
                 self.add_port(port_no=data.data.xgs_ponid,
@@ -1288,6 +1344,9 @@
             if data.gemport_id > 9215:
                 raise Exception('supported range for '
                                 'gem-port is from 1024 to 9215')
+            self.create_pon_id_and_gem_port_to_uni_port_map(
+                data.gemport_id, v_enet
+            )
             gem_port = GemportsConfigData()
             gem_port.CopyFrom(data)
             v_enet.gem_ports[data.name] = gem_port
@@ -1303,6 +1362,10 @@
         if data.itf_ref in self.v_enets:
             v_enet = self.v_enets[data.itf_ref]
             if data.name in v_enet.gem_ports:
+                gem_port = v_enet.gem_ports[data.name]
+                self.delete_pon_id_and_gem_port_to_uni_port_map(
+                    gem_port.gemport_id, v_enet
+                )
                 self.del_all_flow(v_enet)
                 del v_enet.gem_ports[data.name]
                 #To-Do Need to know what to do with flows.
@@ -1318,7 +1381,12 @@
 
     def handle_packet_in(self, ind_info):
         self.log.info('Received-Packet-In', ind_info=ind_info)
-        logical_port = self.get_logical_port_using_gem_port(ind_info['svc_port'])
+        logical_port = self.get_logical_port_from_pon_id_and_gem_port(
+            ind_info['intf_id'],
+            ind_info['svc_port'])
+        if not logical_port:
+            self.log.error("uni-logical_port-not-found")
+            return
         pkt = Ether(ind_info['packet'])
         kw = dict(
                   logical_device_id=self.logical_device_id,
@@ -1368,19 +1436,18 @@
                 self.log.info('Unable-to-find-logical-port-info',
                               logical_port_number=egress_port)
                 return
-            onu_device = None
+
             onu_device = self.adapter_agent.get_device(logical_port.device_id)
+
             if onu_device is None:
                 self.log.info('Unable-to-find-onu_device-info',
                               onu_device_id=logical_port.device_id)
                 return
+
             pkt_info['intf_id'] = onu_device.proxy_address.channel_id
             pkt_info['onu_id'] = onu_device.proxy_address.onu_id
-
             pkt_info['dest_type'] = 'onu'
-            #pkt_info['dest_type'] = 'gem_port'
-            if pkt_info['dest_type'] == 'gem_port':
-                pkt_info['gem_port'] = 1024
+
         self.bal.packet_out(send_pkt, pkt_info)
 
     def update_flow_table(self, flows):
@@ -1948,7 +2015,7 @@
             yield asleep(0.1)
         except Exception as e:
             self.log.exception('failed-to-install-downstream-flow', e=e,
-                               flow_id=flow_id,
+                               flow_id=downlink_flow_id,
                                onu_id=onu_device.proxy_address.onu_id,
                                intf_id=onu_device.proxy_address.channel_id)
 
@@ -2006,4 +2073,3 @@
 
         except Exception as e:
             raise Exception('option-parsing-error: {}'.format(e.message))
-