diff --git a/tuskarclient/common/formatting.py b/tuskarclient/common/formatting.py index 7cf6d40..7ccf766 100644 --- a/tuskarclient/common/formatting.py +++ b/tuskarclient/common/formatting.py @@ -109,40 +109,22 @@ def attr_proxy(attr, formatter=lambda a: a, allow_undefined=True): return formatter_proxy -def capacities_formatter(capacities): - '''Formats a list of capacities for output. Capacity is a dict - containing 'name', 'value' and 'unit' keys. - ''' - sorted_capacities = sorted(capacities, - key=lambda c: c['name']) - return '\n'.join(['{0}: {1} {2}'.format(c['name'], c['value'], c['unit']) - for c in sorted_capacities]) +def attributes_formatter(attributes): + """Given a simple dict format the keyvalue pairs with one on each line. + """ + return u"\n".join(u"{0}={1}".format(k, v) for k, v in + sorted(attributes.items())) -def links_formatter(links): - '''Formats a list of links. Link is a dict that has 'href' and - 'rel' keys. - ''' - sorted_links = sorted(links, key=lambda l: l['rel']) - return '\n'.join(['{0}: {1}'.format(l['rel'], l['href']) - for l in sorted_links]) +def counts_formatter(counts): + """Given a list of dicts that represent Overcloud Roles output the + Overcloud Role ID with the num_noces + """ + pretty_counts = [] -def resource_links_formatter(links): - '''Formats an array of resource links. Resource link is a dict - with keys 'id' and 'links'. Under 'links' key there is an array of - links. Link is a dict with 'href' and 'rel' keys. Currently we - expect only one link to be in the array, so we print the first - one. (We cannot fetch by 'rel', values in 'rel' are not used - consistently.) - ''' - sorted_links = sorted(links, key=lambda l: l['id']) - return '\n'.join(['{0}: {1}'.format(l['id'], l['links'][0]['href']) - for l in sorted_links]) + for count in counts: + line = "{0}={1}".format(count['overcloud_role_id'], count['num_nodes']) + pretty_counts.append(line) - -def resource_link_formatter(link): - '''Formats one resource link. See docs of - `resource_links_formatter` for more details. - ''' - return resource_links_formatter([link]) + return u"\n".join(pretty_counts) diff --git a/tuskarclient/common/utils.py b/tuskarclient/common/utils.py index 8eadff3..89c5f70 100644 --- a/tuskarclient/common/utils.py +++ b/tuskarclient/common/utils.py @@ -138,3 +138,63 @@ def exit(msg=''): if msg: print(msg, file=sys.stderr) sys.exit(1) + + +def format_attributes(params): + '''Reformat attributes into dict of format expected by the API.''' + + if not params: + return {} + + # expect multiple invocations of --parameters but fall back + # to ; delimited if only one --parameters is specified + if len(params) == 1: + params = params[0].split(';') + + parameters = {} + for p in params: + try: + (n, v) = p.split(('='), 1) + except ValueError: + msg = '%s(%s). %s.' % ('Malformed parameter', p, + 'Use the key=value format') + raise exc.CommandError(msg) + + if n not in parameters: + parameters[n] = v + else: + if not isinstance(parameters[n], list): + parameters[n] = [parameters[n]] + parameters[n].append(v) + + return parameters + + +def format_roles(params): + '''Reformat attributes into dict of format expected by the API.''' + + if not params: + return [] + + # expect multiple invocations of --parameters but fall back + # to ; delimited if only one --parameters is specified + if len(params) == 1: + params = params[0].split(';') + + parameters = [] + for p in params: + try: + (n, v) = p.split(('='), 1) + except ValueError: + msg = '%s(%s). %s.' % ('Malformed parameter', p, + 'Use the key=value format') + raise exc.CommandError(msg) + + v = int(v) + + parameters.append({ + 'overcloud_role_id': n, + 'num_nodes': v + }) + + return parameters diff --git a/tuskarclient/tests/common/test_formatting.py b/tuskarclient/tests/common/test_formatting.py index 7b7ae44..d315c6e 100644 --- a/tuskarclient/tests/common/test_formatting.py +++ b/tuskarclient/tests/common/test_formatting.py @@ -75,57 +75,27 @@ class PrintTest(tutils.TestCase): class FormattersTest(tutils.TestCase): - def test_attr_formatter_plain(self): - obj = mock.Mock() - obj.foo = 'bar' - foo_formatter = fmt.attr_proxy('foo') - self.assertEqual('bar', foo_formatter(obj)) + def test_attributes_formatter(self): + """Test the attributes formatter displays the attributes correctly.""" - def test_attr_formatter_chained(self): - obj = mock.Mock() - obj.letters = ['a', 'b', 'c'] - letters_formatter = fmt.attr_proxy('letters', len) - self.assertEqual(3, letters_formatter(obj)) - - def test_capacities_formatter(self): - capacities = [ - {'name': 'memory', 'value': '1024', 'unit': 'MB'}, - {'name': 'cpu', 'value': '2', 'unit': 'CPU'}, - ] - self.assertEqual( - ('cpu: 2 CPU\n' - 'memory: 1024 MB'), - fmt.capacities_formatter(capacities), - ) - - def test_links_formatter(self): - links = [ - {'rel': 'self', 'href': 'http://self-url'}, - {'rel': 'parent', 'href': 'http://parent-url'}, - ] - self.assertEqual( - ('parent: http://parent-url\n' - 'self: http://self-url'), - fmt.links_formatter(links), - ) - - def test_resource_links_formatter(self): - resource_links = [ - {'id': 3, 'links': [{'rel': 'self', 'href': 'http://three'}]}, - {'id': 5, 'links': [{'rel': 'self', 'href': 'http://five'}]}, - ] - self.assertEqual( - ('3: http://three\n' - '5: http://five'), - fmt.resource_links_formatter(resource_links), - ) - - def test_resource_link_formatter(self): - resource_link = { - 'id': 3, - 'links': [{'rel': 'self', 'href': 'http://three'}] + attributes = { + 'password': 'pass', + 'mysql_host': 'http://somewhere', + 'a thing': 'a value' } self.assertEqual( - ('3: http://three'), - fmt.resource_link_formatter(resource_link), + ("a thing=a value\nmysql_host=http://somewhere\npassword=pass"), + fmt.attributes_formatter(attributes), + ) + + def test_counts_formatter(self): + + resource_link = [ + {'overcloud_role_id': 1, 'num_nodes': 10}, + {'overcloud_role_id': 2, 'num_nodes': 20} + ] + + self.assertEqual( + ("1=10\n2=20"), + fmt.counts_formatter(resource_link), ) diff --git a/tuskarclient/tests/integration/test_help_command.py b/tuskarclient/tests/integration/test_help_command.py index 2a0933f..4dbb641 100644 --- a/tuskarclient/tests/integration/test_help_command.py +++ b/tuskarclient/tests/integration/test_help_command.py @@ -59,6 +59,224 @@ tests = [ 'error: unrecognized arguments: -r', ], 'return_code': 2, + }, + # Overcloud Role - Create + { + 'commands': ['overcloud-role-create -h', + 'overcloud-role-create --help', + 'help overcloud-role-create'], + 'test_identifiers': ['test_overcloud_role_create_dash_h', + 'test_overcloud_role_create_dashdash_help', + 'test_help_overcloud_role_create'], + 'out_includes': [ + '-d , --description ', + '-i , --image-name ', + '-f , --flavor-id ' + ], + 'out_excludes': [ + '-i , --id ', + 'overcloud-role-list', + 'overcloud-create', + '--os-username OS_USERNAME', + 'Display help for ', + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud Role - Delete + { + 'commands': ['overcloud-role-delete -h', + 'overcloud-role-delete --help', + 'help overcloud-role-delete'], + 'test_identifiers': ['test_overcloud_role_delete_dash_h', + 'test_overcloud_role_delete_dashdash_help', + 'test_help_overcloud_role_delete'], + 'out_includes': [ + ' ID of Overcloud Role to show.', + ], + 'out_excludes': [ + 'overcloud-role-list', + 'overcloud-delete', + '--os-username OS_USERNAME', + 'Display help for ', + '-d , --description ', + '-i , --image-name ', + '-f , --flavor-id ' + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud Role - List + { + 'commands': ['overcloud-role-list -h', + 'overcloud-role-list --help', + 'help overcloud-role-list'], + 'test_identifiers': ['test_overcloud_role_list_dash_h', + 'test_overcloud_role_list_dashdash_help', + 'test_help_overcloud_role_list'], + 'out_includes': [ + ], + 'out_excludes': [ + 'overcloud-role-delete', + 'overcloud-list', + '--os-username OS_USERNAME', + 'Display help for ', + '-n , --name ', + '-d , --description ', + '-i , --image-name ', + '-f , --flavor-id ' + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud Role - Show + { + 'commands': ['overcloud-role-show -h', + 'overcloud-role-show --help', + 'help overcloud-role-show'], + 'test_identifiers': ['test_overcloud_role_show_dash_h', + 'test_overcloud_role_show_dashdash_help', + 'test_help_overcloud_role_show'], + 'out_includes': [ + ' ID of Overcloud Role to show.', + ], + 'out_excludes': [ + 'overcloud-role-list', + 'overcloud-show', + '--os-username OS_USERNAME', + 'Display help for ', + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud Role - Update + { + 'commands': ['overcloud-role-update -h', + 'overcloud-role-update --help', + 'help overcloud-role-update'], + 'test_identifiers': ['test_overcloud_role_update_dash_h', + 'test_overcloud_role_update_dashdash_help', + 'test_help_overcloud_role_update'], + 'out_includes': [ + '-n , --name ', + '-d , --description ', + '-i , --image-name ', + '-f , --flavor-id ' + ], + 'out_excludes': [ + 'overcloud-role-list', + 'overcloud-update', + '--os-username OS_USERNAME', + 'Display help for ', + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud - Create + { + 'commands': ['overcloud-create -h', + 'overcloud-create --help', + 'help overcloud-create'], + 'test_identifiers': ['test_overcloud_create_dash_h', + 'test_overcloud_create_dashdash_help', + 'test_help_overcloud_create'], + 'out_includes': [ + ], + 'out_excludes': [ + '-i , --id ', + 'overcloud-list', + 'overcloud-role-create', + '--os-username OS_USERNAME', + 'Display help for ', + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud - Delete + { + 'commands': ['overcloud-delete -h', + 'overcloud-delete --help', + 'help overcloud-delete'], + 'test_identifiers': ['test_overcloud_delete_dash_h', + 'test_overcloud_delete_dashdash_help', + 'test_help_overcloud_delete'], + 'out_includes': [ + ' ID of Overcloud to show.', + ], + 'out_excludes': [ + 'overcloud-list', + 'overcloud-role-delete', + '--os-username OS_USERNAME', + 'Display help for ', + '-d , --description ', + '-i , --image-name ', + '-f , --flavor-id ' + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud - List + { + 'commands': ['overcloud-list -h', + 'overcloud-list --help', + 'help overcloud-list'], + 'test_identifiers': ['test_overcloud_list_dash_h', + 'test_overcloud_list_dashdash_help', + 'test_help_overcloud_list'], + 'out_includes': [ + ], + 'out_excludes': [ + 'overcloud-delete', + 'overcloud-role-list', + '--os-username OS_USERNAME', + 'Display help for ', + '-n , --name ', + '-d , --description ', + '-i , --image-name ', + '-f , --flavor-id ' + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud - Show + { + 'commands': ['overcloud-show -h', + 'overcloud-show --help', + 'help overcloud-show'], + 'test_identifiers': ['test_overcloud_show_dash_h', + 'test_overcloud_show_dashdash_help', + 'test_help_overcloud_show'], + 'out_includes': [ + ' ID of Overcloud to show.', + ], + 'out_excludes': [ + 'overcloud-list', + 'overcloud-role-show', + '--os-username OS_USERNAME', + 'Display help for ', + ], + 'err_string': '', + 'return_code': 0, + }, + # Overcloud - Update + { + 'commands': ['overcloud-update -h', + 'overcloud-update --help', + 'help overcloud-update'], + 'test_identifiers': ['test_overcloud_update_dash_h', + 'test_overcloud_update_dashdash_help', + 'test_help_overcloud_update'], + 'out_includes': [ + '-n , --name ' + ], + 'out_excludes': [ + 'overcloud-list', + 'overcloud-role-update', + '--os-username OS_USERNAME', + 'Display help for ', + ], + 'err_string': '', + 'return_code': 0, } ] @@ -77,16 +295,33 @@ def create_test_method(command, expected_values): )) return test_command_method -# creates a method for each command found in tests -# to let developer see what test is failing in test results, -# ie: ... HelpCommandTest.test_help_flavor_list -# this way dev can "just search" for "test_help_flavor_list" -# and he will find actual data used in failing test +# Create a method for each command found in the above tests. The tests will be +# constructed on the HelpCommandTest class with the name given in the test +# identifiers. This way the developer can search the above structure for the +# identifier and find the actual data used in the test. +duplicated = [] + for test in tests: commands = test.get('commands') for index, command in enumerate(commands): test_command_method = create_test_method(command, test) test_command_method.__name__ = test.get('test_identifiers')[index] + + if hasattr(HelpCommandTest, test_command_method.__name__): + duplicated.append(test_command_method.__name__) + setattr(HelpCommandTest, test_command_method.__name__, test_command_method) + + +# Finally add a meta test to verify that no test identifiers were used twice +# which would result in only the last test being added. +def _meta_verify_test_builder(self): + self.assertEqual( + duplicated, [], "Expected no test identifiers to be " + "duplicated but found {0}".format(len(duplicated)) + ) + +setattr(HelpCommandTest, 'test_test_builder_for_duplicates', + _meta_verify_test_builder) diff --git a/tuskarclient/tests/v1/test_overcloud_roles_shell.py b/tuskarclient/tests/v1/test_overcloud_roles_shell.py new file mode 100644 index 0000000..3792d6d --- /dev/null +++ b/tuskarclient/tests/v1/test_overcloud_roles_shell.py @@ -0,0 +1,127 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import io +import mock + +import tuskarclient.tests.utils as tutils +from tuskarclient.v1 import overcloud_roles_shell + + +def empty_args(): + args = mock.Mock(spec=[]) + for attr in ['id', 'name', 'subnet', 'capacities', 'slots', + 'resource_class']: + setattr(args, attr, None) + return args + + +def mock_overcloud(): + overcloud = mock.Mock() + overcloud.id = '5' + overcloud.name = 'Testing Overcloud Role' + return overcloud + + +class RacksShellTest(tutils.TestCase): + + def setUp(self): + + self.outfile = io.StringIO() + self.tuskar = mock.MagicMock() + self.shell = overcloud_roles_shell + super(RacksShellTest, self).setUp() + + @mock.patch('tuskarclient.common.utils.find_resource') + @mock.patch('tuskarclient.v1.overcloud_roles_shell.print_role_detail') + def test_overcloud_role_show(self, mock_print_detail, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.id = '5' + + self.shell.do_overcloud_role_show(self.tuskar, args, + outfile=self.outfile) + mock_find_resource.assert_called_with(self.tuskar.overcloud_roles, '5') + mock_print_detail.assert_called_with(mock_find_resource.return_value, + outfile=self.outfile) + + @mock.patch('tuskarclient.common.formatting.print_list') + def test_overcloud_role_list(self, mock_print_list): + args = empty_args() + + self.shell.do_overcloud_role_list(self.tuskar, args, + outfile=self.outfile) + # testing the other arguments would be just copy-paste + mock_print_list.assert_called_with( + self.tuskar.overcloud_roles.list.return_value, mock.ANY, + outfile=self.outfile + ) + + @mock.patch('tuskarclient.common.utils.find_resource') + @mock.patch('tuskarclient.v1.overcloud_roles_shell.print_role_detail') + def test_overcloud_role_create(self, mock_print, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.name = 'My Overcloud Role' + args.description = 'This is an Overcloud Role.' + args.image_name = 'image' + args.flavor_id = '1' + + self.shell.do_overcloud_role_create(self.tuskar, args, + outfile=self.outfile) + self.tuskar.overcloud_roles.create.assert_called_with( + name='My Overcloud Role', + flavor_id='1', + description='This is an Overcloud Role.', + image_name='image' + ) + mock_print.assert_called_with( + self.tuskar.overcloud_roles.create.return_value, + outfile=self.outfile + ) + + @mock.patch('tuskarclient.common.utils.find_resource') + @mock.patch('tuskarclient.v1.overcloud_roles_shell.print_role_detail') + def test_overcloud_role_update(self, mock_print, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.id = '5' + args.name = 'My Overcloud Role' + args.description = 'This is an Overcloud Role.' + args.image_name = 'image' + args.flavor_id = '1' + + self.shell.do_overcloud_role_update(self.tuskar, args, + outfile=self.outfile) + self.tuskar.overcloud_roles.update.assert_called_with( + '5', + name='My Overcloud Role', + flavor_id='1', + description='This is an Overcloud Role.', + image_name='image' + ) + mock_print.assert_called_with( + self.tuskar.overcloud_roles.update.return_value, + outfile=self.outfile + ) + + @mock.patch('tuskarclient.common.utils.find_resource') + def test_overcloud_role_delete(self, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.id = '5' + + self.shell.do_overcloud_role_delete(self.tuskar, args, + outfile=self.outfile) + self.tuskar.overcloud_roles.delete.assert_called_with('5') + self.assertEqual('Deleted Overcloud Role "Testing Overcloud Role".\n', + self.outfile.getvalue()) diff --git a/tuskarclient/tests/v1/test_overclouds_shell.py b/tuskarclient/tests/v1/test_overclouds_shell.py new file mode 100644 index 0000000..e6c7aa0 --- /dev/null +++ b/tuskarclient/tests/v1/test_overclouds_shell.py @@ -0,0 +1,114 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import io +import mock + +import tuskarclient.tests.utils as tutils +from tuskarclient.v1 import overclouds_shell + + +def empty_args(): + args = mock.Mock(spec=[]) + for attr in ['id', 'name', 'subnet', 'capacities', 'slots', + 'resource_class']: + setattr(args, attr, None) + return args + + +def mock_overcloud(): + overcloud = mock.Mock() + overcloud.id = '5' + overcloud.name = 'My Overcloud' + return overcloud + + +class RacksShellTest(tutils.TestCase): + + def setUp(self): + + self.outfile = io.StringIO() + self.tuskar = mock.MagicMock() + self.shell = overclouds_shell + super(RacksShellTest, self).setUp() + + @mock.patch('tuskarclient.common.utils.find_resource') + @mock.patch('tuskarclient.v1.overclouds_shell.print_overcloud_detail') + def test_overcloud_show(self, mock_print_detail, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.id = '5' + + self.shell.do_overcloud_show(self.tuskar, args, outfile=self.outfile) + mock_find_resource.assert_called_with(self.tuskar.overclouds, '5') + mock_print_detail.assert_called_with(mock_find_resource.return_value, + outfile=self.outfile) + + @mock.patch('tuskarclient.common.formatting.print_list') + def test_overcloud_list(self, mock_print_list): + args = empty_args() + + self.shell.do_overcloud_list(self.tuskar, args, outfile=self.outfile) + # testing the other arguments would be just copy-paste + mock_print_list.assert_called_with( + self.tuskar.overclouds.list.return_value, mock.ANY, mock.ANY, + outfile=self.outfile + ) + + @mock.patch('tuskarclient.common.utils.find_resource') + @mock.patch('tuskarclient.v1.overclouds_shell.print_overcloud_detail') + def test_overcloud_create(self, mock_print_detail, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.name = 'my_overcloud' + args.attributes = None + args.roles = None + + self.shell.do_overcloud_create(self.tuskar, args, outfile=self.outfile) + self.tuskar.overclouds.create.assert_called_with( + name='my_overcloud', + counts=[], + attributes={} + ) + mock_print_detail.assert_called_with( + self.tuskar.overclouds.create.return_value, outfile=self.outfile) + + @mock.patch('tuskarclient.common.utils.find_resource') + @mock.patch('tuskarclient.v1.overclouds_shell.print_overcloud_detail') + def test_overcloud_update(self, mock_print_detail, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.id = '5' + args.name = 'my_overcloud' + args.attributes = None + args.roles = None + + self.shell.do_overcloud_update(self.tuskar, args, outfile=self.outfile) + self.tuskar.overclouds.update.assert_called_with( + '5', + name='my_overcloud', + attributes={}, + counts=[] + ) + mock_print_detail.assert_called_with( + self.tuskar.overclouds.update.return_value, outfile=self.outfile) + + @mock.patch('tuskarclient.common.utils.find_resource') + def test_overcloud_delete(self, mock_find_resource): + mock_find_resource.return_value = mock_overcloud() + args = empty_args() + args.id = '5' + + self.shell.do_overcloud_delete(self.tuskar, args, outfile=self.outfile) + self.tuskar.overclouds.delete.assert_called_with('5') + self.assertEqual('Deleted Overcloud "My Overcloud".\n', + self.outfile.getvalue()) diff --git a/tuskarclient/v1/overcloud_roles_shell.py b/tuskarclient/v1/overcloud_roles_shell.py new file mode 100644 index 0000000..44ad1b0 --- /dev/null +++ b/tuskarclient/v1/overcloud_roles_shell.py @@ -0,0 +1,93 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import print_function + +import sys + +import tuskarclient.common.formatting as fmt +from tuskarclient.common import utils + + +@utils.arg('id', metavar="", help="ID of Overcloud Role to show.") +def do_overcloud_role_show(tuskar, args, outfile=sys.stdout): + """Given a Tuskar client instance and the command line arguments display + the detail to the user. + """ + overcloud_role = utils.find_resource(tuskar.overcloud_roles, args.id) + print_role_detail(overcloud_role, outfile=outfile) + + +def do_overcloud_role_list(tuskar, args, outfile=sys.stdout): + overcloud_roles = tuskar.overcloud_roles.list() + fields = ['id', 'name', 'image_name', 'flavor_id'] + fmt.print_list(overcloud_roles, fields, outfile=outfile) + + +@utils.arg('name', help="Name of the Overcloud Role to create.") +@utils.arg('-d', '--description', metavar="", + help='User-readable text describing the overcloud.') +@utils.arg('-i', '--image-name', metavar="", + help='Name of the image in Glance to be used for this Role.') +@utils.arg('-f', '--flavor-id', metavar="", + help='UUID of the flavor of node this role should be deployed on.') +def do_overcloud_role_create(tuskar, args, outfile=sys.stdout): + overcloud_role_dict = create_overcloud_role_dict(args) + overcloud_role = tuskar.overcloud_roles.create(**overcloud_role_dict) + print_role_detail(overcloud_role, outfile=outfile) + + +@utils.arg('id', metavar="", help="ID of overcloud to show.") +@utils.arg('-n', '--name', metavar="", + help='Name of the Overcloud Role to update.') +@utils.arg('-d', '--description', metavar="", + help='User-readable text describing the overcloud.') +@utils.arg('-i', '--image-name', metavar="", + help='Name of the image in Glance to be used for this Role.') +@utils.arg('-f', '--flavor-id', metavar="", + help='UUID of the flavor of node this role should be deployed on.') +def do_overcloud_role_update(tuskar, args, outfile=sys.stdout): + overcloud_role = utils.find_resource(tuskar.overcloud_roles, args.id) + overcloud_role_dict = create_overcloud_role_dict(args) + updated_overcloud_role = tuskar.overcloud_roles.update( + overcloud_role.id, + **overcloud_role_dict + ) + print_role_detail(updated_overcloud_role, outfile=outfile) + + +@utils.arg('id', metavar="", help="ID of Overcloud Role to show.") +def do_overcloud_role_delete(tuskar, args, outfile=sys.stdout): + overcloud_role = utils.find_resource(tuskar.overcloud_roles, args.id) + tuskar.overcloud_roles.delete(args.id) + print(u'Deleted Overcloud Role "%s".' % overcloud_role.name, file=outfile) + + +def create_overcloud_role_dict(args): + """Marshal command line arguments to an API request dict.""" + overcloud_role_dict = {} + simple_fields = ['name', 'description', 'image_name', 'flavor_id'] + for field_name in simple_fields: + field_value = vars(args)[field_name] + if field_value is not None: + overcloud_role_dict[field_name] = field_value + + utils.marshal_association(args, overcloud_role_dict, 'resource_class') + + return overcloud_role_dict + + +def print_role_detail(overcloud_role, outfile=sys.stdout): + """Print detailed Overcloud Role information (overcloud-role-show etc.).""" + + overcloud_role_dict = overcloud_role.to_dict() + fmt.print_dict(overcloud_role_dict, outfile=outfile) diff --git a/tuskarclient/v1/overclouds_shell.py b/tuskarclient/v1/overclouds_shell.py new file mode 100644 index 0000000..0d5ff7c --- /dev/null +++ b/tuskarclient/v1/overclouds_shell.py @@ -0,0 +1,115 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import print_function + +import sys + +import tuskarclient.common.formatting as fmt +from tuskarclient.common import utils + + +@utils.arg('id', metavar="", help="ID of Overcloud to show.") +def do_overcloud_show(tuskar, args, outfile=sys.stdout): + """Given a Tuskar client instance and the command line arguments display + the detail to the user. + """ + overcloud = utils.find_resource(tuskar.overclouds, args.id) + print_overcloud_detail(overcloud, outfile=outfile) + + +def do_overcloud_list(tuskar, args, outfile=sys.stdout): + overclouds = tuskar.overclouds.list() + fields = ['id', 'name', 'description', 'stack_id', 'attributes', 'counts'] + + formatters = { + 'attributes': fmt.attributes_formatter, + 'counts': fmt.counts_formatter, + } + + fmt.print_list(overclouds, fields, formatters, outfile=outfile) + + +@utils.arg('name', help="Name of the Overcloud to create.") +@utils.arg('-d', '--description', metavar="", + help='User-readable text describing the overcloud.') +@utils.arg('-s', '--stack-id', metavar="", + help='UID of the stack in Heat.') +@utils.arg('--attributes', metavar='', + help='This can be specified multiple times, or once with parameters' + ' separated by semicolon.', + action='append') +@utils.arg('--roles', metavar='', + help='This can be specified multiple times, or once with parameters' + ' separated by semicolon.', + action='append') +def do_overcloud_create(tuskar, args, outfile=sys.stdout): + overcloud_dict = create_overcloud_dict(args) + overcloud = tuskar.overclouds.create(**overcloud_dict) + print_overcloud_detail(overcloud, outfile=outfile) + + +@utils.arg('id', metavar="", help="ID of Overcloud to show.") +@utils.arg('-n', '--name', metavar="", + help='Name of the Overcloud Role to update.') +@utils.arg('-d', '--description', metavar="", + help='User-readable text describing the overcloud.') +@utils.arg('-s', '--stack-id', metavar="", + help='UID of the stack in Heat.') +@utils.arg('--attributes', metavar='', + help='This can be specified multiple times, or once with parameters' + ' separated by semicolon.', + action='append') +@utils.arg('--roles', metavar='', + help='This can be specified multiple times, or once with parameters' + ' separated by semicolon.', + action='append') +def do_overcloud_update(tuskar, args, outfile=sys.stdout): + overcloud = utils.find_resource(tuskar.overclouds, args.id) + overcloud_dict = create_overcloud_dict(args) + updated_overcloud = tuskar.overclouds.update(overcloud.id, + **overcloud_dict) + print_overcloud_detail(updated_overcloud, outfile=outfile) + + +@utils.arg('id', metavar="", help="ID of Overcloud to show.") +def do_overcloud_delete(tuskar, args, outfile=sys.stdout): + overcloud = utils.find_resource(tuskar.overclouds, args.id) + tuskar.overclouds.delete(args.id) + print(u'Deleted Overcloud "%s".' % overcloud.name, file=outfile) + + +def create_overcloud_dict(args): + """Marshal command line arguments to an API request dict.""" + overcloud_dict = {} + simple_fields = ['name', 'description'] + for field_name in simple_fields: + field_value = vars(args).get(field_name) + if field_value is not None: + overcloud_dict[field_name] = field_value + + overcloud_dict['attributes'] = utils.format_attributes(args.attributes) + overcloud_dict['counts'] = utils.format_roles(args.roles) + + utils.marshal_association(args, overcloud_dict, 'resource_class') + return overcloud_dict + + +def print_overcloud_detail(overcloud, outfile=sys.stdout): + """Print detailed overcloud information (for overcloud-show etc.).""" + + formatters = { + 'attributes': fmt.attributes_formatter, + 'counts': fmt.counts_formatter, + } + overcloud_dict = overcloud.to_dict() + fmt.print_dict(overcloud_dict, formatters, outfile=outfile) diff --git a/tuskarclient/v1/shell.py b/tuskarclient/v1/shell.py index bd7e2d3..ba8de8d 100644 --- a/tuskarclient/v1/shell.py +++ b/tuskarclient/v1/shell.py @@ -11,8 +11,12 @@ # under the License. from tuskarclient.common import utils +from tuskarclient.v1 import overcloud_roles_shell +from tuskarclient.v1 import overclouds_shell COMMAND_MODULES = [ + overcloud_roles_shell, + overclouds_shell ]