From 7e5a98bca91e0734f3440fdbeb12cd59273cd6a4 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Sun, 4 Dec 2016 00:29:03 +0800 Subject: [PATCH] Add some options to "volume create" command Add "--bootable", "--non-bootable", "--read-only" and "--read-write" options to "volume create" command for setting some attributes at the time of crration. Change-Id: I71b4e9fccb4ee0ab1a90e7179d6d2d34dbbae909 Implements: bp cinder-command-support --- doc/source/command-objects/volume.rst | 18 +++ .../tests/unit/volume/v1/test_volume.py | 136 ++++++++++++++++ .../tests/unit/volume/v2/test_volume.py | 148 ++++++++++++++++++ openstackclient/volume/v1/volume.py | 38 +++++ openstackclient/volume/v2/volume.py | 38 +++++ ...nder-command-support-ff7acc531baae8c3.yaml | 5 + 6 files changed, 383 insertions(+) create mode 100644 releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index df4d68806f..5383386f5b 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -24,6 +24,8 @@ Create new volume [--property [...] ] [--hint [...] ] [--multi-attach] + [--bootable | --non-bootable] + [--read-only | --read-write] .. option:: --size @@ -89,6 +91,22 @@ Create new volume Allow volume to be attached more than once (default to False) +.. option:: --bootable + + Mark volume as bootable + +.. option:: --non-bootable + + Mark volume as non-bootable (default) + +.. option:: --read-only + + Set volume to read-only access mode + +.. option:: --read-write + + Set volume to read-write access mode (default) + .. _volume_create-name: .. describe:: diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 7a44dea85c..6c6d9a1d6f 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -431,6 +431,142 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_create_with_bootable_and_readonly(self): + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + self.new_volume.size, + None, + None, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, True) + self.volumes_mock.update_readonly_flag.assert_called_with( + self.new_volume.id, True) + + def test_volume_create_with_nonbootable_and_readwrite(self): + arglist = [ + '--non-bootable', + '--read-write', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('bootable', False), + ('non_bootable', True), + ('read_only', False), + ('read_write', True), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + self.new_volume.size, + None, + None, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, False) + self.volumes_mock.update_readonly_flag.assert_called_with( + self.new_volume.id, False) + + @mock.patch.object(volume.LOG, 'error') + def test_volume_create_with_bootable_and_readonly_fail( + self, mock_error): + + self.volumes_mock.set_bootable.side_effect = ( + exceptions.CommandError()) + + self.volumes_mock.update_readonly_flag.side_effect = ( + exceptions.CommandError()) + + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + self.new_volume.size, + None, + None, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + + self.assertEqual(2, mock_error.call_count) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, True) + self.volumes_mock.update_readonly_flag.assert_called_with( + self.new_volume.id, True) + def test_volume_create_without_size(self): arglist = [ self.new_volume.display_name, diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 417283425a..1529c2e25b 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -450,6 +450,154 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_create_with_bootable_and_readonly(self): + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=self.new_volume.size, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, True) + self.volumes_mock.update_readonly_flag.assert_called_with( + self.new_volume.id, True) + + def test_volume_create_with_nonbootable_and_readwrite(self): + arglist = [ + '--non-bootable', + '--read-write', + '--size', str(self.new_volume.size), + self.new_volume.name, + ] + verifylist = [ + ('bootable', False), + ('non_bootable', True), + ('read_only', False), + ('read_write', True), + ('size', self.new_volume.size), + ('name', self.new_volume.name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=self.new_volume.size, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, False) + self.volumes_mock.update_readonly_flag.assert_called_with( + self.new_volume.id, False) + + @mock.patch.object(volume.LOG, 'error') + def test_volume_create_with_bootable_and_readonly_fail( + self, mock_error): + + self.volumes_mock.set_bootable.side_effect = ( + exceptions.CommandError()) + + self.volumes_mock.update_readonly_flag.side_effect = ( + exceptions.CommandError()) + + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=self.new_volume.size, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, + ) + + self.assertEqual(2, mock_error.call_count) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, True) + self.volumes_mock.update_readonly_flag.assert_called_with( + self.new_volume.id, True) + def test_volume_create_with_source_replicated(self): self.volumes_mock.get.return_value = self.new_volume arglist = [ diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 0087bad480..739484dfc0 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -114,6 +114,28 @@ class CreateVolume(command.ShowOne): help=_('Set a property on this volume ' '(repeat option to set multiple properties)'), ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable (default)") + ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode (default)") + ) return parser @@ -166,6 +188,22 @@ class CreateVolume(command.ShowOne): parsed_args.property, image, ) + + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + # Map 'metadata' column to 'properties' volume._info.update( { diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 80abfb5501..301bf5e41f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -132,6 +132,28 @@ class CreateVolume(command.ShowOne): help=_("Allow volume to be attached more than once " "(default to False)") ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable (default)") + ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode (default)") + ) return parser def take_action(self, parsed_args): @@ -199,6 +221,22 @@ class CreateVolume(command.ShowOne): multiattach=parsed_args.multi_attach, scheduler_hints=parsed_args.hint, ) + + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + # Remove key links from being displayed volume._info.update( { diff --git a/releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml b/releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml new file mode 100644 index 0000000000..d8126b1e8c --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--bootable``, ``--non-bootable``, ``--read-only`` and ``--read-write`` + options to ``volume create`` command. + [Blueprint `cinder-command-support `_]