Adds Flavor CRUD CLI commands
Adds tests for Flavor CRUD CLI commands. Implements: blueprint tripleo-flavors-cli https://blueprints.launchpad.net/python-tuskarclient/+spec/tripleo-flavors-cli Change-Id: If00f281b6287e34773dacd43af849b221a93cd8d
This commit is contained in:
311
tuskarclient/tests/v1/test_flavors_shell.py
Normal file
311
tuskarclient/tests/v1/test_flavors_shell.py
Normal file
@@ -0,0 +1,311 @@
|
||||
# Copyright (c) 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# 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 mock
|
||||
|
||||
import tuskarclient.tests.utils as tutils
|
||||
from tuskarclient.v1 import flavors_shell
|
||||
|
||||
|
||||
class FlavorsShellListTest(tutils.TestCase):
|
||||
|
||||
def empty_args(self):
|
||||
args = mock.Mock(spec=[])
|
||||
for attr in ['id', 'resource_class_id', 'capacities', 'max_vms']:
|
||||
setattr(args, attr, None)
|
||||
return args
|
||||
|
||||
@mock.patch.object(flavors_shell, 'fetch_resource_class')
|
||||
@mock.patch.object(flavors_shell, 'fmt')
|
||||
def test_list_works_if_resource_class_exists(
|
||||
self, mocked_fmt, mocked_fetch_resource_class):
|
||||
tuskar = mock.MagicMock()
|
||||
args = self.empty_args()
|
||||
args.resource_class_id = 1
|
||||
|
||||
flavors_shell.do_flavor_list(tuskar, args)
|
||||
tuskar.flavors.list.assert_called_with(
|
||||
mocked_fetch_resource_class.return_value.id)
|
||||
mocked_fmt.print_list.assert_called_with(
|
||||
tuskar.flavors.list.return_value,
|
||||
mock.ANY, mock.ANY, mock.ANY
|
||||
)
|
||||
|
||||
def test_show_works_with_integer_id(self):
|
||||
self.show_works_with_id({'resource_class_id': 1,
|
||||
'flavor_id': 1})
|
||||
|
||||
def test_show_works_with_char_id(self):
|
||||
self.show_works_with_id({'resource_class_id': 1,
|
||||
'flavor_id': '1'})
|
||||
|
||||
def test_show_works_with_string_id(self):
|
||||
self.show_works_with_id({'resource_class_id': 1,
|
||||
'flavor_id': 'string'})
|
||||
|
||||
@mock.patch.object(flavors_shell, 'fetch_flavor')
|
||||
@mock.patch.object(flavors_shell, 'print_flavor_detail')
|
||||
def show_works_with_id(self,
|
||||
parameters,
|
||||
mocked_print_flavor_detail,
|
||||
mocked_fetch_flavor):
|
||||
flavor_id = parameters.get('flavor_id')
|
||||
resource_class_id = parameters.get('resource_class_id')
|
||||
tuskar = mock.MagicMock()
|
||||
args = self.empty_args()
|
||||
args.id = flavor_id
|
||||
args.resource_class_id = resource_class_id
|
||||
|
||||
flavors_shell.do_flavor_show(tuskar, args)
|
||||
mocked_fetch_flavor.assert_called_with(tuskar,
|
||||
resource_class_id,
|
||||
args.id)
|
||||
mocked_print_flavor_detail.assert_called_with(
|
||||
mocked_fetch_flavor.return_value,
|
||||
)
|
||||
|
||||
def test_delete_works_with_integer_id(self):
|
||||
self.delete_works_with_id({'resource_class_id': 1,
|
||||
'flavor_id': 1})
|
||||
|
||||
def test_delete_works_with_char_id(self):
|
||||
self.delete_works_with_id({'resource_class_id': 1,
|
||||
'flavor_id': '1'})
|
||||
|
||||
def test_delete_works_with_string_id(self):
|
||||
self.delete_works_with_id({'resource_class_id': 1,
|
||||
'flavor_id': 'string'})
|
||||
|
||||
@mock.patch.object(flavors_shell, 'fetch_flavor')
|
||||
def delete_works_with_id(self, parameters, mocked_fetch_flavor):
|
||||
flavor_id = parameters.get('flavor_id')
|
||||
tuskar = mock.MagicMock()
|
||||
args = self.empty_args()
|
||||
args.id = flavor_id
|
||||
|
||||
flavors_shell.do_flavor_delete(tuskar, args)
|
||||
mocked_fetch_flavor.assert_called_with(tuskar, args.id)
|
||||
tuskar.flavors.delete.assert_called_with(args.id)
|
||||
|
||||
def test_create_works_with_name(self):
|
||||
self.create_works_with({'resource_class_id': 1,
|
||||
'name': 'name',
|
||||
'capacities': None,
|
||||
'max_vms': None})
|
||||
|
||||
def test_create_works_with_name_and_capacities(self):
|
||||
self.create_works_with({'resource_class_id': 1,
|
||||
'name': 'name',
|
||||
'capacities':
|
||||
'total_memory:2048:MB,total_cpu:3:CPU',
|
||||
'max_vms': None})
|
||||
|
||||
@mock.patch.object(flavors_shell, 'fetch_resource_class')
|
||||
@mock.patch.object(flavors_shell, 'print_flavor_detail')
|
||||
def create_works_with(
|
||||
self, parameters, mocked_print_flavor_detail,
|
||||
mocked_fetch_resource_class):
|
||||
name = parameters.get('name')
|
||||
resource_class_id = parameters.get('resource_class_id')
|
||||
capacities = parameters.get('capacities')
|
||||
max_vms = parameters.get('max_vms')
|
||||
tuskar = mock.MagicMock()
|
||||
args = self.empty_args()
|
||||
args.name = name
|
||||
args.resource_class_id = resource_class_id
|
||||
args.capacities = capacities
|
||||
args.max_vms = max_vms
|
||||
|
||||
expected_params = {
|
||||
'name': name,
|
||||
'max_vms': max_vms,
|
||||
}
|
||||
|
||||
if capacities is not None:
|
||||
expected_params['capacities'] = \
|
||||
flavors_shell.parse_capacities(capacities)
|
||||
|
||||
mocked_fetch_resource_class.return_value.id = resource_class_id
|
||||
|
||||
flavors_shell.do_flavor_create(tuskar,
|
||||
args)
|
||||
tuskar.flavors.create.assert_called_with(
|
||||
resource_class_id,
|
||||
**expected_params
|
||||
)
|
||||
mocked_print_flavor_detail.assert_called_with(
|
||||
tuskar.flavors.create.return_value
|
||||
)
|
||||
|
||||
def test_update_works_with_integer_id(self):
|
||||
self.update_works({'flavor_id': 1,
|
||||
'resource_class_id': 1,
|
||||
'name': None,
|
||||
'capacities': None,
|
||||
'max_vms': None},
|
||||
{})
|
||||
|
||||
def test_update_works_with_char_id(self):
|
||||
self.update_works({'flavor_id': '1',
|
||||
'resource_class_id': 1,
|
||||
'name': None,
|
||||
'capacities': None,
|
||||
'max_vms': None},
|
||||
{})
|
||||
|
||||
def test_update_works_with_string_id(self):
|
||||
self.update_works({'flavor_id': 'string',
|
||||
'resource_class_id': 1,
|
||||
'name': None,
|
||||
'capacities': None,
|
||||
'max_vms': None},
|
||||
{})
|
||||
|
||||
def test_update_works_with_id_and_name(self):
|
||||
self.update_works({'flavor_id': 1,
|
||||
'resource_class_id': 1,
|
||||
'name': 'name',
|
||||
'capacities': None,
|
||||
'max_vms': None},
|
||||
{'name': 'name'})
|
||||
|
||||
def test_update_works_with_id_and_empty_capacities(self):
|
||||
self.update_works({'flavor_id': 1,
|
||||
'resource_class_id': 1,
|
||||
'name': None,
|
||||
'capacities': '',
|
||||
'max_vms': None},
|
||||
{'capacities': []})
|
||||
|
||||
def test_update_works_with_id_and_capacities(self):
|
||||
self.update_works({'flavor_id': 1,
|
||||
'resource_class_id': 1,
|
||||
'name': None,
|
||||
'capacities':
|
||||
'total_memory:2048:MB,total_cpu:3:CPU',
|
||||
'max_vms': None},
|
||||
{'capacities': [
|
||||
{'unit': 'MB',
|
||||
'name': 'total_memory',
|
||||
'value': '2048'},
|
||||
{'unit': 'CPU',
|
||||
'name': 'total_cpu',
|
||||
'value': '3'}]})
|
||||
|
||||
def test_update_works_with_id_name_and_capacities(self):
|
||||
self.update_works({'flavor_id': 1,
|
||||
'resource_class_id': 1,
|
||||
'name': 'name',
|
||||
'capacities':
|
||||
'total_memory:2048:MB,total_cpu:3:CPU',
|
||||
'max_vms': None},
|
||||
{'name': 'name',
|
||||
'capacities': [
|
||||
{'unit': 'MB',
|
||||
'name': 'total_memory',
|
||||
'value': '2048'},
|
||||
{'unit': 'CPU',
|
||||
'name': 'total_cpu',
|
||||
'value': '3'}]})
|
||||
|
||||
@mock.patch.object(flavors_shell, 'fetch_flavor')
|
||||
@mock.patch.object(flavors_shell, 'print_flavor_detail')
|
||||
def update_works(self,
|
||||
parameters,
|
||||
expected_parameters,
|
||||
mocked_print_flavor_detail,
|
||||
mocked_fetch_flavor):
|
||||
flavor_id = parameters.get('flavor_id')
|
||||
resource_class_id = parameters.get('resource_class_id')
|
||||
name = parameters.get('name')
|
||||
capacities = parameters.get('capacities')
|
||||
max_vms = parameters.get('max_vms')
|
||||
tuskar = mock.MagicMock()
|
||||
args = self.empty_args()
|
||||
args.id = flavor_id
|
||||
args.resource_class_id = resource_class_id
|
||||
args.name = name
|
||||
args.max_vms = max_vms
|
||||
args.capacities = capacities
|
||||
|
||||
flavors_shell.do_flavor_update(tuskar, args)
|
||||
mocked_fetch_flavor.assert_called_with(tuskar,
|
||||
resource_class_id,
|
||||
args.id)
|
||||
tuskar.flavors.update.assert_called_with(
|
||||
resource_class_id,
|
||||
flavor_id,
|
||||
**expected_parameters
|
||||
)
|
||||
mocked_print_flavor_detail.assert_called_with(
|
||||
tuskar.flavors.update.return_value
|
||||
)
|
||||
|
||||
@mock.patch.object(flavors_shell.utils, 'find_resource')
|
||||
def test_fetch_flavor_works(self,
|
||||
mocked_find_resource):
|
||||
tuskar = mock.MagicMock()
|
||||
flavor_id = 1
|
||||
resource_class_id = 1
|
||||
|
||||
return_value = flavors_shell.fetch_flavor(
|
||||
tuskar, resource_class_id, flavor_id)
|
||||
tuskar.flavors.get.assert_called_with(resource_class_id,
|
||||
flavor_id)
|
||||
self.assertEqual(return_value, tuskar.flavors.get.return_value)
|
||||
|
||||
@mock.patch.object(flavors_shell.utils, 'find_resource')
|
||||
def test_fetch_flavor_blows(self,
|
||||
mocked_find_resource):
|
||||
tuskar = mock.MagicMock()
|
||||
flavor_id = 1
|
||||
resource_class_id = 1
|
||||
e = flavors_shell.exc.HTTPNotFound(
|
||||
"Flavor not found: 1")
|
||||
|
||||
mocked_find_resource.side_effect = e
|
||||
tuskar.flavors.get.side_effect = e
|
||||
self.assertRaises(flavors_shell.exc.CommandError,
|
||||
flavors_shell.fetch_flavor,
|
||||
tuskar, resource_class_id, flavor_id)
|
||||
tuskar.flavors.get.assert_called_with(resource_class_id,
|
||||
flavor_id)
|
||||
|
||||
@mock.patch.object(flavors_shell, 'fmt')
|
||||
def test_print_flavor_detail_works(self,
|
||||
mocked_fmt):
|
||||
flavor = mock.MagicMock()
|
||||
|
||||
flavors_shell.print_flavor_detail(flavor)
|
||||
mocked_fmt.print_dict.assert_called_with(
|
||||
flavor.to_dict.return_value,
|
||||
mock.ANY)
|
||||
|
||||
def test_create_flavor_dict_works(self):
|
||||
args = self.empty_args()
|
||||
args.name = 'name-value'
|
||||
args.capacities = 'total_memory:2048:MB,total_cpu:3:CPU'
|
||||
args.other = 'other-value'
|
||||
|
||||
expected_flavor_dict = {'name': 'name-value',
|
||||
'capacities': [{'name': 'total_memory',
|
||||
'value': '2048',
|
||||
'unit': 'MB'},
|
||||
{'name': 'total_cpu',
|
||||
'value': '3',
|
||||
'unit': 'CPU'}]
|
||||
}
|
||||
flavor_dict = flavors_shell.create_flavor_dict(args)
|
||||
self.assertEqual(flavor_dict, expected_flavor_dict)
|
||||
@@ -9,3 +9,154 @@
|
||||
# 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 tuskarclient.common.formatting as fmt
|
||||
from tuskarclient.common import utils
|
||||
from tuskarclient import exc
|
||||
|
||||
|
||||
@utils.arg('resource_class_id', metavar="<RESOURCE CLASS NAME or ID>",
|
||||
help="Name or ID of resource class which flavors to show.")
|
||||
def do_flavor_list(tuskar, args):
|
||||
# TODO(pblaho):
|
||||
# temp. fetch resource_class - API do return 200 OK
|
||||
# when getting list of flavors for non-existing resource class
|
||||
resource_class = fetch_resource_class(tuskar, args.resource_class_id)
|
||||
flavors = tuskar.flavors.list(resource_class.id)
|
||||
fields = ['id', 'name', 'capacities', 'max_vms']
|
||||
labels = {'racks': '# of racks'}
|
||||
formatters = {'racks': len, 'capacities': fmt.capacities_formatter}
|
||||
|
||||
fmt.print_list(flavors, fields, formatters, labels)
|
||||
|
||||
|
||||
@utils.arg('resource_class_id', metavar="<RESOURCE CLASS NAME or ID>",
|
||||
help="Name or ID of resource class associated to.")
|
||||
@utils.arg('id', metavar="<FLAVOR NAME or ID>",
|
||||
help="Name or ID of the flavor to update.")
|
||||
def do_flavor_show(tuskar, args):
|
||||
fetch_resource_class(tuskar, args.resource_class_id)
|
||||
flavor = fetch_flavor(tuskar, args.resource_class_id, args.id)
|
||||
print_flavor_detail(flavor)
|
||||
|
||||
|
||||
@utils.arg('resource_class_id', metavar="<RESOURCE CLASS NAME or ID>",
|
||||
help="Name or ID of resource class associated to.")
|
||||
@utils.arg('name',
|
||||
help="Name of the flavor to create.")
|
||||
@utils.arg('--capacities',
|
||||
help="Capacities of the flavor to create.")
|
||||
@utils.arg('--max-vms',
|
||||
help="Maximum # of VMs of the flavor to create.")
|
||||
def do_flavor_create(tuskar, args):
|
||||
resource_class = fetch_resource_class(tuskar, args.resource_class_id)
|
||||
flavor_dict = {
|
||||
'name': args.name,
|
||||
'max_vms': args.max_vms,
|
||||
}
|
||||
|
||||
if args.capacities is not None:
|
||||
flavor_dict['capacities'] = parse_capacities(args.capacities)
|
||||
|
||||
flavor = tuskar.flavors.create(resource_class.id, **flavor_dict)
|
||||
print_flavor_detail(flavor)
|
||||
|
||||
|
||||
@utils.arg('resource_class_id', metavar="<RESOURCE CLASS NAME or ID>",
|
||||
help="Name or ID of resource class associated to.")
|
||||
@utils.arg('id', metavar="<FLAVOR NAME or ID>",
|
||||
help="Name or ID of the flavor to update.")
|
||||
@utils.arg('--name',
|
||||
help="New name of the flavor to update.")
|
||||
@utils.arg('--capacities',
|
||||
help="Capacities of the flavor to update.")
|
||||
@utils.arg('--max-vms',
|
||||
help="Maximum # of VMs of the flavor to update.")
|
||||
def do_flavor_update(tuskar, args):
|
||||
flavor_dict = create_flavor_dict(args)
|
||||
fetch_resource_class(tuskar, args.resource_class_id)
|
||||
fetch_flavor(tuskar, args.resource_class_id, args.id)
|
||||
|
||||
flavor = tuskar.flavors.update(args.resource_class_id,
|
||||
args.id,
|
||||
**flavor_dict)
|
||||
print_flavor_detail(flavor)
|
||||
|
||||
|
||||
def do_flavor_delete(tuskar, args):
|
||||
fetch_flavor(tuskar, args.id)
|
||||
|
||||
tuskar.flavors.delete(args.id)
|
||||
|
||||
|
||||
def print_flavor_detail(flavor):
|
||||
flavor_dict = flavor.to_dict()
|
||||
formatters = {'links': fmt.links_formatter,
|
||||
'capacities': fmt.capacities_formatter,
|
||||
}
|
||||
fmt.print_dict(flavor_dict, formatters)
|
||||
|
||||
|
||||
def fetch_flavor(tuskar, resource_class_id, flavor_id):
|
||||
try:
|
||||
flavor = tuskar.flavors.get(resource_class_id, flavor_id)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError(
|
||||
"Flavor not found: %s" % flavor_id)
|
||||
|
||||
return flavor
|
||||
|
||||
|
||||
# TODO(pblaho):
|
||||
# temp. fetch resource_class - API do return 200 OK
|
||||
# when getting list of flavors for non-existing resource class
|
||||
def fetch_resource_class(tuskar, resource_class_id):
|
||||
try:
|
||||
resource_class = utils.find_resource(tuskar.resource_classes,
|
||||
resource_class_id)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError(
|
||||
"Resource Class not found: %s" % resource_class_id)
|
||||
|
||||
return resource_class
|
||||
|
||||
|
||||
# TODO(pblaho): duplicate now
|
||||
# refactor after merged both this and https://review.openstack.org/#/c/44281
|
||||
def parse_capacities(capacities_str):
|
||||
'''Take capacities from CLI and parse them into format for API.
|
||||
|
||||
:param capacities_string: string of capacities like
|
||||
'total_cpu:64:CPU,total_memory:1024:MB'
|
||||
:return: array of capacities dicts usable for requests to API
|
||||
'''
|
||||
if capacities_str == '':
|
||||
return []
|
||||
|
||||
capacities = []
|
||||
for capacity_str in capacities_str.split(','):
|
||||
fields = capacity_str.split(':')
|
||||
if len(fields) != 3:
|
||||
raise exc.CommandError(
|
||||
'Capacity info "{0}" should be 3 fields separated by colons. '
|
||||
'(Use commas to separate multiple capacities.)'
|
||||
.format(capacity_str))
|
||||
capacities.append(
|
||||
{'name': fields[0], 'value': fields[1], 'unit': fields[2]})
|
||||
|
||||
return capacities
|
||||
|
||||
|
||||
def create_flavor_dict(args):
|
||||
flavor_dict = {}
|
||||
|
||||
simple_fields = ['name', 'max_vms']
|
||||
for field_name in simple_fields:
|
||||
field_value = vars(args)[field_name]
|
||||
if field_value is not None:
|
||||
flavor_dict[field_name] = field_value
|
||||
|
||||
if args.capacities is not None:
|
||||
flavor_dict['capacities'] = parse_capacities(args.capacities)
|
||||
|
||||
return flavor_dict
|
||||
|
||||
Reference in New Issue
Block a user