text: # Copyright 2015 Huawei.
  # Copyright 2016 Red Hat, Inc.
  # Copyright 2017 Intel Corporation.
  #
  # 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 neutron_lib import constants as n_consts
  from oslo_config import cfg
  from oslo_log import log as logging
  
  from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
      as ovs_consts
  from neutron.plugins.ml2.drivers.openvswitch.agent import vlanmanager
  
  from networking_sfc.services.sfc.agent.extensions import sfc
  from networking_sfc.services.sfc.common import ovs_ext_lib
  from networking_sfc.services.sfc.drivers.ovs import constants
  
  LOG = logging.getLogger(__name__)
  
  cfg.CONF.import_group('OVS', 'neutron.plugins.ml2.drivers.openvswitch.agent.'
                               'common.config')
  
  # This table is used to process the traffic across differet subnet scenario.
  # Flow 1: pri=1, ip,dl_dst=nexthop_mac,nw_src=nexthop_subnet. actions=
  # push_mpls:0x8847,set_mpls_label,set_mpls_ttl,push_vlan,output:(patch port
  # or resubmit to table(INGRESS_TABLE)
  # Flow 2: pri=0, ip,dl_dst=nexthop_mac,, action=push_mpls:0x8847,
  # set_mpls_label,set_mpls_ttl,push_vlan,output:(patch port or resubmit to
  # table(INGRESS_TABLE)
  ACROSS_SUBNET_TABLE = 5
  
  # The table has multiple flows that steer traffic for the different chains
  # to the ingress port of different service functions hosted on this Compute
  # node.
  INGRESS_TABLE = 10
  
  # port chain default flow rule priority
  PC_DEF_PRI = 20
  PC_INGRESS_PRI = 30
  
  # Reverse group number offset for dump_group
  REVERSE_GROUP_NUMBER_OFFSET = 7000
  
  TAP_CLASSIFIER_TABLE = 7
  # This table floods TAP packets on tunnel ports
  TAP_TUNNEL_OUTPUT_TABLE = 25
  
  # actions
  RESUBMIT_TAP_TABLE = ',resubmit(,%s)' % TAP_CLASSIFIER_TABLE
  NORMAL_ACTION = ",NORMAL"
  
  
  class SfcOVSAgentDriver(sfc.SfcAgentDriver):
      """This class will support MPLS frame
  
      Ethernet + MPLS
      IPv4 Packet:
      +-------------------------------+---------------+--------------------+
      |Outer Ethernet, ET=0x8847      | MPLS head,    | original IP Packet |
      +-------------------------------+---------------+--------------------+
      """
  
      def __init__(self):
          super(SfcOVSAgentDriver, self).__init__()
          self.agent_api = None
          self.br_int = None
          self.br_tun = None
  
          self.local_ip = None
          self.patch_tun_ofport = None
          self.vlan_manager = None
  
      def consume_api(self, agent_api):
          self.agent_api = agent_api
  
      def initialize(self):
          self.br_int = ovs_ext_lib.SfcOVSBridgeExt(
              self.agent_api.request_int_br())
  
          self.local_ip = cfg.CONF.OVS.local_ip
          self.patch_tun_ofport = self.br_int.get_port_ofport(
              cfg.CONF.OVS.int_peer_patch_port)
          self.vlan_manager = vlanmanager.LocalVlanManager()
  
          self._clear_sfc_flow_on_int_br()
  
          self.br_tun = ovs_ext_lib.SfcOVSBridgeExt(
              self.agent_api.request_tun_br())
          self.patch_int_ofport = self.br_tun.get_port_ofport(
              cfg.CONF.OVS.tun_peer_patch_port)
          self._clear_sfc_flow_on_tun_br()
  
      def update_flow_rules(self, flowrule, flowrule_status):
          if flowrule['fwd_path'] is False and flowrule['node_type'] == \
                  'sf_node':
              flowrule['ingress'], flowrule['egress'] = flowrule['egress'], \
                  flowrule['ingress']
          try:
              LOG.debug('update_flow_rule, flowrule = %s', flowrule)
  
              if flowrule.get('egress', None):
                  self._setup_egress_flow_rules(flowrule)
              if flowrule.get('ingress', None) and not flowrule.get(
                      'skip_ingress_flow_config', None):
                  self._setup_ingress_flow_rules(flowrule)
              flowrule_status_temp = {'id': flowrule['id'],
                                      'status': constants.STATUS_ACTIVE}
              flowrule_status.append(flowrule_status_temp)
          except Exception as e:
              flowrule_status_temp = {'id': flowrule['id'],
                                      'status': constants.STATUS_ERROR}
              flowrule_status.append(flowrule_status_temp)
              LOG.exception(e)
              LOG.error("update_flow_rules failed")
  
      def delete_flow_rule(self, flowrule, flowrule_status):
          if flowrule['fwd_path'] is False and flowrule['node_type'] == \
                  'sf_node':
              flowrule['ingress'], flowrule['egress'] = flowrule['egress'], \
                  flowrule['ingress']
          try:
              LOG.debug("delete_flow_rule, flowrule = %s", flowrule)
              pc_corr = flowrule['pc_corr']
  
              # delete tunnel table flow rule on br-int(egress match)
              if flowrule['egress'] is not None:
                  self._setup_local_switch_flows_on_int_br(flowrule,
                                                           flowrule['del_fcs'],
                                                           None,
                                                           add_flow=False,
                                                           match_inport=True)
                  # delete group table, need to check again
                  group_id = flowrule.get('next_group_id', None)
                  if group_id and flowrule.get('group_refcnt', None) <= 1:
                      if flowrule['fwd_path']:
                          self.br_int.delete_group(group_id=group_id)
                      else:
                          self.br_int.delete_group(group_id=group_id +
                                                   REVERSE_GROUP_NUMBER_OFFSET)
                      self._delete_across_subnet_table_flows(flowrule)
  
              if flowrule['ingress'] is not None:
                  # delete table INGRESS_TABLE ingress match flow rule
                  # on br-int(ingress match)
                  vif_port = self.br_int.get_vif_port_by_id(flowrule['ingress'])
                  if vif_port:
                      # third, install br-int flow rule on table INGRESS_TABLE
                      # for ingress traffic
                      if pc_corr == 'mpls':
                          self._delete_flows_mpls(flowrule, vif_port)
                      elif pc_corr == 'nsh':
                          self._delete_flows_nsh(flowrule, vif_port)
          except Exception as e:
              flowrule_status_temp = {'id': flowrule['id'],
                                      'status': constants.STATUS_ERROR}
              flowrule_status.append(flowrule_status_temp)
              LOG.exception(e)
              LOG.error("delete_flow_rule failed")
  
      def _clear_sfc_flow_on_int_br(self):
          self.br_int.delete_group(group_id='all')
          self.br_int.delete_flows(table=ACROSS_SUBNET_TABLE)
          self.br_int.delete_flows(table=INGRESS_TABLE)
          self.br_int.delete_flows(table=TAP_CLASSIFIER_TABLE)
          self.br_int.install_goto(dest_table_id=INGRESS_TABLE,
                                   priority=PC_DEF_PRI,
                                   eth_type=constants.ETH_TYPE_MPLS)
          self.br_int.install_goto(dest_table_id=INGRESS_TABLE,
                                   priority=PC_DEF_PRI,
                                   eth_type=constants.ETH_TYPE_NSH)
          self.br_int.install_drop(table_id=INGRESS_TABLE)
  
      def _parse_flow_classifier(self, flow_classifier):
          eth_type, nw_proto, source_port_masks, destination_port_masks = (
              (None, ) * 4)
  
          if (not flow_classifier['source_port_range_min'] and
                  not flow_classifier['source_port_range_max']):
              # wildcard
              source_port_masks = ['0/0x0']
          elif not flow_classifier['source_port_range_min']:
              source_port_masks = ovs_ext_lib.get_port_mask(
                  1,
                  flow_classifier['source_port_range_max'])
          elif not flow_classifier['source_port_range_max']:
              source_port_masks = ovs_ext_lib.get_port_mask(
                  flow_classifier['source_port_range_min'],
                  65535)
          else:
              source_port_masks = ovs_ext_lib.get_port_mask(
                  flow_classifier['source_port_range_min'],
                  flow_classifier['source_port_range_max'])
  
          if (not flow_classifier['destination_port_range_min'] and
                  not flow_classifier['destination_port_range_max']):
              # wildcard
              destination_port_masks = ['0/0x0']
          elif not flow_classifier['destination_port_range_min']:
              destination_port_masks = ovs_ext_lib.get_port_mask(
                  1,
                  flow_classifier['destination_port_range_max'])
          elif not flow_classifier['destination_port_range_max']:
              destination_port_masks = ovs_ext_lib.get_port_mask(
                  flow_classifier['destination_port_range_min'],
                  65535)
          else:
              destination_port_masks = ovs_ext_lib.get_port_mask(
                  flow_classifier['destination_port_range_min'],
                  flow_classifier['destination_port_range_max'])
  
          if flow_classifier['ethertype'] == "IPv4":
              eth_type = constants.ETH_TYPE_IP
              if n_consts.PROTO_NAME_TCP == flow_classifier['protocol']:
                  nw_proto = n_consts.PROTO_NUM_TCP
              elif n_consts.PROTO_NAME_UDP == flow_classifier['protocol']:
                  nw_proto = n_consts.PROTO_NUM_UDP
              elif n_consts.PROTO_NAME_ICMP == flow_classifier['protocol']:
                  nw_proto = n_consts.PROTO_NUM_ICMP
              else:
                  nw_proto = None
          elif flow_classifier['ethertype'] == "IPv6":
              LOG.error("Current portchain agent doesn't support IPv6")
          else:
              LOG.error("invalid protocol input")
  
          return (eth_type, nw_proto,
                  source_port_masks,
                  destination_port_masks)
  
      def _get_flow_infos_from_flow_classifier(self, flow_classifier, flowrule):
          flow_infos = []
          nw_src, nw_dst, tp_src, tp_dst = ((None, ) * 4)
  
          if flow_classifier['ethertype'] != "IPv4":
              LOG.error("Current portchain agent only supports IPv4")
              return flow_infos
  
          # parse and transfer flow info to match field info
          eth_type, nw_proto, source_port_masks, destination_port_masks = (
              self._parse_flow_classifier(flow_classifier))
  
          if flowrule['fwd_path']:
              if flow_classifier['source_ip_prefix']:
                  nw_src = flow_classifier['source_ip_prefix']
              else:
                  nw_src = '0.0.0.0/0.0.0.0'
              if flow_classifier['destination_ip_prefix']:
                  nw_dst = flow_classifier['destination_ip_prefix']
              else:
                  nw_dst = '0.0.0.0/0.0.0.0'
          else:
              if flow_classifier['source_ip_prefix']:
                  nw_src = flow_classifier['destination_ip_prefix']
              else:
                  nw_src = '0.0.0.0/0.0.0.0'
              if flow_classifier['destination_ip_prefix']:
                  nw_dst = flow_classifier['source_ip_prefix']
              else:
                  nw_dst = '0.0.0.0/0.0.0.0'
  
          if source_port_masks and destination_port_masks:
              for destination_port in destination_port_masks:
                  for source_port in source_port_masks:
                      if flowrule['fwd_path']:
                          tp_src = '%s' % source_port
                          tp_dst = '%s' % destination_port
                      else:
                          tp_dst = '%s' % source_port
                          tp_src = '%s' % destination_port
                      flow_info = {'eth_type': eth_type,
                                   'nw_src': nw_src,
                                   'nw_dst': nw_dst,
                                   'tp_src': tp_src,
                                   'tp_dst': tp_dst}
                      if nw_proto:
                          flow_info['nw_proto'] = nw_proto
                      flow_infos.append(flow_info)
  
          return flow_infos
  
      def _get_flow_infos_from_flow_classifier_list(self, flow_classifier_list,
                                                    flowrule):
          flow_infos = []
  
          if not flow_classifier_list:
              return flow_infos
          for flow_classifier in flow_classifier_list:
              flow_infos.extend(
                  self._get_flow_infos_from_flow_classifier(flow_classifier,
                                                            flowrule))
  
          return flow_infos
  
      def _match_by_header(self, match_info, nsp, nsi):
          match_info['reg0'] = (nsp << 8) | nsi
          # on header-matching there's no in_port
          match_info.pop('in_port', None)
  
      def _setup_local_switch_flows_on_int_br(self, flowrule,
                                              flow_classifier_list, actions,
                                              add_flow=True, match_inport=True):
          inport_match = {}
          priority = PC_DEF_PRI
          # no pp_corr means that classification will not be based on encap
          pp_corr = flowrule.get('pp_corr')
          node_type = flowrule['node_type']
          branch_info = flowrule.get('branch_info')
          on_add = None
          flow_count = 1
          if branch_info and node_type == constants.SRC_NODE:
              # for branching, we need as many flows (per flow info) as branches
              # because we can't AND-match the same field in a single flow
              flow_count = len(branch_info.get('matches'))
              on_add = branch_info.get('on_add')
  
          if match_inport is True:
              egress_port = self.br_int.get_vif_port_by_id(flowrule['egress'])
              if egress_port:
                  inport_match = {'in_port': egress_port.ofport}
                  priority = PC_INGRESS_PRI
  
          for flow_info in self._get_flow_infos_from_flow_classifier_list(
                  flow_classifier_list, flowrule):
              match_info = dict(inport_match)
              match_info.update(flow_info)
              if node_type == constants.SF_NODE:
                  if pp_corr:
                      match_info = {'in_port': match_info['in_port']}
                      if pp_corr == 'mpls':
                          match_info = self._build_classification_match_sfc_mpls(
                              flowrule, match_info)
                      elif pp_corr == 'nsh':
                          match_info = self._build_classification_match_sfc_nsh(
                              flowrule, match_info)
  
              for i in range(flow_count):
                  if branch_info:
                      # for Service Graphs (branching):
                      nsp = branch_info['matches'][i][0]
                      nsi = branch_info['matches'][i][1]
                  if add_flow:
                      if on_add:
                          self._match_by_header(match_info, nsp, nsi)
                      self.br_int.add_flow(table=ovs_consts.LOCAL_SWITCHING,
                                           priority=priority,
                                           actions=actions,
                                           **match_info)
                  else:
                      if on_add is False:
                          self._match_by_header(match_info, nsp, nsi)
                      self.br_int.delete_flows(table=ovs_consts.LOCAL_SWITCHING,
                                               priority=priority,
                                               strict=True,
                                               **match_info)
  
      def _setup_egress_flow_rules(self, flowrule, match_inport=True):
          group_id = flowrule.get('next_group_id', None)
          next_hops = flowrule.get('next_hops', None)
          pc_corr = flowrule.get('pc_corr', 'mpls')
          pp_corr = flowrule.get('pp_corr', None)
          node_type = flowrule.get('node_type')
          next_hop_tap_enabled = None
          # if the group is not none, install the egress rule for this SF
          if group_id and next_hops:
              # 1st, install br-int flow rule on table ACROSS_SUBNET_TABLE
              # and group table
              buckets = []
              vlan = self._get_vlan_by_port(flowrule['egress'])
  
              if isinstance(next(iter(next_hops)), dict):
                  next_hop_tap_enabled = next_hops[0].get('tap_enabled')
  
              for item in next_hops:
                  # all next hops share same pp_corr, enforced by higher layers
                  pp_corr_nh = item.get('pp_corr', None)
                  if flowrule['fwd_path']:
                      bucket = (
                          'bucket=weight=%d, mod_dl_dst:%s, resubmit(,%d)' % (
                              item['weight'],
                              item['in_mac_address'],
                              ACROSS_SUBNET_TABLE))
                  else:
                      bucket = (
                          'bucket=weight=%d, mod_dl_dst:%s, resubmit(,%d)' % (
                              item['weight'],
                              item['mac_address'],
                              ACROSS_SUBNET_TABLE))
                  buckets.append(bucket)
                  subnet_actions_list = []
  
                  across_flow = "mod_vlan_vid:%d," % vlan
                  # the classic encapsulation of packets in ACROSS_SUBNET_TABLE
                  # is kept unchanged for the same scenarios, i.e. when the next
                  # hops don't support encapsulation and neither the current one.
                  if not pp_corr and pp_corr_nh is None:
                      if pc_corr == 'mpls':
                          push_encap = self._build_push_mpls(flowrule['nsp'],
                                                             flowrule['nsi'])
                      elif pc_corr == 'nsh':
                          push_encap = self._build_push_nsh(flowrule['nsp'],
                                                            flowrule['nsi'])
                      across_flow = push_encap + across_flow
  
                  subnet_actions_list.append(across_flow)
  
                  if item['local_endpoint'] == self.local_ip:
                      subnet_actions = 'resubmit(,%d)' % INGRESS_TABLE
                  else:
                      # same subnet with next hop
                      subnet_actions = 'output:%s' % self.patch_tun_ofport
                  subnet_actions_list.append(subnet_actions)
  
                  eth_type = constants.ETH_TYPE_IP
                  if pp_corr == 'mpls' or pp_corr_nh == 'mpls':
                      eth_type = constants.ETH_TYPE_MPLS
                  elif pp_corr == 'nsh' or pp_corr_nh == 'nsh':
                      eth_type = constants.ETH_TYPE_NSH
  
                  if item.get('tap_enabled'):
                      self._add_tap_classification_flows(flowrule,
                                                         item,
                                                         subnet_actions_list)
                  else:
                      self._configure_across_subnet_flow(flowrule,
                                                         item,
                                                         subnet_actions_list,
                                                         eth_type)
  
              if not next_hop_tap_enabled:
                  self._add_group_table(buckets, flowrule, group_id)
  
              # 2nd, install br-int flow rule on table 0 for egress traffic
              enc_actions = ""
              # we only encapsulate on table 0 if we know the next hops will
              # support that encapsulation but the current hop doesn't already.
              if not pp_corr and pp_corr_nh:
                  if pc_corr == 'mpls':
                      enc_actions = self._build_push_mpls(flowrule['nsp'],
                                                          flowrule['nsi'])
                  elif pc_corr == 'nsh':
                      enc_actions = self._build_push_nsh(flowrule['nsp'],
                                                         flowrule['nsi'])
              if flowrule['fwd_path']:
                  enc_actions += "group:%d" % group_id
              else:
                  rev_group_id = group_id + REVERSE_GROUP_NUMBER_OFFSET
                  enc_actions += "group:%d" % rev_group_id
  
              enc_actions = self._update_enc_actions(enc_actions, flowrule,
                                                     next_hop_tap_enabled)
              # to uninstall the removed flow classifiers
              self._setup_local_switch_flows_on_int_br(
                  flowrule,
                  flowrule['del_fcs'],
                  None,
                  add_flow=False,
                  match_inport=match_inport)
              # to install the added flow classifiers
              self._setup_local_switch_flows_on_int_br(
                  flowrule,
                  flowrule['add_fcs'],
                  enc_actions,
                  add_flow=True,
                  match_inport=match_inport)
          else:
              end_of_chain_actions = 'normal'
              # at the end of the chain, the header must be removed (if used)
              if (node_type != constants.SRC_NODE) and pp_corr:
                  branch_point = flowrule.get('branch_point')
                  if branch_point:
                      nsp = flowrule['nsp']
                      nsi = flowrule['nsi']
                      sfpi = (nsp << 8) | nsi
                      if pc_corr == 'mpls':
                          end_of_chain_actions = (
                              'load:%s->NXM_NX_REG0[],'
                              'pop_mpls:0x%04x,resubmit(,0)' % (
                                  hex(sfpi), constants.ETH_TYPE_IP))
                      elif pc_corr == 'nsh':
                          end_of_chain_actions = (
                              "load:%s->NXM_NX_REG0[],"
                              "decap(),decap(),resubmit(,0)" % (
                                  hex(sfpi)))
                  else:
                      if pc_corr == 'mpls':
                          end_of_chain_actions = ("pop_mpls:0x%04x,%s" % (
                                                  constants.ETH_TYPE_IP,
                                                  end_of_chain_actions))
                      elif pc_corr == 'nsh':
                          end_of_chain_actions = ("decap(),decap(),%s" % (
                                                  end_of_chain_actions))
  
              if flowrule.get('tap_enabled'):
                  end_of_chain_actions += RESUBMIT_TAP_TABLE
  
              # to uninstall the removed flow classifiers
              if 'del_fcs' in flowrule:
                  self._setup_local_switch_flows_on_int_br(
                      flowrule,
                      flowrule['del_fcs'],
                      None,
                      add_flow=False,
                      match_inport=True)
  
              if 'add_fcs' in flowrule:
                  # to install the added flow classifiers
                  self._setup_local_switch_flows_on_int_br(
                      flowrule,
                      flowrule['add_fcs'],
                      actions=end_of_chain_actions,
                      add_flow=True,
                      match_inport=True)
  
      def _get_vlan_by_port(self, port_id):
          try:
              net_uuid = self.vlan_manager.get_net_uuid(port_id)
              return self.vlan_manager.get(net_uuid).vlan
          except (vlanmanager.VifIdNotFound, vlanmanager.MappingNotFound):
              return None
  
      def _setup_ingress_flow_rules(self, flowrule):
          vif_port = self.br_int.get_vif_port_by_id(flowrule['ingress'])
          if vif_port:
              vlan = self._get_vlan_by_port(flowrule['ingress'])
              pc_corr = flowrule['pc_corr']
              pp_corr = flowrule['pp_corr']
  
              # install br-int flow rule on table 0 for ingress traffic
              # install an SFC Proxy if the port pair doesn't support the
              # SFC encapsulation (pc_corr) specified in the chain
              if pc_corr == 'mpls':
                  if flowrule.get('tap_enabled'):
                      return self._add_tap_ingress_flow(flowrule, vif_port, vlan)
                  if pp_corr is None:
                      match_field = self._build_proxy_sfc_mpls(flowrule,
                                                               vif_port, vlan)
                  elif pp_corr == 'mpls':
                      match_field = self._build_forward_sfc_mpls(flowrule,
                                                                 vif_port, vlan)
              elif pc_corr == 'nsh':
                  if pp_corr is None:
                      match_field = self._build_proxy_sfc_nsh(flowrule,
                                                              vif_port, vlan)
                  elif pp_corr == 'nsh':
                      match_field = self._build_forward_sfc_nsh(flowrule,
                                                                vif_port, vlan)
              self.br_int.add_flow(**match_field)
  
      def _build_classification_match_sfc_mpls(self, flowrule, match_info):
          match_info['eth_type'] = constants.ETH_TYPE_MPLS
          match_info['mpls_label'] = flowrule['nsp'] << 8 | flowrule['nsi']
          return match_info
  
      def _build_classification_match_sfc_nsh(self, flowrule, match_info):
          match_info['eth_type'] = constants.ETH_TYPE_NSH
          match_info['nsh_mdtype'] = 1
          match_info['nsh_spi'] = flowrule['nsp']
          match_info['nsh_si'] = flowrule['nsi']
          return match_info
  
      def _build_push_mpls(self, nsp, nsi):
          return (
              "push_mpls:0x%04x,"
              "set_mpls_label:%d,"
              "set_mpls_ttl:%d," %
              (constants.ETH_TYPE_MPLS, nsp << 8 | nsi, nsi))
  
      def _build_push_nsh(self, nsp, nsi):
          return (
              "encap(nsh,prop(class=nsh,type=md_type,val=1)),"
              "set_field:%s->nsh_spi,set_field:%s->nsh_si,"
              "encap(ethernet)," %
              (hex(nsp), hex(nsi)))
  
      def _build_ingress_common_match_field(self, vif_port, vlan):
          return dict(
              table=INGRESS_TABLE,
              priority=1,
              dl_dst=vif_port.vif_mac,
              dl_vlan=vlan)
  
      def _build_ingress_match_field_sfc_mpls(self, flowrule, vif_port, vlan):
          match_field = self._build_ingress_common_match_field(vif_port, vlan)
          match_field['eth_type'] = constants.ETH_TYPE_MPLS
          match_field['mpls_label'] = flowrule['nsp'] << 8 | flowrule['nsi'] + 1
          return match_field
  
      def _build_ingress_match_field_sfc_nsh(self, flowrule, vif_port, vlan):
          match_field = self._build_ingress_common_match_field(vif_port, vlan)
          match_field['eth_type'] = constants.ETH_TYPE_NSH
          match_field['nsh_mdtype'] = 1
          match_field['nsh_spi'] = flowrule['nsp']
          match_field['nsh_si'] = flowrule['nsi'] + 1
          return match_field
  
      def _build_proxy_sfc_mpls(self, flowrule, vif_port, vlan):
          match_field = self._build_ingress_match_field_sfc_mpls(
              flowrule, vif_port, vlan)
          actions = ("strip_vlan, pop_mpls:0x%04x,"
                     "output:%s" % (constants.ETH_TYPE_IP, vif_port.ofport))
          match_field['actions'] = actions
          return match_field
  
      def _build_proxy_sfc_nsh(self, flowrule, vif_port, vlan):
          match_field = self._build_ingress_match_field_sfc_nsh(
              flowrule, vif_port, vlan)
          actions = ("strip_vlan,move:NXM_OF_ETH_DST->OXM_OF_PKT_REG0[0..47],"
                     "decap(),decap(),"
                     "move:OXM_OF_PKT_REG0[0..47]->NXM_OF_ETH_DST,output:%s"
                     "" % vif_port.ofport)
          match_field['actions'] = actions
          return match_field
  
      def _build_forward_sfc_mpls(self, flowrule, vif_port, vlan):
          match_field = self._build_ingress_match_field_sfc_mpls(
              flowrule, vif_port, vlan)
          actions = ("strip_vlan, output:%s" % vif_port.ofport)
          match_field['actions'] = actions
          return match_field
  
      def _build_forward_sfc_nsh(self, flowrule, vif_port, vlan):
          match_field = self._build_ingress_match_field_sfc_nsh(
              flowrule, vif_port, vlan)
          actions = ("strip_vlan, output:%s" % vif_port.ofport)
          match_field['actions'] = actions
          return match_field
  
      def _delete_flows_mpls(self, flowrule, vif_port):
          if flowrule.get('tap_enabled'):
              self.br_int.delete_flows(
                  table=INGRESS_TABLE,
                  eth_type=constants.ETH_TYPE_MPLS,
                  dl_src=flowrule['mac_address'],
                  mpls_label=flowrule['nsp'] << 8 | flowrule['nsi']
              )
          else:
              self.br_int.delete_flows(
                  table=INGRESS_TABLE,
                  eth_type=constants.ETH_TYPE_MPLS,
                  dl_dst=vif_port.vif_mac,
                  mpls_label=flowrule['nsp'] << 8 | flowrule['nsi'] + 1
              )
  
      def _add_group_table(self, buckets, flowrule, group_id):
          group_content = self.br_int.dump_group_for_id(group_id)
          buckets = ','.join(buckets)
          if flowrule['fwd_path']:
              if group_content.find('group_id=%d' % group_id) == -1:
                  self.br_int.add_group(group_id=group_id,
                                        type='select',
                                        buckets=buckets)
              else:
                  self.br_int.mod_group(group_id=group_id,
                                        type='select',
                                        buckets=buckets)
          else:
              # set different id for rev_group
              rev_group_id = group_id + REVERSE_GROUP_NUMBER_OFFSET
              if group_content.find('group_id=%d' % (rev_group_id)) == -1:
                  self.br_int.add_group(group_id=rev_group_id,
                                        type='select',
                                        buckets=buckets)
              else:
                  self.br_int.mod_group(group_id=rev_group_id,
                                        type='select',
                                        buckets=buckets)
  
      def _configure_across_subnet_flow(self, flowrule, item,
                                        subnet_actions_list, eth_type):
          if flowrule['fwd_path']:
              self.br_int.add_flow(
                  table=ACROSS_SUBNET_TABLE,
                  priority=0,
                  dl_dst=item['in_mac_address'],
                  eth_type=eth_type,
                  actions="%s" % ','.join(subnet_actions_list))
          else:
              self.br_int.add_flow(
                  table=ACROSS_SUBNET_TABLE,
                  priority=0,
                  dl_dst=item['mac_address'],
                  eth_type=eth_type,
                  actions="%s" % ','.join(subnet_actions_list))
  
      def _add_tap_classification_flows(self, flowrule, item,
                                        subnet_actions_list):
          egress_port = self.br_int.get_vif_port_by_id(flowrule['egress'])
          vlan = self._get_vlan_by_port(flowrule['egress'])
          if not egress_port:
              return
          in_port = egress_port.ofport
          vif_mac = egress_port.vif_mac
  
          tap_action = ""
          if flowrule['pc_corr'] == 'mpls':
              tap_action += self._build_push_mpls(item['nsp'], item['nsi'])
          tap_action += "mod_vlan_vid:%d," % vlan
          subnet_actions_list[0] = tap_action
          ovs_rule = dict()
  
          self._get_eth_type(flowrule, item, ovs_rule)
          ovs_rule.update(table=TAP_CLASSIFIER_TABLE,
                          priority=0,
                          in_port=in_port,
                          dl_src=vif_mac,
                          actions="%s" % ''.join(subnet_actions_list)
                          )
          self.br_int.add_flow(**ovs_rule)
          if item['local_endpoint'] != self.local_ip:
              self._configure_tunnel_bridge_flows(flowrule, item, vif_mac)
  
      def _get_eth_type(self, flowrule, item, ovs_rule):
          # eth_type is decided based on current node's pp_corr and next node of
          # Tap node's pp_corr
          if flowrule['pp_corr'] == item.get('pp_corr_tap_nh') is None:
              ovs_rule.update(eth_type=constants.ETH_TYPE_IP)
          elif flowrule['pp_corr'] and not item.get('pp_corr_tap_nh'):
              ovs_rule.update(eth_type=constants.ETH_TYPE_IP)
          elif not flowrule['pp_corr'] and item.get('pp_corr_tap_nh'):
              if item['pp_corr_tap_nh'] == 'mpls':
                  eth_type = constants.ETH_TYPE_MPLS
                  mpls_label = flowrule['nsp'] << 8 | flowrule['nsi']
                  ovs_rule.update(mpls_label=mpls_label,
                                  eth_type=eth_type)
          else:
              if flowrule['pp_corr'] == 'mpls' or item.get(
                      'pp_corr_tap_nh') == 'mpls':
                  ovs_rule.update(eth_type=constants.ETH_TYPE_MPLS)
  
      def _configure_tunnel_bridge_flows(self, flowrule, item, vif_mac):
          local_tunnel_ports = [port for port in
                                self.br_tun.get_bridge_ports()
                                if port != self.patch_int_ofport]
          match_info = {'in_port': self.patch_int_ofport,
                        'dl_src': vif_mac}
          if flowrule['pc_corr'] == 'mpls':
              self._build_classification_match_sfc_mpls(item, match_info)
          self.br_tun.add_flow(
              table=0,
              priority=30,
              actions="resubmit(,%s)" % TAP_TUNNEL_OUTPUT_TABLE,
              **match_info
          )
          output_actions = "strip_vlan,load:%s->NXM_NX_TUN_ID[]" % (
              hex(flowrule['segment_id']))
          for port in local_tunnel_ports:
              output_actions += (",output:%d" % port)
          self.br_tun.add_flow(
              table=TAP_TUNNEL_OUTPUT_TABLE,
              priority=0,
              actions=output_actions,
              **match_info
          )
  
      def _build_buckets(self, buckets, flowrule, item):
          if item.get('tap_enabled'):
              # Tap PPG doesn't use bucket as of now.
              return
          if flowrule['fwd_path']:
              bucket = (
                  'bucket=weight=%d, mod_dl_dst:%s, resubmit(,%d)' % (
                      item['weight'],
                      item['in_mac_address'],
                      ACROSS_SUBNET_TABLE))
          else:
              bucket = (
                  'bucket=weight=%d, mod_dl_dst:%s, resubmit(,%d)' % (
                      item['weight'],
                      item['mac_address'],
                      ACROSS_SUBNET_TABLE))
          buckets.append(bucket)
  
      def _update_enc_actions(self, enc_actions, flow_rule,
                              next_hop_tap_enabled):
          # Add resubmit action to send to TAP table.
          if next_hop_tap_enabled:
              pp_corr = flow_rule['pp_corr']
              pp_corr_tap_nh = flow_rule['next_hops'][0].get('pp_corr_tap_nh')
              tap_nh_node_type = flow_rule['next_hops'][0].get(
                  'tap_nh_node_type', constants.DST_NODE)
              group_action = enc_actions.split(',')[-1]
              enc_actions = ""
              if tap_nh_node_type == constants.SF_NODE:
                  if not pp_corr and pp_corr_tap_nh:
                      if flow_rule.get('pc_corr', 'mpls') == 'mpls':
                          mpls_act = self._build_push_mpls(flow_rule['nsp'],
                                                           flow_rule['nsi'])
                          enc_actions += mpls_act
                      # enc_actions += group_action
                  enc_actions += group_action
              else:
                  if flow_rule['pc_corr'] == 'mpls':
                      # For DST Node
                      if flow_rule['pp_corr']:
                          enc_actions = ('pop_mpls:0x%04x,%s' % (
                              constants.ETH_TYPE_IP, NORMAL_ACTION))
                      else:
                          enc_actions += NORMAL_ACTION
              return enc_actions + RESUBMIT_TAP_TABLE
          elif flow_rule.get('tap_enabled'):
              return enc_actions + RESUBMIT_TAP_TABLE
          return enc_actions
  
      def _delete_across_subnet_table_flows(self, flowrule):
          if not flowrule['next_hops']:
              return
          tap_enabled = flowrule['next_hops'][0].get('tap_enabled', False)
          if tap_enabled:
              egress_port = self.br_int.get_vif_port_by_id(flowrule['egress'])
              for item in flowrule['next_hops']:
                  if flowrule['fwd_path']:
                      self.br_int.delete_flows(
                          table=TAP_CLASSIFIER_TABLE,
                          dl_src=egress_port.vif_mac)
                  else:
                      self.br_int.delete_flows(
                          table=TAP_CLASSIFIER_TABLE,
                          dl_src=egress_port.vif_mac)
  
                  if item['local_endpoint'] != self.local_ip:
                      self._delete_tunnel_bridge_flows(flowrule,
                                                       egress_port.vif_mac)
          else:
              for item in flowrule['next_hops']:
                  if flowrule['fwd_path']:
                      self.br_int.delete_flows(
                          table=ACROSS_SUBNET_TABLE,
                          dl_dst=item['in_mac_address'])
                  else:
                      self.br_int.delete_flows(
                          table=ACROSS_SUBNET_TABLE,
                          dl_dst=item['mac_address'])
  
      def _add_tap_ingress_flow(self, flowrule, vif_port, vlan):
          match_field = self._build_tap_ingress_match_field_sfc_mpls(
              flowrule, vif_port, vlan)
          actions = ('strip_vlan, pop_mpls:0x%04x,output:%s'
                     % (constants.ETH_TYPE_MPLS,
                        vif_port.ofport))
          match_field['actions'] = actions
          match_field.pop('dl_dst', None)
          match_field.update(dl_src=flowrule['mac_address'])
          self.br_int.add_flow(**match_field)
  
      def _build_tap_ingress_match_field_sfc_mpls(self, flowrule, vif_port,
                                                  vlan):
          match_field = self._build_ingress_common_match_field(vif_port, vlan)
          match_field['eth_type'] = constants.ETH_TYPE_MPLS
          match_field['mpls_label'] = flowrule['nsp'] << 8 | flowrule['nsi']
          return match_field
  
      def _delete_tunnel_bridge_flows(self, flowrule, src_mac):
          match_info = {'in_port': self.patch_int_ofport,
                        'dl_src': src_mac}
          # Use Tap 'nsi'
          flowrule_copy = flowrule.copy()
          flowrule_copy['nsi'], flowrule_copy['nsp'] = (
              flowrule['next_hops'][0]['nsi'], flowrule['next_hops'][0]['nsp'])
          self._build_classification_match_sfc_mpls(flowrule_copy, match_info)
          self.br_tun.delete_flows(table=0,
                                   **match_info)
          self.br_tun.delete_flows(table=TAP_TUNNEL_OUTPUT_TABLE,
                                   **match_info)
  
      def _clear_sfc_flow_on_tun_br(self):
          self.br_tun.delete_flows(table=0, eth_type=constants.ETH_TYPE_MPLS)
          self.br_tun.delete_flows(table=TAP_TUNNEL_OUTPUT_TABLE)
  
      def _delete_flows_nsh(self, flowrule, vif_port):
          self.br_int.delete_flows(
              table=INGRESS_TABLE,
              eth_type=constants.ETH_TYPE_NSH,
              dl_dst=vif_port.vif_mac,
              nsh_mdtype=1,
              nsh_spi=flowrule['nsp'],
              nsh_si=flowrule['nsi'] + 1
          )
  