]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Fix DVR flow problems for IPv6 subnet
authorXu Han Peng <xuhanp@cn.ibm.com>
Wed, 3 Dec 2014 06:58:34 +0000 (14:58 +0800)
committerXu Han Peng <xuhanp@linux.vnet.ibm.com>
Fri, 26 Dec 2014 03:38:11 +0000 (03:38 +0000)
This code fixes DVR flow problems by changing proto='ip' to
proto='ipv6' and changing nw_dst to ipv6_dst.

When DVR is enabled, RADVD is spawned by l3 agent on each compute
node. This code also prevent IPv6 Router Advertisement from
sending to other compute nodes.

Change-Id: Id94acd85ea124eff6cfdfbfc546f5dd4ca81ef43
Closes-Bug: 1398244
Closes-Bug: 1398627
Partial-Bug: 1376325

neutron/plugins/openvswitch/agent/ovs_dvr_neutron_agent.py
neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py

index cd53e2a586e6d7ef347a8fe9f9e4876e8b958721..f7dcfd763449e2f4097ce6fba8df11437d0564ab 100644 (file)
@@ -388,23 +388,27 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
         ofports = ','.join(map(str, ldm.get_compute_ofports().values()))
         if csnat_ofport != constants.OFPORT_INVALID:
             ofports = str(csnat_ofport) + ',' + ofports
+        ip_version = subnet_info['ip_version']
         if ofports:
-            self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                                 priority=2,
-                                 proto='ip',
-                                 dl_vlan=local_vlan,
-                                 nw_dst=ip_subnet,
-                                 actions="strip_vlan,mod_dl_src:%s,"
-                                 "output:%s" %
-                                 (subnet_info['gateway_mac'], ofports))
-
-        self.tun_br.add_flow(table=constants.DVR_PROCESS,
-                             priority=3,
-                             dl_vlan=local_vlan,
-                             proto='arp',
-                             nw_dst=subnet_info['gateway_ip'],
-                             actions="drop")
+            args = self._get_flow_args_by_version(
+                ip_version, constants.DVR_TO_SRC_MAC, local_vlan, ip_subnet,
+                2, ("strip_vlan,mod_dl_src:%s,output:%s" %
+                    (subnet_info['gateway_mac'], ofports)))
+            self.int_br.add_flow(**args)
+
+        args = {'table': constants.DVR_PROCESS,
+                'priority': 3,
+                'dl_vlan': local_vlan,
+                'actions': "drop"}
+        if ip_version == 4:
+            args['proto'] = 'arp'
+            args['nw_dst'] = subnet_info['gateway_ip']
+        else:
+            args['proto'] = 'icmp6'
+            args['icmp_type'] = n_const.ICMPV6_TYPE_RA
+            args['dl_src'] = subnet_info['gateway_mac']
 
+        self.tun_br.add_flow(**args)
         self.tun_br.add_flow(table=constants.DVR_PROCESS,
                              priority=2,
                              dl_vlan=local_vlan,
@@ -477,14 +481,12 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
 
             if csnat_ofport != constants.OFPORT_INVALID:
                 ofports = str(csnat_ofport) + ',' + ofports
-            self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                                 priority=2,
-                                 proto='ip',
-                                 dl_vlan=local_vlan,
-                                 nw_dst=ip_subnet,
-                                 actions="strip_vlan,mod_dl_src:%s,"
-                                 " output:%s" %
-                                 (subnet_info['gateway_mac'], ofports))
+            ip_version = subnet_info['ip_version']
+            args = self._get_flow_args_by_version(
+                ip_version, constants.DVR_TO_SRC_MAC, local_vlan, ip_subnet,
+                2, ("strip_vlan,mod_dl_src:%s,output:%s" %
+                    (subnet_info['gateway_mac'], ofports)))
+            self.int_br.add_flow(**args)
 
     def _bind_centralized_snat_port_on_dvr_subnet(self, port, fixed_ips,
                                                   device_owner, local_vlan):
@@ -532,14 +534,15 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
         ofports = ','.join(map(str, ldm.get_compute_ofports().values()))
         ofports = str(ldm.get_csnat_ofport()) + ',' + ofports
         ip_subnet = subnet_info['cidr']
-        self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                             priority=2,
-                             proto='ip',
-                             dl_vlan=local_vlan,
-                             nw_dst=ip_subnet,
-                             actions="strip_vlan,mod_dl_src:%s,"
-                             " output:%s" %
-                             (subnet_info['gateway_mac'], ofports))
+
+        # TODO(xuhanp) remove the IPv6 related add_flow once SNAT is not
+        # used for IPv6 DVR.
+        ip_version = subnet_info['ip_version']
+        args = self._get_flow_args_by_version(
+            ip_version, constants.DVR_TO_SRC_MAC, local_vlan, ip_subnet,
+            2, ("strip_vlan,mod_dl_src:%s,output:%s" %
+                (subnet_info['gateway_mac'], ofports)))
+        self.int_br.add_flow(**args)
 
     def bind_port_to_dvr(self, port, network_type, fixed_ips,
                          device_owner, local_vlan_id):
@@ -592,32 +595,39 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
                                          dl_vlan=local_vlan,
                                          dl_dst=ovsport.get_mac())
             ldm.remove_all_compute_ofports()
-
+            ip_version = subnet_info['ip_version']
             if ldm.get_csnat_ofport() != -1:
                 # If there is a csnat port on this agent, preserve
                 # the local_dvr_map state
                 ofports = str(ldm.get_csnat_ofport())
-                self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                                     priority=2,
-                                     proto='ip',
-                                     dl_vlan=local_vlan,
-                                     nw_dst=ip_subnet,
-                                     actions="strip_vlan,mod_dl_src:%s,"
-                                     " output:%s" %
-                                     (subnet_info['gateway_mac'], ofports))
+                args = self._get_flow_args_by_version(
+                    ip_version, constants.DVR_TO_SRC_MAC,
+                    local_vlan, ip_subnet,
+                    2, ("strip_vlan,mod_dl_src:%s,output:%s" %
+                        (subnet_info['gateway_mac'], ofports)))
+                self.int_br.add_flow(**args)
+
             else:
                 # removed port is a distributed router interface
-                self.int_br.delete_flows(table=constants.DVR_TO_SRC_MAC,
-                                         proto='ip', dl_vlan=local_vlan,
-                                         nw_dst=ip_subnet)
+                args = self._get_flow_args_by_version(
+                    ip_version, constants.DVR_TO_SRC_MAC,
+                    local_vlan, ip_subnet, None, None)
+                self.int_br.delete_flows(**args)
                 # remove subnet from local_dvr_map as no dvr (or) csnat
                 # ports available on this agent anymore
                 self.local_dvr_map.pop(sub_uuid, None)
 
-            self.tun_br.delete_flows(table=constants.DVR_PROCESS,
-                                     dl_vlan=local_vlan,
-                                     proto='arp',
-                                     nw_dst=subnet_info['gateway_ip'])
+            args = {'table': constants.DVR_PROCESS,
+                    'dl_vlan': local_vlan}
+            if ip_version == 4:
+                args['proto'] = 'arp'
+                args['nw_dst'] = subnet_info['gateway_ip']
+            else:
+                args['proto'] = 'icmp6'
+                args['icmp_type'] = n_const.ICMPV6_TYPE_RA
+                args['dl_src'] = subnet_info['gateway_mac']
+
+            self.tun_br.delete_flows(**args)
             ovsport.remove_subnet(sub_uuid)
 
         self.tun_br.delete_flows(table=constants.DVR_PROCESS,
@@ -649,7 +659,7 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
             ldm.remove_compute_ofport(port.vif_id)
             ofports = ','.join(map(str, ldm.get_compute_ofports().values()))
             ip_subnet = subnet_info['cidr']
-
+            ip_version = subnet_info['ip_version']
             # first remove this vm port rule
             self.int_br.delete_flows(table=constants.DVR_TO_SRC_MAC,
                                      dl_vlan=local_vlan,
@@ -658,33 +668,29 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
                 # If there is a csnat port on this agent, preserve
                 # the local_dvr_map state
                 ofports = str(ldm.get_csnat_ofport()) + ',' + ofports
-                self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                                     priority=2,
-                                     proto='ip',
-                                     dl_vlan=local_vlan,
-                                     nw_dst=ip_subnet,
-                                     actions="strip_vlan,mod_dl_src:%s,"
-                                     " output:%s" %
-                                     (subnet_info['gateway_mac'], ofports))
+                args = self._get_flow_args_by_version(
+                    ip_version, constants.DVR_TO_SRC_MAC,
+                    local_vlan, ip_subnet,
+                    2, ("strip_vlan,mod_dl_src:%s,output:%s" %
+                        (subnet_info['gateway_mac'], ofports)))
+                self.int_br.add_flow(**args)
             else:
                 if ofports:
-                    self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                                         priority=2,
-                                         proto='ip',
-                                         dl_vlan=local_vlan,
-                                         nw_dst=ip_subnet,
-                                         actions="strip_vlan,mod_dl_src:%s,"
-                                         " output:%s" %
-                                         (subnet_info['gateway_mac'],
-                                          ofports))
+                    args = self._get_flow_args_by_version(
+                        ip_version, constants.DVR_TO_SRC_MAC,
+                        local_vlan, ip_subnet,
+                        2, ("strip_vlan,mod_dl_src:%s,output:%s" %
+                            (subnet_info['gateway_mac'], ofports)))
+                    self.int_br.add_flow(**args)
                 else:
                     # remove the flow altogether, as no ports (both csnat/
                     # compute) are available on this subnet in this
                     # agent
-                    self.int_br.delete_flows(table=constants.DVR_TO_SRC_MAC,
-                                             proto='ip',
-                                             dl_vlan=local_vlan,
-                                             nw_dst=ip_subnet)
+                    args = self._get_flow_args_by_version(
+                        ip_version, constants.DVR_TO_SRC_MAC,
+                        local_vlan, ip_subnet, None, None)
+                    self.int_br.delete_flows(**args)
+
         # release port state
         self.local_ports.pop(port.vif_id, None)
 
@@ -708,22 +714,23 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
         self.int_br.delete_flows(table=constants.DVR_TO_SRC_MAC,
                                  dl_vlan=local_vlan,
                                  dl_dst=ovsport.get_mac())
-
         ofports = ','.join(map(str, ldm.get_compute_ofports().values()))
+        ip_version = subnet_info['ip_version']
         if ofports:
-            self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                                 priority=2,
-                                 proto='ip',
-                                 dl_vlan=local_vlan,
-                                 nw_dst=ip_subnet,
-                                 actions="strip_vlan,mod_dl_src:%s,"
-                                 " output:%s" %
-                                 (subnet_info['gateway_mac'], ofports))
+            # TODO(xuhanp) remove the IPv6 related add_flow once SNAT is not
+            # used for IPv6 DVR.
+            args = self._get_flow_args_by_version(
+                ip_version, constants.DVR_TO_SRC_MAC,
+                local_vlan, ip_subnet,
+                2, ("strip_vlan,mod_dl_src:%s,output:%s" %
+                    (subnet_info['gateway_mac'], ofports)))
+            self.int_br.add_flow(**args)
         else:
-            self.int_br.delete_flows(table=constants.DVR_TO_SRC_MAC,
-                                     proto='ip',
-                                     dl_vlan=local_vlan,
-                                     nw_dst=ip_subnet)
+            args = self._get_flow_args_by_version(
+                ip_version, constants.DVR_TO_SRC_MAC,
+                local_vlan, ip_subnet, None, None)
+            self.int_br.delete_flows(**args)
+
         if not ldm.is_dvr_owned():
             # if not owned by DVR (only used for csnat), remove this
             # subnet state altogether
@@ -732,6 +739,27 @@ class OVSDVRNeutronAgent(dvr_rpc.DVRAgentRpcApiMixin):
         # release port state
         self.local_ports.pop(port.vif_id, None)
 
+    def _get_flow_args_by_version(self, ip_version, table,
+                                  vlan, subnet, priority, actions):
+        """
+        Get flow args for DVR by IP version.
+        priority and actions are optional to support both add_flows
+        and delete_flows
+        """
+        args = {'table': table,
+                'dl_vlan': vlan}
+        if ip_version == 4:
+            args['proto'] = 'ip'
+            args['nw_dst'] = subnet
+        else:
+            args['proto'] = 'ipv6'
+            args['ipv6_dst'] = subnet
+        if priority:
+            args['priority'] = priority
+        if actions:
+            args['actions'] = actions
+        return args
+
     def unbind_port_from_dvr(self, vif_port, local_vlan_id):
         if not self.in_distributed_mode():
             return
index b098b710b0152447e5719326c222fd3465c97f43..e846532522b7438019c34870b4c911e3a16f33a9 100644 (file)
@@ -219,6 +219,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                     self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
                     return_value={'gateway_ip': '1.1.1.1',
                                   'cidr': '1.1.1.0/24',
+                                  'ip_version': 4,
                                   'gateway_mac': 'aa:bb:cc:11:22:33'}),
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                     'get_ports_on_host_by_subnet',
@@ -241,8 +242,14 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                 self.assertTrue(add_flow_tun_fn.called)
                 self.assertTrue(delete_flows_int_fn.called)
 
-    def _test_port_bound_for_dvr(self, device_owner):
+    def _test_port_bound_for_dvr(self, device_owner, ip_version=4):
         self._setup_for_dvr_test()
+        if ip_version == 4:
+            gateway_ip = '1.1.1.1'
+            cidr = '1.1.1.0/24'
+        else:
+            gateway_ip = '2001:100::1'
+            cidr = '2001:100::0/64'
         with mock.patch('neutron.agent.linux.ovs_lib.OVSBridge.'
                         'set_db_attribute',
                         return_value=True):
@@ -253,8 +260,9 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                                   'get_subnet_for_dvr',
                                   return_value={
-                                      'gateway_ip': '1.1.1.1',
-                                      'cidr': '1.1.1.0/24',
+                                      'gateway_ip': gateway_ip,
+                                      'cidr': cidr,
+                                      'ip_version': ip_version,
                                       'gateway_mac': 'aa:bb:cc:11:22:33'}),
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                     'get_ports_on_host_by_subnet',
@@ -274,24 +282,96 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                     None, None, self._fixed_ips,
                     n_const.DEVICE_OWNER_DVR_INTERFACE,
                     False)
+                expected = [
+                    mock.call(
+                        table=constants.TUN_TABLE['vxlan'],
+                        priority=1, tun_id=None,
+                        actions="mod_vlan_vid:%s,"
+                        "resubmit(,%s)" %
+                        (self.agent.local_vlan_map[self._net_uuid].vlan,
+                         constants.DVR_NOT_LEARN)),
+                    mock.call(
+                        table=constants.DVR_PROCESS, priority=2,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan),
+                        dl_dst=self._port.vif_mac,
+                        actions='drop'),
+                    mock.call(
+                        table=constants.DVR_PROCESS, priority=1,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan),
+                        dl_src=self._port.vif_mac,
+                        actions="mod_dl_src:%s,resubmit(,%s)" % (
+                            self.agent.dvr_agent.dvr_mac_address,
+                            constants.PATCH_LV_TO_TUN))]
+                if ip_version == 4:
+                    expected.insert(1, mock.call(
+                        proto='arp',
+                        nw_dst=gateway_ip, actions='drop',
+                        priority=3, table=constants.DVR_PROCESS,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan)))
+                else:
+                    expected.insert(1, mock.call(
+                        icmp_type=n_const.ICMPV6_TYPE_RA, proto='icmp6',
+                        dl_src='aa:bb:cc:11:22:33', actions='drop',
+                        priority=3, table=constants.DVR_PROCESS,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan)))
+
+                self.assertEqual(expected, add_flow_tun_fn.call_args_list)
                 self.agent.port_bound(self._compute_port, self._net_uuid,
                                       'vxlan', None, None,
                                       self._compute_fixed_ips,
                                       device_owner, False)
-                self.assertTrue(add_flow_tun_fn.called)
-                self.assertTrue(add_flow_int_fn.called)
+                expected = [
+                    mock.call(table=constants.DVR_TO_SRC_MAC, priority=4,
+                        dl_dst=self._compute_port.vif_mac,
+                        dl_vlan=self.agent.local_vlan_map[self._net_uuid].vlan,
+                        actions="strip_vlan,mod_dl_src:%s,"
+                        "output:%s" %
+                        ('aa:bb:cc:11:22:33', self._compute_port.ofport))
+                ]
+                if ip_version == 4:
+                    expected.append(mock.call(
+                        table=constants.DVR_TO_SRC_MAC,
+                        priority=2, proto='ip',
+                        nw_dst=cidr,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan),
+                        actions="strip_vlan,mod_dl_src:%s,"
+                        "output:%s" %
+                        ('aa:bb:cc:11:22:33', self._compute_port.ofport)))
+                else:
+                    expected.append(mock.call(
+                        table=constants.DVR_TO_SRC_MAC,
+                        priority=2, proto='ipv6',
+                        ipv6_dst=cidr,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan),
+                        actions="strip_vlan,mod_dl_src:%s,"
+                        "output:%s" %
+                        ('aa:bb:cc:11:22:33', self._compute_port.ofport)))
+                self.assertEqual(expected, add_flow_int_fn.call_args_list)
                 self.assertTrue(delete_flows_int_fn.called)
 
     def test_port_bound_for_dvr_with_compute_ports(self):
-        self._test_port_bound_for_dvr(device_owner="compute:None")
+        self._test_port_bound_for_dvr(
+            device_owner="compute:None")
+        self._test_port_bound_for_dvr(
+            device_owner="compute:None", ip_version=6)
 
     def test_port_bound_for_dvr_with_lbaas_vip_ports(self):
         self._test_port_bound_for_dvr(
             device_owner=n_const.DEVICE_OWNER_LOADBALANCER)
+        self._test_port_bound_for_dvr(
+            device_owner=n_const.DEVICE_OWNER_LOADBALANCER, ip_version=6)
 
     def test_port_bound_for_dvr_with_dhcp_ports(self):
         self._test_port_bound_for_dvr(
             device_owner=n_const.DEVICE_OWNER_DHCP)
+        self._test_port_bound_for_dvr(
+            device_owner=n_const.DEVICE_OWNER_DHCP, ip_version=6)
 
     def test_port_bound_for_dvr_with_csnat_ports(self, ofport=10):
         self._setup_for_dvr_test()
@@ -306,6 +386,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                     self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
                     return_value={'gateway_ip': '1.1.1.1',
                                   'cidr': '1.1.1.0/24',
+                                  'ip_version': 4,
                                   'gateway_mac': 'aa:bb:cc:11:22:33'}),
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                     'get_ports_on_host_by_subnet',
@@ -329,7 +410,19 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                 self.assertTrue(delete_flows_int_fn.called)
 
     def test_treat_devices_removed_for_dvr_interface(self, ofport=10):
+        self._test_treat_devices_removed_for_dvr_interface(ofport)
+        self._test_treat_devices_removed_for_dvr_interface(
+            ofport, ip_version=6)
+
+    def _test_treat_devices_removed_for_dvr_interface(self, ofport=10,
+                                                      ip_version=4):
         self._setup_for_dvr_test()
+        if ip_version == 4:
+            gateway_ip = '1.1.1.1'
+            cidr = '1.1.1.0/24'
+        else:
+            gateway_ip = '2001:100::1'
+            cidr = '2001:100::0/64'
         with mock.patch('neutron.agent.linux.ovs_lib.OVSBridge.'
                         'set_db_attribute',
                         return_value=True):
@@ -339,8 +432,9 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                            return_value=str(self._old_local_vlan)),
                 mock.patch.object(
                     self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
-                    return_value={'gateway_ip': '1.1.1.1',
-                                  'cidr': '1.1.1.0/24',
+                    return_value={'gateway_ip': gateway_ip,
+                                  'cidr': cidr,
+                                  'ip_version': ip_version,
                                   'gateway_mac': 'aa:bb:cc:11:22:33'}),
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                     'get_ports_on_host_by_subnet',
@@ -374,11 +468,58 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                                                    delete_flows_int_fn,
                                                    delete_flows_tun_fn):
                 self.agent.treat_devices_removed([self._port.vif_id])
-                self.assertTrue(delete_flows_int_fn.called)
-                self.assertTrue(delete_flows_tun_fn.called)
-
-    def _test_treat_devices_removed_for_dvr(self, device_owner):
+                if ip_version == 4:
+                    expected = [mock.call(
+                        table=constants.DVR_TO_SRC_MAC,
+                        nw_dst=cidr,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan),
+                        proto='ip')]
+                else:
+                    expected = [mock.call(
+                        table=constants.DVR_TO_SRC_MAC,
+                        ipv6_dst=cidr,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan),
+                        proto='ipv6')]
+                self.assertEqual(expected,
+                                 delete_flows_int_fn.call_args_list)
+                if ip_version == 4:
+                    expected = [mock.call(
+                        proto='arp',
+                        nw_dst=gateway_ip,
+                        table=constants.DVR_PROCESS,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan))]
+                else:
+                    expected = [mock.call(
+                        icmp_type=n_const.ICMPV6_TYPE_RA, proto='icmp6',
+                        dl_src='aa:bb:cc:11:22:33',
+                        table=constants.DVR_PROCESS,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan))]
+                expected.extend([
+                    mock.call(
+                        table=constants.DVR_PROCESS,
+                        dl_dst=self._port.vif_mac,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan)),
+                    mock.call(
+                        table=constants.DVR_PROCESS,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan),
+                        dl_src=self._port.vif_mac)
+                ])
+                self.assertEqual(expected, delete_flows_tun_fn.call_args_list)
+
+    def _test_treat_devices_removed_for_dvr(self, device_owner, ip_version=4):
         self._setup_for_dvr_test()
+        if ip_version == 4:
+            gateway_ip = '1.1.1.1'
+            cidr = '1.1.1.0/24'
+        else:
+            gateway_ip = '2001:100::1'
+            cidr = '2001:100::0/64'
         with mock.patch('neutron.agent.linux.ovs_lib.OVSBridge.'
                         'set_db_attribute',
                         return_value=True):
@@ -388,8 +529,9 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                            return_value=str(self._old_local_vlan)),
                 mock.patch.object(
                     self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
-                    return_value={'gateway_ip': '1.1.1.1',
-                                  'cidr': '1.1.1.0/24',
+                    return_value={'gateway_ip': gateway_ip,
+                                  'cidr': cidr,
+                                  'ip_version': ip_version,
                                   'gateway_mac': 'aa:bb:cc:11:22:33'}),
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                     'get_ports_on_host_by_subnet',
@@ -427,18 +569,45 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                                                    update_dev_down_fn,
                                                    delete_flows_int_fn):
                 self.agent.treat_devices_removed([self._compute_port.vif_id])
-                self.assertTrue(delete_flows_int_fn.called)
+                expected = [
+                    mock.call(
+                        table=constants.DVR_TO_SRC_MAC,
+                        dl_dst=self._compute_port.vif_mac,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan))]
+                if ip_version == 4:
+                    expected.append(mock.call(
+                        table=constants.DVR_TO_SRC_MAC,
+                        proto='ip', nw_dst=cidr,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan))
+                    )
+                else:
+                    expected.append(mock.call(
+                        table=constants.DVR_TO_SRC_MAC,
+                        proto='ipv6', ipv6_dst=cidr,
+                        dl_vlan=(
+                            self.agent.local_vlan_map[self._net_uuid].vlan))
+                    )
+                self.assertEqual(expected, delete_flows_int_fn.call_args_list)
 
     def test_treat_devices_removed_for_dvr_with_compute_ports(self):
-        self._test_treat_devices_removed_for_dvr(device_owner="compute:None")
+        self._test_treat_devices_removed_for_dvr(
+            device_owner="compute:None")
+        self._test_treat_devices_removed_for_dvr(
+            device_owner="compute:None", ip_version=6)
 
     def test_treat_devices_removed_for_dvr_with_lbaas_vip_ports(self):
         self._test_treat_devices_removed_for_dvr(
             device_owner=n_const.DEVICE_OWNER_LOADBALANCER)
+        self._test_treat_devices_removed_for_dvr(
+            device_owner=n_const.DEVICE_OWNER_LOADBALANCER, ip_version=6)
 
     def test_treat_devices_removed_for_dvr_with_dhcp_ports(self):
         self._test_treat_devices_removed_for_dvr(
             device_owner=n_const.DEVICE_OWNER_DHCP)
+        self._test_treat_devices_removed_for_dvr(
+            device_owner=n_const.DEVICE_OWNER_DHCP, ip_version=6)
 
     def test_treat_devices_removed_for_dvr_csnat_port(self, ofport=10):
         self._setup_for_dvr_test()
@@ -453,6 +622,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                     self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
                     return_value={'gateway_ip': '1.1.1.1',
                                   'cidr': '1.1.1.0/24',
+                                  'ip_version': 4,
                                   'gateway_mac': 'aa:bb:cc:11:22:33'}),
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                     'get_ports_on_host_by_subnet',