Browse Source

Fix an issue on release_address

On ipam_release_address, kuryr unset all the neutron ports with
the released IP address. This is incorrect because IP address
is not unique across an OpenStack deployment.

This patch adds a check for verifying the subnet of a port
before reseting the port. If the port's subnet doesn't
match the subnet of the pool, the port is not the target and
won't be reset.

In order to identify the subnet given a pool ID. This patch
tags each kuryr created subnet with the pool ID.

Change-Id: I8f85ae53c924aa1f4ff877f52a56d267f984dbf5
Closes-Bug: #1782947
Related-Bug: #1782942
Hongbin Lu 9 months ago
parent
commit
3299f82199

+ 14
- 4
kuryr_libnetwork/controllers.py View File

@@ -598,7 +598,10 @@ def _create_kuryr_subnet(pool_cidr, subnet_cidr, pool_id, network_id, gateway):
598 598
     if gateway:
599 599
         new_kuryr_subnet[0]['gateway_ip'] = gateway
600 600
 
601
-    app.neutron.create_subnet({'subnets': new_kuryr_subnet})
601
+    subnets = app.neutron.create_subnet({'subnets': new_kuryr_subnet})
602
+    if app.tag_ext:
603
+        for subnet in subnets['subnets']:
604
+            _neutron_subnet_add_tag(subnet['id'], pool_id)
602 605
     LOG.debug("Created kuryr subnet %s", new_kuryr_subnet)
603 606
 
604 607
 
@@ -1885,6 +1888,10 @@ def ipam_release_address():
1885 1888
     if not len(subnets):
1886 1889
         LOG.info("Subnet already deleted.")
1887 1890
         return flask.jsonify(const.SCHEMA['SUCCESS'])
1891
+    if app.tag_ext:
1892
+        subnets = [subnet
1893
+                   for subnet in subnets
1894
+                   if pool_id in subnet.get('tags') or []]
1888 1895
 
1889 1896
     iface = ipaddress.ip_interface(six.text_type(rel_address))
1890 1897
     rel_ip_address = six.text_type(iface.ip)
@@ -1904,9 +1911,12 @@ def ipam_release_address():
1904 1911
                                 'binding:host_id': ''}
1905 1912
                 if port['name'].startswith(port['device_id']):
1906 1913
                     updated_port["device_id"] = ''
1907
-                app.neutron.update_port(port['id'], {'port': updated_port})
1908
-                _neutron_port_remove_tag(port['id'],
1909
-                                         const.KURYR_EXISTING_NEUTRON_PORT)
1914
+                for subnet in subnets:
1915
+                    if (port['fixed_ips'][0]['subnet_id'] == subnet['id']):
1916
+                        app.neutron.update_port(
1917
+                            port['id'], {'port': updated_port})
1918
+                        _neutron_port_remove_tag(
1919
+                            port['id'], const.KURYR_EXISTING_NEUTRON_PORT)
1910 1920
     except n_exceptions.NeutronClientException as ex:
1911 1921
         LOG.error("Error happened while fetching "
1912 1922
                   "and deleting port, %s", ex)

+ 1
- 3
kuryr_libnetwork/tests/unit/base.py View File

@@ -19,7 +19,6 @@ from oslotest import base
19 19
 from kuryr.lib import constants as lib_const
20 20
 from kuryr.lib import utils as lib_utils
21 21
 from kuryr_libnetwork import app
22
-from kuryr_libnetwork import constants as const
23 22
 from kuryr_libnetwork import controllers
24 23
 from kuryr_libnetwork.port_driver import driver
25 24
 from kuryr_libnetwork import utils
@@ -267,8 +266,7 @@ class TestKuryrBase(TestCase):
267 266
         }
268 267
         if subnetpool_id:
269 268
             fake_v4_subnet['subnet'].update(subnetpool_id=subnetpool_id)
270
-            if not str(name).startswith(const.SUBNET_NAME_PREFIX):
271
-                fake_v4_subnet['subnet'].get('tags').append(subnetpool_id)
269
+            fake_v4_subnet['subnet'].get('tags').append(subnetpool_id)
272 270
 
273 271
         return fake_v4_subnet
274 272
 

+ 1
- 1
kuryr_libnetwork/tests/unit/test_kuryr.py View File

@@ -603,7 +603,7 @@ class TestKuryr(base.TestKuryrBase):
603 603
         for tag in tags:
604 604
             mock_add_tag.assert_any_call('networks',
605 605
                 fake_neutron_net_id, tag)
606
-        mock_add_tag.assert_called_with('networks', fake_neutron_net_id,
606
+        mock_add_tag.assert_any_call('networks', fake_neutron_net_id,
607 607
             utils.existing_net_tag(docker_network_id))
608 608
         mock_create_subnet.assert_any_call(fake_v4_subnet_request)
609 609
         mock_create_subnet.assert_any_call(fake_v6_subnet_request)

+ 91
- 1
kuryr_libnetwork/tests/unit/test_kuryr_ipam.py View File

@@ -1576,7 +1576,7 @@ class TestKuryrIpam(base.TestKuryrBase):
1576 1576
     @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnets')
1577 1577
     @mock.patch('kuryr_libnetwork.controllers.app')
1578 1578
     @ddt.data((False), (True))
1579
-    def test_ipam_driver_release_address_w_existing_port_w_same_ip_and_cidr(
1579
+    def test_ipam_driver_release_address_w_same_ip_and_cidr(
1580 1580
             self, use_tag_ext, mock_app, mock_list_subnets, mock_list_ports,
1581 1581
             mock_delete_port):
1582 1582
         # It checks only the kuryr port is removed even if the other port
@@ -1646,3 +1646,93 @@ class TestKuryrIpam(base.TestKuryrBase):
1646 1646
         mock_list_ports.assert_called()
1647 1647
         mock_delete_port.assert_called_once_with(
1648 1648
             fake_port['port']['id'])
1649
+
1650
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.remove_tag')
1651
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.update_port')
1652
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.delete_port')
1653
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports')
1654
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnets')
1655
+    @mock.patch('kuryr_libnetwork.controllers.app')
1656
+    def test_ipam_driver_release_address_w_existing_port_w_same_ip_and_cidr(
1657
+            self, mock_app, mock_list_subnets, mock_list_ports,
1658
+            mock_delete_port, mock_update_port, mock_remove_tag):
1659
+        # TODO(hongbin): Current implementation still delete existing ports
1660
+        # if tag extension is not enabled. This needs to be fixed and test
1661
+        # case needs to be added after.
1662
+        mock_app.tag_ext = True
1663
+        fake_kuryr_subnetpool_id = uuidutils.generate_uuid()
1664
+        fake_kuryr_subnetpool_id2 = uuidutils.generate_uuid()
1665
+        # faking list_subnets
1666
+        docker_network_id = lib_utils.get_hash()
1667
+        docker_network_id2 = lib_utils.get_hash()
1668
+        docker_endpoint_id = lib_utils.get_hash()
1669
+        docker_endpoint_id2 = lib_utils.get_hash()
1670
+        subnet_v4_id = uuidutils.generate_uuid()
1671
+        subnet_v4_id2 = uuidutils.generate_uuid()
1672
+        fake_v4_subnet = self._get_fake_v4_subnet(
1673
+            docker_network_id, docker_endpoint_id, subnet_v4_id,
1674
+            subnetpool_id=fake_kuryr_subnetpool_id,
1675
+            cidr=FAKE_IP4_CIDR)
1676
+        fake_v4_subnet2 = self._get_fake_v4_subnet(
1677
+            docker_network_id2, docker_endpoint_id2, subnet_v4_id2,
1678
+            subnetpool_id=fake_kuryr_subnetpool_id2,
1679
+            cidr=FAKE_IP4_CIDR)
1680
+        fake_subnet_response = {
1681
+            'subnets': [
1682
+                fake_v4_subnet['subnet'], fake_v4_subnet2['subnet']
1683
+            ]
1684
+        }
1685
+        mock_list_subnets.return_value = fake_subnet_response
1686
+
1687
+        fake_ip4 = '10.0.0.5'
1688
+        # faking list_ports and delete_port
1689
+        neutron_network_id = uuidutils.generate_uuid()
1690
+        fake_neutron_port_id = uuidutils.generate_uuid()
1691
+        fake_port = base.TestKuryrBase._get_fake_port(
1692
+            docker_endpoint_id, neutron_network_id,
1693
+            fake_neutron_port_id, lib_const.PORT_STATUS_ACTIVE,
1694
+            subnet_v4_id,
1695
+            neutron_subnet_v4_address=fake_ip4,
1696
+            device_owner=lib_const.DEVICE_OWNER,
1697
+            tags=[const.KURYR_EXISTING_NEUTRON_PORT])
1698
+        fake_port2 = base.TestKuryrBase._get_fake_port(
1699
+            docker_endpoint_id2, neutron_network_id,
1700
+            fake_neutron_port_id, lib_const.PORT_STATUS_ACTIVE,
1701
+            subnet_v4_id2,
1702
+            neutron_subnet_v4_address=fake_ip4,
1703
+            device_owner=lib_const.DEVICE_OWNER,
1704
+            tags=[const.KURYR_EXISTING_NEUTRON_PORT])
1705
+
1706
+        fake_port['port']['fixed_ips'] = [
1707
+            {'subnet_id': subnet_v4_id, 'ip_address': fake_ip4}
1708
+        ]
1709
+        fake_port2['port']['fixed_ips'] = [
1710
+            {'subnet_id': subnet_v4_id2, 'ip_address': fake_ip4}
1711
+        ]
1712
+
1713
+        list_port_response = {'ports': [fake_port['port'],
1714
+                                        fake_port2['port']]}
1715
+        mock_list_ports.return_value = list_port_response
1716
+
1717
+        mock_delete_port.return_value = {}
1718
+
1719
+        fake_request = {
1720
+            'PoolID': fake_kuryr_subnetpool_id,
1721
+            'Address': fake_ip4
1722
+        }
1723
+        response = self.app.post('/IpamDriver.ReleaseAddress',
1724
+                                content_type='application/json',
1725
+                                data=jsonutils.dumps(fake_request))
1726
+
1727
+        self.assertEqual(200, response.status_code)
1728
+        mock_list_subnets.assert_called_with(
1729
+            subnetpool_id=fake_kuryr_subnetpool_id)
1730
+        mock_list_ports.assert_called()
1731
+        expect_updated_port = {'name': '', 'device_owner': '',
1732
+                               'device_id': '', 'binding:host_id': ''}
1733
+        mock_update_port.assert_called_once_with(fake_port['port']['id'],
1734
+                                            {'port': expect_updated_port})
1735
+        mock_delete_port.assert_not_called()
1736
+        mock_remove_tag.assert_called_once_with('ports',
1737
+                                           fake_port['port']['id'],
1738
+                                           const.KURYR_EXISTING_NEUTRON_PORT)

Loading…
Cancel
Save