Merge "Add user messages for extend volume operation"

This commit is contained in:
Zuul 2018-06-06 16:02:10 +00:00 committed by Gerrit Code Review
commit 7d6df90ee3
7 changed files with 104 additions and 15 deletions

View File

@ -27,6 +27,8 @@ from requests import exceptions as request_exceptions
from cinder.db import base from cinder.db import base
from cinder import exception from cinder import exception
from cinder.message import api as message_api
from cinder.message import message_field
from cinder import service_auth from cinder import service_auth
nova_opts = [ nova_opts = [
@ -131,6 +133,9 @@ def novaclient(context, privileged_user=False, timeout=None, api_version=None):
class API(base.Base): class API(base.Base):
"""API for interacting with novaclient.""" """API for interacting with novaclient."""
def __init__(self):
self.message_api = message_api.API()
def _get_volume_extended_event(self, server_id, volume_id): def _get_volume_extended_event(self, server_id, volume_id):
return {'name': 'volume-extended', return {'name': 'volume-extended',
'server_uuid': server_id, 'server_uuid': server_id,
@ -143,12 +148,14 @@ class API(base.Base):
response = nova.server_external_events.create(events) response = nova.server_external_events.create(events)
except nova_exceptions.NotFound: except nova_exceptions.NotFound:
LOG.warning('Nova returned NotFound for events: %s.', events) LOG.warning('Nova returned NotFound for events: %s.', events)
return False
except Exception: except Exception:
LOG.exception('Failed to notify nova on events: %s.', events) LOG.exception('Failed to notify nova on events: %s.', events)
return False
else: else:
if not isinstance(response, list): if not isinstance(response, list):
LOG.error('Error response returned from nova: %s.', response) LOG.error('Error response returned from nova: %s.', response)
return return False
response_error = False response_error = False
for event in response: for event in response:
code = event.get('code') code = event.get('code')
@ -162,6 +169,8 @@ class API(base.Base):
LOG.info('Nova event response: %s.', event) LOG.info('Nova event response: %s.', event)
if response_error: if response_error:
LOG.error('Error response returned from nova: %s.', response) LOG.error('Error response returned from nova: %s.', response)
return False
return True
def has_extension(self, context, extension, timeout=None): def has_extension(self, context, extension, timeout=None):
try: try:
@ -207,4 +216,11 @@ class API(base.Base):
api_version = '2.51' api_version = '2.51'
events = [self._get_volume_extended_event(server_id, volume_id) events = [self._get_volume_extended_event(server_id, volume_id)
for server_id in server_ids] for server_id in server_ids]
self._send_events(context, events, api_version=api_version) result = self._send_events(context, events, api_version=api_version)
if not result:
self.message_api.create(
context,
message_field.Action.EXTEND_VOLUME,
resource_uuid=volume_id,
detail=message_field.Detail.NOTIFY_COMPUTE_SERVICE_FAILED)
return result

View File

@ -37,13 +37,15 @@ class Action(object):
UPDATE_ATTACHMENT = ('004', _('update attachment')) UPDATE_ATTACHMENT = ('004', _('update attachment'))
COPY_IMAGE_TO_VOLUME = ('005', _('copy image to volume')) COPY_IMAGE_TO_VOLUME = ('005', _('copy image to volume'))
UNMANAGE_VOLUME = ('006', _('unmanage volume')) UNMANAGE_VOLUME = ('006', _('unmanage volume'))
EXTEND_VOLUME = ('007', _('extend volume'))
ALL = (SCHEDULE_ALLOCATE_VOLUME, ALL = (SCHEDULE_ALLOCATE_VOLUME,
ATTACH_VOLUME, ATTACH_VOLUME,
COPY_VOLUME_TO_IMAGE, COPY_VOLUME_TO_IMAGE,
UPDATE_ATTACHMENT, UPDATE_ATTACHMENT,
COPY_IMAGE_TO_VOLUME, COPY_IMAGE_TO_VOLUME,
UNMANAGE_VOLUME UNMANAGE_VOLUME,
EXTEND_VOLUME
) )
@ -68,6 +70,12 @@ class Detail(object):
UNMANAGE_ENC_NOT_SUPPORTED = ( UNMANAGE_ENC_NOT_SUPPORTED = (
'008', '008',
_("Unmanaging encrypted volumes is not supported.")) _("Unmanaging encrypted volumes is not supported."))
NOTIFY_COMPUTE_SERVICE_FAILED = (
'009',
_("Compute service failed to extend volume."))
DRIVER_FAILED_EXTEND = (
'010',
_("Volume Driver failed to extend volume."))
ALL = (UNKNOWN_ERROR, ALL = (UNKNOWN_ERROR,
DRIVER_NOT_INITIALIZED, DRIVER_NOT_INITIALIZED,
@ -77,6 +85,8 @@ class Detail(object):
QUOTA_EXCEED, QUOTA_EXCEED,
NOT_ENOUGH_SPACE_FOR_IMAGE, NOT_ENOUGH_SPACE_FOR_IMAGE,
UNMANAGE_ENC_NOT_SUPPORTED, UNMANAGE_ENC_NOT_SUPPORTED,
NOTIFY_COMPUTE_SERVICE_FAILED,
DRIVER_FAILED_EXTEND
) )
# Exception and detail mappings # Exception and detail mappings

View File

@ -40,6 +40,7 @@ from cinder import flow_utils
from cinder.i18n import _ from cinder.i18n import _
from cinder import manager from cinder import manager
from cinder.message import api as mess_api from cinder.message import api as mess_api
from cinder.message import message_field
from cinder import objects from cinder import objects
from cinder.objects import fields from cinder.objects import fields
from cinder import quota from cinder import quota
@ -450,6 +451,11 @@ class SchedulerManager(manager.CleanableManager, manager.Manager):
QUOTAS.rollback(context, reservations, QUOTAS.rollback(context, reservations,
project_id=volume.project_id) project_id=volume.project_id)
_extend_volume_set_error(self, context, ex, request_spec) _extend_volume_set_error(self, context, ex, request_spec)
self.message_api.create(
context,
message_field.Action.EXTEND_VOLUME,
resource_uuid=volume.id,
exception=ex)
def _set_volume_state_and_notify(self, method, updates, context, ex, def _set_volume_state_and_notify(self, method, updates, context, ex,
request_spec, msg=None): request_spec, msg=None):

View File

@ -12,10 +12,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ddt
import mock import mock
from cinder.compute import nova from cinder.compute import nova
from cinder import context from cinder import context
from cinder.message import message_field
from cinder import test from cinder import test
from keystoneauth1 import loading as ks_loading from keystoneauth1 import loading as ks_loading
from novaclient import exceptions as nova_exceptions from novaclient import exceptions as nova_exceptions
@ -196,6 +198,7 @@ class FakeNovaClient(object):
pass pass
@ddt.ddt
class NovaApiTestCase(test.TestCase): class NovaApiTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(NovaApiTestCase, self).setUp() super(NovaApiTestCase, self).setUp()
@ -228,8 +231,10 @@ class NovaApiTestCase(test.TestCase):
mock.patch.object(self.novaclient.server_external_events, mock.patch.object(self.novaclient.server_external_events,
'create') as mock_create_event: 'create') as mock_create_event:
mock_novaclient.return_value = self.novaclient mock_novaclient.return_value = self.novaclient
mock_create_event.return_value = []
self.api.extend_volume(self.ctx, server_ids, 'volume_id') result = self.api.extend_volume(self.ctx, server_ids, 'volume_id')
self.assertTrue(result)
mock_novaclient.assert_called_once_with(self.ctx, mock_novaclient.assert_called_once_with(self.ctx,
privileged_user=True, privileged_user=True,
@ -242,3 +247,36 @@ class NovaApiTestCase(test.TestCase):
'server_uuid': 'server-id-2', 'server_uuid': 'server-id-2',
'tag': 'volume_id'}, 'tag': 'volume_id'},
]) ])
@ddt.data(nova_exceptions.NotFound,
Exception,
'illegal_list',
[{'code': None}])
@mock.patch('cinder.message.api.API.create')
def test_extend_volume_failed(self, nova_result, mock_create):
server_ids = ['server-id-1', 'server-id-2']
with mock.patch.object(nova, 'novaclient') as mock_novaclient, \
mock.patch.object(self.novaclient.server_external_events,
'create') as mock_create_event:
mock_novaclient.return_value = self.novaclient
mock_create_event.side_effect = [nova_result]
result = self.api.extend_volume(self.ctx, server_ids, 'volume_id')
self.assertFalse(result)
mock_novaclient.assert_called_once_with(self.ctx,
privileged_user=True,
api_version='2.51')
mock_create.assert_called_once_with(
self.ctx,
message_field.Action.EXTEND_VOLUME,
resource_uuid='volume_id',
detail=message_field.Detail.NOTIFY_COMPUTE_SERVICE_FAILED)
mock_create_event.assert_called_once_with([
{'name': 'volume-extended',
'server_uuid': 'server-id-1',
'tag': 'volume_id'},
{'name': 'volume-extended',
'server_uuid': 'server-id-2',
'tag': 'volume_id'},
])

View File

@ -181,7 +181,9 @@ class SchedulerManagerTestCase(test.TestCase):
'cinder.scheduler.host_manager.BackendState.consume_from_volume') 'cinder.scheduler.host_manager.BackendState.consume_from_volume')
@mock.patch('cinder.volume.rpcapi.VolumeAPI.extend_volume') @mock.patch('cinder.volume.rpcapi.VolumeAPI.extend_volume')
@mock.patch('cinder.quota.QUOTAS.rollback') @mock.patch('cinder.quota.QUOTAS.rollback')
def test_extend_volume_no_valid_host(self, status, mock_rollback, @mock.patch('cinder.message.api.API.create')
def test_extend_volume_no_valid_host(self, status, mock_create,
mock_rollback,
mock_extend, mock_consume, mock_extend, mock_consume,
mock_backend_passes): mock_backend_passes):
volume = fake_volume.fake_volume_obj(self.context, volume = fake_volume.fake_volume_obj(self.context,
@ -202,6 +204,11 @@ class SchedulerManagerTestCase(test.TestCase):
self.context, 'fake_reservation', project_id=volume.project_id) self.context, 'fake_reservation', project_id=volume.project_id)
mock_consume.assert_not_called() mock_consume.assert_not_called()
mock_extend.assert_not_called() mock_extend.assert_not_called()
mock_create.assert_called_once_with(
self.context,
message_field.Action.EXTEND_VOLUME,
resource_uuid=volume.id,
exception=no_valid_backend)
@mock.patch('cinder.quota.QuotaEngine.expire') @mock.patch('cinder.quota.QuotaEngine.expire')
def test_clean_expired_reservation(self, mock_clean): def test_clean_expired_reservation(self, mock_clean):

View File

@ -36,6 +36,7 @@ from cinder import context
from cinder import coordination from cinder import coordination
from cinder import db from cinder import db
from cinder import exception from cinder import exception
from cinder.message import message_field
from cinder import objects from cinder import objects
from cinder.objects import fields from cinder.objects import fields
from cinder.policies import volumes as vol_policy from cinder.policies import volumes as vol_policy
@ -2441,16 +2442,22 @@ class VolumeTestCase(base.BaseVolumeTestCase):
fake_reservations = ['RESERVATION'] fake_reservations = ['RESERVATION']
# Test driver exception # Test driver exception
with mock.patch.object(self.volume.driver, with mock.patch.object(
'extend_volume') as extend_volume: self.volume.driver, 'extend_volume',
extend_volume.side_effect =\ side_effect=exception.CinderException('fake exception')):
exception.CinderException('fake exception') with mock.patch.object(
volume['status'] = 'extending' self.volume.message_api, 'create') as mock_create:
self.volume.extend_volume(self.context, volume, '4', volume['status'] = 'extending'
fake_reservations) self.volume.extend_volume(self.context, volume, '4',
volume.refresh() fake_reservations)
self.assertEqual(2, volume.size) volume.refresh()
self.assertEqual('error_extending', volume.status) self.assertEqual(2, volume.size)
self.assertEqual('error_extending', volume.status)
mock_create.assert_called_once_with(
self.context,
message_field.Action.EXTEND_VOLUME,
resource_uuid=volume.id,
detail=message_field.Detail.DRIVER_FAILED_EXTEND)
@mock.patch('cinder.compute.API') @mock.patch('cinder.compute.API')
def _test_extend_volume_manager_successful(self, volume, nova_api): def _test_extend_volume_manager_successful(self, volume, nova_api):

View File

@ -2605,6 +2605,11 @@ class VolumeManager(manager.CleanableManager,
except Exception: except Exception:
LOG.exception("Extend volume failed.", LOG.exception("Extend volume failed.",
resource=volume) resource=volume)
self.message_api.create(
context,
message_field.Action.EXTEND_VOLUME,
resource_uuid=volume.id,
detail=message_field.Detail.DRIVER_FAILED_EXTEND)
try: try:
self.db.volume_update(context, volume.id, self.db.volume_update(context, volume.id,
{'status': 'error_extending'}) {'status': 'error_extending'})