support list resources with details
Currently, users can list resources without details, or show specific resource with details. However, they cannot list all resources with details. It is desirable to support that. With consistency concern, we should use {list URI}/detail as pattern for resources detail list just like other detail list API, but this pattern will conflict with {resources list URI}/{resource name} when resoruce name is 'detail', so implement this feature as a param in list API seems to be this only option in this case. DocImpact APIImpact Change-Id: I59f464d1803009d7ae2ec35d9fdf0671096967e4 Closes-Bug: #1467268
This commit is contained in:
parent
347a0b55e4
commit
11efe222b3
@ -79,23 +79,34 @@ class ResourceController(object):
|
|||||||
self.options = options
|
self.options = options
|
||||||
self.rpc_client = rpc_client.EngineClient()
|
self.rpc_client = rpc_client.EngineClient()
|
||||||
|
|
||||||
|
def _extract_to_param(self, req, rpc_param, extractor, default):
|
||||||
|
key = rpc_param
|
||||||
|
if key in req.params:
|
||||||
|
try:
|
||||||
|
return extractor(key, req.params[key])
|
||||||
|
except ValueError as e:
|
||||||
|
raise exc.HTTPBadRequest(six.text_type(e))
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
|
||||||
@util.identified_stack
|
@util.identified_stack
|
||||||
def index(self, req, identity):
|
def index(self, req, identity):
|
||||||
"""
|
"""
|
||||||
Lists summary information for all resources
|
Lists information for all resources
|
||||||
"""
|
"""
|
||||||
|
nested_depth = self._extract_to_param(req,
|
||||||
nested_depth = 0
|
rpc_api.PARAM_NESTED_DEPTH,
|
||||||
key = rpc_api.PARAM_NESTED_DEPTH
|
param_utils.extract_int,
|
||||||
if key in req.params:
|
default=0)
|
||||||
try:
|
with_detail = self._extract_to_param(req,
|
||||||
nested_depth = param_utils.extract_int(key, req.params[key])
|
rpc_api.PARAM_WITH_DETAIL,
|
||||||
except ValueError as e:
|
param_utils.extract_bool,
|
||||||
raise exc.HTTPBadRequest(six.text_type(e))
|
default=False)
|
||||||
|
|
||||||
res_list = self.rpc_client.list_stack_resources(req.context,
|
res_list = self.rpc_client.list_stack_resources(req.context,
|
||||||
identity,
|
identity,
|
||||||
nested_depth)
|
nested_depth,
|
||||||
|
with_detail)
|
||||||
|
|
||||||
return {'resources': [format_resource(req, res) for res in res_list]}
|
return {'resources': [format_resource(req, res) for res in res_list]}
|
||||||
|
|
||||||
|
@ -267,7 +267,7 @@ class EngineService(service.Service):
|
|||||||
by the RPC caller.
|
by the RPC caller.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RPC_API_VERSION = '1.11'
|
RPC_API_VERSION = '1.12'
|
||||||
|
|
||||||
def __init__(self, host, topic, manager=None):
|
def __init__(self, host, topic, manager=None):
|
||||||
super(EngineService, self).__init__()
|
super(EngineService, self).__init__()
|
||||||
@ -1256,12 +1256,13 @@ class EngineService(service.Service):
|
|||||||
if resource_name is None or name == resource_name]
|
if resource_name is None or name == resource_name]
|
||||||
|
|
||||||
@context.request_context
|
@context.request_context
|
||||||
def list_stack_resources(self, cnxt, stack_identity, nested_depth=0):
|
def list_stack_resources(self, cnxt, stack_identity,
|
||||||
|
nested_depth=0, with_detail=False):
|
||||||
s = self._get_stack(cnxt, stack_identity, show_deleted=True)
|
s = self._get_stack(cnxt, stack_identity, show_deleted=True)
|
||||||
stack = parser.Stack.load(cnxt, stack=s)
|
stack = parser.Stack.load(cnxt, stack=s)
|
||||||
depth = min(nested_depth, cfg.CONF.max_nested_stack_depth)
|
depth = min(nested_depth, cfg.CONF.max_nested_stack_depth)
|
||||||
|
|
||||||
return [api.format_stack_resource(resource, detail=False)
|
return [api.format_stack_resource(resource, detail=with_detail)
|
||||||
for resource in stack.iter_resources(depth)]
|
for resource in stack.iter_resources(depth)]
|
||||||
|
|
||||||
@context.request_context
|
@context.request_context
|
||||||
|
@ -19,13 +19,13 @@ PARAM_KEYS = (
|
|||||||
PARAM_SHOW_DELETED, PARAM_SHOW_NESTED, PARAM_EXISTING,
|
PARAM_SHOW_DELETED, PARAM_SHOW_NESTED, PARAM_EXISTING,
|
||||||
PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT,
|
PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT,
|
||||||
PARAM_NESTED_DEPTH, PARAM_TAGS, PARAM_SHOW_HIDDEN, PARAM_TAGS_ANY,
|
PARAM_NESTED_DEPTH, PARAM_TAGS, PARAM_SHOW_HIDDEN, PARAM_TAGS_ANY,
|
||||||
PARAM_NOT_TAGS, PARAM_NOT_TAGS_ANY, TEMPLATE_TYPE,
|
PARAM_NOT_TAGS, PARAM_NOT_TAGS_ANY, TEMPLATE_TYPE, PARAM_WITH_DETAIL
|
||||||
) = (
|
) = (
|
||||||
'timeout_mins', 'disable_rollback', 'adopt_stack_data',
|
'timeout_mins', 'disable_rollback', 'adopt_stack_data',
|
||||||
'show_deleted', 'show_nested', 'existing',
|
'show_deleted', 'show_nested', 'existing',
|
||||||
'clear_parameters', 'global_tenant', 'limit',
|
'clear_parameters', 'global_tenant', 'limit',
|
||||||
'nested_depth', 'tags', 'show_hidden', 'tags_any',
|
'nested_depth', 'tags', 'show_hidden', 'tags_any',
|
||||||
'not_tags', 'not_tags_any', 'template_type',
|
'not_tags', 'not_tags_any', 'template_type', 'with_detail',
|
||||||
)
|
)
|
||||||
|
|
||||||
STACK_KEYS = (
|
STACK_KEYS = (
|
||||||
|
@ -32,6 +32,7 @@ class EngineClient(object):
|
|||||||
1.9 - Add template_type option to generate_template()
|
1.9 - Add template_type option to generate_template()
|
||||||
1.10 - Add support for software config list
|
1.10 - Add support for software config list
|
||||||
1.11 - Add support for template versions list
|
1.11 - Add support for template versions list
|
||||||
|
1.12 - Add with_detail option for stack resources list
|
||||||
'''
|
'''
|
||||||
|
|
||||||
BASE_RPC_API_VERSION = '1.0'
|
BASE_RPC_API_VERSION = '1.0'
|
||||||
@ -421,16 +422,21 @@ class EngineClient(object):
|
|||||||
stack_identity=stack_identity,
|
stack_identity=stack_identity,
|
||||||
resource_name=resource_name))
|
resource_name=resource_name))
|
||||||
|
|
||||||
def list_stack_resources(self, ctxt, stack_identity, nested_depth=0):
|
def list_stack_resources(self, ctxt, stack_identity,
|
||||||
|
nested_depth=0, with_detail=False):
|
||||||
"""
|
"""
|
||||||
List the resources belonging to a stack.
|
List the resources belonging to a stack.
|
||||||
:param ctxt: RPC context.
|
:param ctxt: RPC context.
|
||||||
:param stack_identity: Name of the stack.
|
:param stack_identity: Name of the stack.
|
||||||
:param nested_depth: Levels of nested stacks of which list resources.
|
:param nested_depth: Levels of nested stacks of which list resources.
|
||||||
|
:param with_detail: show detail for resoruces in list.
|
||||||
"""
|
"""
|
||||||
return self.call(ctxt, self.make_msg('list_stack_resources',
|
return self.call(ctxt,
|
||||||
|
self.make_msg('list_stack_resources',
|
||||||
stack_identity=stack_identity,
|
stack_identity=stack_identity,
|
||||||
nested_depth=nested_depth))
|
nested_depth=nested_depth,
|
||||||
|
with_detail=with_detail),
|
||||||
|
version='1.12')
|
||||||
|
|
||||||
def stack_suspend(self, ctxt, stack_identity):
|
def stack_suspend(self, ctxt, stack_identity):
|
||||||
return self.call(ctxt, self.make_msg('stack_suspend',
|
return self.call(ctxt, self.make_msg('stack_suspend',
|
||||||
|
@ -1598,7 +1598,9 @@ class CfnStackControllerTest(common.HeatTestCase):
|
|||||||
rpc_client.EngineClient.call(
|
rpc_client.EngineClient.call(
|
||||||
dummy_req.context,
|
dummy_req.context,
|
||||||
('list_stack_resources', {'stack_identity': identity,
|
('list_stack_resources', {'stack_identity': identity,
|
||||||
'nested_depth': 0})
|
'nested_depth': 0,
|
||||||
|
'with_detail': False}),
|
||||||
|
version='1.12'
|
||||||
).AndReturn(engine_resp)
|
).AndReturn(engine_resp)
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
@ -2237,7 +2237,10 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
|
|||||||
rpc_client.EngineClient.call(
|
rpc_client.EngineClient.call(
|
||||||
req.context,
|
req.context,
|
||||||
('list_stack_resources', {'stack_identity': stack_identity,
|
('list_stack_resources', {'stack_identity': stack_identity,
|
||||||
'nested_depth': 0})
|
'nested_depth': 0,
|
||||||
|
'with_detail': False,
|
||||||
|
}),
|
||||||
|
version='1.12'
|
||||||
).AndReturn(engine_resp)
|
).AndReturn(engine_resp)
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
@ -2274,13 +2277,16 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
|
|||||||
rpc_client.EngineClient.call(
|
rpc_client.EngineClient.call(
|
||||||
req.context,
|
req.context,
|
||||||
('list_stack_resources', {'stack_identity': stack_identity,
|
('list_stack_resources', {'stack_identity': stack_identity,
|
||||||
'nested_depth': 0})
|
'nested_depth': 0,
|
||||||
|
'with_detail': False}),
|
||||||
|
version='1.12'
|
||||||
).AndRaise(to_remote_error(error))
|
).AndRaise(to_remote_error(error))
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
resp = request_with_middleware(fault.FaultWrapper,
|
resp = request_with_middleware(fault.FaultWrapper,
|
||||||
self.controller.index,
|
self.controller.index,
|
||||||
req, tenant_id=self.tenant,
|
req,
|
||||||
|
tenant_id=self.tenant,
|
||||||
stack_name=stack_identity.stack_name,
|
stack_name=stack_identity.stack_name,
|
||||||
stack_id=stack_identity.stack_id)
|
stack_id=stack_identity.stack_id)
|
||||||
|
|
||||||
@ -2300,7 +2306,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
|
|||||||
rpc_client.EngineClient.call(
|
rpc_client.EngineClient.call(
|
||||||
req.context,
|
req.context,
|
||||||
('list_stack_resources', {'stack_identity': stack_identity,
|
('list_stack_resources', {'stack_identity': stack_identity,
|
||||||
'nested_depth': 99})
|
'nested_depth': 99,
|
||||||
|
'with_detail': False}),
|
||||||
|
version='1.12'
|
||||||
).AndReturn([])
|
).AndReturn([])
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
@ -2348,6 +2356,80 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
|
|||||||
self.assertEqual(403, resp.status_int)
|
self.assertEqual(403, resp.status_int)
|
||||||
self.assertIn('403 Forbidden', six.text_type(resp))
|
self.assertIn('403 Forbidden', six.text_type(resp))
|
||||||
|
|
||||||
|
def test_index_detail(self, mock_enforce):
|
||||||
|
self._mock_enforce_setup(mock_enforce, 'index', True)
|
||||||
|
res_name = 'WikiDatabase'
|
||||||
|
stack_identity = identifier.HeatIdentifier(self.tenant,
|
||||||
|
'wordpress', '1')
|
||||||
|
res_identity = identifier.ResourceIdentifier(resource_name=res_name,
|
||||||
|
**stack_identity)
|
||||||
|
|
||||||
|
req = self._get(stack_identity._tenant_path() + '/resources',
|
||||||
|
{'with_detail': 'true'})
|
||||||
|
|
||||||
|
resp_parameters = {
|
||||||
|
"OS::project_id": "3ab5b02fa01f4f95afa1e254afc4a435",
|
||||||
|
"network": "cf05086d-07c7-4ed6-95e5-e4af724677e6",
|
||||||
|
"OS::stack_name": "s1", "admin_pass": "******",
|
||||||
|
"key_name": "kk", "image": "fa5d387e-541f-4dfb-ae8a-83a614683f84",
|
||||||
|
"db_port": "50000",
|
||||||
|
"OS::stack_id": "723d7cee-46b3-4433-9c21-f3378eb0bfc4",
|
||||||
|
"flavor": "1"
|
||||||
|
},
|
||||||
|
|
||||||
|
engine_resp = [
|
||||||
|
{
|
||||||
|
u'resource_identity': dict(res_identity),
|
||||||
|
u'stack_name': stack_identity.stack_name,
|
||||||
|
u'resource_name': res_name,
|
||||||
|
u'resource_status_reason': None,
|
||||||
|
u'updated_time': u'2012-07-23T13:06:00Z',
|
||||||
|
u'stack_identity': stack_identity,
|
||||||
|
u'resource_action': u'CREATE',
|
||||||
|
u'resource_status': u'COMPLETE',
|
||||||
|
u'physical_resource_id':
|
||||||
|
u'a3455d8c-9f88-404d-a85b-5315293e67de',
|
||||||
|
u'resource_type': u'AWS::EC2::Instance',
|
||||||
|
u'parameters': resp_parameters,
|
||||||
|
u'description': u'Hello description',
|
||||||
|
u'stack_user_project_id': u'6f38bcfebbc4400b82d50c1a2ea3057d',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
|
||||||
|
rpc_client.EngineClient.call(
|
||||||
|
req.context,
|
||||||
|
('list_stack_resources', {'stack_identity': stack_identity,
|
||||||
|
'nested_depth': 0,
|
||||||
|
'with_detail': True}),
|
||||||
|
version='1.12'
|
||||||
|
).AndReturn(engine_resp)
|
||||||
|
self.m.ReplayAll()
|
||||||
|
|
||||||
|
result = self.controller.index(req, tenant_id=self.tenant,
|
||||||
|
stack_name=stack_identity.stack_name,
|
||||||
|
stack_id=stack_identity.stack_id)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'resources': [{'links': [{'href': self._url(res_identity),
|
||||||
|
'rel': 'self'},
|
||||||
|
{'href': self._url(stack_identity),
|
||||||
|
'rel': 'stack'}],
|
||||||
|
u'resource_name': res_name,
|
||||||
|
u'logical_resource_id': res_name,
|
||||||
|
u'resource_status_reason': None,
|
||||||
|
u'updated_time': u'2012-07-23T13:06:00Z',
|
||||||
|
u'resource_status': u'CREATE_COMPLETE',
|
||||||
|
u'physical_resource_id':
|
||||||
|
u'a3455d8c-9f88-404d-a85b-5315293e67de',
|
||||||
|
u'resource_type': u'AWS::EC2::Instance',
|
||||||
|
u'parameters': resp_parameters,
|
||||||
|
u'description': u'Hello description',
|
||||||
|
u'stack_user_project_id':
|
||||||
|
u'6f38bcfebbc4400b82d50c1a2ea3057d'}]}
|
||||||
|
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
self.m.VerifyAll()
|
||||||
|
|
||||||
def test_show(self, mock_enforce):
|
def test_show(self, mock_enforce):
|
||||||
self._mock_enforce_setup(mock_enforce, 'show', True)
|
self._mock_enforce_setup(mock_enforce, 'show', True)
|
||||||
res_name = 'WikiDatabase'
|
res_name = 'WikiDatabase'
|
||||||
|
@ -39,7 +39,7 @@ class ServiceEngineTest(common.HeatTestCase):
|
|||||||
|
|
||||||
def test_make_sure_rpc_version(self):
|
def test_make_sure_rpc_version(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'1.11',
|
'1.12',
|
||||||
service.EngineService.RPC_API_VERSION,
|
service.EngineService.RPC_API_VERSION,
|
||||||
('RPC version is changed, please update this test to new version '
|
('RPC version is changed, please update this test to new version '
|
||||||
'and make sure additional test cases are added for RPC APIs '
|
'and make sure additional test cases are added for RPC APIs '
|
||||||
|
@ -230,7 +230,8 @@ class EngineRpcAPITestCase(common.HeatTestCase):
|
|||||||
def test_list_stack_resources(self):
|
def test_list_stack_resources(self):
|
||||||
self._test_engine_api('list_stack_resources', 'call',
|
self._test_engine_api('list_stack_resources', 'call',
|
||||||
stack_identity=self.identity,
|
stack_identity=self.identity,
|
||||||
nested_depth=0)
|
nested_depth=0,
|
||||||
|
with_detail=False)
|
||||||
|
|
||||||
def test_stack_suspend(self):
|
def test_stack_suspend(self):
|
||||||
self._test_engine_api('stack_suspend', 'call',
|
self._test_engine_api('stack_suspend', 'call',
|
||||||
|
Loading…
Reference in New Issue
Block a user