Add CLI support for Overclouds and Overcloud Roles
Add support the full range of CRUD commands for Overclouds and Overcloud Roles in the CLI interface. Change-Id: I34b42674586bff081eb3936cb5934d539510e371
This commit is contained in:
parent
55d9fda3d3
commit
29dca9d6a0
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
)
|
||||
|
@ -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>, --description <DESCRIPTION>',
|
||||
'-i <IMAGE NAME>, --image-name <IMAGE NAME>',
|
||||
'-f <FLAVOR ID>, --flavor-id <FLAVOR ID>'
|
||||
],
|
||||
'out_excludes': [
|
||||
'-i <ID>, --id <ID>',
|
||||
'overcloud-role-list',
|
||||
'overcloud-create',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
],
|
||||
'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> ID of Overcloud Role to show.',
|
||||
],
|
||||
'out_excludes': [
|
||||
'overcloud-role-list',
|
||||
'overcloud-delete',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
'-d <DESCRIPTION>, --description <DESCRIPTION>',
|
||||
'-i <IMAGE NAME>, --image-name <IMAGE NAME>',
|
||||
'-f <FLAVOR ID>, --flavor-id <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 <subcommand>',
|
||||
'-n <NAME>, --name <NAME>',
|
||||
'-d <DESCRIPTION>, --description <DESCRIPTION>',
|
||||
'-i <IMAGE NAME>, --image-name <IMAGE NAME>',
|
||||
'-f <FLAVOR ID>, --flavor-id <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> ID of Overcloud Role to show.',
|
||||
],
|
||||
'out_excludes': [
|
||||
'overcloud-role-list',
|
||||
'overcloud-show',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
],
|
||||
'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>, --name <NAME>',
|
||||
'-d <DESCRIPTION>, --description <DESCRIPTION>',
|
||||
'-i <IMAGE NAME>, --image-name <IMAGE NAME>',
|
||||
'-f <FLAVOR ID>, --flavor-id <FLAVOR ID>'
|
||||
],
|
||||
'out_excludes': [
|
||||
'overcloud-role-list',
|
||||
'overcloud-update',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
],
|
||||
'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>, --id <ID>',
|
||||
'overcloud-list',
|
||||
'overcloud-role-create',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
],
|
||||
'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> ID of Overcloud to show.',
|
||||
],
|
||||
'out_excludes': [
|
||||
'overcloud-list',
|
||||
'overcloud-role-delete',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
'-d <DESCRIPTION>, --description <DESCRIPTION>',
|
||||
'-i <IMAGE NAME>, --image-name <IMAGE NAME>',
|
||||
'-f <FLAVOR ID>, --flavor-id <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 <subcommand>',
|
||||
'-n <NAME>, --name <NAME>',
|
||||
'-d <DESCRIPTION>, --description <DESCRIPTION>',
|
||||
'-i <IMAGE NAME>, --image-name <IMAGE NAME>',
|
||||
'-f <FLAVOR ID>, --flavor-id <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> ID of Overcloud to show.',
|
||||
],
|
||||
'out_excludes': [
|
||||
'overcloud-list',
|
||||
'overcloud-role-show',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
],
|
||||
'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>, --name <NAME>'
|
||||
],
|
||||
'out_excludes': [
|
||||
'overcloud-list',
|
||||
'overcloud-role-update',
|
||||
'--os-username OS_USERNAME',
|
||||
'Display help for <subcommand>',
|
||||
],
|
||||
'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)
|
||||
|
127
tuskarclient/tests/v1/test_overcloud_roles_shell.py
Normal file
127
tuskarclient/tests/v1/test_overcloud_roles_shell.py
Normal file
@ -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())
|
114
tuskarclient/tests/v1/test_overclouds_shell.py
Normal file
114
tuskarclient/tests/v1/test_overclouds_shell.py
Normal file
@ -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())
|
93
tuskarclient/v1/overcloud_roles_shell.py
Normal file
93
tuskarclient/v1/overcloud_roles_shell.py
Normal file
@ -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="<ID>", 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="<DESCRIPTION>",
|
||||
help='User-readable text describing the overcloud.')
|
||||
@utils.arg('-i', '--image-name', metavar="<IMAGE NAME>",
|
||||
help='Name of the image in Glance to be used for this Role.')
|
||||
@utils.arg('-f', '--flavor-id', metavar="<FLAVOR ID>",
|
||||
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="<ID>", help="ID of overcloud to show.")
|
||||
@utils.arg('-n', '--name', metavar="<NAME>",
|
||||
help='Name of the Overcloud Role to update.')
|
||||
@utils.arg('-d', '--description', metavar="<DESCRIPTION>",
|
||||
help='User-readable text describing the overcloud.')
|
||||
@utils.arg('-i', '--image-name', metavar="<IMAGE NAME>",
|
||||
help='Name of the image in Glance to be used for this Role.')
|
||||
@utils.arg('-f', '--flavor-id', metavar="<FLAVOR ID>",
|
||||
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="<ID>", 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)
|
115
tuskarclient/v1/overclouds_shell.py
Normal file
115
tuskarclient/v1/overclouds_shell.py
Normal file
@ -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="<ID>", 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="<DESCRIPTION>",
|
||||
help='User-readable text describing the overcloud.')
|
||||
@utils.arg('-s', '--stack-id', metavar="<STACK ID>",
|
||||
help='UID of the stack in Heat.')
|
||||
@utils.arg('--attributes', metavar='<KEY1=VALUE1;KEY2=VALUE2...>',
|
||||
help='This can be specified multiple times, or once with parameters'
|
||||
' separated by semicolon.',
|
||||
action='append')
|
||||
@utils.arg('--roles', metavar='<ROLE NAME=COUNT;ROLE NAME=COUNT...>',
|
||||
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="<ID>", help="ID of Overcloud to show.")
|
||||
@utils.arg('-n', '--name', metavar="<NAME>",
|
||||
help='Name of the Overcloud Role to update.')
|
||||
@utils.arg('-d', '--description', metavar="<DESCRIPTION>",
|
||||
help='User-readable text describing the overcloud.')
|
||||
@utils.arg('-s', '--stack-id', metavar="<STACK ID>",
|
||||
help='UID of the stack in Heat.')
|
||||
@utils.arg('--attributes', metavar='<KEY1=VALUE1;KEY2=VALUE2...>',
|
||||
help='This can be specified multiple times, or once with parameters'
|
||||
' separated by semicolon.',
|
||||
action='append')
|
||||
@utils.arg('--roles', metavar='<ROLE NAME=COUNT;ROLE NAME=COUNT...>',
|
||||
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="<ID>", 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)
|
@ -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
|
||||
]
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user