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
This commit is contained in:
Hongbin Lu 2018-10-28 20:47:41 +00:00
parent 542cb5aabd
commit 68cb29267a
2 changed files with 152 additions and 4 deletions

View File

@ -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}"

View File

@ -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')