Don't fail if VIP already exist or has been deleted before

Sometimes there is a race condition on creation of VIP port
or deleting it that ends with exception and blocks LB stack
creation / deletion.

Because for now we don't have running master branch
for OVN provider driver this change will be applied
first on stable/train in networking-ovn tree and then cherry-picked.

Change-Id: I2aaae7c407caba7a57e2ca2d4ed524f3bb63953f
Closes-Bug: #1860141
This commit is contained in:
Maciej Józefczyk 2020-01-17 15:08:27 +00:00
parent 439fc8f4b0
commit 42106d4696
2 changed files with 81 additions and 10 deletions

View File

@ -1015,12 +1015,8 @@ class OvnProviderHelper(object):
'loadbalancers': [{"id": loadbalancer['id'],
"provisioning_status": constants.ERROR,
"operating_status": constants.ERROR}]}
try:
# Delete VIP port from neutron.
self.delete_vip_port(port_id)
except n_exc.PortNotFoundClient:
LOG.warning("Port %s could not be found. Please "
"check Neutron logs", port_id)
# Delete VIP port from neutron.
self.delete_vip_port(port_id)
return status
def _lb_delete(self, loadbalancer, ovn_lb, status):
@ -1756,11 +1752,36 @@ class OvnProviderHelper(object):
except KeyError:
pass
network_driver = get_network_driver()
return network_driver.neutron_client.create_port(port)
try:
return network_driver.neutron_client.create_port(port)
except n_exc.IpAddressAlreadyAllocatedClient:
# Sometimes the VIP is already created (race-conditions)
# Lets get the it from Neutron API.
ports = network_driver.neutron_client.list_ports(
network_id=vip_d['vip_network_id'],
name='%s%s' % (ovn_const.LB_VIP_PORT_PREFIX, lb_id))
if not ports['ports']:
LOG.error('Cannot create/get LoadBalancer VIP port with '
'fixed IP: %s', vip_d['vip_address'])
status = {'loadbalancers': [{
"id": lb_id,
"provisioning_status": constants.ERROR,
"operating_status": constants.ERROR}]}
self._update_status_to_octavia(status)
return
# there should only be one port returned
port = ports['ports'][0]
LOG.debug('VIP Port already exists, uuid: %s', port['id'])
return {'port': port}
def delete_vip_port(self, port_id):
network_driver = get_network_driver()
network_driver.neutron_client.delete_port(port_id)
try:
network_driver.neutron_client.delete_port(port_id)
except n_exc.PortNotFoundClient:
LOG.warning("Port %s could not be found. Please "
"check Neutron logs. Perhaps port "
"was already deleted.", port_id)
def handle_vip_fip(self, fip_info):
ovn_lb = fip_info['ovn_lb']

View File

@ -1004,8 +1004,8 @@ class TestOvnProviderHelper(TestOvnOctaviaBase):
@mock.patch('ovn_octavia_provider.driver.get_network_driver')
@mock.patch.object(ovn_driver.OvnProviderHelper, 'delete_vip_port')
def test_lb_delete_port_not_found(self, del_port, net_dr):
net_dr.return_value.neutron_client.delete_port.return_value = None
del_port.side_effect = [n_exc.PortNotFoundClient]
net_dr.return_value.neutron_client.delete_port.side_effect = (
[n_exc.PortNotFoundClient])
status = self.helper.lb_delete(self.ovn_lb)
self.assertEqual(status['loadbalancers'][0]['provisioning_status'],
constants.DELETED)
@ -2519,6 +2519,56 @@ class TestOvnProviderHelper(TestOvnOctaviaBase):
mock.call().neutron_client.create_port(expected_dict)]
gn.assert_has_calls(expected_call)
@mock.patch('ovn_octavia_provider.driver.get_network_driver')
def test_create_vip_port_vip_selected_already_exist(self, net_dr):
net_dr.return_value.neutron_client.create_port.side_effect = [
n_exc.IpAddressAlreadyAllocatedClient]
net_dr.return_value.neutron_client.list_ports.return_value = {
'ports': [
{'name': 'ovn-lb-vip-' + self.loadbalancer_id,
'id': self.loadbalancer_id}]}
self.vip_dict['vip_address'] = '10.1.10.1'
ret = self.helper.create_vip_port(
self.project_id,
self.loadbalancer_id,
self.vip_dict)
expected = {
'port': {
'name': '%s%s' % (ovn_const.LB_VIP_PORT_PREFIX,
self.loadbalancer_id),
'id': self.loadbalancer_id}}
self.assertDictEqual(expected, ret)
expected_call = [
mock.call().neutron_client.list_ports(
network_id='%s' % self.vip_dict['vip_network_id'],
name='%s%s' % (ovn_const.LB_VIP_PORT_PREFIX,
self.loadbalancer_id))]
net_dr.assert_has_calls(expected_call)
@mock.patch('ovn_octavia_provider.driver.get_network_driver')
def test_create_vip_port_vip_selected_other_allocation_exist(self, net_dr):
net_dr.return_value.neutron_client.create_port.side_effect = [
n_exc.IpAddressAlreadyAllocatedClient]
net_dr.return_value.neutron_client.list_ports.return_value = {
'ports': []}
self.vip_dict['vip_address'] = '10.1.10.1'
ret = self.helper.create_vip_port(
self.project_id,
self.loadbalancer_id,
self.vip_dict)
self.assertIsNone(ret)
expected_call = [
mock.call().neutron_client.list_ports(
network_id='%s' % self.vip_dict['vip_network_id'],
name='%s%s' % (ovn_const.LB_VIP_PORT_PREFIX,
self.loadbalancer_id))]
net_dr.assert_has_calls(expected_call)
self.helper._update_status_to_octavia.assert_called_once_with(
{'loadbalancers':
[{'id': self.loadbalancer_id,
'provisioning_status': 'ERROR',
'operating_status': 'ERROR'}]})
def test_get_member_info(self):
ret = self.helper.get_member_info(self.pool_id)
self.assertEqual([(self.member_id, '%s:%s' % (self.member_address,