Extend share will go through scheduler
Add an force parameter to the API layer that lets the user choose whether to go through the scheduler or not, which is boolean.and default is False,set True means extend share directly, set False means extend share will go through scheduler. Add an new min_version 2.64 to extend share api. force parameter only support min_version >= 2.64. Closes-Bug:#1855391 Change-Id: I6da36a687a37c78a7fb7d3f252318d03d4a05133
This commit is contained in:
parent
1d95424dd6
commit
cdbc428b69
@ -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…
x
Reference in New Issue
Block a user