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.

Closes-Bug:#1855391
Change-Id: I6da36a687a37c78a7fb7d3f252318d03d4a05133
This commit is contained in:
haixin 2019-12-23 16:33:04 +08:00
parent ad4315eb36
commit 39a2700ffc
13 changed files with 148 additions and 16 deletions

View File

@ -2628,6 +2628,14 @@ share_new_size:
in: body
required: true
type: integer
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
share_proto:
description: |
The Shared File Systems protocol. A valid value

View File

@ -1,5 +1,6 @@
{
"extend": {
"new_size": 2
"new_size": 2,
"force": "true"
}
}

View File

@ -351,6 +351,7 @@ Request
- share_id: share_id
- extend: extend
- new_size: share_new_size
- force: share_force_extend
Request example

View File

@ -512,11 +512,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:
@ -527,7 +527,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:
@ -537,15 +537,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)

View File

@ -422,6 +422,16 @@ shares_policies = [
],
deprecated_rule=deprecated_share_extend
),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'force_extend',
check_str=base.RULE_ADMIN_API,
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,

View File

@ -316,3 +316,34 @@ 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, 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)
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)

View File

@ -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, new_size, reservations,
request_spec, filter_properties=None):
call_context = self.client.prepare(version='1.11')
msg_args = {
'share': share,
'new_size': new_size,
'reservations': reservations,
'request_spec': request_spec,
'filter_properties': filter_properties,
}
return call_context.cast(context, 'extend_share', **msg_args)

View File

@ -2147,8 +2147,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 = {
@ -2242,7 +2245,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, new_size,
reservations, request_spec)
LOG.info("Extend share request issued successfully.",
resource=share)

View File

@ -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": ""},

View File

@ -2413,7 +2413,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": ""},

View File

@ -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='fake_share',
new_size='fake_size',
reservations='fake_reservations',
request_spec='fake_request_spec',
filter_properties='filter_properties',
version='1.10',)

View File

@ -2938,9 +2938,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)
@ -2972,15 +2982,18 @@ 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(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, new_size, mock.ANY, mock.ANY
)
quota.QUOTAS.reserve.assert_called_once_with(
self.context, **expected_deltas)

View File

@ -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.