Browse Source

Handle single ip port on dual-stack

In the case of dual-stack network, users might create a container
with existing neutron port. In before, kuryr assumes the
existing port is a dual-port (with both v4 and v6 addresses)
but this assumption is not always true. In face, it is
possible to create a v4 only port in a dual-stack network and
use it as an existing port.

This commit handle the case that the container is created from
a dual-net with a specified v4-only port. In this case, kuryr
will create a v6 port in ipam_request_address as a place holder.
The v6 port will be removed at network_driver_create_endpoint.

Related-Bug: #1800375
Change-Id: Id988abf1b6560332b18a60d99658a8768d46c343
Hongbin Lu 5 months ago
parent
commit
68cb29267a
2 changed files with 152 additions and 4 deletions
  1. 17
    4
      kuryr_libnetwork/controllers.py
  2. 135
    0
      kuryr_libnetwork/tests/unit/test_kuryr.py

+ 17
- 4
kuryr_libnetwork/controllers.py View File

@@ -317,15 +317,28 @@ def _create_or_update_port(neutron_network_id, endpoint_id,
317 317
     # For the container boot from dual-net, request_address will
318 318
     # create two ports(v4 and v6 address), we should only allow one
319 319
     # for port bind.
320
+    # There are two cases:
321
+    # 1. User specifies an existing port with v4 address only.
322
+    #    In this case, Kuryr creates the v6 port in ipam_request_address.
323
+    #    We will bind the v4 port and remove the v6 port.
324
+    # 2. Users doesn't specify a port. In this case Kuryr creates
325
+    #    the v4 and v6 ports in ipam_request_address and
326
+    #    we will delete both ports then re-create a dual-port.
320 327
     elif num_port == 2:
328
+        response_port = None
321 329
         for port in filtered_ports.get('ports', []):
322 330
             port_name = port.get('name')
323 331
             if str(port_name).startswith(const.KURYR_UNBOUND_PORT):
324 332
                 app.neutron.delete_port(port['id'])
325
-        fixed_ips = (
326
-            lib_utils.get_dict_format_fixed_ips_from_kv_format(fixed_ips))
327
-        response_port = _create_port(endpoint_id, neutron_network_id,
328
-                                     interface_mac, fixed_ips)
333
+            else:
334
+                port_driver = get_driver(port)
335
+                response_port = port_driver.update_port(port, endpoint_id,
336
+                                                        interface_mac)
337
+        if not response_port:
338
+            fixed_ips = (
339
+                lib_utils.get_dict_format_fixed_ips_from_kv_format(fixed_ips))
340
+            response_port = _create_port(endpoint_id, neutron_network_id,
341
+                                         interface_mac, fixed_ips)
329 342
     else:
330 343
         raise exceptions.DuplicatedResourceException(
331 344
             "Multiple ports exist for the cidrs {0} and {1}"

+ 135
- 0
kuryr_libnetwork/tests/unit/test_kuryr.py View File

@@ -1723,6 +1723,141 @@ class TestKuryr(base.TestKuryrBase):
1723 1723
         expected = {'Interface': {}}
1724 1724
         self.assertEqual(expected, decoded_json)
1725 1725
 
1726
+    @mock.patch('kuryr_libnetwork.controllers.DEFAULT_DRIVER'
1727
+                '.create_host_iface')
1728
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks')
1729
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.show_port')
1730
+    @mock.patch('kuryr_libnetwork.controllers.DEFAULT_DRIVER.update_port')
1731
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.delete_port')
1732
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports')
1733
+    @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnets')
1734
+    @mock.patch('kuryr_libnetwork.controllers.app')
1735
+    @ddt.data(
1736
+        (False), (True))
1737
+    def test_network_driver_create_v4_endpoint_in_dual_net(
1738
+            self, vif_plug_is_fatal,
1739
+            mock_vif, mock_list_subnets, mock_list_ports, mock_delete_port,
1740
+            mock_update_port, mock_show_port, mock_list_networks,
1741
+            mock_create_host_iface):
1742
+        mock_vif.vif_plug_is_fatal = vif_plug_is_fatal
1743
+        fake_docker_network_id = lib_utils.get_hash()
1744
+        fake_docker_endpoint_id = lib_utils.get_hash()
1745
+        fake_neutron_net_id = uuidutils.generate_uuid()
1746
+        t = utils.make_net_tags(fake_docker_network_id)
1747
+        te = t + ',' + utils.existing_net_tag(fake_docker_network_id)
1748
+
1749
+        def mock_network(*args, **kwargs):
1750
+            if kwargs['tags'] == te:
1751
+                return self._get_fake_list_network(
1752
+                    fake_neutron_net_id,
1753
+                    check_existing=True)
1754
+            elif kwargs['tags'] == t:
1755
+                return self._get_fake_list_network(
1756
+                    fake_neutron_net_id)
1757
+        mock_list_networks.side_effect = mock_network
1758
+        fake_neutron_network = self._get_fake_list_network(
1759
+            fake_neutron_net_id)
1760
+
1761
+        # The following fake response is retrieved from the Neutron doc:
1762
+        #   http://developer.openstack.org/api-ref-networking-v2.html#createSubnet  # noqa
1763
+        subnet_v4_id = uuidutils.generate_uuid()
1764
+        subnet_v6_id = uuidutils.generate_uuid()
1765
+        fake_v4_subnet = self._get_fake_v4_subnet(
1766
+            fake_neutron_net_id, fake_docker_endpoint_id, subnet_v4_id)
1767
+        fake_v6_subnet = self._get_fake_v6_subnet(
1768
+            fake_neutron_net_id, fake_docker_endpoint_id, subnet_v6_id)
1769
+        fake_v4_subnet_response = {
1770
+            "subnets": [
1771
+                fake_v4_subnet['subnet']
1772
+            ]
1773
+        }
1774
+        fake_v6_subnet_response = {
1775
+            "subnets": [
1776
+                fake_v6_subnet['subnet']
1777
+            ]
1778
+        }
1779
+
1780
+        def mock_fake_subnet(*args, **kwargs):
1781
+            if kwargs['cidr'] == '192.168.1.0/24':
1782
+                return fake_v4_subnet_response
1783
+            elif kwargs['cidr'] == 'fe80::/64':
1784
+                return fake_v6_subnet_response
1785
+        mock_list_subnets.side_effect = mock_fake_subnet
1786
+
1787
+        fake_fixed_ips = ['subnet_id=%s' % subnet_v4_id,
1788
+                          'ip_address=192.168.1.2']
1789
+        fake_mac_address = 'fa:16:3e:20:57:c5'
1790
+        fake_v4_port_id = uuidutils.generate_uuid()
1791
+        fake_new_port_response = self._get_fake_port(
1792
+            fake_docker_endpoint_id, fake_neutron_net_id,
1793
+            fake_v4_port_id, lib_const.PORT_STATUS_ACTIVE,
1794
+            subnet_v4_id, neutron_mac_address=fake_mac_address)
1795
+
1796
+        fake_v4_port_response = self._get_fake_port(
1797
+            "fake-name1", fake_neutron_net_id,
1798
+            fake_v4_port_id, lib_const.PORT_STATUS_DOWN,
1799
+            subnet_v4_id)
1800
+
1801
+        fake_v6_port_id = uuidutils.generate_uuid()
1802
+        fake_v6_port_response = self._get_fake_port(
1803
+            "fake-name2", fake_neutron_net_id,
1804
+            fake_v6_port_id, lib_const.PORT_STATUS_DOWN,
1805
+            subnet_v6_id, name=constants.KURYR_UNBOUND_PORT,
1806
+            neutron_mac_address="fa:16:3e:20:57:c4")
1807
+
1808
+        fake_ports_response = {
1809
+            "ports": [
1810
+                fake_v4_port_response['port'],
1811
+                fake_v6_port_response['port']
1812
+            ]
1813
+        }
1814
+        mock_list_ports.return_value = fake_ports_response
1815
+
1816
+        mock_update_port.return_value = fake_new_port_response['port']
1817
+
1818
+        fake_neutron_subnets = [fake_v4_subnet['subnet']]
1819
+        fake_create_iface_response = ('fake stdout', '')
1820
+
1821
+        mock_create_host_iface.return_value = fake_create_iface_response
1822
+
1823
+        if vif_plug_is_fatal:
1824
+            fake_neutron_ports_response_2 = self._get_fake_port(
1825
+                fake_docker_endpoint_id, fake_neutron_net_id,
1826
+                fake_v4_port_id, lib_const.PORT_STATUS_ACTIVE,
1827
+                subnet_v4_id, subnet_v6_id)
1828
+            mock_show_port.return_value = fake_neutron_ports_response_2
1829
+
1830
+        data = {
1831
+            'NetworkID': fake_docker_network_id,
1832
+            'EndpointID': fake_docker_endpoint_id,
1833
+            'Options': {},
1834
+            'Interface': {
1835
+                'Address': '192.168.1.2/24',
1836
+                'MacAddress': fake_mac_address
1837
+            }
1838
+        }
1839
+        response = self.app.post('/NetworkDriver.CreateEndpoint',
1840
+                                 content_type='application/json',
1841
+                                 data=jsonutils.dumps(data))
1842
+
1843
+        self.assertEqual(200, response.status_code)
1844
+        mock_list_subnets.assert_any_call(
1845
+            network_id=fake_neutron_net_id, cidr='192.168.1.0/24')
1846
+        mock_list_ports.assert_called_with(fixed_ips=fake_fixed_ips)
1847
+        mock_delete_port.assert_any_call(fake_v6_port_id)
1848
+        mock_update_port.assert_called_with(
1849
+            fake_v4_port_response['port'], fake_docker_endpoint_id,
1850
+            fake_mac_address)
1851
+        mock_list_networks.assert_any_call(tags=t)
1852
+        mock_create_host_iface.assert_called_with(fake_docker_endpoint_id,
1853
+            fake_new_port_response['port'], fake_neutron_subnets,
1854
+            fake_neutron_network['networks'][0])
1855
+        if vif_plug_is_fatal:
1856
+            mock_show_port.assert_called_with(fake_v4_port_id)
1857
+        decoded_json = jsonutils.loads(response.data)
1858
+        expected = {'Interface': {}}
1859
+        self.assertEqual(expected, decoded_json)
1860
+
1726 1861
     @mock.patch('kuryr_libnetwork.controllers.DEFAULT_DRIVER'
1727 1862
                 '.create_host_iface')
1728 1863
     @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks')

Loading…
Cancel
Save