Merge "Unscoped List Stacks"
This commit is contained in:
commit
be7969586f
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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':
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user