Add ability to filter stacks by tag

blueprint stack-tags

Change-Id: I4b5bc741fc271cb08ec588ae962573c59286dae5
This commit is contained in:
Jason Dunsmore 2015-02-04 16:19:57 -06:00
parent ce3f1d299c
commit 3c30bf8727
14 changed files with 498 additions and 55 deletions

View File

@ -186,6 +186,10 @@ class StackController(object):
'show_deleted': 'single',
'show_nested': 'single',
'show_hidden': 'single',
'tags': 'single',
'tags_any': 'single',
'not_tags': 'single',
'not_tags_any': 'single',
}
params = util.get_allowed_params(req.params, whitelist)
filter_params = util.get_allowed_params(req.params, filter_whitelist)
@ -212,6 +216,30 @@ class StackController(object):
params[rpc_api.PARAM_SHOW_HIDDEN])
show_hidden = params[rpc_api.PARAM_SHOW_HIDDEN]
tags = None
if rpc_api.PARAM_TAGS in params:
params[rpc_api.PARAM_TAGS] = param_utils.extract_tags(
params[rpc_api.PARAM_TAGS])
tags = params[rpc_api.PARAM_TAGS]
tags_any = None
if rpc_api.PARAM_TAGS_ANY in params:
params[rpc_api.PARAM_TAGS_ANY] = param_utils.extract_tags(
params[rpc_api.PARAM_TAGS_ANY])
tags_any = params[rpc_api.PARAM_TAGS_ANY]
not_tags = None
if rpc_api.PARAM_NOT_TAGS in params:
params[rpc_api.PARAM_NOT_TAGS] = param_utils.extract_tags(
params[rpc_api.PARAM_NOT_TAGS])
not_tags = params[rpc_api.PARAM_NOT_TAGS]
not_tags_any = None
if rpc_api.PARAM_NOT_TAGS_ANY in params:
params[rpc_api.PARAM_NOT_TAGS_ANY] = param_utils.extract_tags(
params[rpc_api.PARAM_NOT_TAGS_ANY])
not_tags_any = params[rpc_api.PARAM_NOT_TAGS_ANY]
# get the with_count value, if invalid, raise ValueError
with_count = False
if req.params.get('with_count'):
@ -236,7 +264,11 @@ class StackController(object):
tenant_safe=tenant_safe,
show_deleted=show_deleted,
show_nested=show_nested,
show_hidden=show_hidden)
show_hidden=show_hidden,
tags=tags,
tags_any=tags_any,
not_tags=not_tags,
not_tags_any=not_tags_any)
except AttributeError as exc:
LOG.warn(_LW("Old Engine Version: %s") % exc)

View File

@ -53,3 +53,12 @@ def extract_int(name, value, allow_zero=True, allow_negative=False):
{'name': name, 'value': value})
return result
def extract_tags(subject):
tags = subject.split(',')
for tag in tags:
if len(tag) > 80:
raise ValueError(_('Invalid tag, "%s" is longer than 80 '
'characters') % tag)
return tags

View File

@ -142,10 +142,13 @@ def stack_get_by_name(context, stack_name):
def stack_get_all(context, limit=None, sort_keys=None, marker=None,
sort_dir=None, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
return IMPL.stack_get_all(context, limit, sort_keys,
marker, sort_dir, filters, tenant_safe,
show_deleted, show_nested, show_hidden)
show_deleted, show_nested, show_hidden,
tags, tags_any, not_tags, not_tags_any)
def stack_get_all_by_owner_id(context, owner_id):
@ -153,12 +156,18 @@ def stack_get_all_by_owner_id(context, owner_id):
def stack_count_all(context, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
return IMPL.stack_count_all(context, filters=filters,
tenant_safe=tenant_safe,
show_deleted=show_deleted,
show_nested=show_nested,
show_hidden=show_hidden)
show_hidden=show_hidden,
tags=tags,
tags_any=tags_any,
not_tags=not_tags,
not_tags_any=not_tags_any)
def stack_create(context, values):

View File

@ -23,6 +23,7 @@ import osprofiler.sqlalchemy
import six
import sqlalchemy
from sqlalchemy import orm
from sqlalchemy.orm import aliased as orm_aliased
from sqlalchemy.orm import session as orm_session
from heat.common import crypt
@ -399,7 +400,8 @@ def _paginate_query(context, query, model, limit=None, sort_keys=None,
def _query_stack_get_all(context, tenant_safe=True, show_deleted=False,
show_nested=False, show_hidden=False):
show_nested=False, show_hidden=False, tags=None,
tags_any=None, not_tags=None, not_tags_any=None):
if show_nested:
query = soft_delete_aware_query(
context, models.Stack, show_deleted=show_deleted
@ -412,6 +414,33 @@ def _query_stack_get_all(context, tenant_safe=True, show_deleted=False,
if tenant_safe:
query = query.filter_by(tenant=context.tenant_id)
if tags:
for tag in tags:
tag_alias = orm_aliased(models.StackTag)
query = query.join(tag_alias, models.Stack.tags)
query = query.filter(tag_alias.tag == tag)
if tags_any:
query = query.filter(
models.Stack.tags.any(
models.StackTag.tag.in_(tags_any)))
if not_tags:
subquery = soft_delete_aware_query(
context, models.Stack, show_deleted=show_deleted
)
for tag in not_tags:
tag_alias = orm_aliased(models.StackTag)
subquery = subquery.join(tag_alias, models.Stack.tags)
subquery = subquery.filter(tag_alias.tag == tag)
not_stack_ids = [s.id for s in subquery.all()]
query = query.filter(models.Stack.id.notin_(not_stack_ids))
if not_tags_any:
query = query.filter(
~models.Stack.tags.any(
models.StackTag.tag.in_(not_tags_any)))
if not show_hidden:
query = query.filter(
~models.Stack.tags.any(
@ -422,11 +451,15 @@ def _query_stack_get_all(context, tenant_safe=True, show_deleted=False,
def stack_get_all(context, limit=None, sort_keys=None, marker=None,
sort_dir=None, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
query = _query_stack_get_all(context, tenant_safe,
show_deleted=show_deleted,
show_nested=show_nested,
show_hidden=show_hidden)
show_hidden=show_hidden, tags=tags,
tags_any=tags_any, not_tags=not_tags,
not_tags_any=not_tags_any)
return _filter_and_page_query(context, query, limit, sort_keys,
marker, sort_dir, filters).all()
@ -448,11 +481,15 @@ def _filter_and_page_query(context, query, limit=None, sort_keys=None,
def stack_count_all(context, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
query = _query_stack_get_all(context, tenant_safe=tenant_safe,
show_deleted=show_deleted,
show_nested=show_nested,
show_hidden=show_hidden)
show_hidden=show_hidden, tags=tags,
tags_any=tags_any, not_tags=not_tags,
not_tags_any=not_tags_any)
query = db_filters.exact_filter(query, models.Stack, filters)
return query.count()

View File

@ -266,7 +266,7 @@ class EngineService(service.Service):
by the RPC caller.
"""
RPC_API_VERSION = '1.7'
RPC_API_VERSION = '1.8'
def __init__(self, host, topic, manager=None):
super(EngineService, self).__init__()
@ -467,7 +467,9 @@ class EngineService(service.Service):
@context.request_context
def list_stacks(self, cnxt, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
"""
The list_stacks method returns attributes of all stacks. It supports
pagination (``limit`` and ``marker``), sorting (``sort_keys`` and
@ -483,18 +485,31 @@ class EngineService(service.Service):
:param show_deleted: if true, show soft-deleted stacks
:param show_nested: if true, show nested stacks
:param show_hidden: if true, show hidden stacks
:param tags: show stacks containing these tags, combine multiple
tags using the boolean AND expression
:param tags_any: show stacks containing these tags, combine multiple
tags using the boolean OR expression
:param not_tags: show stacks not containing these tags, combine
multiple tags using the boolean AND expression
:param not_tags_any: show stacks not containing these tags, combine
multiple tags using the boolean OR expression
:returns: a list of formatted stacks
"""
stacks = parser.Stack.load_all(cnxt, limit, marker, sort_keys,
sort_dir, filters, tenant_safe,
show_deleted, resolve_data=False,
show_nested=show_nested,
show_hidden=show_hidden)
show_hidden=show_hidden,
tags=tags, tags_any=tags_any,
not_tags=not_tags,
not_tags_any=not_tags_any)
return [api.format_stack(stack) for stack in stacks]
@context.request_context
def count_stacks(self, cnxt, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
"""
Return the number of stacks that match the given filters
:param cnxt: RPC context.
@ -502,7 +517,15 @@ class EngineService(service.Service):
:param tenant_safe: if true, scope the request by the current tenant
:param show_deleted: if true, count will include the deleted stacks
:param show_nested: if true, count will include nested stacks
:param show_hidden: if true, show hidden stacks
:param show_hidden: if true, count will include hidden stacks
:param tags: count stacks containing these tags, combine multiple tags
using the boolean AND expression
:param tags_any: count stacks containing these tags, combine multiple
tags using the boolean OR expression
:param not_tags: count stacks not containing these tags, combine
multiple tags using the boolean AND expression
:param not_tags_any: count stacks not containing these tags, combine
multiple tags using the boolean OR expression
:returns: a integer representing the number of matched stacks
"""
return stack_object.Stack.count_all(
@ -511,7 +534,11 @@ class EngineService(service.Service):
tenant_safe=tenant_safe,
show_deleted=show_deleted,
show_nested=show_nested,
show_hidden=show_hidden)
show_hidden=show_hidden,
tags=tags,
tags_any=tags_any,
not_tags=not_tags,
not_tags_any=not_tags_any)
def _validate_deferred_auth_context(self, cnxt, stack):
if cfg.CONF.deferred_auth_method != 'password':

View File

@ -354,7 +354,8 @@ class Stack(collections.Mapping):
def load_all(cls, context, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None, tenant_safe=True,
show_deleted=False, resolve_data=True,
show_nested=False, show_hidden=False):
show_nested=False, show_hidden=False, tags=None,
tags_any=None, not_tags=None, not_tags_any=None):
stacks = stack_object.Stack.get_all(
context,
limit,
@ -365,7 +366,11 @@ class Stack(collections.Mapping):
tenant_safe,
show_deleted,
show_nested,
show_hidden) or []
show_hidden,
tags,
tags_any,
not_tags,
not_tags_any) or []
for stack in stacks:
yield cls._from_db(context, stack, resolve_data=resolve_data)

View File

@ -18,12 +18,14 @@ PARAM_KEYS = (
PARAM_TIMEOUT, PARAM_DISABLE_ROLLBACK, PARAM_ADOPT_STACK_DATA,
PARAM_SHOW_DELETED, PARAM_SHOW_NESTED, PARAM_EXISTING,
PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT,
PARAM_NESTED_DEPTH, PARAM_TAGS, PARAM_SHOW_HIDDEN
PARAM_NESTED_DEPTH, PARAM_TAGS, PARAM_SHOW_HIDDEN, PARAM_TAGS_ANY,
PARAM_NOT_TAGS, PARAM_NOT_TAGS_ANY
) = (
'timeout_mins', 'disable_rollback', 'adopt_stack_data',
'show_deleted', 'show_nested', 'existing',
'clear_parameters', 'global_tenant', 'limit',
'nested_depth', 'tags', 'show_hidden'
'nested_depth', 'tags', 'show_hidden', 'tags_any',
'not_tags', 'not_tags_any'
)
STACK_KEYS = (

View File

@ -91,7 +91,9 @@ class EngineClient(object):
def list_stacks(self, ctxt, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
"""
The list_stacks method returns attributes of all stacks. It supports
pagination (``limit`` and ``marker``), sorting (``sort_keys`` and
@ -107,6 +109,14 @@ class EngineClient(object):
:param show_deleted: if true, show soft-deleted stacks
:param show_nested: if true, show nested stacks
:param show_hidden: if true, show hidden stacks
:param tags: show stacks containing these tags, combine multiple
tags using the boolean AND expression
:param tags_any: show stacks containing these tags, combine multiple
tags using the boolean OR expression
:param not_tags: show stacks not containing these tags, combine
multiple tags using the boolean AND expression
:param not_tags_any: show stacks not containing these tags, combine
multiple tags using the boolean OR expression
:returns: a list of stacks
"""
return self.call(ctxt,
@ -116,10 +126,16 @@ class EngineClient(object):
tenant_safe=tenant_safe,
show_deleted=show_deleted,
show_nested=show_nested,
show_hidden=show_hidden))
show_hidden=show_hidden,
tags=tags, tags_any=tags_any,
not_tags=not_tags,
not_tags_any=not_tags_any),
version='1.8')
def count_stacks(self, ctxt, filters=None, tenant_safe=True,
show_deleted=False, show_nested=False, show_hidden=False):
show_deleted=False, show_nested=False, show_hidden=False,
tags=None, tags_any=None, not_tags=None,
not_tags_any=None):
"""
Return the number of stacks that match the given filters
:param ctxt: RPC context.
@ -127,7 +143,15 @@ class EngineClient(object):
:param tenant_safe: if true, scope the request by the current tenant
:param show_deleted: if true, count will include the deleted stacks
:param show_nested: if true, count will include nested stacks
:param show_hidden: if true, show hidden stacks
:param show_hidden: if true, count will include hidden stacks
:param tags: count stacks containing these tags, combine multiple tags
using the boolean AND expression
:param tags_any: count stacks containing these tags, combine multiple
tags using the boolean OR expression
:param not_tags: count stacks not containing these tags, combine
multiple tags using the boolean AND expression
:param not_tags_any: count stacks not containing these tags, combine
multiple tags using the boolean OR expression
:returns: a integer representing the number of matched stacks
"""
return self.call(ctxt, self.make_msg('count_stacks',
@ -135,7 +159,12 @@ class EngineClient(object):
tenant_safe=tenant_safe,
show_deleted=show_deleted,
show_nested=show_nested,
show_hidden=show_hidden))
show_hidden=show_hidden,
tags=tags,
tags_any=tags_any,
not_tags=not_tags,
not_tags_any=not_tags_any),
version='1.8')
def show_stack(self, ctxt, stack_identity):
"""
@ -205,7 +234,7 @@ class EngineClient(object):
user_creds_id=user_creds_id,
stack_user_project_id=stack_user_project_id,
parent_resource_name=parent_resource_name),
version='1.7')
version='1.8')
def update_stack(self, ctxt, stack_identity, template, params,
files, args):

View File

@ -574,6 +574,113 @@ class SqlAlchemyTest(common.HeatTestCase):
for stack in st_db_visible:
self.assertNotEqual(stacks[0].id, stack.id)
def test_stack_get_all_by_tags(self):
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag1']
stacks[0].store()
stacks[1].tags = ['tag1', 'tag2']
stacks[1].store()
stacks[2].tags = ['tag1', 'tag2', 'tag3']
stacks[2].store()
st_db = db_api.stack_get_all(self.ctx, tags=['tag2'])
self.assertEqual(2, len(st_db))
st_db = db_api.stack_get_all(self.ctx, tags=['tag1', 'tag2'])
self.assertEqual(2, len(st_db))
st_db = db_api.stack_get_all(self.ctx, tags=['tag1', 'tag2', 'tag3'])
self.assertEqual(1, len(st_db))
def test_stack_get_all_by_tags_any(self):
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag2']
stacks[0].store()
stacks[1].tags = ['tag1', 'tag2']
stacks[1].store()
stacks[2].tags = ['tag1', 'tag3']
stacks[2].store()
st_db = db_api.stack_get_all(self.ctx, tags_any=['tag1'])
self.assertEqual(2, len(st_db))
st_db = db_api.stack_get_all(self.ctx, tags_any=['tag1', 'tag2',
'tag3'])
self.assertEqual(3, len(st_db))
def test_stack_get_all_by_not_tags(self):
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag1']
stacks[0].store()
stacks[1].tags = ['tag1', 'tag2']
stacks[1].store()
stacks[2].tags = ['tag1', 'tag2', 'tag3']
stacks[2].store()
st_db = db_api.stack_get_all(self.ctx, not_tags=['tag2'])
self.assertEqual(1, len(st_db))
st_db = db_api.stack_get_all(self.ctx, not_tags=['tag1', 'tag2'])
self.assertEqual(1, len(st_db))
st_db = db_api.stack_get_all(self.ctx, not_tags=['tag1', 'tag2',
'tag3'])
self.assertEqual(2, len(st_db))
def test_stack_get_all_by_not_tags_any(self):
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag2']
stacks[0].store()
stacks[1].tags = ['tag1', 'tag2']
stacks[1].store()
stacks[2].tags = ['tag1', 'tag3']
stacks[2].store()
st_db = db_api.stack_get_all(self.ctx, not_tags_any=['tag1'])
self.assertEqual(1, len(st_db))
st_db = db_api.stack_get_all(self.ctx, not_tags_any=['tag1', 'tag2',
'tag3'])
self.assertEqual(0, len(st_db))
def test_stack_get_all_by_tag_with_pagination(self):
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag1']
stacks[0].store()
stacks[1].tags = ['tag2']
stacks[1].store()
stacks[2].tags = ['tag1']
stacks[2].store()
st_db = db_api.stack_get_all(self.ctx, tags=['tag1'])
self.assertEqual(2, len(st_db))
st_db = db_api.stack_get_all(self.ctx, tags=['tag1'], limit=1)
self.assertEqual(1, len(st_db))
self.assertEqual(stacks[2].id, st_db[0].id)
st_db = db_api.stack_get_all(self.ctx, tags=['tag1'], limit=1,
marker=stacks[2].id)
self.assertEqual(1, len(st_db))
self.assertEqual(stacks[0].id, st_db[0].id)
def test_stack_get_all_by_tag_with_show_hidden(self):
cfg.CONF.set_override('hidden_stack_tags', ['hidden'])
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag1']
stacks[0].store()
stacks[1].tags = ['hidden', 'tag1']
stacks[1].store()
st_db = db_api.stack_get_all(self.ctx, tags=['tag1'],
show_hidden=True)
self.assertEqual(2, len(st_db))
st_db = db_api.stack_get_all(self.ctx, tags=['tag1'],
show_hidden=False)
self.assertEqual(1, len(st_db))
def test_stack_count_all(self):
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
@ -609,6 +716,38 @@ class SqlAlchemyTest(common.HeatTestCase):
st_db_visible = db_api.stack_count_all(self.ctx, show_hidden=False)
self.assertEqual(2, st_db_visible)
def test_count_all_by_tags(self):
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag1']
stacks[0].store()
stacks[1].tags = ['tag2']
stacks[1].store()
stacks[2].tags = ['tag2']
stacks[2].store()
st_db = db_api.stack_count_all(self.ctx, tags=['tag1'])
self.assertEqual(1, st_db)
st_db = db_api.stack_count_all(self.ctx, tags=['tag2'])
self.assertEqual(2, st_db)
def test_count_all_by_tag_with_show_hidden(self):
cfg.CONF.set_override('hidden_stack_tags', ['hidden'])
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
stacks[0].tags = ['tag1']
stacks[0].store()
stacks[1].tags = ['hidden', 'tag1']
stacks[1].store()
st_db = db_api.stack_count_all(self.ctx, tags=['tag1'],
show_hidden=True)
self.assertEqual(2, st_db)
st_db = db_api.stack_count_all(self.ctx, tags=['tag1'],
show_hidden=False)
self.assertEqual(1, st_db)
def test_stack_count_all_with_filters(self):
self._setup_test_stack('foo', UUID1)
self._setup_test_stack('bar', UUID2)

View File

@ -162,9 +162,11 @@ class CfnStackControllerTest(common.HeatTestCase):
default_args = {'limit': None, 'sort_keys': None, 'marker': None,
'sort_dir': None, 'filters': None, 'tenant_safe': True,
'show_deleted': False, 'show_nested': False,
'show_hidden': False}
'show_hidden': False, 'tags': None,
'tags_any': None, 'not_tags': None,
'not_tags_any': None}
mock_call.assert_called_once_with(
dummy_req.context, ('list_stacks', default_args))
dummy_req.context, ('list_stacks', default_args), version='1.8')
@mock.patch.object(rpc_client.EngineClient, 'call')
def test_list_rmt_aterr(self, mock_call):
@ -180,7 +182,7 @@ class CfnStackControllerTest(common.HeatTestCase):
result = self.controller.list(dummy_req)
self.assertIsInstance(result, exception.HeatInvalidParameterValueError)
mock_call.assert_called_once_with(
dummy_req.context, ('list_stacks', mock.ANY))
dummy_req.context, ('list_stacks', mock.ANY), version='1.8')
@mock.patch.object(rpc_client.EngineClient, 'call')
def test_list_rmt_interr(self, mock_call):
@ -196,7 +198,7 @@ class CfnStackControllerTest(common.HeatTestCase):
result = self.controller.list(dummy_req)
self.assertIsInstance(result, exception.HeatInternalFailureError)
mock_call.assert_called_once_with(
dummy_req.context, ('list_stacks', mock.ANY))
dummy_req.context, ('list_stacks', mock.ANY), version='1.8')
def test_describe_last_updated_time(self):
params = {'Action': 'DescribeStacks'}
@ -508,7 +510,7 @@ class CfnStackControllerTest(common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndRaise(failure)
def _stub_rpc_create_stack_call_success(self, stack_name, engine_parms,
@ -536,7 +538,7 @@ class CfnStackControllerTest(common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndReturn(engine_resp)
self.m.ReplayAll()

View File

@ -386,9 +386,11 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
default_args = {'limit': None, 'sort_keys': None, 'marker': None,
'sort_dir': None, 'filters': None, 'tenant_safe': True,
'show_deleted': False, 'show_nested': False,
'show_hidden': False}
'show_hidden': False, 'tags': None,
'tags_any': None, 'not_tags': None,
'not_tags_any': None}
mock_call.assert_called_once_with(
req.context, ('list_stacks', default_args))
req.context, ('list_stacks', default_args), version='1.8')
@mock.patch.object(rpc_client.EngineClient, 'call')
def test_index_whitelists_pagination_params(self, mock_call, mock_enforce):
@ -407,7 +409,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
rpc_call_args, _ = mock_call.call_args
engine_args = rpc_call_args[1][1]
self.assertEqual(9, len(engine_args))
self.assertEqual(13, len(engine_args))
self.assertIn('limit', engine_args)
self.assertIn('sort_keys', engine_args)
self.assertIn('marker', engine_args)
@ -608,7 +610,11 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
tenant_safe=True,
show_deleted=True,
show_nested=False,
show_hidden=False)
show_hidden=False,
tags=None,
tags_any=None,
not_tags=None,
not_tags_any=None)
@mock.patch.object(rpc_client.EngineClient, 'call')
def test_detail(self, mock_call, mock_enforce):
@ -667,9 +673,11 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
default_args = {'limit': None, 'sort_keys': None, 'marker': None,
'sort_dir': None, 'filters': None, 'tenant_safe': True,
'show_deleted': False, 'show_nested': False,
'show_hidden': False}
'show_hidden': False, 'tags': None,
'tags_any': None, 'not_tags': None,
'not_tags_any': None}
mock_call.assert_called_once_with(
req.context, ('list_stacks', default_args))
req.context, ('list_stacks', default_args), version='1.8')
@mock.patch.object(rpc_client.EngineClient, 'call')
def test_index_rmt_aterr(self, mock_call, mock_enforce):
@ -685,7 +693,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
self.assertEqual(400, resp.json['code'])
self.assertEqual('AttributeError', resp.json['error']['type'])
mock_call.assert_called_once_with(
req.context, ('list_stacks', mock.ANY))
req.context, ('list_stacks', mock.ANY), version='1.8')
def test_index_err_denied_policy(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', False)
@ -713,7 +721,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
self.assertEqual(500, resp.json['code'])
self.assertEqual('Exception', resp.json['error']['type'])
mock_call.assert_called_once_with(
req.context, ('list_stacks', mock.ANY))
req.context, ('list_stacks', mock.ANY), version='1.8')
def test_create(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'create', True)
@ -743,7 +751,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndReturn(dict(identity))
self.m.ReplayAll()
@ -806,7 +814,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndReturn(dict(identity))
self.m.ReplayAll()
@ -893,7 +901,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndReturn(dict(identity))
self.m.ReplayAll()
@ -937,7 +945,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndRaise(to_remote_error(AttributeError()))
rpc_client.EngineClient.call(
req.context,
@ -954,7 +962,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndRaise(to_remote_error(unknown_parameter))
rpc_client.EngineClient.call(
req.context,
@ -971,7 +979,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndRaise(to_remote_error(missing_parameter))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1025,7 +1033,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndRaise(to_remote_error(error))
self.m.ReplayAll()
@ -1106,7 +1114,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.7'
version='1.8'
).AndRaise(to_remote_error(error))
self.m.ReplayAll()

View File

@ -88,3 +88,11 @@ class TestExtractInt(common.HeatTestCase):
param_utils.extract_int, 'num', 'true')
self.assertRaises(ValueError,
param_utils.extract_int, 'num', True)
class TestExtractTags(common.HeatTestCase):
def test_extract_tags(self):
self.assertRaises(ValueError, param_utils.extract_tags, "aaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaa,a")
self.assertEqual(["foo", "bar"], param_utils.extract_tags('foo,bar'))

View File

@ -1736,7 +1736,7 @@ class StackServiceTest(common.HeatTestCase):
def test_make_sure_rpc_version(self):
self.assertEqual(
'1.7',
'1.8',
service.EngineService.RPC_API_VERSION,
('RPC version is changed, please update this test to new version '
'and make sure additional test cases are added for RPC APIs '
@ -2064,6 +2064,10 @@ class StackServiceTest(common.HeatTestCase):
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
@ -2080,6 +2084,10 @@ class StackServiceTest(common.HeatTestCase):
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
@ -2095,6 +2103,10 @@ class StackServiceTest(common.HeatTestCase):
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
@ -2110,6 +2122,10 @@ class StackServiceTest(common.HeatTestCase):
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
@ -2125,6 +2141,10 @@ class StackServiceTest(common.HeatTestCase):
mock.ANY,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
@ -2140,6 +2160,10 @@ class StackServiceTest(common.HeatTestCase):
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
@ -2155,6 +2179,86 @@ class StackServiceTest(common.HeatTestCase):
mock.ANY,
mock.ANY,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
def test_stack_list_tags(self, mock_stack_get_all):
self.eng.list_stacks(self.ctx, tags=['foo', 'bar'])
mock_stack_get_all.assert_called_once_with(mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
['foo', 'bar'],
mock.ANY,
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
def test_stack_list_tags_any(self, mock_stack_get_all):
self.eng.list_stacks(self.ctx, tags_any=['foo', 'bar'])
mock_stack_get_all.assert_called_once_with(mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
['foo', 'bar'],
mock.ANY,
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
def test_stack_list_not_tags(self, mock_stack_get_all):
self.eng.list_stacks(self.ctx, not_tags=['foo', 'bar'])
mock_stack_get_all.assert_called_once_with(mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
['foo', 'bar'],
mock.ANY,
)
@mock.patch.object(stack_object.Stack, 'get_all')
def test_stack_list_not_tags_any(self, mock_stack_get_all):
self.eng.list_stacks(self.ctx, not_tags_any=['foo', 'bar'])
mock_stack_get_all.assert_called_once_with(mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
['foo', 'bar'],
)
@mock.patch.object(stack_object.Stack, 'count_all')
@ -2165,7 +2269,11 @@ class StackServiceTest(common.HeatTestCase):
tenant_safe=mock.ANY,
show_deleted=False,
show_nested=False,
show_hidden=False)
show_hidden=False,
tags=None,
tags_any=None,
not_tags=None,
not_tags_any=None)
@mock.patch.object(stack_object.Stack, 'count_all')
def test_count_stacks_tenant_safe_default_true(self, mock_stack_count_all):
@ -2175,7 +2283,11 @@ class StackServiceTest(common.HeatTestCase):
tenant_safe=True,
show_deleted=False,
show_nested=False,
show_hidden=False)
show_hidden=False,
tags=None,
tags_any=None,
not_tags=None,
not_tags_any=None)
@mock.patch.object(stack_object.Stack, 'count_all')
def test_count_stacks_passes_tenant_safe_info(self, mock_stack_count_all):
@ -2185,7 +2297,11 @@ class StackServiceTest(common.HeatTestCase):
tenant_safe=False,
show_deleted=False,
show_nested=False,
show_hidden=False)
show_hidden=False,
tags=None,
tags_any=None,
not_tags=None,
not_tags_any=None)
@mock.patch.object(stack_object.Stack, 'count_all')
def test_count_stacks_show_nested(self, mock_stack_count_all):
@ -2195,7 +2311,11 @@ class StackServiceTest(common.HeatTestCase):
tenant_safe=True,
show_deleted=False,
show_nested=True,
show_hidden=False)
show_hidden=False,
tags=None,
tags_any=None,
not_tags=None,
not_tags_any=None)
@mock.patch.object(stack_object.Stack, 'count_all')
def test_count_stack_show_deleted(self, mock_stack_count_all):
@ -2205,7 +2325,11 @@ class StackServiceTest(common.HeatTestCase):
tenant_safe=True,
show_deleted=True,
show_nested=False,
show_hidden=False)
show_hidden=False,
tags=None,
tags_any=None,
not_tags=None,
not_tags_any=None)
@mock.patch.object(stack_object.Stack, 'count_all')
def test_count_stack_show_hidden(self, mock_stack_count_all):
@ -2215,7 +2339,11 @@ class StackServiceTest(common.HeatTestCase):
tenant_safe=True,
show_deleted=False,
show_nested=False,
show_hidden=True)
show_hidden=True,
tags=None,
tags_any=None,
not_tags=None,
not_tags_any=None)
@stack_context('service_abandon_stack')
def test_abandon_stack(self):

View File

@ -116,6 +116,10 @@ class EngineRpcAPITestCase(common.HeatTestCase):
'show_deleted': mock.ANY,
'show_nested': mock.ANY,
'show_hidden': mock.ANY,
'tags': mock.ANY,
'tags_any': mock.ANY,
'not_tags': mock.ANY,
'not_tags_any': mock.ANY,
}
self._test_engine_api('list_stacks', 'call', **default_args)
@ -126,6 +130,10 @@ class EngineRpcAPITestCase(common.HeatTestCase):
'show_deleted': mock.ANY,
'show_nested': mock.ANY,
'show_hidden': mock.ANY,
'tags': mock.ANY,
'tags_any': mock.ANY,
'not_tags': mock.ANY,
'not_tags_any': mock.ANY,
}
self._test_engine_api('count_stacks', 'call', **default_args)