Merge "Add user messages for extend volume operation"
This commit is contained in:
commit
7d6df90ee3
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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'},
|
||||||
|
])
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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'})
|
||||||
|
Loading…
Reference in New Issue
Block a user