def clear_flows_and_scheduler_for_logical_port(self, child_device,
                                                   logical_port):
        ofp_port_name = logical_port.ofp_port.name
        port_no = logical_port.ofp_port.port_no
        pon_port = child_device.proxy_address.channel_id
        onu_id = child_device.proxy_address.onu_id
        uni_id = self.platform.uni_id_from_port_num(port_no)

        # TODO: The DEFAULT_TECH_PROFILE_ID is assumed. Right way to do,
        # is probably to maintain a list of Tech-profile table IDs associated
        # with the UNI logical_port. This way, when the logical port is
        # deleted, all the associated tech-profile configuration with the UNI
        # logical_port can be cleared.
        tp_id = self.resource_mgr.get_tech_profile_id_for_onu(pon_port, onu_id,
                                                              uni_id)
        tech_profile_instance = self.tech_profile[pon_port]. \
            get_tech_profile_instance(
            tp_id,
            ofp_port_name)
        flow_ids = self.resource_mgr.get_current_flow_ids(pon_port, onu_id,
                                                          uni_id)
        self.log.debug("outstanding-flows-to-be-cleared", flow_ids=flow_ids)
        for flow_id in flow_ids:
            flow_infos = self.resource_mgr.get_flow_id_info(pon_port, onu_id,
                                                            uni_id, flow_id)
            for flow_info in flow_infos:
                direction = flow_info['flow_type']
                flow_to_remove = openolt_pb2.Flow(flow_id=flow_id,
                                                  flow_type=direction)
                try:
                    self.stub.FlowRemove(flow_to_remove)
                except grpc.RpcError as grpc_e:
                    if grpc_e.code() == grpc.StatusCode.NOT_FOUND:
                        self.log.debug('This flow does not exist on switch, '
                                       'normal after an OLT reboot',
                                       flow=flow_to_remove)
                    else:
                        raise grpc_e

                self.resource_mgr.free_flow_id(pon_port, onu_id, uni_id,
                                               flow_id)

        try:
            tconts = self.tech_profile[pon_port].get_tconts(
                tech_profile_instance)
            self.stub.RemoveTconts(openolt_pb2.Tconts(intf_id=pon_port,
                                                      onu_id=onu_id,
                                                      uni_id=uni_id,
                                                      port_no=port_no,
                                                      tconts=tconts))
        except grpc.RpcError as grpc_e:
            self.log.error('error-removing-tcont-scheduler-queues',
                           err=grpc_e)
    def add_lldp_flow(self, logical_flow, port_no, network_intf_id=0):

        classifier = dict()
        classifier[ETH_TYPE] = LLDP_ETH_TYPE
        classifier[PACKET_TAG_TYPE] = UNTAGGED
        action = dict()
        action[TRAP_TO_HOST] = True

        # LLDP flow is installed to trap LLDP packets on the NNI port.
        # We manage flow_id resource pool on per PON port basis.
        # Since this situation is tricky, as a hack, we pass the NNI port
        # index (network_intf_id) as PON port Index for the flow_id resource
        # pool. Also, there is no ONU Id available for trapping LLDP packets
        # on NNI port, use onu_id as -1 (invalid)
        # ****************** CAVEAT *******************
        # This logic works if the NNI Port Id falls within the same valid
        # range of PON Port Ids. If this doesn't work for some OLT Vendor
        # we need to have a re-look at this.
        # *********************************************
        onu_id = -1
        uni_id = -1
        flow_store_cookie = self._get_flow_store_cookie(classifier)

        if self.resource_mgr.is_flow_cookie_on_kv_store(
                network_intf_id, onu_id, uni_id, flow_store_cookie):
            self.log.debug('flow-exists--not-re-adding')
        else:
            flow_id = self.resource_mgr.get_flow_id(
                network_intf_id, onu_id, uni_id, flow_store_cookie)

            downstream_flow = openolt_pb2.Flow(
                access_intf_id=-1,  # access_intf_id not required
                onu_id=onu_id,  # onu_id not required
                uni_id=uni_id,  # uni_id not used
                flow_id=flow_id,
                flow_type=DOWNSTREAM,
                network_intf_id=network_intf_id,
                gemport_id=-1,  # gemport_id not required
                classifier=self.mk_classifier(classifier),
                action=self.mk_action(action),
                priority=logical_flow.priority,
                port_no=port_no,
                cookie=logical_flow.cookie)

            self.log.debug('add lldp downstream trap', classifier=classifier,
                           action=action, flow=downstream_flow,
                           port_no=port_no)
            if self.add_flow_to_device(downstream_flow, logical_flow):
                flow_info = self._get_flow_info_as_json_blob(downstream_flow,
                                                             flow_store_cookie)
                self.update_flow_info_to_kv_store(
                    network_intf_id, onu_id, uni_id, flow_id, flow_info)
    def remove_flow(self, flow):
        self.log.debug('trying to remove flows from logical flow :',
                       logical_flow=flow)
        device_flows_to_remove = []
        device_flows = self.flows_proxy.get('/').items
        for f in device_flows:
            if f.cookie == flow.id:
                device_flows_to_remove.append(f)

        for f in device_flows_to_remove:
            (id, direction) = self.decode_stored_id(f.id)
            flow_to_remove = openolt_pb2.Flow(flow_id=id, flow_type=direction)
            try:
                self.stub.FlowRemove(flow_to_remove)
            except grpc.RpcError as grpc_e:
                if grpc_e.code() == grpc.StatusCode.NOT_FOUND:
                    self.log.debug('This flow does not exist on the switch, '
                                   'normal after an OLT reboot',
                                   flow=flow_to_remove)
                else:
                    raise grpc_e

            # once we have successfully deleted the flow on the device
            # release the flow_id on resource pool and also clear any
            # data associated with the flow_id on KV store.
            self._clear_flow_id_from_rm(f, id, direction)
            self.log.debug('flow removed from device', flow=f,
                           flow_key=flow_to_remove)

        if len(device_flows_to_remove) > 0:
            new_flows = []
            flows_ids_to_remove = [f.id for f in device_flows_to_remove]
            for f in device_flows:
                if f.id not in flows_ids_to_remove:
                    new_flows.append(f)

            self.flows_proxy.update('/', Flows(items=new_flows))
            self.log.debug('flows removed from the data store',
                           flow_ids_removed=flows_ids_to_remove,
                           number_of_flows_removed=(len(device_flows) - len(
                               new_flows)), expected_flows_removed=len(
                                   device_flows_to_remove))
        else:
            self.log.debug('no device flow to remove for this flow (normal '
                           'for multi table flows)', flow=flow)
    def add_dhcp_trap(self, intf_id, onu_id, uni_id, port_no, classifier,
                      action, logical_flow, alloc_id, gemport_id):

        self.log.debug('add dhcp upstream trap', classifier=classifier,
                       intf_id=intf_id, onu_id=onu_id, uni_id=uni_id,
                       action=action)

        action.clear()
        action[TRAP_TO_HOST] = True
        classifier[UDP_SRC] = 68
        classifier[UDP_DST] = 67
        classifier[PACKET_TAG_TYPE] = SINGLE_TAG
        classifier.pop(VLAN_VID, None)

        flow_store_cookie = self._get_flow_store_cookie(classifier,
                                                        gemport_id)
        if self.resource_mgr.is_flow_cookie_on_kv_store(intf_id, onu_id,
                                                        uni_id,
                                                        flow_store_cookie):
            self.log.debug('flow-exists--not-re-adding')
        else:
            flow_id = self.resource_mgr.get_flow_id(
                intf_id, onu_id, uni_id, flow_store_cookie
            )

            dhcp_flow = openolt_pb2.Flow(
                onu_id=onu_id, uni_id=uni_id, flow_id=flow_id,
                flow_type=UPSTREAM, access_intf_id=intf_id,
                gemport_id=gemport_id, alloc_id=alloc_id,
                network_intf_id=self.data_model.olt_nni_intf_id(),
                priority=logical_flow.priority,
                classifier=self.mk_classifier(classifier),
                action=self.mk_action(action),
                port_no=port_no,
                cookie=logical_flow.cookie)

            if self.add_flow_to_device(dhcp_flow, logical_flow):
                flow_info = self._get_flow_info_as_json_blob(dhcp_flow,
                                                             flow_store_cookie)
                self.update_flow_info_to_kv_store(dhcp_flow.access_intf_id,
                                                  dhcp_flow.onu_id,
                                                  dhcp_flow.uni_id,
                                                  dhcp_flow.flow_id,
                                                  flow_info)
    def add_hsia_flow(self, intf_id, onu_id, uni_id, port_no, classifier,
                      action, direction, logical_flow, alloc_id, gemport_id):

        flow_store_cookie = self._get_flow_store_cookie(classifier,
                                                        gemport_id)

        if self.resource_mgr.is_flow_cookie_on_kv_store(intf_id, onu_id,
                                                        uni_id,
                                                        flow_store_cookie):
            self.log.debug('flow-exists--not-re-adding')
        else:

            # One of the OLT platform (Broadcom BAL) requires that symmetric
            # flows require the same flow_id to be used across UL and DL.
            # Since HSIA flow is the only symmetric flow currently, we need to
            # re-use the flow_id across both direction. The 'flow_category'
            # takes priority over flow_cookie to find any available HSIA_FLOW
            # id for the ONU.
            flow_id = self.resource_mgr.get_flow_id(intf_id, onu_id, uni_id,
                                                    flow_store_cookie,
                                                    HSIA_FLOW)
            if flow_id is None:
                self.log.error("hsia-flow-unavailable")
                return

            flow = openolt_pb2.Flow(
                access_intf_id=intf_id, onu_id=onu_id, uni_id=uni_id,
                flow_id=flow_id, flow_type=direction, alloc_id=alloc_id,
                network_intf_id=self.data_model.olt_nni_intf_id(),
                gemport_id=gemport_id,
                classifier=self.mk_classifier(classifier),
                action=self.mk_action(action), priority=logical_flow.priority,
                port_no=port_no, cookie=logical_flow.cookie)

            if self.add_flow_to_device(flow, logical_flow):
                flow_info = self._get_flow_info_as_json_blob(flow,
                                                             flow_store_cookie,
                                                             HSIA_FLOW)
                self.update_flow_info_to_kv_store(flow.access_intf_id,
                                                  flow.onu_id, flow.uni_id,
                                                  flow.flow_id, flow_info)
    def add_eapol_flow(self, intf_id, onu_id, uni_id, port_no, logical_flow,
                       alloc_id, gemport_id, vlan_id=DEFAULT_MGMT_VLAN):

        uplink_classifier = dict()
        uplink_classifier[ETH_TYPE] = EAP_ETH_TYPE
        uplink_classifier[PACKET_TAG_TYPE] = SINGLE_TAG
        uplink_classifier[VLAN_VID] = vlan_id

        uplink_action = dict()
        uplink_action[TRAP_TO_HOST] = True

        flow_store_cookie = self._get_flow_store_cookie(uplink_classifier,
                                                        gemport_id)

        if self.resource_mgr.is_flow_cookie_on_kv_store(intf_id, onu_id,
                                                        uni_id,
                                                        flow_store_cookie):
            self.log.debug('flow-exists--not-re-adding')
        else:
            # Add Upstream EAPOL Flow.
            uplink_flow_id = self.resource_mgr.get_flow_id(
                intf_id, onu_id, uni_id, flow_store_cookie
            )

            upstream_flow = openolt_pb2.Flow(
                access_intf_id=intf_id, onu_id=onu_id, uni_id=uni_id,
                flow_id=uplink_flow_id, flow_type=UPSTREAM, alloc_id=alloc_id,
                network_intf_id=self.data_model.olt_nni_intf_id(),
                gemport_id=gemport_id,
                classifier=self.mk_classifier(uplink_classifier),
                action=self.mk_action(uplink_action),
                priority=logical_flow.priority,
                port_no=port_no,
                cookie=logical_flow.cookie)

            logical_flow = copy.deepcopy(logical_flow)
            logical_flow.match.oxm_fields.extend(fd.mk_oxm_fields([fd.vlan_vid(
                vlan_id | 0x1000)]))
            logical_flow.match.type = OFPMT_OXM

            if self.add_flow_to_device(upstream_flow, logical_flow):
                flow_info = self._get_flow_info_as_json_blob(upstream_flow,
                                                             flow_store_cookie)
                self.update_flow_info_to_kv_store(upstream_flow.access_intf_id,
                                                  upstream_flow.onu_id,
                                                  upstream_flow.uni_id,
                                                  upstream_flow.flow_id,
                                                  flow_info)

        if vlan_id == DEFAULT_MGMT_VLAN:
            # Add Downstream EAPOL Flow, Only for first EAP flow (BAL
            # requirement)
            # On one of the platforms (Broadcom BAL), when same DL classifier
            # vlan was used across multiple ONUs, eapol flow re-adds after
            # flow delete (cases of onu reboot/disable) fails.
            # In order to generate unique vlan, a combination of intf_id
            # onu_id and uni_id is used.
            # uni_id defaults to 0, so add 1 to it.
            special_vlan_downstream_flow = 4090 - intf_id * onu_id * (uni_id+1)
            # Assert that we do not generate invalid vlans under no condition
            assert special_vlan_downstream_flow >= 2

            downlink_classifier = dict()
            downlink_classifier[PACKET_TAG_TYPE] = SINGLE_TAG
            downlink_classifier[VLAN_VID] = special_vlan_downstream_flow

            downlink_action = dict()
            downlink_action[PUSH_VLAN] = True
            downlink_action[VLAN_VID] = vlan_id

            flow_store_cookie = self._get_flow_store_cookie(
                downlink_classifier, gemport_id)
            if self.resource_mgr.is_flow_cookie_on_kv_store(
                    intf_id, onu_id, uni_id, flow_store_cookie):
                self.log.debug('flow-exists--not-re-adding')
            else:

                downlink_flow_id = self.resource_mgr.get_flow_id(
                    intf_id, onu_id, uni_id, flow_store_cookie
                )

                downstream_flow = openolt_pb2.Flow(
                    access_intf_id=intf_id, onu_id=onu_id, uni_id=uni_id,
                    flow_id=downlink_flow_id, flow_type=DOWNSTREAM,
                    alloc_id=alloc_id,
                    network_intf_id=self.data_model.olt_nni_intf_id(),
                    gemport_id=gemport_id,
                    classifier=self.mk_classifier(downlink_classifier),
                    action=self.mk_action(downlink_action),
                    priority=logical_flow.priority,
                    port_no=port_no,
                    cookie=logical_flow.cookie)

                downstream_logical_flow = ofp_flow_stats(
                    id=logical_flow.id, cookie=logical_flow.cookie,
                    table_id=logical_flow.table_id,
                    priority=logical_flow.priority, flags=logical_flow.flags)

                downstream_logical_flow.match.oxm_fields.extend(
                    fd.mk_oxm_fields(
                        [fd.in_port(fd.get_out_port(logical_flow)),
                         fd.vlan_vid(special_vlan_downstream_flow | 0x1000)]))
                downstream_logical_flow.match.type = OFPMT_OXM

                downstream_logical_flow.instructions.extend(
                    fd.mk_instructions_from_actions([fd.output(
                        self.platform.mk_uni_port_num(intf_id, onu_id,
                                                      uni_id))]))

                if self.add_flow_to_device(downstream_flow,
                                           downstream_logical_flow):
                    flow_info = self._get_flow_info_as_json_blob(
                        downstream_flow, flow_store_cookie)
                    self.update_flow_info_to_kv_store(
                        downstream_flow.access_intf_id, downstream_flow.onu_id,
                        downstream_flow.uni_id, downstream_flow.flow_id,
                        flow_info)