diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 48e00b741..dc2a88d0c 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__) # key is unsupported version, value is appropriate supported alternative REPLACEMENT_VERSIONS = {"1": "3", "2": "3"} -MAX_VERSION = "3.66" +MAX_VERSION = "3.68" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index d39dd4baa..f21c2ab2a 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -453,6 +453,8 @@ class FakeHTTPClient(fakes_base.FakeHTTPClient): 'failover_replication', 'list_replication_targets', 'reset_status'): assert action in body + elif action == 'os-reimage': + assert 'image_id' in body[action] else: raise AssertionError("Unexpected action: %s" % action) return (resp, {}, {}) diff --git a/cinderclient/tests/unit/v3/fakes_base.py b/cinderclient/tests/unit/v3/fakes_base.py index ec75ff079..b5f272844 100644 --- a/cinderclient/tests/unit/v3/fakes_base.py +++ b/cinderclient/tests/unit/v3/fakes_base.py @@ -550,6 +550,8 @@ class FakeHTTPClient(base_client.HTTPClient): _body = body elif action == 'revert': assert 'snapshot_id' in body[action] + elif action == 'os-reimage': + assert 'image_id' in body[action] else: raise AssertionError("Unexpected action: %s" % action) return (resp, {}, _body) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 8f005253c..9eb6ce3ad 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1895,3 +1895,18 @@ class ShellTest(utils.TestCase): 'volume_id': '1234', 'volume_name': volume_name, }) + + def test_reimage(self): + self.run_command('--os-volume-api-version 3.68 reimage 1234 1') + expected = {'os-reimage': {'image_id': '1', + 'reimage_reserved': False}} + self.assert_called('POST', '/volumes/1234/action', body=expected) + + @ddt.data('False', 'True') + def test_reimage_reserved(self, reimage_reserved): + self.run_command( + '--os-volume-api-version 3.68 reimage --reimage-reserved %s 1234 1' + % reimage_reserved) + expected = {'os-reimage': {'image_id': '1', + 'reimage_reserved': reimage_reserved}} + self.assert_called('POST', '/volumes/1234/action', body=expected) diff --git a/cinderclient/tests/unit/v3/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes.py index c733a0917..1c2f8a2b6 100644 --- a/cinderclient/tests/unit/v3/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes.py @@ -201,3 +201,15 @@ class VolumesTest(utils.TestCase): 'force_host_copy': False, 'lock_volume': False}}) self._assert_request_id(vol) + + @ddt.data(False, True) + def test_reimage(self, reimage_reserved): + cs = fakes.FakeClient(api_versions.APIVersion('3.68')) + v = cs.volumes.get('1234') + self._assert_request_id(v) + vol = cs.volumes.reimage(v, '1', reimage_reserved) + cs.assert_called('POST', '/volumes/1234/action', + {'os-reimage': {'image_id': '1', + 'reimage_reserved': + reimage_reserved}}) + self._assert_request_id(vol) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 27f2c7fcc..60239c72a 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2858,3 +2858,23 @@ def do_default_type_unset(cs, args): except Exception as e: print("Unset for default volume type for project %s failed: %s" % (project_id, e)) + + +@api_versions.wraps('3.68') +@utils.arg('volume', + metavar='', + help='Name or ID of volume to reimage') +@utils.arg('image_id', + metavar='', + help='The image id of the image that will be used to reimage ' + 'the volume.') +@utils.arg('--reimage-reserved', + metavar='', + default=False, + help='Enables or disables reimage for a volume that is in ' + 'reserved state otherwise only volumes in "available" ' + ' or "error" status may be re-imaged. Default=False.') +def do_reimage(cs, args): + """Rebuilds a volume, overwriting all content with the specified image""" + volume = utils.find_volume(cs, args.volume) + volume.reimage(args.image_id, args.reimage_reserved) diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index 42527f7e7..0479dc351 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -67,6 +67,10 @@ class Volume(volumes_base.Volume): metadata=metadata, bootable=bootable, cluster=cluster) + def reimage(self, image_id, reimage_reserved=False): + """Rebuilds the volume with the new specified image""" + self.manager.reimage(self, image_id, reimage_reserved) + class VolumeManager(volumes_base.VolumeManager): resource_class = Volume @@ -282,3 +286,21 @@ class VolumeManager(volumes_base.VolumeManager): search_opts=options) return self._get(url, None) + + @api_versions.wraps('3.68') + def reimage(self, volume, image_id, reimage_reserved=False): + """Reimage a volume + + .. warning:: This is a destructive action and the contents of the + volume will be lost. + + :param volume: Volume to reimage. + :param reimage_reserved: Boolean to enable or disable reimage + of a volume that is in 'reserved' state otherwise only + volumes in 'available' status may be re-imaged. + :param image_id: The image id. + """ + return self._action('os-reimage', + volume, + {'image_id': image_id, + 'reimage_reserved': reimage_reserved}) diff --git a/releasenotes/notes/reimage-volume-fea3a1178662e65a.yaml b/releasenotes/notes/reimage-volume-fea3a1178662e65a.yaml new file mode 100644 index 000000000..a95fb1fb9 --- /dev/null +++ b/releasenotes/notes/reimage-volume-fea3a1178662e65a.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + A new ``cinder reimage`` command and related python API binding has been + added which allows a user to replace the current content of a specified + volume with the data of a specified image supplied by the Image service + (Glance). (Note that this is a destructive action, that is, all data + currently contained in the volume is destroyed when the volume is + re-imaged.) This feature requires Block Storage API microversion 3.68 + or greater.