Merge "Extend share will go through scheduler"
This commit is contained in:
commit
8e066548a4
@ -2435,6 +2435,15 @@ share_force_delete:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
share_force_extend:
|
||||
description: |
|
||||
(Admin only). Defines whether to go through scheduler, Set to `True` will
|
||||
extend share directly. Set to `False` will go through scheduler, default
|
||||
is `False`.
|
||||
in: body
|
||||
required: false
|
||||
type: boolean
|
||||
min_version: 2.64
|
||||
share_group_host:
|
||||
description: |
|
||||
The share group host name.
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"extend": {
|
||||
"new_size": 2
|
||||
"new_size": 2,
|
||||
"force": "true"
|
||||
}
|
||||
}
|
||||
|
@ -351,6 +351,7 @@ Request
|
||||
- share_id: share_id
|
||||
- extend: extend
|
||||
- new_size: share_new_size
|
||||
- force: share_force_extend
|
||||
|
||||
|
||||
Request example
|
||||
|
@ -353,3 +353,8 @@ user documentation.
|
||||
endpoint: 'update_security_service', 'update_security_service_check' and
|
||||
'add_security_service_check'.
|
||||
|
||||
2.64
|
||||
----
|
||||
Added 'force' field to extend share api, which can extend share directly
|
||||
without go through share scheduler.
|
||||
|
||||
|
@ -515,11 +515,11 @@ class ShareMixin(object):
|
||||
def _extend(self, req, id, body):
|
||||
"""Extend size of a share."""
|
||||
context = req.environ['manila.context']
|
||||
share, size = self._get_valid_resize_parameters(
|
||||
share, size, force = self._get_valid_extend_parameters(
|
||||
context, id, body, 'os-extend')
|
||||
|
||||
try:
|
||||
self.share_api.extend(context, share, size)
|
||||
self.share_api.extend(context, share, size, force=force)
|
||||
except (exception.InvalidInput, exception.InvalidShare) as e:
|
||||
raise webob.exc.HTTPBadRequest(explanation=six.text_type(e))
|
||||
except exception.ShareSizeExceedsAvailableQuota as e:
|
||||
@ -530,7 +530,7 @@ class ShareMixin(object):
|
||||
def _shrink(self, req, id, body):
|
||||
"""Shrink size of a share."""
|
||||
context = req.environ['manila.context']
|
||||
share, size = self._get_valid_resize_parameters(
|
||||
share, size = self._get_valid_shrink_parameters(
|
||||
context, id, body, 'os-shrink')
|
||||
|
||||
try:
|
||||
@ -540,15 +540,40 @@ class ShareMixin(object):
|
||||
|
||||
return webob.Response(status_int=http_client.ACCEPTED)
|
||||
|
||||
def _get_valid_resize_parameters(self, context, id, body, action):
|
||||
def _get_valid_extend_parameters(self, context, id, body, action):
|
||||
try:
|
||||
share = self.share_api.get(context, id)
|
||||
except exception.NotFound as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=six.text_type(e))
|
||||
|
||||
try:
|
||||
size = int(body.get(action,
|
||||
body.get(action.split('os-')[-1]))['new_size'])
|
||||
size = int(body.get(action, body.get('extend'))['new_size'])
|
||||
except (KeyError, ValueError, TypeError):
|
||||
msg = _("New share size must be specified as an integer.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
# force is True means share extend will extend directly, is False
|
||||
# means will go through scheduler. Default value is False,
|
||||
try:
|
||||
force = strutils.bool_from_string(body.get(
|
||||
action, body.get('extend'))['force'], strict=True)
|
||||
except KeyError:
|
||||
force = False
|
||||
except (ValueError, TypeError):
|
||||
msg = (_('Invalid boolean force : %(value)s') %
|
||||
{'value': body.get('extend')['force']})
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
return share, size, force
|
||||
|
||||
def _get_valid_shrink_parameters(self, context, id, body, action):
|
||||
try:
|
||||
share = self.share_api.get(context, id)
|
||||
except exception.NotFound as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=six.text_type(e))
|
||||
|
||||
try:
|
||||
size = int(body.get(action, body.get('shrink'))['new_size'])
|
||||
except (KeyError, ValueError, TypeError):
|
||||
msg = _("New share size must be specified as an integer.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
@ -391,11 +391,19 @@ class ShareController(shares.ShareMixin,
|
||||
@wsgi.action('os-extend')
|
||||
def extend_legacy(self, req, id, body):
|
||||
"""Extend size of a share."""
|
||||
body.get('os-extend', {}).pop('force', None)
|
||||
return self._extend(req, id, body)
|
||||
|
||||
@wsgi.Controller.api_version('2.7')
|
||||
@wsgi.Controller.api_version('2.7', '2.63')
|
||||
@wsgi.action('extend')
|
||||
def extend(self, req, id, body):
|
||||
"""Extend size of a share."""
|
||||
body.get('extend', {}).pop('force', None)
|
||||
return self._extend(req, id, body)
|
||||
|
||||
@wsgi.Controller.api_version('2.64') # noqa
|
||||
@wsgi.action('extend')
|
||||
def extend(self, req, id, body): # pylint: disable=function-redefined # noqa F811
|
||||
"""Extend size of a share."""
|
||||
return self._extend(req, id, body)
|
||||
|
||||
|
@ -422,6 +422,17 @@ shares_policies = [
|
||||
],
|
||||
deprecated_rule=deprecated_share_extend
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=BASE_POLICY_NAME % 'force_extend',
|
||||
check_str=base.SYSTEM_ADMIN_OR_PROJECT_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description="Force extend share.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/shares/{share_id}/action',
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=BASE_POLICY_NAME % 'shrink',
|
||||
check_str=base.SYSTEM_ADMIN_OR_PROJECT_MEMBER,
|
||||
|
@ -65,7 +65,7 @@ MAPPING = {
|
||||
class SchedulerManager(manager.Manager):
|
||||
"""Chooses a host to create shares."""
|
||||
|
||||
RPC_API_VERSION = '1.10'
|
||||
RPC_API_VERSION = '1.11'
|
||||
|
||||
def __init__(self, scheduler_driver=None, service_name=None,
|
||||
*args, **kwargs):
|
||||
@ -316,3 +316,35 @@ class SchedulerManager(manager.Manager):
|
||||
@coordination.synchronized('locked-clean-expired-messages')
|
||||
def _clean_expired_messages(self, context):
|
||||
self.message_api.cleanup_expired_messages(context)
|
||||
|
||||
def extend_share(self, context, share_id, new_size, reservations,
|
||||
request_spec=None, filter_properties=None):
|
||||
|
||||
def _extend_share_set_error(self, context, ex, request_spec):
|
||||
share_state = {'status': constants.STATUS_AVAILABLE}
|
||||
self._set_share_state_and_notify('extend_share', share_state,
|
||||
context, ex, request_spec)
|
||||
|
||||
share = db.share_get(context, share_id)
|
||||
try:
|
||||
target_host = self.driver.host_passes_filters(
|
||||
context,
|
||||
share['host'],
|
||||
request_spec, filter_properties)
|
||||
target_host.consume_from_share(
|
||||
{'size': int(new_size) - share['size']})
|
||||
share_rpcapi.ShareAPI().extend_share(context, share, new_size,
|
||||
reservations)
|
||||
except exception.NoValidHost as ex:
|
||||
quota.QUOTAS.rollback(context, reservations,
|
||||
project_id=share['project_id'],
|
||||
user_id=share['user_id'],
|
||||
share_type_id=share['share_type_id'])
|
||||
_extend_share_set_error(self, context, ex, request_spec)
|
||||
self.message_api.create(
|
||||
context,
|
||||
message_field.Action.EXTEND,
|
||||
share['project_id'],
|
||||
resource_type=message_field.Resource.SHARE,
|
||||
resource_id=share['id'],
|
||||
exception=ex)
|
||||
|
@ -43,9 +43,10 @@ class SchedulerAPI(object):
|
||||
1.8 - Rename create_consistency_group -> create_share_group method
|
||||
1.9 - Add cached parameter to get_pools method
|
||||
1.10 - Add timestamp to update_service_capabilities
|
||||
1.11 - Add extend_share
|
||||
"""
|
||||
|
||||
RPC_API_VERSION = '1.10'
|
||||
RPC_API_VERSION = '1.11'
|
||||
|
||||
def __init__(self):
|
||||
super(SchedulerAPI, self).__init__()
|
||||
@ -144,3 +145,17 @@ class SchedulerAPI(object):
|
||||
driver_options=driver_options,
|
||||
request_spec=request_spec,
|
||||
filter_properties=filter_properties)
|
||||
|
||||
def extend_share(self, context, share_id, new_size, reservations,
|
||||
request_spec, filter_properties=None):
|
||||
call_context = self.client.prepare(version='1.11')
|
||||
|
||||
msg_args = {
|
||||
'share_id': share_id,
|
||||
'new_size': new_size,
|
||||
'reservations': reservations,
|
||||
'request_spec': request_spec,
|
||||
'filter_properties': filter_properties,
|
||||
}
|
||||
|
||||
return call_context.cast(context, 'extend_share', **msg_args)
|
||||
|
@ -2127,8 +2127,11 @@ class API(base.Base):
|
||||
def get_share_network(self, context, share_net_id):
|
||||
return self.db.share_network_get(context, share_net_id)
|
||||
|
||||
def extend(self, context, share, new_size):
|
||||
policy.check_policy(context, 'share', 'extend')
|
||||
def extend(self, context, share, new_size, force=False):
|
||||
if force:
|
||||
policy.check_policy(context, 'share', 'force_extend')
|
||||
else:
|
||||
policy.check_policy(context, 'share', 'extend')
|
||||
|
||||
if share['status'] != constants.STATUS_AVAILABLE:
|
||||
msg_params = {
|
||||
@ -2222,7 +2225,15 @@ class API(base.Base):
|
||||
message=msg)
|
||||
|
||||
self.update(context, share, {'status': constants.STATUS_EXTENDING})
|
||||
self.share_rpcapi.extend_share(context, share, new_size, reservations)
|
||||
if force:
|
||||
self.share_rpcapi.extend_share(context, share,
|
||||
new_size, reservations)
|
||||
else:
|
||||
share_type = share_types.get_share_type(
|
||||
context, share['instance']['share_type_id'])
|
||||
request_spec = self._get_request_spec_dict(share, share_type)
|
||||
self.scheduler_rpcapi.extend_share(context, share['id'], new_size,
|
||||
reservations, request_spec)
|
||||
LOG.info("Extend share request issued successfully.",
|
||||
resource=share)
|
||||
|
||||
|
@ -1112,7 +1112,7 @@ class ShareActionsTest(test.TestCase):
|
||||
|
||||
share_api.API.get.assert_called_once_with(mock.ANY, id)
|
||||
share_api.API.extend.assert_called_once_with(
|
||||
mock.ANY, share, int(size))
|
||||
mock.ANY, share, int(size), force=False)
|
||||
self.assertEqual(202, actual_response.status_int)
|
||||
|
||||
@ddt.data({"os-extend": ""},
|
||||
|
@ -2418,7 +2418,7 @@ class ShareActionsTest(test.TestCase):
|
||||
|
||||
share_api.API.get.assert_called_once_with(mock.ANY, id)
|
||||
share_api.API.extend.assert_called_once_with(
|
||||
mock.ANY, share, int(size))
|
||||
mock.ANY, share, int(size), force=False)
|
||||
self.assertEqual(202, actual_response.status_int)
|
||||
|
||||
@ddt.data({"os-extend": ""},
|
||||
|
@ -130,3 +130,13 @@ class SchedulerRpcAPITestCase(test.TestCase):
|
||||
request_spec='fake_request_spec',
|
||||
filter_properties='filter_properties',
|
||||
version='1.6')
|
||||
|
||||
def test_extend_share(self):
|
||||
self._test_scheduler_api('extend_share',
|
||||
rpc_method='cast',
|
||||
share_id='share_id',
|
||||
new_size='fake_size',
|
||||
reservations='fake_reservations',
|
||||
request_spec='fake_request_spec',
|
||||
filter_properties='filter_properties',
|
||||
version='1.11',)
|
||||
|
@ -2948,9 +2948,19 @@ class ShareAPITestCase(test.TestCase):
|
||||
project_id='fake',
|
||||
is_admin=False
|
||||
)
|
||||
fake_type = {
|
||||
'id': 'fake_type_id',
|
||||
'extra_specs': {
|
||||
'snapshot_support': False,
|
||||
'create_share_from_snapshot_support': False,
|
||||
'driver_handles_share_servers': False,
|
||||
},
|
||||
}
|
||||
new_size = 123
|
||||
size_increase = int(new_size) - share['size']
|
||||
self.mock_object(quota.QUOTAS, 'reserve')
|
||||
self.mock_object(share_types, 'get_share_type',
|
||||
mock.Mock(return_value=fake_type))
|
||||
|
||||
self.api.extend(diff_user_context, share, new_size)
|
||||
|
||||
@ -2982,15 +2992,19 @@ class ShareAPITestCase(test.TestCase):
|
||||
new_replica_size = size_increase * replica_amount
|
||||
expected_deltas.update({'replica_gigabytes': new_replica_size})
|
||||
self.mock_object(self.api, 'update')
|
||||
self.mock_object(self.api.share_rpcapi, 'extend_share')
|
||||
self.mock_object(self.api.scheduler_rpcapi, 'extend_share')
|
||||
self.mock_object(quota.QUOTAS, 'reserve')
|
||||
self.mock_object(share_types, 'get_share_type')
|
||||
self.mock_object(share_types, 'provision_filter_on_size')
|
||||
self.mock_object(self.api, '_get_request_spec_dict')
|
||||
|
||||
self.api.extend(self.context, share, new_size)
|
||||
|
||||
self.api.update.assert_called_once_with(
|
||||
self.context, share, {'status': constants.STATUS_EXTENDING})
|
||||
self.api.share_rpcapi.extend_share.assert_called_once_with(
|
||||
self.context, share, new_size, mock.ANY
|
||||
|
||||
self.api.scheduler_rpcapi.extend_share.assert_called_once_with(
|
||||
self.context, share['id'], new_size, mock.ANY, mock.ANY
|
||||
)
|
||||
quota.QUOTAS.reserve.assert_called_once_with(
|
||||
self.context, **expected_deltas)
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
`Launchpad bug 1855391 <https://bugs.launchpad.net/manila/+bug/1855391>`_
|
||||
has been fixed. The action of extend share will go through scheduler, if
|
||||
there is no available share backend host, the share will rollback to
|
||||
available state and create an user message about extend.
|
Loading…
Reference in New Issue
Block a user