Merge "OSC plugin for stack resource show and list"

This commit is contained in:
Jenkins
2016-02-15 10:18:54 +00:00
committed by Gerrit Code Review
4 changed files with 278 additions and 7 deletions

View File

@@ -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"""

View File

@@ -365,7 +365,7 @@ class UpdateStack(show.ShowOne):
class ShowStack(show.ShowOne):
"""Show stack details"""
"""Show stack details."""
log = logging.getLogger(__name__ + ".ShowStack")

View File

@@ -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)

View File

@@ -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