Racks CRUD commands
Commands implemented previously and tweaked: rack-show rack-list Newly implemented commands: rack-create rack-update rack-delete We might eventually want to provide a way to set capacities for racks and flavors one by one, but after discussing with multiple people we still don't have obvious correct way to do this, so i'm leaving it out for now and it can be added later. Currently we can set capacities in bulk via --capacities. Partially implements https://blueprints.launchpad.net/python-tuskarclient/+spec/tripleo-racks-cli Change-Id: Ic45f3c157e56b976438c9559245858c831511797
This commit is contained in:
parent
774fa137c8
commit
c4c0c1c043
118
tuskarclient/tests/v1/test_racks_shell.py
Normal file
118
tuskarclient/tests/v1/test_racks_shell.py
Normal file
@ -0,0 +1,118 @@
|
||||
# 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 racks_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_rack():
|
||||
rack = mock.Mock()
|
||||
rack.id = '5'
|
||||
rack.name = 'test_rack'
|
||||
return rack
|
||||
|
||||
|
||||
class RacksShellTest(tutils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.outfile = io.StringIO()
|
||||
self.tuskar = mock.MagicMock()
|
||||
super(RacksShellTest, self).setUp()
|
||||
|
||||
@mock.patch('tuskarclient.common.utils.find_resource')
|
||||
@mock.patch('tuskarclient.v1.racks_shell.print_rack_detail')
|
||||
def test_rack_show(self, mock_print_detail, mock_find_resource):
|
||||
mock_find_resource.return_value = mock_rack()
|
||||
args = empty_args()
|
||||
args.id = '5'
|
||||
|
||||
racks_shell.do_rack_show(self.tuskar, args, outfile=self.outfile)
|
||||
mock_find_resource.assert_called_with(self.tuskar.racks, '5')
|
||||
mock_print_detail.assert_called_with(mock_find_resource.return_value,
|
||||
outfile=self.outfile)
|
||||
|
||||
@mock.patch('tuskarclient.common.formatting.print_list')
|
||||
def test_rack_list(self, mock_print_list):
|
||||
args = empty_args()
|
||||
|
||||
racks_shell.do_rack_list(self.tuskar, args, outfile=self.outfile)
|
||||
# testing the other arguments would be just copy-paste
|
||||
mock_print_list.assert_called_with(
|
||||
self.tuskar.racks.list.return_value, mock.ANY, mock.ANY, mock.ANY,
|
||||
outfile=self.outfile
|
||||
)
|
||||
|
||||
@mock.patch('tuskarclient.common.utils.find_resource')
|
||||
@mock.patch('tuskarclient.v1.racks_shell.print_rack_detail')
|
||||
def test_rack_create(self, mock_print_detail, mock_find_resource):
|
||||
mock_find_resource.return_value = mock_rack()
|
||||
args = empty_args()
|
||||
args.name = 'my_rack'
|
||||
args.subnet = '1.2.3.4/20'
|
||||
args.capacities = 'total_memory:2048:MB,total_cpu:3:CPU'
|
||||
args.slots = '2'
|
||||
args.resource_class = '1'
|
||||
|
||||
racks_shell.do_rack_create(self.tuskar, args, outfile=self.outfile)
|
||||
self.tuskar.racks.create.assert_called_with(
|
||||
name='my_rack',
|
||||
subnet='1.2.3.4/20',
|
||||
capacities=[
|
||||
{'name': 'total_memory', 'value': '2048', 'unit': 'MB'},
|
||||
{'name': 'total_cpu', 'value': '3', 'unit': 'CPU'}],
|
||||
slots='2',
|
||||
resource_class={'id': '1'})
|
||||
mock_print_detail.assert_called_with(
|
||||
self.tuskar.racks.create.return_value, outfile=self.outfile)
|
||||
|
||||
@mock.patch('tuskarclient.common.utils.find_resource')
|
||||
@mock.patch('tuskarclient.v1.racks_shell.print_rack_detail')
|
||||
def test_rack_update(self, mock_print_detail, mock_find_resource):
|
||||
mock_find_resource.return_value = mock_rack()
|
||||
args = empty_args()
|
||||
args.id = '5'
|
||||
args.name = 'my_rack'
|
||||
args.capacities = 'total_memory:2048:MB,total_cpu:3:CPU'
|
||||
args.resource_class = '1'
|
||||
|
||||
racks_shell.do_rack_update(self.tuskar, args, outfile=self.outfile)
|
||||
self.tuskar.racks.update.assert_called_with(
|
||||
'5',
|
||||
name='my_rack',
|
||||
capacities=[
|
||||
{'name': 'total_memory', 'value': '2048', 'unit': 'MB'},
|
||||
{'name': 'total_cpu', 'value': '3', 'unit': 'CPU'}],
|
||||
resource_class={'id': '1'})
|
||||
mock_print_detail.assert_called_with(
|
||||
self.tuskar.racks.update.return_value, outfile=self.outfile)
|
||||
|
||||
@mock.patch('tuskarclient.common.utils.find_resource')
|
||||
def test_rack_delete(self, mock_find_resource):
|
||||
mock_find_resource.return_value = mock_rack()
|
||||
args = empty_args()
|
||||
args.id = '5'
|
||||
|
||||
racks_shell.do_rack_delete(self.tuskar, args, outfile=self.outfile)
|
||||
self.tuskar.racks.delete.assert_called_with('5')
|
||||
self.assertEqual('Deleted rack "test_rack".\n',
|
||||
self.outfile.getvalue())
|
@ -10,18 +10,80 @@
|
||||
# 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
|
||||
from tuskarclient import exc
|
||||
|
||||
|
||||
# TODO(jistr): This is PoC, not final implementation
|
||||
@utils.arg('id', metavar="<NAME or ID>", help="Name or ID of rack to show.")
|
||||
def do_rack_show(tuskar, args):
|
||||
try:
|
||||
rack = utils.find_resource(tuskar.racks, args.id)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError("Rack not found: %s" % args.id)
|
||||
def do_rack_show(tuskar, args, outfile=sys.stdout):
|
||||
rack = utils.find_resource(tuskar.racks, args.id)
|
||||
print_rack_detail(rack, outfile=outfile)
|
||||
|
||||
|
||||
def do_rack_list(tuskar, args, outfile=sys.stdout):
|
||||
racks = tuskar.racks.list()
|
||||
fields = ['id', 'name', 'subnet', 'state', 'nodes']
|
||||
labels = {'nodes': '# of nodes'}
|
||||
formatters = {'nodes': len}
|
||||
fmt.print_list(racks, fields, formatters, labels, outfile=outfile)
|
||||
|
||||
|
||||
@utils.arg('name', help="Name of the rack to create.")
|
||||
@utils.arg('--subnet', required=True,
|
||||
help="Rack's network in IP/CIDR notation.")
|
||||
@utils.arg('--slots', required=True, help="Number of slots in the rack.")
|
||||
@utils.arg('--capacities', help="Total capacities of the rack.")
|
||||
@utils.arg('--resource-class', help="Resource class to assign the rack to.")
|
||||
def do_rack_create(tuskar, args, outfile=sys.stdout):
|
||||
rack_dict = create_rack_dict(args)
|
||||
rack = tuskar.racks.create(**rack_dict)
|
||||
print_rack_detail(rack, outfile=outfile)
|
||||
|
||||
|
||||
@utils.arg('id', metavar="<NAME or ID>", help="Name or ID of rack to show.")
|
||||
@utils.arg('--name', help="Rack's updated name.")
|
||||
@utils.arg('--subnet', help="Rack's network in IP/CIDR notation.")
|
||||
@utils.arg('--capacities', help="Total capacities of the rack.")
|
||||
@utils.arg('--slots', help="Number of slots in the rack.")
|
||||
@utils.arg('--resource-class', help="Resource class to assign the rack to.")
|
||||
def do_rack_update(tuskar, args, outfile=sys.stdout):
|
||||
rack = utils.find_resource(tuskar.racks, args.id)
|
||||
rack_dict = create_rack_dict(args)
|
||||
updated_rack = tuskar.racks.update(rack.id, **rack_dict)
|
||||
print_rack_detail(updated_rack, outfile=outfile)
|
||||
|
||||
|
||||
@utils.arg('id', metavar="<NAME or ID>", help="Name or ID of rack to show.")
|
||||
def do_rack_delete(tuskar, args, outfile=sys.stdout):
|
||||
rack = utils.find_resource(tuskar.racks, args.id)
|
||||
tuskar.racks.delete(args.id)
|
||||
print(u'Deleted rack "%s".' % rack.name, file=outfile)
|
||||
|
||||
|
||||
def create_rack_dict(args):
|
||||
"""Marshal command line arguments to an API request dict."""
|
||||
rack_dict = {}
|
||||
simple_fields = ['name', 'subnet', 'slots']
|
||||
for field_name in simple_fields:
|
||||
field_value = vars(args)[field_name]
|
||||
if field_value is not None:
|
||||
rack_dict[field_name] = field_value
|
||||
|
||||
utils.marshal_association(args, rack_dict, 'resource_class')
|
||||
|
||||
if args.capacities is not None:
|
||||
rack_dict['capacities'] = parse_capacities(args.capacities)
|
||||
|
||||
return rack_dict
|
||||
|
||||
|
||||
def print_rack_detail(rack, outfile=sys.stdout):
|
||||
"""Print detailed rack information (for rack-show etc.)."""
|
||||
formatters = {
|
||||
'capacities': fmt.capacities_formatter,
|
||||
'chassis': fmt.resource_link_formatter,
|
||||
@ -36,14 +98,28 @@ def do_rack_show(tuskar, args):
|
||||
if 'chassis' in rack_dict and not rack_dict['chassis']:
|
||||
del rack_dict['chassis']
|
||||
|
||||
fmt.print_dict(rack_dict, formatters)
|
||||
fmt.print_dict(rack_dict, formatters, outfile=outfile)
|
||||
|
||||
|
||||
# TODO(jistr): This is PoC, not final implementation
|
||||
def do_rack_list(tuskar, args):
|
||||
racks = tuskar.racks.list()
|
||||
fields = ['id', 'name', 'subnet', 'state', 'nodes']
|
||||
labels = {'nodes': '# of nodes'}
|
||||
formatters = {'nodes': len}
|
||||
def parse_capacities(capacities_str):
|
||||
"""Take capacities from CLI and parse them into format for API.
|
||||
|
||||
fmt.print_list(racks, fields, formatters, labels)
|
||||
: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
|
||||
|
Loading…
x
Reference in New Issue
Block a user