Skip to content

Commit 8f4b740

Browse files
committed
Retry attachment delete API call for 504 Gateway Timeout
When cinder-api runs behind a load balancer(eg haproxy), the load balancer can return 504 Gateway Timeout when cinder-api does not respond within timeout. This change ensures nova retries deleting a volume attachment in that case. Also this change makes nova ignore 404 in the API call. This is required because cinder might continue deleting the attachment even if the load balancer returns 504. This also helps us in the situation where the volume attachment was accidentally removed by users. Closes-Bug: #1978444 Change-Id: I593011d9f4c43cdae7a3d53b556c6e2a2b939989
1 parent d869163 commit 8f4b740

File tree

3 files changed

+54
-11
lines changed

3 files changed

+54
-11
lines changed

nova/tests/unit/volume/test_cinder.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -520,16 +520,15 @@ def test_attachment_delete(self, mock_cinderclient):
520520
@mock.patch('nova.volume.cinder.cinderclient')
521521
def test_attachment_delete_failed(self, mock_cinderclient, mock_log):
522522
mock_cinderclient.return_value.attachments.delete.side_effect = (
523-
cinder_exception.NotFound(404, '404'))
523+
cinder_exception.BadRequest(400, '400'))
524524

525525
attachment_id = uuids.attachment
526-
ex = self.assertRaises(exception.VolumeAttachmentNotFound,
526+
ex = self.assertRaises(exception.InvalidInput,
527527
self.api.attachment_delete,
528528
self.ctx,
529529
attachment_id)
530530

531-
self.assertEqual(404, ex.code)
532-
self.assertIn(attachment_id, str(ex))
531+
self.assertEqual(400, ex.code)
533532

534533
@mock.patch('nova.volume.cinder.cinderclient',
535534
side_effect=exception.CinderAPIVersionNotAvailable(
@@ -545,6 +544,16 @@ def test_attachment_delete_unsupported_api_version(self,
545544
mock_cinderclient.assert_called_once_with(self.ctx, '3.44',
546545
skip_version_check=True)
547546

547+
@mock.patch('nova.volume.cinder.cinderclient')
548+
def test_attachment_delete_not_found(self, mock_cinderclient):
549+
mock_cinderclient.return_value.attachments.delete.side_effect = (
550+
cinder_exception.ClientException(404))
551+
552+
attachment_id = uuids.attachment
553+
self.api.attachment_delete(self.ctx, attachment_id)
554+
555+
self.assertEqual(1, mock_cinderclient.call_count)
556+
548557
@mock.patch('nova.volume.cinder.cinderclient')
549558
def test_attachment_delete_internal_server_error(self, mock_cinderclient):
550559
mock_cinderclient.return_value.attachments.delete.side_effect = (
@@ -568,6 +577,29 @@ def test_attachment_delete_internal_server_error_do_not_raise(
568577

569578
self.assertEqual(2, mock_cinderclient.call_count)
570579

580+
@mock.patch('nova.volume.cinder.cinderclient')
581+
def test_attachment_delete_gateway_timeout(self, mock_cinderclient):
582+
mock_cinderclient.return_value.attachments.delete.side_effect = (
583+
cinder_exception.ClientException(504))
584+
585+
self.assertRaises(cinder_exception.ClientException,
586+
self.api.attachment_delete,
587+
self.ctx, uuids.attachment_id)
588+
589+
self.assertEqual(5, mock_cinderclient.call_count)
590+
591+
@mock.patch('nova.volume.cinder.cinderclient')
592+
def test_attachment_delete_gateway_timeout_do_not_raise(
593+
self, mock_cinderclient):
594+
# generate exception, and then have a normal return on the next retry
595+
mock_cinderclient.return_value.attachments.delete.side_effect = [
596+
cinder_exception.ClientException(504), None]
597+
598+
attachment_id = uuids.attachment
599+
self.api.attachment_delete(self.ctx, attachment_id)
600+
601+
self.assertEqual(2, mock_cinderclient.call_count)
602+
571603
@mock.patch('nova.volume.cinder.cinderclient')
572604
def test_attachment_delete_bad_request_exception(self, mock_cinderclient):
573605
mock_cinderclient.return_value.attachments.delete.side_effect = (

nova/volume/cinder.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -888,19 +888,23 @@ def attachment_update(self, context, attachment_id, connector,
888888
@retrying.retry(stop_max_attempt_number=5,
889889
retry_on_exception=lambda e:
890890
(isinstance(e, cinder_exception.ClientException) and
891-
e.code == 500))
891+
e.code in (500, 504)))
892892
def attachment_delete(self, context, attachment_id):
893893
try:
894894
cinderclient(
895895
context, '3.44', skip_version_check=True).attachments.delete(
896896
attachment_id)
897897
except cinder_exception.ClientException as ex:
898-
with excutils.save_and_reraise_exception():
899-
LOG.error('Delete attachment failed for attachment '
900-
'%(id)s. Error: %(msg)s Code: %(code)s',
901-
{'id': attachment_id,
902-
'msg': str(ex),
903-
'code': getattr(ex, 'code', None)})
898+
if ex.code == 404:
899+
LOG.warning('Attachment %(id)s does not exist. Ignoring.',
900+
{'id': attachment_id})
901+
else:
902+
with excutils.save_and_reraise_exception():
903+
LOG.error('Delete attachment failed for attachment '
904+
'%(id)s. Error: %(msg)s Code: %(code)s',
905+
{'id': attachment_id,
906+
'msg': str(ex),
907+
'code': getattr(ex, 'code', None)})
904908

905909
@translate_attachment_exception
906910
def attachment_complete(self, context, attachment_id):
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
`Bug #1978444 <https://bugs.launchpad.net/nova/+bug/1978444>`_: Now nova
5+
retries deleting a volume attachment in case Cinder API returns
6+
``504 Gateway Timeout``. Also, ``404 Not Found`` is now ignored and
7+
leaves only a warning message.

0 commit comments

Comments
 (0)