Merge "Unscoped List Stacks"

This commit is contained in:
Jenkins 2014-03-03 12:38:48 +00:00 committed by Gerrit Code Review
commit be7969586f
9 changed files with 122 additions and 21 deletions

View File

@ -1,6 +1,7 @@
{
"context_is_admin": "role:admin",
"deny_stack_user": "not role:heat_stack_user",
"deny_everybody": "!",
"cloudformation:ListStacks": "rule:deny_stack_user",
"cloudformation:CreateStack": "rule:deny_stack_user",
@ -40,6 +41,7 @@
"stacks:delete": "rule:deny_stack_user",
"stacks:detail": "rule:deny_stack_user",
"stacks:generate_template": "rule:deny_stack_user",
"stacks:global_index": "rule:deny_everybody",
"stacks:index": "rule:deny_stack_user",
"stacks:list_resource_types": "rule:deny_stack_user",
"stacks:lookup": "rule:deny_stack_user",

View File

@ -67,6 +67,7 @@ class FaultWrapper(wsgi.Middleware):
'ResourceNotAvailable': webob.exc.HTTPNotFound,
'PhysicalResourceNotFound': webob.exc.HTTPNotFound,
'InvalidTenant': webob.exc.HTTPForbidden,
'Forbidden': webob.exc.HTTPForbidden,
'StackExists': webob.exc.HTTPConflict,
'StackValidationFailed': webob.exc.HTTPBadRequest,
'InvalidTemplateReference': webob.exc.HTTPBadRequest,

View File

@ -152,11 +152,7 @@ class StackController(object):
def default(self, req, **args):
raise exc.HTTPNotFound()
@util.policy_enforce
def index(self, req):
"""
Lists summary information for all stacks
"""
def _index(self, req, tenant_safe=True):
filter_whitelist = {
'status': 'mixed',
'name': 'mixed',
@ -169,9 +165,9 @@ class StackController(object):
}
params = util.get_allowed_params(req.params, whitelist)
filter_params = util.get_allowed_params(req.params, filter_whitelist)
stacks = self.rpc_client.list_stacks(req.context,
filters=filter_params,
tenant_safe=tenant_safe,
**params)
count = None
@ -180,12 +176,28 @@ class StackController(object):
# Check if engine has been updated to a version with
# support to count_stacks before trying to use it.
count = self.rpc_client.count_stacks(req.context,
filters=filter_params)
filters=filter_params,
tenant_safe=tenant_safe)
except AttributeError as exc:
logger.warning("Old Engine Version: %s" % str(exc))
return stacks_view.collection(req, stacks=stacks, count=count)
@util.policy_enforce
def global_index(self, req):
return self._index(req, tenant_safe=False)
@util.policy_enforce
def index(self, req):
"""
Lists summary information for all stacks
"""
global_tenant = bool(req.params.get('global_tenant', False))
if global_tenant:
return self.global_index(req, req.context.tenant_id)
return self._index(req)
@util.policy_enforce
def detail(self, req):
"""

View File

@ -303,7 +303,7 @@ class EngineService(service.Service):
@request_context
def list_stacks(self, cnxt, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None):
sort_dir=None, filters=None, tenant_safe=True):
"""
The list_stacks method returns attributes of all stacks. It supports
pagination (``limit`` and ``marker``), sorting (``sort_keys`` and
@ -315,6 +315,7 @@ class EngineService(service.Service):
:param sort_keys: an array of fields used to sort the list
:param sort_dir: the direction of the sort ('asc' or 'desc')
:param filters: a dict with attribute:value to filter the list
:param tenant_safe: if true, scope the request by the current tenant
:returns: a list of formatted stacks
"""
@ -331,18 +332,19 @@ class EngineService(service.Service):
yield api.format_stack(stack)
stacks = db_api.stack_get_all(cnxt, limit, sort_keys, marker,
sort_dir, filters) or []
sort_dir, filters, tenant_safe) or []
return list(format_stack_details(stacks))
@request_context
def count_stacks(self, cnxt, filters=None):
def count_stacks(self, cnxt, filters=None, tenant_safe=True):
"""
Return the number of stacks that match the given filters
:param ctxt: RPC context.
:param filters: a dict of ATTR:VALUE to match against stacks
:returns: a integer representing the number of matched stacks
"""
return db_api.stack_count_all(cnxt, filters=filters)
return db_api.stack_count_all(cnxt, filters=filters,
tenant_safe=tenant_safe)
def _validate_deferred_auth_context(self, cnxt, stack):
if cfg.CONF.deferred_auth_method != 'password':

View File

@ -52,7 +52,7 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy):
stack_name=stack_name))
def list_stacks(self, ctxt, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None):
sort_dir=None, filters=None, tenant_safe=True):
"""
The list_stacks method returns attributes of all stacks. It supports
pagination (``limit`` and ``marker``), sorting (``sort_keys`` and
@ -64,21 +64,25 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy):
:param sort_keys: an array of fields used to sort the list
:param sort_dir: the direction of the sort ('asc' or 'desc')
:param filters: a dict with attribute:value to filter the list
:param tenant_safe: if true, scope the request by the current tenant
:returns: a list of stacks
"""
return self.call(ctxt, self.make_msg('list_stacks', limit=limit,
sort_keys=sort_keys, marker=marker,
sort_dir=sort_dir, filters=filters))
sort_dir=sort_dir, filters=filters,
tenant_safe=tenant_safe))
def count_stacks(self, ctxt, filters=None):
def count_stacks(self, ctxt, filters=None, tenant_safe=True):
"""
Return the number of stacks that match the given filters
:param ctxt: RPC context.
:param filters: a dict of ATTR:VALUE to match against stacks
:param tenant_safe: if true, scope the request by the current tenant
:returns: a integer representing the number of matched stacks
"""
return self.call(ctxt, self.make_msg('count_stacks',
filters=filters))
filters=filters,
tenant_safe=tenant_safe))
def show_stack(self, ctxt, stack_identity):
"""

View File

@ -159,7 +159,7 @@ class CfnStackControllerTest(HeatTestCase):
u'StackStatus': u'CREATE_COMPLETE'}]}}}
self.assertEqual(expected, result)
default_args = {'limit': None, 'sort_keys': None, 'marker': None,
'sort_dir': None, 'filters': None}
'sort_dir': None, 'filters': None, 'tenant_safe': True}
mock_call.assert_called_once_with(dummy_req.context, self.topic,
{'namespace': None,
'method': 'list_stacks',

View File

@ -377,7 +377,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
}
self.assertEqual(expected, result)
default_args = {'limit': None, 'sort_keys': None, 'marker': None,
'sort_dir': None, 'filters': {}}
'sort_dir': None, 'filters': {}, 'tenant_safe': True}
mock_call.assert_called_once_with(req.context, self.topic,
{'namespace': None,
'method': 'list_stacks',
@ -402,12 +402,13 @@ class StackControllerTest(ControllerTest, HeatTestCase):
rpc_call_args, _ = mock_call.call_args
engine_args = rpc_call_args[2]['args']
self.assertEqual(5, len(engine_args))
self.assertEqual(6, len(engine_args))
self.assertIn('limit', engine_args)
self.assertIn('sort_keys', engine_args)
self.assertIn('marker', engine_args)
self.assertIn('sort_dir', engine_args)
self.assertIn('filters', engine_args)
self.assertIn('tenant_safe', engine_args)
self.assertNotIn('balrog', engine_args)
@mock.patch.object(rpc, 'call')
@ -474,6 +475,31 @@ class StackControllerTest(ControllerTest, HeatTestCase):
result = self.controller.index(req, tenant_id=self.tenant)
self.assertNotIn('count', result)
def test_index_enforces_global_index_if_global_tenant(self, mock_enforce):
params = {'global_tenant': 'True'}
req = self._get('/stacks', params=params)
rpc_client = self.controller.rpc_client
rpc_client.list_stacks = mock.Mock(return_value=[])
rpc_client.count_stacks = mock.Mock()
self.controller.index(req, tenant_id=self.tenant)
mock_enforce.assert_called_with(action='global_index',
scope=self.controller.REQUEST_SCOPE,
context=self.context)
def test_global_index_sets_tenant_safe_to_false(self, mock_enforce):
rpc_client = self.controller.rpc_client
rpc_client.list_stacks = mock.Mock(return_value=[])
rpc_client.count_stacks = mock.Mock()
params = {'global_tenant': 'True'}
req = self._get('/stacks', params=params)
self.controller.index(req, tenant_id=self.tenant)
rpc_client.list_stacks.assert_called_once_with(mock.ANY,
filters=mock.ANY,
tenant_safe=False)
@mock.patch.object(rpc, 'call')
def test_detail(self, mock_call, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'detail', True)
@ -529,7 +555,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
self.assertEqual(expected, result)
default_args = {'limit': None, 'sort_keys': None, 'marker': None,
'sort_dir': None, 'filters': None}
'sort_dir': None, 'filters': None, 'tenant_safe': True}
mock_call.assert_called_once_with(req.context, self.topic,
{'namespace': None,
'method': 'list_stacks',

View File

@ -1657,9 +1657,55 @@ class StackServiceTest(HeatTestCase):
mock.ANY,
mock.ANY,
mock.ANY,
filters
filters,
mock.ANY,
)
@mock.patch.object(db_api, 'stack_get_all')
def test_stack_list_tenant_safe_defaults_to_true(self, mock_stack_get_all):
self.eng.list_stacks(self.ctx)
mock_stack_get_all.assert_called_once_with(mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
True,
)
@mock.patch.object(db_api, 'stack_get_all')
def test_stack_list_passes_tenant_safe_info(self, mock_stack_get_all):
self.eng.list_stacks(self.ctx, tenant_safe=False)
mock_stack_get_all.assert_called_once_with(mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
False,
)
@mock.patch.object(db_api, 'stack_count_all')
def test_count_stacks_passes_filter_info(self, mock_stack_count_all):
self.eng.count_stacks(self.ctx, filters={'foo': 'bar'})
mock_stack_count_all.assert_called_once_with(mock.ANY,
filters={'foo': 'bar'},
tenant_safe=mock.ANY)
@mock.patch.object(db_api, 'stack_count_all')
def test_count_stacks_tenant_safe_default_true(self, mock_stack_count_all):
self.eng.count_stacks(self.ctx)
mock_stack_count_all.assert_called_once_with(mock.ANY,
filters=mock.ANY,
tenant_safe=True)
@mock.patch.object(db_api, 'stack_count_all')
def test_count_stacks_passes_tenant_safe_info(self, mock_stack_count_all):
self.eng.count_stacks(self.ctx, tenant_safe=False)
mock_stack_count_all.assert_called_once_with(mock.ANY,
filters=mock.ANY,
tenant_safe=False)
@stack_context('service_abandon_stack')
def test_abandon_stack(self):
self.m.StubOutWithMock(parser.Stack, 'load')

View File

@ -88,10 +88,18 @@ class EngineRpcAPITestCase(testtools.TestCase):
'sort_keys': mock.ANY,
'marker': mock.ANY,
'sort_dir': mock.ANY,
'filters': mock.ANY
'filters': mock.ANY,
'tenant_safe': mock.ANY,
}
self._test_engine_api('list_stacks', 'call', **default_args)
def test_count_stacks(self):
default_args = {
'filters': mock.ANY,
'tenant_safe': mock.ANY,
}
self._test_engine_api('count_stacks', 'call', **default_args)
def test_identify_stack(self):
self._test_engine_api('identify_stack', 'call',
stack_name='wordpress')