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:
parent
5f76d399f9
commit
ab5771827e
2
TODO.rst
2
TODO.rst
@ -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
|
||||
--
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user