API support for CLUSTER_RESIZE operation

This patch adds support to CLUSTER_RESIZE operation at API level.
TODO: revise API doc related to this change.

Change-Id: I72fc50d5119f86e44cd83d324a660834f1201664
This commit is contained in:
tengqm 2015-05-13 04:51:22 -04:00
parent 5f76d399f9
commit ab5771827e
3 changed files with 314 additions and 6 deletions

View File

@ -35,6 +35,8 @@ API
of the resource to be created. This is a requirement from API WG.
- API resource names should not include underscores. A guideline from API
WG.
- Add API doc for CLUSTER_RESIZE operation.
- Add API doc for webhook APIs operation.
DB
--

View File

@ -15,13 +15,13 @@
Cluster endpoint for Senlin v1 ReST API.
"""
from webob import exc
from oslo_config import cfg
from oslo_log import log as logging
from webob import exc
from senlin.api.openstack.v1 import util
from senlin.common import consts
from senlin.common import exception as senlin_exc
from senlin.common.i18n import _
from senlin.common import serializers
from senlin.common import utils
@ -123,10 +123,10 @@ class ClusterController(object):
REQUEST_SCOPE = 'clusters'
SUPPORTED_ACTIONS = (
ADD_NODES, DEL_NODES, SCALE_OUT, SCALE_IN,
ADD_NODES, DEL_NODES, SCALE_OUT, SCALE_IN, RESIZE,
POLICY_ATTACH, POLICY_DETACH, POLICY_UPDATE,
) = (
'add_nodes', 'del_nodes', 'scale_out', 'scale_in',
'add_nodes', 'del_nodes', 'scale_out', 'scale_in', 'resize',
'policy_attach', 'policy_detach', 'policy_update',
)
@ -218,6 +218,50 @@ class ClusterController(object):
raise exc.HTTPAccepted()
def _do_resize(self, req, cluster_id, this_action, body):
data = body.get(this_action)
adj_type = data.get('adjustment_type')
number = data.get('number')
min_size = data.get('min_size')
max_size = data.get('max_size')
min_step = data.get('min_step')
strict = data.get('strict')
if adj_type is not None:
if adj_type not in consts.ADJUSTMENT_TYPES:
raise senlin_exc.InvalidParameter(name='adjustment_type',
value=adj_type)
if number is None:
msg = _("Missing number value for resize operation.")
raise exc.HTTPBadRequest(msg)
if number is not None:
if adj_type is None:
msg = _("Missing adjustment_type value for resize "
"operation.")
raise exc.HTTPBadRequest(msg)
number = utils.parse_int_param('number', number)
if min_size is not None:
min_size = utils.parse_int_param('min_size', min_size)
if max_size is not None:
max_size = utils.parse_int_param('max_size', max_size,
allow_negative=True)
if (min_size is not None and max_size is not None and
max_size > 0 and min_size > max_size):
msg = _("The specified min_size (%(n)s) is greater than the "
"specified max_size (%(m)s).") % {'m': max_size,
'n': min_size}
raise exc.HTTPBadRequest(msg)
if min_step is not None:
min_step = utils.parse_int_param('min_step', min_step)
if strict is not None:
strict = utils.parse_bool_param('strict', strict)
return self.rpc_client.cluster_resize(req.context, cluster_id,
adj_type, number, min_size,
max_size, min_step, strict)
@util.policy_enforce
def action(self, req, cluster_id, body=None):
'''Perform specified action on a cluster.'''
@ -245,6 +289,8 @@ class ClusterController(object):
raise exc.HTTPBadRequest(_('No node to delete'))
res = self.rpc_client.cluster_del_nodes(
req.context, cluster_id, nodes)
elif this_action == self.RESIZE:
return self._do_resize(req, cluster_id, this_action, body)
elif this_action == self.SCALE_OUT:
count = body.get(this_action).get('count')
res = self.rpc_client.cluster_scale_out(req.context, cluster_id,

View File

@ -11,13 +11,13 @@
# under the License.
import json
import mock
from oslo_config import cfg
import six
import webob
from webob import exc
from oslo_config import cfg
from senlin.api.middleware import fault
from senlin.api.openstack.v1 import clusters
from senlin.common import exception as senlin_exc
@ -1055,6 +1055,266 @@ class ClusterControllerTest(shared.ControllerTest, base.SenlinTestCase):
self.assertIn('Nodes not found: bad-node-1',
resp.json['error']['message'])
def _test_cluster_action_resize_with_types(self, adj_type, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
cid = 'aaaa-bbbb-cccc'
body = {
'resize': {
'adjustment_type': adj_type,
'number': 1,
'min_size': 0,
'max_size': 10,
'min_step': 1,
'strict': True
}
}
eng_resp = {'action': {'id': 'action-id', 'target': cid}}
req = self._put('/clusters/%(cluster_id)s/action' % {
'cluster_id': cid}, json.dumps(body))
mock_call = self.patchobject(rpc_client.EngineClient, 'call',
return_value=eng_resp)
resp = self.controller.action(req, tenant_id=self.project,
cluster_id=cid,
body=body)
mock_call.assert_called_once_with(
req.context,
('cluster_resize', {
'identity': cid,
'adj_type': adj_type,
'number': 1,
'min_size': 0,
'max_size': 10,
'min_step': 1,
'strict': True
})
)
self.assertEqual(eng_resp, resp)
def test_cluster_action_resize_with_exact_capacity(self, mock_enforce):
self._test_cluster_action_resize_with_types('EXACT_CAPACITY',
mock_enforce)
def test_cluster_action_resize_with_change_capacity(self, mock_enforce):
self._test_cluster_action_resize_with_types('CHANGE_IN_CAPACITY',
mock_enforce)
def test_cluster_action_resize_with_change_percentage(self, mock_enforce):
self._test_cluster_action_resize_with_types('CHANGE_IN_PERCENTAGE',
mock_enforce)
def test_cluster_action_resize_with_bad_type(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
cid = 'aaaa-bbbb-cccc'
body = {
'resize': {
'adjustment_type': 'NOT_QUITE_SURE',
'number': 1
}
}
req = self._put('/clusters/%(cluster_id)s/action' % {
'cluster_id': cid}, json.dumps(body))
error = senlin_exc.InvalidParameter(name='adjustment_type',
value='NOT_QUITE_SURE')
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
mock_call.side_effect = shared.to_remote_error(error)
resp = shared.request_with_middleware(fault.FaultWrapper,
self.controller.action,
req, tenant_id=self.project,
cluster_id=cid,
body=body)
self.assertEqual(400, resp.json['code'])
self.assertEqual('InvalidParameter', resp.json['error']['type'])
self.assertIn("Invalid value 'NOT_QUITE_SURE' specified for "
"'adjustment_type'", resp.json['error']['message'])
def test_cluster_action_resize_missing_number(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
cid = 'aaaa-bbbb-cccc'
body = {
'resize': {
'adjustment_type': 'EXACT_CAPACITY',
}
}
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
ex = self.assertRaises(exc.HTTPBadRequest,
self.controller.action,
req, tenant_id=self.project,
cluster_id=cid,
body=body)
self.assertEqual('Missing number value for resize operation.',
six.text_type(ex))
self.assertEqual(0, mock_call.call_count)
def test_cluster_action_resize_missing_type(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
cid = 'aaaa-bbbb-cccc'
body = {'resize': {'number': 2}}
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
ex = self.assertRaises(exc.HTTPBadRequest,
self.controller.action,
req, tenant_id=self.project,
cluster_id=cid,
body=body)
self.assertEqual('Missing adjustment_type value for resize operation.',
six.text_type(ex))
self.assertEqual(0, mock_call.call_count)
def _test_cluster_resize_param_not_int(self, param, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
cid = 'aaaa-bbbb-cccc'
body = {
'resize': {
'adjustment_type': 'CHANGE_IN_CAPACITY',
'number': 1,
}
}
body['resize'][param] = 'BOGUS'
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
ex = self.assertRaises(senlin_exc.InvalidParameter,
self.controller.action,
req, tenant_id=self.project,
cluster_id=cid,
body=body)
self.assertEqual("Invalid value 'BOGUS' specified for '%s'" %
param, six.text_type(ex))
self.assertEqual(0, mock_call.call_count)
def test_cluster_action_resize_number_not_int(self, mock_enforce):
self._test_cluster_resize_param_not_int('number', mock_enforce)
def test_cluster_action_resize_min_size_not_int(self, mock_enforce):
self._test_cluster_resize_param_not_int('min_size', mock_enforce)
def test_cluster_action_resize_max_size_not_int(self, mock_enforce):
self._test_cluster_resize_param_not_int('max_size', mock_enforce)
def test_cluster_action_resize_min_step_not_int(self, mock_enforce):
self._test_cluster_resize_param_not_int('min_step', mock_enforce)
def test_cluster_action_resize_min_size_non_neg(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
cid = 'aaaa-bbbb-cccc'
body = {'resize': {'min_size': -1}}
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
ex = self.assertRaises(senlin_exc.InvalidParameter,
self.controller.action,
req, tenant_id=self.project,
cluster_id=cid,
body=body)
self.assertEqual("Invalid value '-1' specified for 'min_size'",
six.text_type(ex))
self.assertEqual(0, mock_call.call_count)
def test_cluster_action_resize_max_size_neg_ok(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
cid = 'aaaa-bbbb-cccc'
body = {'resize': {'max_size': -1}}
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
eng_resp = {'action': {'id': 'action-id', 'target': cid}}
mock_call = self.patchobject(rpc_client.EngineClient, 'call',
return_value=eng_resp)
resp = self.controller.action(req, tenant_id=self.project,
cluster_id=cid, body=body)
mock_call.assert_called_once_with(
req.context,
('cluster_resize', {
'identity': cid,
'adj_type': None,
'number': None,
'min_size': None,
'max_size': -1,
'min_step': None,
'strict': None
})
)
self.assertEqual(eng_resp, resp)
def test_cluster_action_resize_max_size_too_small(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
cid = 'aaaa-bbbb-cccc'
body = {'resize': {'min_size': 2, 'max_size': 1}}
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
ex = self.assertRaises(exc.HTTPBadRequest,
self.controller.action,
req, tenant_id=self.project,
cluster_id=cid,
body=body)
self.assertEqual("The specified min_size (2) is greater than "
"the specified max_size (1).", six.text_type(ex))
self.assertEqual(0, mock_call.call_count)
def test_cluster_action_resize_min_with_max_neg(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
cid = 'aaaa-bbbb-cccc'
body = {'resize': {'min_size': 2, 'max_size': -1}}
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
eng_resp = {'action': {'id': 'action-id', 'target': cid}}
mock_call = self.patchobject(rpc_client.EngineClient, 'call',
return_value=eng_resp)
resp = self.controller.action(req, tenant_id=self.project,
cluster_id=cid,
body=body)
mock_call.assert_called_once_with(
req.context,
('cluster_resize', {
'identity': cid,
'adj_type': None,
'number': None,
'min_size': 2,
'max_size': -1,
'min_step': None,
'strict': None
})
)
self.assertEqual(eng_resp, resp)
def test_cluster_action_resize_strict_non_bool(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
cid = 'aaaa-bbbb-cccc'
body = {'resize': {'strict': 'yes'}}
req = self._put('/clusters/%s/action' % cid, json.dumps(body))
ex = self.assertRaises(senlin_exc.InvalidParameter,
self.controller.action,
req, tenant_id=self.project,
cluster_id=cid,
body=body)
self.assertEqual("Invalid value 'yes' specified for 'strict'",
six.text_type(ex))
self.assertEqual(0, mock_call.call_count)
def test_cluster_action_scale_out(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'action', True)
cid = 'aaaa-bbbb-cccc'