# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import copy
from unittest import mock

from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from webob import exc

from senlin.api.common import util
from senlin.api.middleware import fault
from senlin.api.openstack.v1 import clusters
from senlin.common import exception as senlin_exc
from senlin.common import policy
from senlin.objects.requests import clusters as vorc
from senlin.rpc import client as rpc_client
from senlin.tests.unit.api import shared
from senlin.tests.unit.common import base
from senlin.tests.unit.common import utils


@mock.patch.object(policy, 'enforce')
class ClusterControllerTest(shared.ControllerTest, base.SenlinTestCase):
    """Test case for the cluster controller."""

    def setUp(self):
        super(ClusterControllerTest, self).setUp()

        class DummyConfig(object):
            bind_port = 8777

        cfgopts = DummyConfig()
        self.controller = clusters.ClusterController(options=cfgopts)
        self.context = utils.dummy_context()

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_index(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'index', True)
        req = self._get('/clusters')

        engine_resp = [{'foo': 'bar'}]
        mock_call.return_value = engine_resp
        obj = vorc.ClusterListRequest()
        mock_parse.return_value = obj

        result = self.controller.index(req)

        expected = {u'clusters': engine_resp}
        self.assertEqual(expected, result)

        mock_parse.assert_called_once_with('ClusterListRequest', req,
                                           {'project_safe': True})
        mock_call.assert_called_once_with(req.context, 'cluster_list', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_index_with_params(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'index', True)
        fake_id = uuidutils.generate_uuid()
        params = {
            'name': 'name1',
            'status': 'ACTIVE',
            'limit': '3',
            'marker': fake_id,
            'sort': 'name:asc',
            'global_project': 'True',
        }
        req = self._get('/clusters', params=params)
        obj = vorc.ClusterListRequest()
        mock_parse.return_value = obj
        engine_resp = [{'foo': 'bar'}]
        mock_call.return_value = engine_resp

        result = self.controller.index(req)

        expected = {u'clusters': engine_resp}
        self.assertEqual(expected, result)
        mock_parse.assert_called_once_with(
            'ClusterListRequest', req,
            {
                'name': ['name1'],
                'status': ['ACTIVE'],
                'limit': '3',
                'marker': fake_id,
                'sort': 'name:asc',
                'project_safe': False
            })

        mock_call.assert_called_once_with(req.context, 'cluster_list', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_index_failed_with_exception(self, mock_call, mock_parse,
                                         mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'index', True)
        req = self._get('/clusters', params={})
        mock_parse.side_effect = exc.HTTPBadRequest("Boom")
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.index,
                               req)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            "ClusterListRequest", req, {'project_safe': True})
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_index_failed_engine_error(self, mock_call, mock_parse,
                                       mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'index', True)
        params = {'global_project': True}
        req = self._get('/clusters', params=params)
        obj = mock.Mock()
        mock_parse.return_value = obj
        error = senlin_exc.Forbidden()
        mock_call.side_effect = shared.to_remote_error(error)

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.index,
                                              req)

        self.assertEqual(403, resp.json['code'])
        self.assertEqual('Forbidden', resp.json['error']['type'])
        mock_parse.assert_called_once_with(
            "ClusterListRequest", mock.ANY, {'project_safe': False})
        mock_call.assert_called_once_with(req.context, 'cluster_list', obj)

    def test_index_error_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'index', False)
        req = self._get('/clusters')

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.index,
                                              req)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_create(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'create', True)
        body = {
            'cluster': {
                'name': 'test_cluster',
                'desired_capacity': 0,
                'profile_id': 'xxxx-yyyy',
                'min_size': 0,
                'max_size': 0,
                'metadata': {},
                'timeout': None,
            }
        }
        req = self._post('/clusters', jsonutils.dumps(body))
        engine_response = {
            'id': 'FAKE_ID',
            'name': 'test_cluster',
            'desired_capacity': 0,
            'profile_id': 'xxxx-yyyy',
            'min_size': 0,
            'max_size': 0,
            'metadata': {},
            'timeout': 60,
            'action': 'fake_action'
        }
        mock_call.return_value = engine_response
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller.create(req, body=body)

        self.assertEqual(engine_response, resp['cluster'])
        self.assertEqual('/actions/fake_action', resp['location'])
        mock_parse.assert_called_once_with(
            "ClusterCreateRequest", mock.ANY, body, 'cluster')
        mock_call.assert_called_once_with(req.context, 'cluster_create',
                                          obj.cluster)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_create_failed_request(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'create', True)
        body = {'foo': 'bar'}
        req = self._post('/clusters', jsonutils.dumps(body))
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.create,
                               req, body=body)

        self.assertEqual("Boom", str(ex))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_create_failed_engine(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'create', True)
        body = {'foo': 'bar'}
        req = self._post('/clusters', jsonutils.dumps(body))
        obj = mock.Mock()
        mock_parse.return_value = obj
        error = senlin_exc.BadRequest(msg='bad')
        mock_call.side_effect = shared.to_remote_error(error)

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.create,
                                              req, body=body)

        self.assertEqual(400, resp.json['code'])
        self.assertEqual('BadRequest', resp.json['error']['type'])
        mock_parse.assert_called_once_with(
            "ClusterCreateRequest", mock.ANY, {'foo': 'bar'}, 'cluster')
        mock_call.assert_called_once_with(req.context, 'cluster_create',
                                          obj.cluster)

    def test_create_err_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'create', False)
        body = {
            'cluster': {
                'name': 'test_cluster',
                'profile_id': 'xxxx-yyyy',
            }
        }
        req = self._post('/clusters', jsonutils.dumps(body))

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.create,
                                              req, body=body)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_get(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'get', True)
        cid = 'cid'
        req = self._get('/clusters/%s' % cid)
        engine_resp = {'foo': 'bar'}
        mock_call.return_value = engine_resp
        obj = mock.Mock()
        mock_parse.return_value = obj

        response = self.controller.get(req, cluster_id=cid)

        self.assertEqual({'cluster': {'foo': 'bar'}}, response)
        mock_parse.assert_called_once_with(
            "ClusterGetRequest", req, {'identity': 'cid'})
        mock_call.assert_called_once_with(req.context, 'cluster_get', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_get_failed_request(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'get', True)
        cid = 'FAKE_ID'
        req = self._get('/clusters/%s' % cid)
        mock_parse.side_effect = exc.HTTPBadRequest("Boom")
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.get,
                               req, cluster_id=cid)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            "ClusterGetRequest", req, {'identity': cid})
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_get_failed_engine(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'get', True)
        cid = 'non-existent-cluster'
        req = self._get('/clusters/%s' % cid)

        error = senlin_exc.ResourceNotFound(type='cluster', id=cid)
        mock_call.side_effect = shared.to_remote_error(error)

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.get,
                                              req, cluster_id=cid)

        self.assertEqual(404, resp.json['code'])
        self.assertEqual('ResourceNotFound', resp.json['error']['type'])
        mock_parse.assert_called_once_with(
            "ClusterGetRequest", mock.ANY, {'identity': cid})

    def test_get_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'get', False)
        cid = 'cid'
        req = self._get('/clusters/%s' % cid)

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.get,
                                              req, cluster_id=cid)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_update(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'update', True)
        cid = 'aaaa-bbbb-cccc'
        body = {'cluster': {'foo': 'bar'}}
        engine_resp = {
            'id': cid,
            'action': 'fake_action',
        }
        req = self._patch('/clusters/%s' % cid, jsonutils.dumps(body))
        mock_call.return_value = engine_resp
        obj = mock.Mock()
        mock_parse.return_value = obj

        res = self.controller.update(req, cluster_id=cid, body=body)

        self.assertEqual(
            {'cluster': {'id': cid}, 'location': '/actions/fake_action'},
            res)
        mock_parse.assert_called_once_with(
            "ClusterUpdateRequest", req,
            {'identity': 'aaaa-bbbb-cccc', 'foo': 'bar'})
        mock_call.assert_called_once_with(req.context, 'cluster_update', obj)

    def test_update_missing_cluster_key(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'update', True)
        cid = 'aaaa-bbbb-cccc'
        body = {'profile_id': 'xxxx-yyyy-zzzz'}
        req = self._patch('/clusters/%s' % cid, jsonutils.dumps(body))
        mock_call = self.patchobject(rpc_client.EngineClient, 'call')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.update,
                               req, cluster_id=cid, body=body)

        self.assertIn("Malformed request data, missing 'cluster' key "
                      "in request body.", str(ex))
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_update_failed_request(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'update', True)
        cid = 'aaaa-bbbb-cccc'
        body = {'cluster': {'name': 'foo bar'}}
        req = self._patch('/clusters/%s' % cid, jsonutils.dumps(body))
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.update,
                               req, cluster_id=cid, body=body)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            "ClusterUpdateRequest", req,
            {'identity': 'aaaa-bbbb-cccc', 'name': 'foo bar'})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_update_engine_error(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'update', True)
        cid = 'non-existent-cluster'
        body = {'cluster': {'profile_id': 'xxxx-yyyy-zzzz'}}
        req = self._patch('/clusters/%s' % cid, jsonutils.dumps(body))
        obj = mock.Mock()
        mock_parse.return_value = obj
        error = senlin_exc.ResourceNotFound(type='cluster', id=cid)
        mock_call.side_effect = shared.to_remote_error(error)

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.update,
                                              req, cluster_id=cid, body=body)

        self.assertEqual(404, resp.json['code'])
        self.assertEqual('ResourceNotFound', resp.json['error']['type'])
        mock_parse.assert_called_once_with(
            "ClusterUpdateRequest", mock.ANY,
            {'identity': cid, 'profile_id': 'xxxx-yyyy-zzzz'})
        mock_call.assert_called_once_with(req.context, 'cluster_update', obj)

    def test_update_err_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'update', False)
        cid = 'aaaa-bbbb-cccc'
        body = {'cluster': {'profile_id': 'xxxx-yyyy-zzzz'}}

        req = self._patch('/clusters/%(cluster_id)s' % {'cluster_id': cid},
                          jsonutils.dumps(body))

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.update,
                                              req, cluster_id=cid, body=body)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_add_nodes(self, mock_call, mock_parse, mock_enforce):
        req = mock.Mock()
        cid = 'FAKE_ID'
        data = dict(nodes=['NODE1'])
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_add_nodes(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterAddNodesRequest',
            req,
            {'identity': cid, 'nodes': data['nodes']}
        )
        mock_call.assert_called_once_with(
            req.context, 'cluster_add_nodes', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_add_nodes_failed_request(self, mock_call,
                                         mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(nodes=['NODE2'])
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_add_nodes,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterAddNodesRequest',
            req,
            {'identity': cid, 'nodes': data['nodes']}
        )
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_add_nodes_failed_engine(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(nodes=['NODE3'])
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_add_nodes,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterAddNodesRequest',
            req,
            {'identity': cid, 'nodes': data['nodes']}
        )
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_add_nodes', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_del_nodes(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'FAKE_ID'
        data = dict(nodes=['NODE4'], destroy=False)
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_del_nodes(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterDelNodesRequest', req, {'identity': cid,
                                            'nodes': data['nodes'],
                                            'destroy_after_deletion': False})
        mock_call.assert_called_once_with(
            req.context, 'cluster_del_nodes', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_del_nodes_failed_request(self, mock_call,
                                         mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(nodes=['NODE5'], destroy=False)
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_del_nodes,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterDelNodesRequest', req, {'identity': cid,
                                            'nodes': data['nodes'],
                                            'destroy_after_deletion': False})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_del_nodes_failed_engine(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(nodes=['NODE6'], destroy=False)
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_del_nodes,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterDelNodesRequest', req, {'identity': cid,
                                            'nodes': data['nodes'],
                                            'destroy_after_deletion': False})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_del_nodes', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_replace_nodes(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'FAKE_ID'
        data = dict(nodes={'OLD': 'NEW'})
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_replace_nodes(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterReplaceNodesRequest',
            req,
            {'identity': cid, 'nodes': data['nodes']}
        )
        mock_call.assert_called_once_with(
            req.context, 'cluster_replace_nodes', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_replace_nodes_none(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(nodes=None)
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_replace_nodes,
                               req, cid, data)

        self.assertEqual("The data provided is not a map", str(ex))
        self.assertEqual(0, mock_parse.call_count)
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_replace_nodes_failed_request(self, mock_call,
                                             mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(nodes={'OLD': 'NEW'})
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_replace_nodes,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterReplaceNodesRequest',
            req,
            {'identity': cid, 'nodes': data['nodes']}
        )
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_replace_nodes_failed_engine(self, mock_call,
                                            mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(nodes={'OLD': 'NEW'})
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_replace_nodes,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterReplaceNodesRequest',
            req,
            {'identity': cid, 'nodes': data['nodes']}
        )
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_replace_nodes', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def _test_do_resize_with_type(self, adj_type, mock_call, mock_parse):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {
            'adjustment_type': adj_type,
            'number': 1,
            'min_size': 0,
            'max_size': 10,
            'min_step': 1,
            'strict': True
        }
        mock_call.return_value = {'action': 'action-id'}
        # We are using a real object for testing
        obj = vorc.ClusterResizeRequest(identity=cid, **data)
        mock_parse.return_value = obj

        resp = self.controller._do_resize(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        params = copy.deepcopy(data)
        if params['adjustment_type'] != 'CHANGE_IN_PERCENTAGE':
            params.pop('min_step')
        params['identity'] = cid
        mock_parse.assert_called_once_with(
            'ClusterResizeRequest', req, params)
        mock_call.assert_called_once_with(req.context, 'cluster_resize', obj)

    def test_do_resize_exact_capacity(self, mock_enforce):
        self._test_do_resize_with_type('EXACT_CAPACITY')

    def test_do_resize_with_change_capacity(self, mock_enforce):
        self._test_do_resize_with_type('CHANGE_IN_CAPACITY')

    def test_do_resize_with_change_percentage(self, mock_enforce):
        self._test_do_resize_with_type('CHANGE_IN_PERCENTAGE')

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_resize_failed_request(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'adjustment_type': 'EXACT_CAPACITY', 'number': 10}
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_resize,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterResizeRequest', req,
            {
                'identity': cid,
                'adjustment_type': 'EXACT_CAPACITY',
                'number': 10
            })
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_resize_missing_number(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'adjustment_type': 'EXACT_CAPACITY'}
        obj = vorc.ClusterResizeRequest(identity=cid, **data)
        mock_parse.return_value = obj
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_resize,
                               req, cid, data)

        self.assertEqual('Missing number value for size adjustment.',
                         str(ex))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_resize_missing_type(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'number': 2}
        obj = vorc.ClusterResizeRequest(identity=cid, **data)
        mock_parse.return_value = obj

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_resize,
                               req, cid, data)

        self.assertEqual("Missing adjustment_type value for size adjustment.",
                         str(ex))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_resize_max_size_too_small(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'min_size': 2, 'max_size': 1}
        obj = vorc.ClusterResizeRequest(identity=cid, **data)
        mock_parse.return_value = obj

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_resize,
                               req, cid, data)

        self.assertEqual("The specified min_size (2) is greater than "
                         "the specified max_size (1).", str(ex))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_resize_empty_params(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {}

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_resize,
                               req, cid, data)

        self.assertEqual("Not enough parameters to do resize action.",
                         str(ex))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_resize_failed_engine(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'max_size': 200}
        obj = vorc.ClusterResizeRequest(identity=cid, **data)
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_resize,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterResizeRequest', req, {'identity': cid, 'max_size': 200})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_resize', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_scale_out(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(count=1)
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_scale_out(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterScaleOutRequest',
            req,
            {'identity': cid, 'count': data['count']}
        )
        mock_call.assert_called_once_with(
            req.context, 'cluster_scale_out', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_scale_out_failed_request(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(count=2)
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_scale_out,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterScaleOutRequest',
            req,
            {'identity': cid, 'count': data['count']}
        )
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_scale_out_failed_engine(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(count=3)
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_scale_out,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterScaleOutRequest',
            req,
            {'identity': cid, 'count': data['count']}
        )
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_scale_out', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_scale_in(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(count=4)
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_scale_in(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterScaleInRequest',
            req,
            {'identity': cid, 'count': data['count']}
        )
        mock_call.assert_called_once_with(
            req.context, 'cluster_scale_in', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_scale_in_failed_request(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(count=5)
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_scale_in,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterScaleInRequest',
            req,
            {'identity': cid, 'count': data['count']}
        )
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_scale_in_failed_engine(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = dict(count=6)
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_scale_in,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterScaleInRequest',
            req,
            {'identity': cid, 'count': data['count']}
        )
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_scale_in', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_attach(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_policy_attach(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterAttachPolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        mock_call.assert_called_once_with(
            req.context, 'cluster_policy_attach', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_attach_failed_request(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_policy_attach,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterAttachPolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_attach_failed_engine(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_policy_attach,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterAttachPolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_policy_attach', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_detach(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_policy_detach(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterDetachPolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        mock_call.assert_called_once_with(
            req.context, 'cluster_policy_detach', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_detach_failed_request(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_policy_detach,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterDetachPolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_detach_failed_engine(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_policy_detach,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterDetachPolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_policy_detach', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_update(self, mock_call, mock_parse, _ign):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        mock_call.return_value = {'action': 'action-id'}
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller._do_policy_update(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterUpdatePolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        mock_call.assert_called_once_with(
            req.context, 'cluster_policy_update', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_update_failed_request(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_policy_update,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterUpdatePolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_policy_update_failed_engine(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'policy_id': 'xxxx-yyyy'}
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_policy_update,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterUpdatePolicyRequest', req,
            {'identity': cid, 'policy_id': 'xxxx-yyyy'})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_policy_update', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_check(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'op': 'value'}
        obj = mock.Mock()
        mock_parse.return_value = obj
        eng_resp = {'action': 'action-id'}
        mock_call.return_value = eng_resp

        resp = self.controller._do_check(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterCheckRequest', req,
            {'identity': cid, 'params': {'op': 'value'}})
        mock_call.assert_called_once_with(req.context, 'cluster_check', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_check_failed_request(self, mock_call, mock_parse, _ign):
        data = {}
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_check,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterCheckRequest', req, {'identity': cid, 'params': {}})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_check_failed_engine(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {}
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_check,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterCheckRequest', req, {'identity': cid, 'params': {}})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_check', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_recover(self, mock_call, mock_parse, _ignore):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {'op': 'value'}
        obj = mock.Mock()
        mock_parse.return_value = obj
        eng_resp = {'action': 'action-id'}
        mock_call.return_value = eng_resp

        resp = self.controller._do_recover(req, cid, data)

        self.assertEqual({'action': 'action-id'}, resp)
        mock_parse.assert_called_once_with(
            'ClusterRecoverRequest', req,
            {'identity': cid, 'params': {'op': 'value'}})
        mock_call.assert_called_once_with(req.context, 'cluster_recover', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_recover_failed_request(self, mock_call, mock_parse, _ign):
        data = {}
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller._do_recover,
                               req, cid, data)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterRecoverRequest', req, {'identity': cid, 'params': {}})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_do_recover_failed_engine(self, mock_call, mock_parse, _i):
        req = mock.Mock()
        cid = 'aaaa-bbbb-cccc'
        data = {}
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller._do_recover,
                               req, cid, data)

        mock_parse.assert_called_once_with(
            'ClusterRecoverRequest', req, {'identity': cid, 'params': {}})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_recover', obj)

    def test_cluster_action_missing_action(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'action', True)
        cid = 'aaaa-bbbb-cccc'
        body = {}
        req = self._post('/clusters/%s/actions' % cid, jsonutils.dumps(body))

        mock_call = self.patchobject(rpc_client.EngineClient, 'call')
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.action,
                               req, cluster_id=cid, body=body)
        self.assertEqual('No action specified', str(ex))
        self.assertFalse(mock_call.called)

    def test_cluster_action_multiple_actions(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'action', True)
        cid = 'aaaa-bbbb-cccc'
        body = {'action_1': {}, 'action_2': {}}
        req = self._post('/clusters/%s/actions' % cid, jsonutils.dumps(body))

        mock_call = self.patchobject(rpc_client.EngineClient, 'call')
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.action,
                               req, cluster_id=cid, body=body)
        self.assertEqual('Multiple actions specified', str(ex))
        self.assertFalse(mock_call.called)

    def test_cluster_action_unsupported_action(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'action', True)
        cid = 'aaaa-bbbb-cccc'
        body = {'fly': None}
        req = self._post('/clusters/%s/actions' % cid, jsonutils.dumps(body))

        mock_call = self.patchobject(rpc_client.EngineClient, 'call')
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.action,
                               req, cluster_id=cid, body=body)
        self.assertEqual("Unrecognized action 'fly' specified",
                         str(ex))
        self.assertFalse(mock_call.called)

    def test_cluster_action_err_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'action', False)
        cid = 'aaaa-bbbb-cccc'
        body = {'someaction': {'param': 'value'}}

        req = self._post('/clusters/%s/actions' % cid, jsonutils.dumps(body))

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.action,
                                              req, cluster_id=cid, body=body)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))

    def test_cluster_action_data_not_map(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'action', True)
        cid = 'aaaa-bbbb-cccc'
        body = {'resize': ['param1', 'param2']}

        req = self._post('/clusters/%s/actions' % cid, jsonutils.dumps(body))

        mock_call = self.patchobject(rpc_client.EngineClient, 'call')
        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.action,
                               req, cluster_id=cid, body=body)
        self.assertEqual('The data provided is not a map', str(ex))
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_collect(self, mock_call, mock_parse, mock_enforce):
        req = mock.Mock(context=self.context)
        cid = 'aaaa-bbbb-cccc'
        path = 'foo.bar'
        eng_resp = {'cluster_attributes': [{'key': 'value'}]}
        mock_call.return_value = eng_resp
        obj = vorc.ClusterResizeRequest(identity=cid, path=path)
        mock_parse.return_value = obj

        resp = self.controller.collect(req, cluster_id=cid, path=path)

        self.assertEqual(eng_resp, resp)
        mock_call.assert_called_once_with(req.context, 'cluster_collect', obj)

    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_collect_version_mismatch(self, mock_call, mock_enforce):
        # NOTE: we skip the mock_enforce setup below because api version check
        #       comes before the policy enforcement and the check fails in
        #       this test case.
        cid = 'aaaa-bbbb-cccc'
        path = 'foo.bar'
        req = self._get('/clusters/%(cid)s/attrs/%(path)s' %
                        {'cid': cid, 'path': path}, version='1.1')

        ex = self.assertRaises(senlin_exc.MethodVersionNotFound,
                               self.controller.collect,
                               req, cluster_id=cid, path=path)

        self.assertEqual(0, mock_call.call_count)
        self.assertEqual("API version '1.1' is not supported on this method.",
                         str(ex))

    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_collect_path_not_provided(self, mock_call, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'collect', True)
        req = mock.Mock(context=self.context)
        cid = 'aaaa-bbbb-cccc'
        path = '    '
        req = self._get('/clusters/%(cid)s/attrs/%(path)s' %
                        {'cid': cid, 'path': path}, version='1.2')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.collect,
                               req, cluster_id=cid, path=path)

        self.assertEqual('Required path attribute is missing.',
                         str(ex))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_collect_path_is_none(self, mock_call, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'collect', True)
        req = mock.Mock(context=self.context)
        cid = 'aaaa-bbbb-cccc'
        path = 'None'
        req = self._get('/clusters/%(cid)s/attrs/%(path)s' %
                        {'cid': cid, 'path': path}, version='1.2')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.collect,
                               req, cluster_id=cid, path=path)

        self.assertEqual('Required path attribute is missing.',
                         str(ex))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_collect_failed_request(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'collect', True)
        req = mock.Mock(context=self.context)
        cid = 'aaaa-bbbb-cccc'
        path = 'foo.bar'
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.collect,
                               req, cluster_id=cid, path=path)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterCollectRequest', req, {'identity': cid, 'path': path})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_collect_failed_engine(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'collect', True)
        req = mock.Mock(context=self.context)
        cid = 'aaaa-bbbb-cccc'
        path = 'foo.bar'
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.side_effect = senlin_exc.BadRequest(msg='Boom')

        ex = self.assertRaises(senlin_exc.BadRequest,
                               self.controller.collect,
                               req, cluster_id=cid, path=path)

        mock_parse.assert_called_once_with(
            'ClusterCollectRequest', req, {'identity': cid, 'path': path})
        self.assertEqual("Boom.", str(ex))
        mock_call.assert_called_once_with(
            req.context, 'cluster_collect', obj)

    def test_collect_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'collect', False)
        cid = 'aaaa-bbbb-cccc'
        path = 'foo.bar'
        req = self._get('/clusters/%(cid)s/attrs/%(path)s' %
                        {'cid': cid, 'path': path}, version='1.2')
        mock_call = self.patchobject(rpc_client.EngineClient, 'call')

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.collect,
                                              req, cluster_id=cid, path=path)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))
        self.assertEqual(0, mock_call.call_count)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_operation(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'operation', True)
        cid = 'aaaa-bbbb-cccc'
        body = {
            'dance': {
                'params': {
                    'style': 'tango'
                },
                'filters': {
                    'role': 'slave'
                }
            }
        }
        req = self._post('/clusters/aaaa-bbbb-cccc/ops',
                         jsonutils.dumps(body), version='1.4')
        eng_resp = {'action': 'ACTION_ID'}
        mock_call.return_value = eng_resp
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = self.controller.operation(req, cluster_id=cid, body=body)

        self.assertEqual(eng_resp, resp)
        mock_call.assert_called_once_with(req.context, 'cluster_op', obj)

    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_operation_version_mismatch(self, mock_call, mock_enforce):
        cid = 'aaaa-bbbb-cccc'
        body = {'dance': {}}
        req = self._post('/clusters/aaaa-bbbb/ops', jsonutils.dumps(body),
                         version='1.1')

        ex = self.assertRaises(senlin_exc.MethodVersionNotFound,
                               self.controller.operation,
                               req, cluster_id=cid, body=body)

        self.assertEqual(0, mock_call.call_count)
        self.assertEqual("API version '1.1' is not supported on this method.",
                         str(ex))

    def test_operation_no_operations(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'operation', True)
        cid = 'aaaa-bbbb-cccc'
        body = {}
        req = self._post('/clusters/aaaa-bbbb-cccc/ops',
                         jsonutils.dumps(body), version='1.4')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.operation,
                               req, cluster_id=cid, body=body)

        self.assertEqual("No operation specified", str(ex))

    def test_operation_multi_operations(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'operation', True)
        cid = 'aaaa-bbbb-cccc'
        body = {'dance': {}, 'sing': {}}
        req = self._post('/clusters/aaaa-bbbb-cccc/ops',
                         jsonutils.dumps(body), version='1.4')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.operation,
                               req, cluster_id=cid, body=body)

        self.assertEqual("Multiple operations specified", str(ex))

    def test_cluster_operation_err_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'operation', False)
        body = {'someoperation': {}}
        req = self._post('/clusters/abc/ops', jsonutils.dumps(body),
                         version='1.4')

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.operation,
                                              req, cluster_id='abc', body=body)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_delete(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'delete', True)
        req = mock.Mock(context=self.context)
        req.params.get.return_value = 'false'
        cid = 'aaaa-bbbb-cccc'
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.return_value = {'action': 'FAKE_ID'}

        res = self.controller.delete(req, cluster_id=cid)

        result = {'location': '/actions/FAKE_ID'}
        self.assertEqual(result, res)
        mock_parse.assert_called_once_with(
            'ClusterDeleteRequest', req, {'identity': cid, 'force': False})

        mock_call.assert_called_with(req.context, 'cluster_delete', obj)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_delete_failed_request(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'delete', True)
        cid = 'fake-cluster'
        req = mock.Mock(context=self.context)
        req.params.get.return_value = 'false'
        mock_parse.side_effect = exc.HTTPBadRequest('Boom')

        ex = self.assertRaises(exc.HTTPBadRequest,
                               self.controller.delete,
                               req, cluster_id=cid)

        self.assertEqual("Boom", str(ex))
        mock_parse.assert_called_once_with(
            'ClusterDeleteRequest', req, {'identity': cid, 'force': False})
        self.assertFalse(mock_call.called)

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_delete_failed_engine(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'delete', True)
        cid = 'aaaa-bbbb-cccc'
        req = self._delete('/clusters/%s' % cid, params={'force': 'false'})
        error = senlin_exc.ResourceNotFound(type='cluster', id=cid)
        mock_call.side_effect = shared.to_remote_error(error)
        obj = mock.Mock()
        mock_parse.return_value = obj

        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.delete,
                                              req, cluster_id=cid)

        self.assertEqual(404, resp.json['code'])
        self.assertEqual('ResourceNotFound', resp.json['error']['type'])

    def test_delete_err_denied_policy(self, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'delete', False)
        cid = 'aaaa-bbbb-cccc'
        req = self._delete('/clusters/%s' % cid)
        resp = shared.request_with_middleware(fault.FaultWrapper,
                                              self.controller.delete,
                                              req, cluster_id=cid)

        self.assertEqual(403, resp.status_int)
        self.assertIn('403 Forbidden', str(resp))

    @mock.patch.object(util, 'parse_request')
    @mock.patch.object(rpc_client.EngineClient, 'call')
    def test_delete_force(self, mock_call, mock_parse, mock_enforce):
        self._mock_enforce_setup(mock_enforce, 'delete', True)
        req = mock.Mock(context=self.context)
        req.params.get.return_value = 'true'
        cid = 'aaaa-bbbb-cccc'
        obj = mock.Mock()
        mock_parse.return_value = obj
        mock_call.return_value = {'action': 'FAKE_ID'}

        params = {'cluster_id': cid, 'body': {'force': True}}
        res = self.controller.delete(req, **params)

        result = {'location': '/actions/FAKE_ID'}
        self.assertEqual(result, res)
        mock_parse.assert_called_once_with(
            'ClusterDeleteRequest', req, {'identity': cid, 'force': True})

        mock_call.assert_called_with(req.context, 'cluster_delete', obj)
