From 68cb29267a1b0db19c31709959c2b2e88501780d Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sun, 28 Oct 2018 20:47:41 +0000 Subject: [PATCH] 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 --- kuryr_libnetwork/controllers.py | 21 +++- kuryr_libnetwork/tests/unit/test_kuryr.py | 135 ++++++++++++++++++++++ 2 files changed, 152 insertions(+), 4 deletions(-) diff --git a/kuryr_libnetwork/controllers.py b/kuryr_libnetwork/controllers.py index 9ef71b43..dc86c7d2 100644 --- a/kuryr_libnetwork/controllers.py +++ b/kuryr_libnetwork/controllers.py @@ -317,15 +317,28 @@ def _create_or_update_port(neutron_network_id, endpoint_id, # For the container boot from dual-net, request_address will # create two ports(v4 and v6 address), we should only allow one # for port bind. + # There are two cases: + # 1. User specifies an existing port with v4 address only. + # In this case, Kuryr creates the v6 port in ipam_request_address. + # We will bind the v4 port and remove the v6 port. + # 2. Users doesn't specify a port. In this case Kuryr creates + # the v4 and v6 ports in ipam_request_address and + # we will delete both ports then re-create a dual-port. elif num_port == 2: + response_port = None for port in filtered_ports.get('ports', []): port_name = port.get('name') if str(port_name).startswith(const.KURYR_UNBOUND_PORT): app.neutron.delete_port(port['id']) - fixed_ips = ( - lib_utils.get_dict_format_fixed_ips_from_kv_format(fixed_ips)) - response_port = _create_port(endpoint_id, neutron_network_id, - interface_mac, fixed_ips) + else: + port_driver = get_driver(port) + response_port = port_driver.update_port(port, endpoint_id, + interface_mac) + if not response_port: + fixed_ips = ( + lib_utils.get_dict_format_fixed_ips_from_kv_format(fixed_ips)) + response_port = _create_port(endpoint_id, neutron_network_id, + interface_mac, fixed_ips) else: raise exceptions.DuplicatedResourceException( "Multiple ports exist for the cidrs {0} and {1}" diff --git a/kuryr_libnetwork/tests/unit/test_kuryr.py b/kuryr_libnetwork/tests/unit/test_kuryr.py index bc3921e8..8872faa1 100644 --- a/kuryr_libnetwork/tests/unit/test_kuryr.py +++ b/kuryr_libnetwork/tests/unit/test_kuryr.py @@ -1723,6 +1723,141 @@ class TestKuryr(base.TestKuryrBase): expected = {'Interface': {}} self.assertEqual(expected, decoded_json) + @mock.patch('kuryr_libnetwork.controllers.DEFAULT_DRIVER' + '.create_host_iface') + @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks') + @mock.patch('kuryr_libnetwork.controllers.app.neutron.show_port') + @mock.patch('kuryr_libnetwork.controllers.DEFAULT_DRIVER.update_port') + @mock.patch('kuryr_libnetwork.controllers.app.neutron.delete_port') + @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports') + @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnets') + @mock.patch('kuryr_libnetwork.controllers.app') + @ddt.data( + (False), (True)) + def test_network_driver_create_v4_endpoint_in_dual_net( + self, vif_plug_is_fatal, + mock_vif, mock_list_subnets, mock_list_ports, mock_delete_port, + mock_update_port, mock_show_port, mock_list_networks, + mock_create_host_iface): + mock_vif.vif_plug_is_fatal = vif_plug_is_fatal + fake_docker_network_id = lib_utils.get_hash() + fake_docker_endpoint_id = lib_utils.get_hash() + fake_neutron_net_id = uuidutils.generate_uuid() + t = utils.make_net_tags(fake_docker_network_id) + te = t + ',' + utils.existing_net_tag(fake_docker_network_id) + + def mock_network(*args, **kwargs): + if kwargs['tags'] == te: + return self._get_fake_list_network( + fake_neutron_net_id, + check_existing=True) + elif kwargs['tags'] == t: + return self._get_fake_list_network( + fake_neutron_net_id) + mock_list_networks.side_effect = mock_network + fake_neutron_network = self._get_fake_list_network( + fake_neutron_net_id) + + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createSubnet # noqa + subnet_v4_id = uuidutils.generate_uuid() + subnet_v6_id = uuidutils.generate_uuid() + fake_v4_subnet = self._get_fake_v4_subnet( + fake_neutron_net_id, fake_docker_endpoint_id, subnet_v4_id) + fake_v6_subnet = self._get_fake_v6_subnet( + fake_neutron_net_id, fake_docker_endpoint_id, subnet_v6_id) + fake_v4_subnet_response = { + "subnets": [ + fake_v4_subnet['subnet'] + ] + } + fake_v6_subnet_response = { + "subnets": [ + fake_v6_subnet['subnet'] + ] + } + + def mock_fake_subnet(*args, **kwargs): + if kwargs['cidr'] == '192.168.1.0/24': + return fake_v4_subnet_response + elif kwargs['cidr'] == 'fe80::/64': + return fake_v6_subnet_response + mock_list_subnets.side_effect = mock_fake_subnet + + fake_fixed_ips = ['subnet_id=%s' % subnet_v4_id, + 'ip_address=192.168.1.2'] + fake_mac_address = 'fa:16:3e:20:57:c5' + fake_v4_port_id = uuidutils.generate_uuid() + fake_new_port_response = self._get_fake_port( + fake_docker_endpoint_id, fake_neutron_net_id, + fake_v4_port_id, lib_const.PORT_STATUS_ACTIVE, + subnet_v4_id, neutron_mac_address=fake_mac_address) + + fake_v4_port_response = self._get_fake_port( + "fake-name1", fake_neutron_net_id, + fake_v4_port_id, lib_const.PORT_STATUS_DOWN, + subnet_v4_id) + + fake_v6_port_id = uuidutils.generate_uuid() + fake_v6_port_response = self._get_fake_port( + "fake-name2", fake_neutron_net_id, + fake_v6_port_id, lib_const.PORT_STATUS_DOWN, + subnet_v6_id, name=constants.KURYR_UNBOUND_PORT, + neutron_mac_address="fa:16:3e:20:57:c4") + + fake_ports_response = { + "ports": [ + fake_v4_port_response['port'], + fake_v6_port_response['port'] + ] + } + mock_list_ports.return_value = fake_ports_response + + mock_update_port.return_value = fake_new_port_response['port'] + + fake_neutron_subnets = [fake_v4_subnet['subnet']] + fake_create_iface_response = ('fake stdout', '') + + mock_create_host_iface.return_value = fake_create_iface_response + + if vif_plug_is_fatal: + fake_neutron_ports_response_2 = self._get_fake_port( + fake_docker_endpoint_id, fake_neutron_net_id, + fake_v4_port_id, lib_const.PORT_STATUS_ACTIVE, + subnet_v4_id, subnet_v6_id) + mock_show_port.return_value = fake_neutron_ports_response_2 + + data = { + 'NetworkID': fake_docker_network_id, + 'EndpointID': fake_docker_endpoint_id, + 'Options': {}, + 'Interface': { + 'Address': '192.168.1.2/24', + 'MacAddress': fake_mac_address + } + } + response = self.app.post('/NetworkDriver.CreateEndpoint', + content_type='application/json', + data=jsonutils.dumps(data)) + + self.assertEqual(200, response.status_code) + mock_list_subnets.assert_any_call( + network_id=fake_neutron_net_id, cidr='192.168.1.0/24') + mock_list_ports.assert_called_with(fixed_ips=fake_fixed_ips) + mock_delete_port.assert_any_call(fake_v6_port_id) + mock_update_port.assert_called_with( + fake_v4_port_response['port'], fake_docker_endpoint_id, + fake_mac_address) + mock_list_networks.assert_any_call(tags=t) + mock_create_host_iface.assert_called_with(fake_docker_endpoint_id, + fake_new_port_response['port'], fake_neutron_subnets, + fake_neutron_network['networks'][0]) + if vif_plug_is_fatal: + mock_show_port.assert_called_with(fake_v4_port_id) + decoded_json = jsonutils.loads(response.data) + expected = {'Interface': {}} + self.assertEqual(expected, decoded_json) + @mock.patch('kuryr_libnetwork.controllers.DEFAULT_DRIVER' '.create_host_iface') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks')