diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 0898df528b..4d5651ac90 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -2,7 +2,7 @@ volume type =========== -Volume v1 +Volume v1, v2 volume type create ------------------ @@ -13,9 +13,29 @@ Create new volume type .. code:: bash os volume type create + [--description <description>] + [--public | --private] [--property <key=value> [...] ] <name> +.. option:: --description <description> + + New volume type description + + .. versionadded:: 2 + +.. option:: --public + + Volume type is accessible to the public + + .. versionadded:: 2 + +.. option:: --private + + Volume type is not accessible to the public + + .. versionadded:: 2 + .. option:: --property <key=value> Set a property on this volume type (repeat option to set multiple properties) @@ -57,6 +77,8 @@ List volume types volume type set --------------- +*Only supported for Volume API v1* + Set volume type properties .. program:: volume type set @@ -77,6 +99,8 @@ Set volume type properties volume type unset ----------------- +*Only supported for Volume API v1* + Unset volume type properties .. program:: volume type unset diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 6cc988b2dc..3963496634 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -28,6 +28,135 @@ class TestType(volume_fakes.TestVolume): self.types_mock.reset_mock() +class TestTypeCreate(TestType): + + def setUp(self): + super(TestTypeCreate, self).setUp() + + self.types_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + # Get the command object to test + self.cmd = volume_type.CreateVolumeType(self.app, None) + + def test_type_create_public(self): + arglist = [ + volume_fakes.type_name, + "--description", volume_fakes.type_description, + "--public" + ] + verifylist = [ + ("name", volume_fakes.type_name), + ("description", volume_fakes.type_description), + ("public", True), + ("private", False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + volume_fakes.type_name, + description=volume_fakes.type_description, + public=True, + ) + + collist = ( + 'description', + 'id', + 'name', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.type_description, + volume_fakes.type_id, + volume_fakes.type_name, + ) + self.assertEqual(datalist, data) + + def test_type_create_private(self): + arglist = [ + volume_fakes.type_name, + "--description", volume_fakes.type_description, + "--private" + ] + verifylist = [ + ("name", volume_fakes.type_name), + ("description", volume_fakes.type_description), + ("public", False), + ("private", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + volume_fakes.type_name, + description=volume_fakes.type_description, + private=True, + ) + + collist = ( + 'description', + 'id', + 'name', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.type_description, + volume_fakes.type_id, + volume_fakes.type_name, + ) + self.assertEqual(datalist, data) + + +class TestTypeList(TestType): + def setUp(self): + super(TestTypeList, self).setUp() + + self.types_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + ] + # get the command to test + self.cmd = volume_type.ListVolumeType(self.app, None) + + def test_type_list_without_options(self): + arglist = [] + verifylist = [ + ("long", False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + collist = ["ID", "Name"] + self.assertEqual(collist, columns) + datalist = (( + volume_fakes.type_id, + volume_fakes.type_name, + ),) + self.assertEqual(datalist, tuple(data)) + + def test_type_list_with_options(self): + arglist = ["--long"] + verifylist = [("long", True)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + collist = ["ID", "Name", "Description", "Properties"] + self.assertEqual(collist, columns) + datalist = (( + volume_fakes.type_id, + volume_fakes.type_name, + volume_fakes.type_description, + "foo='bar'" + ),) + self.assertEqual(datalist, tuple(data)) + + class TestTypeShow(TestType): def setUp(self): super(TestTypeShow, self).setUp() diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index ae5cc8b892..c785021f50 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -17,12 +17,79 @@ import logging from cliff import command +from cliff import lister from cliff import show import six +from openstackclient.common import parseractions from openstackclient.common import utils +class CreateVolumeType(show.ShowOne): + """Create new volume type""" + + log = logging.getLogger(__name__ + ".CreateVolumeType") + + def get_parser(self, prog_name): + parser = super(CreateVolumeType, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="<name>", + help="New volume type name" + ) + parser.add_argument( + "--description", + metavar="<description>", + help="New volume type description", + ) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + dest="public", + action="store_true", + default=False, + help="Volume type is accessible to the public", + ) + public_group.add_argument( + "--private", + dest="private", + action="store_true", + default=False, + help="Volume type is not accessible to the public", + ) + parser.add_argument( + '--property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help='Property to add for this volume type' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + volume_client = self.app.client_manager.volume + + kwargs = {} + if parsed_args.public: + kwargs['public'] = True + if parsed_args.private: + kwargs['private'] = True + + volume_type = volume_client.volume_types.create( + parsed_args.name, + description=parsed_args.description, + **kwargs + ) + volume_type._info.pop('extra_specs') + if parsed_args.property: + result = volume_type.set_keys(parsed_args.property) + volume_type._info.update({'properties': utils.format_dict(result)}) + + return zip(*sorted(six.iteritems(volume_type._info))) + + class DeleteVolumeType(command.Command): """Delete volume type""" @@ -46,6 +113,36 @@ class DeleteVolumeType(command.Command): return +class ListVolumeType(lister.Lister): + """List volume types""" + + log = logging.getLogger(__name__ + '.ListVolumeType') + + def get_parser(self, prog_name): + parser = super(ListVolumeType, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + if parsed_args.long: + columns = ['ID', 'Name', 'Description', 'Extra Specs'] + column_headers = ['ID', 'Name', 'Description', 'Properties'] + else: + columns = ['ID', 'Name'] + column_headers = columns + data = self.app.client_manager.volume.volume_types.list() + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Extra Specs': utils.format_dict}, + ) for s in data)) + + class ShowVolumeType(show.ShowOne): """Display volume type details""" diff --git a/setup.cfg b/setup.cfg index 826605738b..d73a09ec9b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -393,7 +393,9 @@ openstack.volume.v2 = volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_show = openstackclient.volume.v2.volume:ShowVolume + volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType + volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType volume_qos_associate = openstackclient.volume.v2.qos_specs:AssociateQos