diff options
-rw-r--r-- | openstackclient/compute/v2/server.py | 21 | ||||
-rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server.py | 14 | ||||
-rw-r--r-- | openstackclient/tests/unit/volume/v1/test_volume_backup.py | 52 | ||||
-rw-r--r-- | openstackclient/tests/unit/volume/v2/test_volume_backup.py | 74 | ||||
-rw-r--r-- | openstackclient/volume/v1/volume_backup.py | 19 | ||||
-rw-r--r-- | openstackclient/volume/v2/volume_backup.py | 44 | ||||
-rw-r--r-- | releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml | 16 | ||||
-rw-r--r-- | releasenotes/notes/bug-1597189-02a8d8a402725860.yaml | 10 |
8 files changed, 224 insertions, 26 deletions
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 436c2597..b02ee6ff 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2458,6 +2458,27 @@ class ListServer(command.Lister): if c in ('Security Groups', 'security_groups'): columns += ('security_groups_name',) column_headers += ('Security Groups',) + if c in ("Task State", "task_state"): + columns += ('OS-EXT-STS:task_state',) + column_headers += ('Task State',) + if c in ("Power State", "power_state"): + columns += ('OS-EXT-STS:power_state',) + column_headers += ('Power State',) + if c in ("Image ID", "image_id"): + columns += ('Image ID',) + column_headers += ('Image ID',) + if c in ("Flavor ID", "flavor_id"): + columns += ('Flavor ID',) + column_headers += ('Flavor ID',) + if c in ('Availability Zone', "availability_zone"): + columns += ('OS-EXT-AZ:availability_zone',) + column_headers += ('Availability Zone',) + if c in ('Host', "host"): + columns += ('OS-EXT-SRV-ATTR:host',) + column_headers += ('Host',) + if c in ('Properties', "properties"): + columns += ('Metadata',) + column_headers += ('Properties',) # convert back to tuple column_headers = tuple(column_headers) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 19e90a43..004f3a05 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4486,6 +4486,13 @@ class TestServerList(_TestServerList): '-c', 'User ID', '-c', 'Created At', '-c', 'Security Groups', + '-c', 'Task State', + '-c', 'Power State', + '-c', 'Image ID', + '-c', 'Flavor ID', + '-c', 'Availability Zone', + '-c', 'Host', + '-c', 'Properties', '--long' ] verifylist = [ @@ -4500,6 +4507,13 @@ class TestServerList(_TestServerList): self.assertIn('User ID', columns) self.assertIn('Created At', columns) self.assertIn('Security Groups', columns) + self.assertIn('Task State', columns) + self.assertIn('Power State', columns) + self.assertIn('Image ID', columns) + self.assertIn('Flavor ID', columns) + self.assertIn('Availability Zone', columns) + self.assertIn('Host', columns) + self.assertIn('Properties', columns) def test_server_list_no_name_lookup_option(self): self.data = tuple( diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index f25a5ffa..2f568929 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -319,29 +319,69 @@ class TestBackupRestore(TestBackup): attrs={'volume_id': volume.id}) def setUp(self): - super(TestBackupRestore, self).setUp() + super().setUp() self.backups_mock.get.return_value = self.backup self.volumes_mock.get.return_value = self.volume - self.restores_mock.restore.return_value = None + self.restores_mock.restore.return_value = ( + volume_fakes.FakeVolume.create_one_volume( + {'id': self.volume['id']}, + ) + ) # Get the command object to mock self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): arglist = [ self.backup.id, - self.backup.volume_id ] verifylist = [ ("backup", self.backup.id), - ("volume", self.backup.volume_id) + ("volume", None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.restores_mock.restore.assert_called_with(self.backup.id, - self.backup.volume_id) - self.assertIsNone(result) + None) + self.assertIsNotNone(result) + + def test_backup_restore_with_existing_volume(self): + arglist = [ + self.backup.id, + self.backup.volume_id, + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", self.backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with( + self.backup.id, self.backup.volume_id, + ) + self.assertIsNotNone(result) + + def test_backup_restore_with_invalid_volume(self): + arglist = [ + self.backup.id, + "unexist_volume", + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", "unexist_volume"), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + with mock.patch.object( + utils, 'find_resource', + side_effect=exceptions.CommandError(), + ): + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) class TestBackupShow(TestBackup): diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 4b9212d0..ffd84901 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -458,35 +458,95 @@ class TestBackupRestore(TestBackup): volume = volume_fakes.FakeVolume.create_one_volume() backup = volume_fakes.FakeBackup.create_one_backup( - attrs={'volume_id': volume.id}) + attrs={'volume_id': volume.id}, + ) def setUp(self): - super(TestBackupRestore, self).setUp() + super().setUp() self.backups_mock.get.return_value = self.backup self.volumes_mock.get.return_value = self.volume self.restores_mock.restore.return_value = ( volume_fakes.FakeVolume.create_one_volume( - {'id': self.volume['id']})) + {'id': self.volume['id']}, + ) + ) # Get the command object to mock self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): + self.volumes_mock.get.side_effect = exceptions.CommandError() + self.volumes_mock.find.side_effect = exceptions.CommandError() + arglist = [ + self.backup.id + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with( + self.backup.id, None, None, + ) + self.assertIsNotNone(result) + + def test_backup_restore_with_volume(self): + self.volumes_mock.get.side_effect = exceptions.CommandError() + self.volumes_mock.find.side_effect = exceptions.CommandError() + arglist = [ + self.backup.id, + self.backup.volume_id, + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", self.backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with( + self.backup.id, None, self.backup.volume_id, + ) + self.assertIsNotNone(result) + + def test_backup_restore_with_volume_force(self): arglist = [ + "--force", self.backup.id, - self.backup.volume_id + self.volume.name, ] verifylist = [ + ("force", True), ("backup", self.backup.id), - ("volume", self.backup.volume_id) + ("volume", self.volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.restores_mock.restore.assert_called_with(self.backup.id, - self.backup.volume_id) + self.restores_mock.restore.assert_called_with( + self.backup.id, self.volume.id, None, + ) self.assertIsNotNone(result) + def test_backup_restore_with_volume_existing(self): + arglist = [ + self.backup.id, + self.volume.name, + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", self.volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + class TestBackupSet(TestBackup): diff --git a/openstackclient/volume/v1/volume_backup.py b/openstackclient/volume/v1/volume_backup.py index 1a83a3c0..790cb463 100644 --- a/openstackclient/volume/v1/volume_backup.py +++ b/openstackclient/volume/v1/volume_backup.py @@ -231,18 +231,23 @@ class RestoreVolumeBackup(command.Command): parser.add_argument( 'volume', metavar='<volume>', - help=_('Volume to restore to (name or ID)') + nargs='?', + help=_('Volume to restore to (name or ID) (default to None)') ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - backup = utils.find_resource(volume_client.backups, - parsed_args.backup) - destination_volume = utils.find_resource(volume_client.volumes, - parsed_args.volume) - return volume_client.restores.restore(backup.id, - destination_volume.id) + backup = utils.find_resource( + volume_client.backups, parsed_args.backup, + ) + volume_id = None + if parsed_args.volume is not None: + volume_id = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ).id + return volume_client.restores.restore(backup.id, volume_id) class ShowVolumeBackup(command.ShowOne): diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index f2d89dc7..d96b28e9 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -363,18 +363,50 @@ class RestoreVolumeBackup(command.ShowOne): parser.add_argument( "volume", metavar="<volume>", - help=_("Volume to restore to (name or ID)") + nargs="?", + help=_( + "Volume to restore to " + "(name or ID for existing volume, name only for new volume) " + "(default to None)" + ) + ) + parser.add_argument( + "--force", + action="store_true", + help=_( + "Restore the backup to an existing volume " + "(default to False)" + ) ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + backup = utils.find_resource(volume_client.backups, parsed_args.backup) - destination_volume = utils.find_resource(volume_client.volumes, - parsed_args.volume) - backup = volume_client.restores.restore(backup.id, - destination_volume.id) - return zip(*sorted(backup._info.items())) + + volume_name = None + volume_id = None + try: + volume_id = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ).id + except Exception: + volume_name = parsed_args.volume + else: + # If we didn't fail, the volume must already exist. We only allow + # this to work if the user forced things + if not parsed_args.force: + msg = _( + "Volume '%s' already exists; if you want to restore the " + "backup to it you need to specify the '--force' option" + ) % parsed_args.volume + raise exceptions.CommandError(msg) + + return volume_client.restores.restore( + backup.id, volume_id, volume_name, + ) class SetVolumeBackup(command.Command): diff --git a/releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml b/releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml new file mode 100644 index 00000000..a4bf0043 --- /dev/null +++ b/releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml @@ -0,0 +1,16 @@ +--- +feature: + - | + The ``server list`` command now allows users to select the following + additional columns using the ``-c COLUMN`` option: + + - Task State + - Power State + - Image ID + - Flavor ID + - Availability Zone + - Host + - Properties + + These correspond to columns displayed by default when using the ``--long`` + option.
\ No newline at end of file diff --git a/releasenotes/notes/bug-1597189-02a8d8a402725860.yaml b/releasenotes/notes/bug-1597189-02a8d8a402725860.yaml new file mode 100644 index 00000000..69d1eb77 --- /dev/null +++ b/releasenotes/notes/bug-1597189-02a8d8a402725860.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + The ``volume`` argument of the ``volume backup restore`` command is now + optional and can refer to the name of a new volume that should be created + rather than a name or ID of an existing volume (which would be + overwritten). If not provided, cinder will generate a new volume with a + unique name. To restore a backup to an existing volume, you must now + specify the ``--force`` option (volume v2, v3 only). + [Bug `1597189 <https://bugs.launchpad.net/bugs/1597189>`_] |