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:
Jiri Stransky 2013-08-26 17:54:54 +02:00 committed by Petr Blaho
parent 774fa137c8
commit c4c0c1c043
2 changed files with 208 additions and 14 deletions

View 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())

View File

@ -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:
def do_rack_show(tuskar, args, outfile=sys.stdout):
rack = utils.find_resource(tuskar.racks, args.id)
except exc.HTTPNotFound:
raise exc.CommandError("Rack not found: %s" % 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