Merge "OSC plugin for stack resource show and list"
This commit is contained in:
@@ -16,13 +16,120 @@
|
||||
import logging
|
||||
import six
|
||||
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
from openstackclient.common import exceptions as exc
|
||||
from openstackclient.common import utils
|
||||
from openstackclient.i18n import _
|
||||
|
||||
from heatclient.common import format_utils
|
||||
from heatclient import exc as heat_exc
|
||||
|
||||
|
||||
class ResourceShow(show.ShowOne):
|
||||
"""Display stack resource."""
|
||||
|
||||
log = logging.getLogger(__name__ + '.ResourceShowStack')
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ResourceShow, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'stack',
|
||||
metavar='<stack>',
|
||||
help=_('Name or ID of stack to query')
|
||||
)
|
||||
parser.add_argument(
|
||||
'resource',
|
||||
metavar='<resource>',
|
||||
help=_('Name or ID of resource')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--with-attr',
|
||||
metavar='<attribute>',
|
||||
action='append',
|
||||
help=_('Attribute to show, can be specified multiple times')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug('take_action(%s)', parsed_args)
|
||||
|
||||
client = self.app.client_manager.orchestration
|
||||
|
||||
try:
|
||||
resource = client.resources.get(parsed_args.stack,
|
||||
parsed_args.resource,
|
||||
with_attr=parsed_args.with_attr)
|
||||
except heat_exc.HTTPNotFound:
|
||||
msg = (_('Stack or resource not found: %(stack)s %(resource)s') %
|
||||
{'stack': parsed_args.stack,
|
||||
'resource': parsed_args.resource})
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
return self.dict2columns(resource.to_dict())
|
||||
|
||||
|
||||
class ResourceList(lister.Lister):
|
||||
"""List stack resources."""
|
||||
|
||||
log = logging.getLogger(__name__ + '.ResourceListStack')
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ResourceList, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'stack',
|
||||
metavar='<stack>',
|
||||
help=_('Name or ID of stack to query')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--long',
|
||||
action='store_true',
|
||||
help=_('Enable detailed information presented for each resource '
|
||||
'in resource list')
|
||||
)
|
||||
parser.add_argument(
|
||||
'-n', '--nested-depth',
|
||||
metavar='<DEPTH>',
|
||||
type=int,
|
||||
help=_('Depth of nested stacks from which to display resources')
|
||||
)
|
||||
# TODO(jonesbr):
|
||||
# Add --filter once https://review.openstack.org/#/c/257864/ is merged
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug('take_action(%s)', parsed_args)
|
||||
|
||||
client = self.app.client_manager.orchestration
|
||||
|
||||
fields = {
|
||||
'nested_depth': parsed_args.nested_depth,
|
||||
'with_detail': parsed_args.long,
|
||||
}
|
||||
|
||||
try:
|
||||
resources = client.resources.list(parsed_args.stack, **fields)
|
||||
except heat_exc.HTTPNotFound:
|
||||
msg = _('Stack not found: %s') % parsed_args.stack
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
columns = ['physical_resource_id', 'resource_type', 'resource_status',
|
||||
'updated_time']
|
||||
|
||||
if len(resources) >= 1 and not hasattr(resources[0], 'resource_name'):
|
||||
columns.insert(0, 'logical_resource_id')
|
||||
else:
|
||||
columns.insert(0, 'resource_name')
|
||||
|
||||
if parsed_args.nested_depth or parsed_args.long:
|
||||
columns.append('stack_name')
|
||||
|
||||
return (
|
||||
columns,
|
||||
(utils.get_item_properties(r, columns) for r in resources)
|
||||
)
|
||||
|
||||
|
||||
class ResourceMetadata(format_utils.JsonFormat):
|
||||
"""Show resource metadata"""
|
||||
|
||||
|
@@ -365,7 +365,7 @@ class UpdateStack(show.ShowOne):
|
||||
|
||||
|
||||
class ShowStack(show.ShowOne):
|
||||
"""Show stack details"""
|
||||
"""Show stack details."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ShowStack")
|
||||
|
||||
|
@@ -11,6 +11,7 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import copy
|
||||
import mock
|
||||
|
||||
from openstackclient.common import exceptions as exc
|
||||
@@ -18,12 +19,174 @@ from openstackclient.common import exceptions as exc
|
||||
from heatclient import exc as heat_exc
|
||||
from heatclient.osc.v1 import resources
|
||||
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
|
||||
from heatclient.v1 import resources as v1_resources
|
||||
|
||||
|
||||
class TestResource(orchestration_fakes.TestOrchestrationv1):
|
||||
def setUp(self):
|
||||
super(TestResource, self).setUp()
|
||||
self.mock_client = self.app.client_manager.orchestration
|
||||
self.resource_client = self.app.client_manager.orchestration.resources
|
||||
|
||||
|
||||
class TestStackResourceShow(TestResource):
|
||||
|
||||
response = {
|
||||
'attributes': {},
|
||||
'creation_time': '2016-02-01T20:20:53',
|
||||
'description': 'a resource',
|
||||
'links': [
|
||||
{'rel': 'stack',
|
||||
"href": "http://heat.example.com:8004/my_stack/12"}
|
||||
],
|
||||
'logical_resource_id': 'my_resource',
|
||||
'physical_resource_id': '1234',
|
||||
'required_by': [],
|
||||
'resource_name': 'my_resource',
|
||||
'resource_status': 'CREATE_COMPLETE',
|
||||
'resource_status_reason': 'state changed',
|
||||
'resource_type': 'OS::Heat::None',
|
||||
'updated_time': '2016-02-01T20:20:53',
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestStackResourceShow, self).setUp()
|
||||
self.cmd = resources.ResourceShow(self.app, None)
|
||||
self.resource_client.get = mock.MagicMock(
|
||||
return_value=v1_resources.Resource(None, self.response))
|
||||
|
||||
def test_resource_show(self):
|
||||
arglist = ['my_stack', 'my_resource']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.resource_client.get.assert_called_with('my_stack', 'my_resource',
|
||||
with_attr=None)
|
||||
for key in self.response:
|
||||
self.assertIn(key, columns)
|
||||
self.assertIn(self.response[key], data)
|
||||
|
||||
def test_resource_show_with_attr(self):
|
||||
arglist = ['my_stack', 'my_resource',
|
||||
'--with-attr', 'foo', '--with-attr', 'bar']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.resource_client.get.assert_called_with('my_stack', 'my_resource',
|
||||
with_attr=['foo', 'bar'])
|
||||
for key in self.response:
|
||||
self.assertIn(key, columns)
|
||||
self.assertIn(self.response[key], data)
|
||||
|
||||
def test_resource_show_not_found(self):
|
||||
arglist = ['my_stack', 'bad_resource']
|
||||
self.resource_client.get.side_effect = heat_exc.HTTPNotFound
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
error = self.assertRaises(exc.CommandError,
|
||||
self.cmd.take_action, parsed_args)
|
||||
self.assertEqual('Stack or resource not found: my_stack bad_resource',
|
||||
str(error))
|
||||
|
||||
|
||||
class TestStackResourceList(TestResource):
|
||||
|
||||
response = {
|
||||
'attributes': {},
|
||||
'creation_time': '2016-02-01T20:20:53',
|
||||
'description': 'a resource',
|
||||
'links': [
|
||||
{'rel': 'stack',
|
||||
"href": "http://heat.example.com:8004/my_stack/12"}
|
||||
],
|
||||
'logical_resource_id': '1234',
|
||||
'physical_resource_id': '1234',
|
||||
'required_by': [],
|
||||
'resource_name': 'my_resource',
|
||||
'resource_status': 'CREATE_COMPLETE',
|
||||
'resource_status_reason': 'state changed',
|
||||
'resource_type': 'OS::Heat::None',
|
||||
'updated_time': '2016-02-01T20:20:53',
|
||||
}
|
||||
|
||||
columns = ['resource_name', 'physical_resource_id', 'resource_type',
|
||||
'resource_status', 'updated_time']
|
||||
|
||||
data = ['my_resource', '1234', 'OS::Heat::None',
|
||||
'CREATE_COMPLETE', '2016-02-01T20:20:53']
|
||||
|
||||
def setUp(self):
|
||||
super(TestStackResourceList, self).setUp()
|
||||
self.cmd = resources.ResourceList(self.app, None)
|
||||
self.resource_client.list = mock.MagicMock(
|
||||
return_value=[v1_resources.Resource(None, self.response)])
|
||||
|
||||
def test_resource_list(self):
|
||||
arglist = ['my_stack']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.resource_client.list.assert_called_with(
|
||||
'my_stack', with_detail=False, nested_depth=None)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertEqual(tuple(self.data), list(data)[0])
|
||||
|
||||
def test_resource_list_not_found(self):
|
||||
arglist = ['bad_stack']
|
||||
self.resource_client.list.side_effect = heat_exc.HTTPNotFound
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
|
||||
|
||||
def test_resource_list_with_detail(self):
|
||||
arglist = ['my_stack', '--long']
|
||||
cols = copy.deepcopy(self.columns)
|
||||
cols.append('stack_name')
|
||||
out = copy.deepcopy(self.data)
|
||||
out.append('my_stack')
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.resource_client.list.assert_called_with(
|
||||
'my_stack', with_detail=True, nested_depth=None)
|
||||
self.assertEqual(cols, columns)
|
||||
self.assertEqual(tuple(out), list(data)[0])
|
||||
|
||||
def test_resource_list_nested_depth(self):
|
||||
arglist = ['my_stack', '--nested-depth', '3']
|
||||
cols = copy.deepcopy(self.columns)
|
||||
cols.append('stack_name')
|
||||
out = copy.deepcopy(self.data)
|
||||
out.append('my_stack')
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.resource_client.list.assert_called_with(
|
||||
'my_stack', with_detail=False, nested_depth=3)
|
||||
self.assertEqual(cols, columns)
|
||||
self.assertEqual(tuple(out), list(data)[0])
|
||||
|
||||
def test_resource_list_no_resource_name(self):
|
||||
arglist = ['my_stack']
|
||||
resp = copy.deepcopy(self.response)
|
||||
del resp['resource_name']
|
||||
cols = copy.deepcopy(self.columns)
|
||||
cols[0] = 'logical_resource_id'
|
||||
out = copy.deepcopy(self.data)
|
||||
out[1] = '1234'
|
||||
self.resource_client.list.return_value = [
|
||||
v1_resources.Resource(None, resp)]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.resource_client.list.assert_called_with(
|
||||
'my_stack', with_detail=False, nested_depth=None)
|
||||
self.assertEqual(cols, columns)
|
||||
|
||||
|
||||
class TestResourceMetadata(TestResource):
|
||||
@@ -31,14 +194,13 @@ class TestResourceMetadata(TestResource):
|
||||
def setUp(self):
|
||||
super(TestResourceMetadata, self).setUp()
|
||||
self.cmd = resources.ResourceMetadata(self.app, None)
|
||||
self.mock_client.resources.metadata = mock.Mock(
|
||||
return_value={})
|
||||
self.resource_client.metadata = mock.Mock(return_value={})
|
||||
|
||||
def test_resource_metadata(self):
|
||||
arglist = ['my_stack', 'my_resource']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.mock_client.resources.metadata.assert_called_with(**{
|
||||
self.resource_client.metadata.assert_called_with(**{
|
||||
'stack_id': 'my_stack',
|
||||
'resource_name': 'my_resource'
|
||||
})
|
||||
@@ -47,7 +209,7 @@ class TestResourceMetadata(TestResource):
|
||||
arglist = ['my_stack', 'my_resource', '--format', 'yaml']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.mock_client.resources.metadata.assert_called_with(**{
|
||||
self.resource_client.metadata.assert_called_with(**{
|
||||
'stack_id': 'my_stack',
|
||||
'resource_name': 'my_resource'
|
||||
})
|
||||
@@ -55,6 +217,6 @@ class TestResourceMetadata(TestResource):
|
||||
def test_resource_metadata_error(self):
|
||||
arglist = ['my_stack', 'my_resource']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
self.mock_client.resources.metadata = mock.Mock(
|
||||
self.resource_client.metadata = mock.Mock(
|
||||
side_effect=heat_exc.HTTPNotFound)
|
||||
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
|
||||
|
@@ -50,7 +50,9 @@ openstack.orchestration.v1 =
|
||||
stack_list = heatclient.osc.v1.stack:ListStack
|
||||
stack_output_list = heatclient.osc.v1.stack:OutputListStack
|
||||
stack_output_show = heatclient.osc.v1.stack:OutputShowStack
|
||||
stack_resource_list = heatclient.osc.v1.resources:ResourceList
|
||||
stack_resource_metadata = heatclient.osc.v1.resources:ResourceMetadata
|
||||
stack_resource_show = heatclient.osc.v1.resources:ResourceShow
|
||||
stack_resume = heatclient.osc.v1.stack:ResumeStack
|
||||
stack_show = heatclient.osc.v1.stack:ShowStack
|
||||
stack_snapshot_list = heatclient.osc.v1.snapshot:ListSnapshot
|
||||
|
Reference in New Issue
Block a user