SEBA-728 Fix hardcoded UNI port for Ponsim

Change-Id: I5cb2d072c7c5e232c1c9813e3f7318e1346e0d41
diff --git a/tests/atests/build/Makefile b/tests/atests/build/Makefile
index 854c68e..f247d66 100644
--- a/tests/atests/build/Makefile
+++ b/tests/atests/build/Makefile
@@ -113,8 +113,6 @@
 	touch $@
 
 $(M)/simtype-bbsim: | $(M)/voltha-running
-	cd $(HOME)/cord/helm-charts; helm upgrade --install ponnet ponnet
-	$(HOME)/cord/helm-charts/scripts/wait_for_pods.sh kube-system
 	cd $(HOME)/cord/helm-charts; helm upgrade --install bbsim bbsim -f configs/seba-ponsim.yaml --set images.bbsim.repository=voltha/voltha-bbsim,images.bbsim.tag=latest,images.bbsim.pullPolicy=Never
 	touch $@
 
@@ -122,19 +120,17 @@
 	cd $(HOME)/cord/helm-charts; helm upgrade --install ponnet ponnet
 	$(HOME)/cord/helm-charts/scripts/wait_for_pods.sh kube-system
 	cd $(HOME)/cord/helm-charts; helm upgrade --install ponsimv2 ponsimv2 -f configs/seba-ponsim.yaml --set images.olt.repository=voltha-ponsim,images.olt.tag=latest,images.olt.pullPolicy=Never,images.onu.repository=voltha-ponsim,images.onu.tag=latest,images.onu.pullPolicy=Never,images.rg.repository=voltha-tester,images.rg.tag=latest,images.rg.pullPolicy=Never
-	touch $@
-
-$(M)/pon0-fwd: | $(M)/voltha-running
-	echo 8 > /tmp/pon0_group_fwd_mask
-	until sudo cp /tmp/pon0_group_fwd_mask /sys/class/net/pon0/bridge/group_fwd_mask; \
+	$(HOME)/cord/helm-charts/scripts/wait_for_pods.sh voltha
+	echo 8 > /tmp/group_fwd_mask
+	for bridge in /sys/class/net/pon*; \
 	do \
-		echo "waiting for pon0..."; \
-		sleep 5; \
+		echo Setting up forwarding on `basename $$bridge`; \
+		sudo cp /tmp/group_fwd_mask $$bridge/bridge/group_fwd_mask; \
 	done
-	rm /tmp/pon0_group_fwd_mask
+	rm /tmp/group_fwd_mask
 	touch $@
 	
-$(M)/voltha-simtype-running: | $(M)/pon0-fwd
+$(M)/voltha-simtype-running:
 	$(HOME)/cord/helm-charts/scripts/wait_for_pods.sh
 	until http -a karaf:karaf --ignore-stdin --check-status GET http://127.0.0.1:30120/onos/v1/configuration/org.opencord.olt.impl.Olt; \
 	do \
@@ -148,7 +144,7 @@
 
 remove-chart-milestones:
 	cd $(M); sudo rm -f setup kafka kafka-running onos voltha etcd-operator-ready etcd-cluster \
-		voltha-running simtype-ponsim simtype-bbsim pon0-fwd voltha-simtype-running
+		voltha-running simtype-ponsim simtype-bbsim voltha-simtype-running
 remove-kube-milestones:
 	cd $(M); sudo rm -f kubeadm helm-init
 
@@ -161,3 +157,5 @@
 	sudo rm -f /var/lib/cni/networks/pon*/* || true
 	sudo rm -f /var/lib/cni/networks/nni*/* || true
 	sudo rm -f /var/lib/cni/networks/k8s-pod-network/* || true
+	for br in /sys/class/net/pon*; do sudo ip link delete `basename $$br` type bridge; done || true
+	for br in /sys/class/net/nni*; do sudo ip link delete `basename $$br` type bridge; done || true
diff --git a/voltha/adapters/ponsim_olt/ponsim_olt.py b/voltha/adapters/ponsim_olt/ponsim_olt.py
index 2c32904..72b30f7 100644
--- a/voltha/adapters/ponsim_olt/ponsim_olt.py
+++ b/voltha/adapters/ponsim_olt/ponsim_olt.py
@@ -388,7 +388,6 @@
         self.pm_metrics = None
         self.alarms = None
         self.frames = None
-        self.uni_ports = []
         self.ctag_map = {}
 
     def __del__(self):
@@ -548,7 +547,6 @@
                 vlan=vlan_id,
                 serial_number=onu.serial_number
             )
-            self.uni_ports.append(int(onu.uni_port))
 
         if self.ponsim_comm == 'grpc':
             self.log.info('starting-frame-grpc-stream')
@@ -692,71 +690,73 @@
                 action.output.port = ofp.OFPP_CONTROLLER
                 self.log.info('sending flow to controller')
 
-    # Lookup subscriber ctag for a particular PON port
-    def get_subscriber_ctag(self, flows, port):
-        self.log.debug('looking from subscriber flow for port', port=port)
-
-        for flow in flows:
-            in_port = fd.get_in_port(flow)
-            out_port = fd.get_out_port(flow)
-            if in_port == port and out_port == self.nni_port.port_no:
-                fields = fd.get_ofb_fields(flow)
-                self.log.debug('subscriber flow found', fields=fields)
-                for field in fields:
-                    if field.type == fd.VLAN_VID:
-                        self.log.debug('subscriber ctag found',
-                                       vlan_id=field.vlan_vid)
-                        return field.vlan_vid & 0x0fff
-        self.log.debug('No subscriber flow found', port=port)
-        return None
-
     # Lookup UNI port for a particular subscriber ctag
     def get_subscriber_uni_port(self, ctag):
-        self.log.debug('get_subscriber_uni_port', ctag=ctag, ctag_map=self.ctag_map)
+        self.log.debug('get_subscriber_uni_port', ctag=ctag)
         c = int(ctag)
         if c in self.ctag_map:
             return self.ctag_map[c]
-        # return None
-        # HACK: temporarily pass atest
-        return int(128)
-
-    def clear_ctag_map(self):
-        self.ctag_map = {}
+        elif self.is_uni_port(c):
+            return c
+        self.log.debug('get_subscriber_uni_port: no mapping found', ctag=ctag, ctag_map=self.ctag_map)
+        return None
 
     def update_ctag_map(self, ctag, uni_port):
-        c = int(ctag)
-        u = int(uni_port)
-        if not self.is_uni_port(u):
-            self.log.warning('update_ctag_map: unknown UNI port', uni_port=u)
-        if c in self.ctag_map and self.ctag_map[c] != u:
-            self.log.warning('update_ctag_map: changing UNI port for ctag',
-                ctag=c, old=self.ctag_map[c], new=u)
-        self.ctag_map[c] = u
+        if ctag is None:
+            for (c, u) in self.ctag_map.iteritems():
+                if u == int(uni_port):
+                    self.log.debug('deleting ctag mapping', ctag=c, uni_port=u)
+                    del self.ctag_map[c]
+                    return
+        else:
+            c = int(ctag)
+            u = int(uni_port)
+            if not self.is_uni_port(u):
+                self.log.warning('unknown UNI port', uni_port=u)
+            if c in self.ctag_map:
+                if self.ctag_map[c] == u:
+                    return
+                else:
+                    self.log.warning('changing UNI port for ctag',
+                        ctag=c, old=self.ctag_map[c], new=u)
 
-    # Create a new flow that's a copy of the old flow but change the vlan_vid
-    # Used to create per-subscriber DHCP and EAPOL flows
-    def create_secondary_flow(self, flow, vlan_id):
-        secondary_flow = copy.deepcopy(flow)
-        for field in fd.get_ofb_fields(secondary_flow):
-            if field.type == fd.VLAN_VID:
-                field.vlan_vid = vlan_id | 0x1000
-        return secondary_flow
+            self.ctag_map[c] = u
+            self.log.debug('added mapping', ctag=c, uni_port=u)
 
     def is_uni_port(self, vlan_id):
-        return int(vlan_id) in self.uni_ports
+        for onu in self.adapter_agent.get_child_devices(self.device_id):
+            if onu.vlan == vlan_id:
+                return True
+        return False
 
-    def create_secondary_flows(self, trapflows, allflows, type):
-        secondary_flows = []
-        for vlan_vid, flow in trapflows.iteritems():
-            if self.is_uni_port(vlan_vid):
-                self.update_ctag_map(vlan_vid, vlan_vid)
-                ctag = self.get_subscriber_ctag(allflows, fd.get_in_port(flow))
-                if ctag is not None:
-                    self.update_ctag_map(ctag, vlan_vid)
-                    if ctag not in trapflows:
-                        self.log.info('add secondary %s flow' % type, ctag=ctag)
-                        secondary_flows.append(self.create_secondary_flow(flow, ctag))
-        return secondary_flows
+    def get_classifier_info(self, flow):
+        classifier_info = {}
+        for field in fd.get_ofb_fields(flow):
+            if field.type == fd.ETH_TYPE:
+                classifier_info[ETH_TYPE] = field.eth_type
+            elif field.type == fd.IP_PROTO:
+                classifier_info[IP_PROTO] = field.ip_proto
+            elif field.type == fd.IN_PORT:
+                classifier_info[IN_PORT] = field.port
+            elif field.type == fd.VLAN_VID:
+                classifier_info[VLAN_VID] = field.vlan_vid & 0xfff
+            elif field.type == fd.VLAN_PCP:
+                classifier_info[VLAN_PCP] = field.vlan_pcp
+            elif field.type == fd.UDP_DST:
+                classifier_info[UDP_DST] = field.udp_dst
+            elif field.type == fd.UDP_SRC:
+                classifier_info[UDP_SRC] = field.udp_src
+            elif field.type == fd.IPV4_DST:
+                classifier_info[IPV4_DST] = field.ipv4_dst
+            elif field.type == fd.IPV4_SRC:
+                classifier_info[IPV4_SRC] = field.ipv4_src
+            elif field.type == fd.METADATA:
+                classifier_info[METADATA] = field.table_metadata
+            else:
+                self.log.debug('field-type-unhandled field.type={}'.format(
+                    field.type))
+
+        return classifier_info
 
     # VOLTHA's flow decomposition removes the information about which flows
     # are trap flows where traffic should be forwarded to the controller.
@@ -766,38 +766,11 @@
         stub = ponsim_pb2_grpc.PonSimStub(self.get_channel())
         self.log.info('pushing-olt-flow-table')
 
-        self.clear_ctag_map()
-        dhcp_upstream_flows = {}
         eapol_flows = {}
-        secondary_flows = []
         eapol_flow_without_vlan = False
 
         for flow in flows:
-            classifier_info = {}
-            for field in fd.get_ofb_fields(flow):
-                if field.type == fd.ETH_TYPE:
-                    classifier_info[ETH_TYPE] = field.eth_type
-                elif field.type == fd.IP_PROTO:
-                    classifier_info[IP_PROTO] = field.ip_proto
-                elif field.type == fd.IN_PORT:
-                    classifier_info[IN_PORT] = field.port
-                elif field.type == fd.VLAN_VID:
-                    classifier_info[VLAN_VID] = field.vlan_vid & 0xfff
-                elif field.type == fd.VLAN_PCP:
-                    classifier_info[VLAN_PCP] = field.vlan_pcp
-                elif field.type == fd.UDP_DST:
-                    classifier_info[UDP_DST] = field.udp_dst
-                elif field.type == fd.UDP_SRC:
-                    classifier_info[UDP_SRC] = field.udp_src
-                elif field.type == fd.IPV4_DST:
-                    classifier_info[IPV4_DST] = field.ipv4_dst
-                elif field.type == fd.IPV4_SRC:
-                    classifier_info[IPV4_SRC] = field.ipv4_src
-                elif field.type == fd.METADATA:
-                    classifier_info[METADATA] = field.table_metadata
-                else:
-                    self.log.debug('field-type-unhandled field.type={}'.format(
-                        field.type))
+            classifier_info = self.get_classifier_info(flow)
 
             self.log.debug('classifier_info', classifier_info=classifier_info)
 
@@ -806,8 +779,6 @@
                     if UDP_SRC in classifier_info:
                         if classifier_info[UDP_SRC] == 68:
                             self.log.info('dhcp upstream flow add')
-                            if VLAN_VID in classifier_info:
-                                dhcp_upstream_flows[classifier_info[VLAN_VID]] = flow
                         elif classifier_info[UDP_SRC] == 67:
                             self.log.info('dhcp downstream flow add')
                     self.to_controller(flow)
@@ -826,11 +797,6 @@
                     else:
                         eapol_flow_without_vlan = True
 
-            self.log.info('out_port', out_port=fd.get_out_port(flow))
-
-        flows.extend(self.create_secondary_flows(dhcp_upstream_flows, flows, "DHCP"))
-        flows.extend(self.create_secondary_flows(eapol_flows, flows, "EAPOL"))
-
         # The OLT app is now adding EAPOL flows with VLAN_VID=4091 but Ponsim can't
         # properly handle this because it uses VLAN_VID to encode the UNI port ID.
         # Add an EAPOL trap flow with no VLAN_VID match if we see the 4091 match.
@@ -845,8 +811,6 @@
             flows.extend(new_eapol_flow)
             self.log.info('add eapol flow with no VLAN_VID match')
 
-        self.log.debug('ctag_map', ctag_map=self.ctag_map)
-
         stub.UpdateFlowTable(FlowTable(
             port=0,
             flows=flows
@@ -875,6 +839,33 @@
         if isinstance(msg, FlowTable):
             stub = ponsim_pb2_grpc.PonSimStub(self.get_channel())
             self.log.info('pushing-onu-flow-table', port=msg.port)
+
+            # Extract ctag -> uni_port mapping from ONU flows.
+            # Below we assume that a downstream flow whose VLAN_VID is not
+            # equal to the logcal port is stripping the ctag.
+            # If we find such a flow we add the mapping to the ctag_map.
+            # Note that this wouldn't be necessary if we actually knew the logical
+            # port that an upstream packet arrived on.
+            logical_port_id = "uni-{}".format(msg.port)
+            logical_port = self.adapter_agent.get_logical_port(self.logical_device_id, logical_port_id)
+            if logical_port:
+                uni_port_id = logical_port.device_port_no
+                ctag = None
+
+                for flow in msg.flows:
+                    classifier_info = self.get_classifier_info(flow)
+                    self.log.debug('classifier_info', classifier_info=classifier_info)
+
+                    if VLAN_VID in classifier_info and IN_PORT in classifier_info:
+                        if classifier_info[IN_PORT] != uni_port_id and classifier_info[VLAN_VID] != msg.port:
+                            if ctag is not None:
+                                self.log.error('more than one ctag inferred', ctag1=ctag, ctag2=classifier_info[VLAN_VID])
+                            ctag = classifier_info[VLAN_VID]
+
+                self.update_ctag_map(ctag, msg.port)
+            else:
+                self.log.error('no logical port found', id=logical_port_id)
+
             res = stub.UpdateFlowTable(msg)
             self.adapter_agent.receive_proxied_message(proxy_address, res)
         elif isinstance(msg, PonSimMetricsRequest):