OnMetal Driver Can't Handle 404s on Port Delete

When quark tries to delete an onmetal port that no longer
exists downstream, it should happily ignore the 404 and
move on.Instead, it complains and retries before eventually
giving up. This is due to not catching the correctexception
from the neutron client.This doesn't present as a 500 to
any clients, as the exception is caught and ignored,
but it gums up the logs and wastes time making requests
multiple times.

Implements: Exception handling
Closes-Bug: #1738277
Change-Id: I4ccd9be891e6c533ceb68427d47ed658bf27f4a8
This commit is contained in:
inimitableharish 2017-12-12 00:04:31 +05:30 committed by MultipleCrashes
parent 92fa456c71
commit 43c8359db5
2 changed files with 42 additions and 12 deletions

View File

@ -20,6 +20,7 @@ import json
import netaddr import netaddr
from neutron_lib import exceptions as n_exc from neutron_lib import exceptions as n_exc
from neutronclient.neutron import client as neutron_client
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import importutils from oslo_utils import importutils
@ -397,13 +398,13 @@ class IronicDriver(base.BaseDriver):
def _delete_port(self, context, port_id): def _delete_port(self, context, port_id):
try: try:
return self._client.delete_port(port_id) return self._client.delete_port(port_id)
except neutron_client.utils.exceptions.PortNotFoundClient:
# Port not found exception encountered, such as port deleted
LOG.error("port %s not found downstream."
"ignoring delete." % (port_id))
return None
except Exception as e: except Exception as e:
# This doesn't get wrapped by the client unfortunately. # Any generic exception (say 500 or others)
if "404 not found" in str(e).lower():
LOG.error("port %s not found downstream. ignoring delete."
% (port_id))
return
msg = ("failed to delete downstream port. " msg = ("failed to delete downstream port. "
"exception: %s" % (e)) "exception: %s" % (e))
LOG.exception(msg) LOG.exception(msg)

View File

@ -14,18 +14,17 @@
# under the License. # under the License.
import contextlib import contextlib
import netaddr
import json import json
import mock import mock
import netaddr
from neutronclient.neutron import client as neutron_client
from oslo_config import cfg from oslo_config import cfg
import quark.drivers.ironic_driver import quark.drivers.ironic_driver
from quark import network_strategy from quark import network_strategy
from quark.tests import test_base from quark.tests import test_base
CONF = cfg.CONF
class TestIronicDriverBase(test_base.TestBase): class TestIronicDriverBase(test_base.TestBase):
@ -569,6 +568,29 @@ class TestIronicDriverCreatePort(TestIronicDriverBase):
class TestIronicDriverDeletePort(TestIronicDriverBase): class TestIronicDriverDeletePort(TestIronicDriverBase):
@contextlib.contextmanager
def test_port_not_found_exception_no_retry(self):
'''When port not found we must only call delete once.'''
ironic_driver = quark.drivers.ironic_driver.IronicDriver()
ironic_driver._client = mock.MagicMock()
ironic_driver._client.delete_port.return_value = None
side_effect = neutron_client.utils.exceptions.PortNotFoundClient
ironic_driver._client.delete_port.side_effect = side_effect
port_id = 'very_fake_port_id'
ironic_driver.delete_port(self.context, port_id)
ironic_driver._client.delete_port.assert_called_once_with(port_id)
@contextlib.contextmanager
def test_port_not_found_exception_with_retry(self):
'''When port delete fails for unknown reasons, retry.'''
ironic_driver = quark.drivers.ironic_driver.IronicDriver()
ironic_driver._client = mock.MagicMock()
ironic_driver._client.delete_port.side_effect = Exception
port_id = 'very_fake_port_id'
ironic_driver.delete_port(self.context, port_id)
self.assertEqual(ironic_driver._client.delete_port.call_count,
CONF.IRONIC.operation_retries)
def test_delete_port(self): def test_delete_port(self):
with self._stubs(delete_port=[None]) as (driver, client, with self._stubs(delete_port=[None]) as (driver, client,
_, delete): _, delete):
@ -576,6 +598,7 @@ class TestIronicDriverDeletePort(TestIronicDriverBase):
delete.assert_called_once_with("foo") delete.assert_called_once_with("foo")
def test_delete_port_retries(self): def test_delete_port_retries(self):
delete_port = [Exception("uhoh"), None] delete_port = [Exception("uhoh"), None]
with self._stubs(delete_port=delete_port) as (driver, with self._stubs(delete_port=delete_port) as (driver,
client, _, client, _,
@ -601,12 +624,18 @@ class TestIronicDriverDeletePort(TestIronicDriverBase):
mock.call("foo")]) mock.call("foo")])
def test_delete_port_ignores_404(self): def test_delete_port_ignores_404(self):
'''Any exception other than neutronclient.PortNotFound would be
considered as generic exception, one retry for generic exception
'''
delete_port = [Exception("404 not found"), None] delete_port = [Exception("404 not found"), None]
with self._stubs(delete_port=delete_port) as (driver, with self._stubs(delete_port=delete_port) as (driver,
client, _, client, _,
delete): delete):
driver.delete_port(self.context, "foo") driver.delete_port(self.context, "foo")
delete.assert_called_once_with("foo") self.assertEqual(delete.call_count, 2)
class TestIronicDriverUpdatePort(TestIronicDriverBase): class TestIronicDriverUpdatePort(TestIronicDriverBase):