Skip to content

Commit

Permalink
Support volume re-image
Browse files Browse the repository at this point in the history
This patch adds volume re-image API to enable the ability to
re-image a specific volume.

Implements: blueprint add-volume-re-image-api

Co-Authored-by: Rajat Dhasmana <rajatdhasmana@gmail.com>

Change-Id: I031aae50ee82198648f46c503bba04c6e231bbe5
  • Loading branch information
Yikun authored and rajathere committed Feb 24, 2022
1 parent 55ea01c commit d69e89e
Show file tree
Hide file tree
Showing 19 changed files with 493 additions and 6 deletions.
16 changes: 16 additions & 0 deletions api-ref/source/v3/parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,13 @@ os-migrate_volume_completion:
in: body
required: true
type: object
os-reimage:
description: |
The ``os-reimage`` action.
in: body
required: true
type: object
min_version: 3.68
os-reserve:
description: |
The ``os-reserve`` action.
Expand Down Expand Up @@ -2479,6 +2486,15 @@ reference:
in: body
required: true
type: object
reimage_reserved:
description: |
Normally, volumes to be re-imaged are in ``available`` or ``error`` status.
When ``true``, this parameter will allow a volume in the ``reserved`` status
to be re-imaged. The ability to re-image a volume in ``reserved`` status
may be restricted to administrators in some clouds. Default value is ``false``.
in: body
required: false
type: boolean
remove_project_access:
description: |
Removes volume type access from a project.
Expand Down
4 changes: 2 additions & 2 deletions api-ref/source/v3/samples/versions/version-show-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
],
"min_version": "3.0",
"status": "CURRENT",
"updated": "2021-12-16T00:00:00Z",
"version": "3.67"
"updated": "2022-03-30T00:00:00Z",
"version": "3.68"
}
]
}
4 changes: 2 additions & 2 deletions api-ref/source/v3/samples/versions/versions-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
],
"min_version": "3.0",
"status": "CURRENT",
"updated": "2021-12-16T00:00:00Z",
"version": "3.67"
"updated": "2022-03-30T00:00:00Z",
"version": "3.68"
}
]
}
6 changes: 6 additions & 0 deletions api-ref/source/v3/samples/volume-os-reimage-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"os-reimage": {
"image_id": "71543ced-a8af-45b6-a5c4-a46282108a90",
"reimage_reserved": false
}
}
41 changes: 41 additions & 0 deletions api-ref/source/v3/volumes-v3-volumes-actions.inc
Original file line number Diff line number Diff line change
Expand Up @@ -973,3 +973,44 @@ Request Example

.. literalinclude:: ./samples/volume-readonly-update-request.json
:language: javascript


Reimage a volume
~~~~~~~~~~~~~~~~

.. rest_method:: POST /v3/{project_id}/volumes/{volume_id}/action

Re-image a volume with a specific image. Specify the ``os-reimage`` action
in the request body.

A volume in ``available`` or ``error`` status can be re-imaged directly. To
re-image a volume in ``reserved`` status, you must include the
``reimage_reserved`` parameter set to ``true``.

.. note:: Image signature verification is currently unsupported when
re-imaging a volume.

Response codes
--------------

.. rest_status_code:: success ../status.yaml

- 202


Request
-------

.. rest_parameters:: parameters.yaml

- project_id: project_id_path
- volume_id: volume_id_path
- image_id: image_id
- reimage_reserved: reimage_reserved
- os-reimage: os-reimage

Request Example
---------------

.. literalinclude:: ./samples/volume-os-reimage-request.json
:language: javascript
20 changes: 20 additions & 0 deletions cinder/api/contrib/volume_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,26 @@ def _set_bootable(self, req, id, body):

self.volume_api.update(context, volume, update_dict)

@wsgi.Controller.api_version(mv.SUPPORT_REIMAGE_VOLUME)
@wsgi.response(HTTPStatus.ACCEPTED)
@wsgi.action('os-reimage')
@validation.schema(volume_action.reimage, mv.SUPPORT_REIMAGE_VOLUME)
def _reimage(self, req, id, body):
"""Re-image a volume with specific image."""
context = req.environ['cinder.context']
# Not found exception will be handled at the wsgi level
volume = self.volume_api.get(context, id)
params = body['os-reimage']
reimage_reserved = params.get('reimage_reserved', 'False')
reimage_reserved = strutils.bool_from_string(reimage_reserved,
strict=True)
image_id = params['image_id']
try:
self.volume_api.reimage(context, volume, image_id,
reimage_reserved)
except exception.InvalidVolume as error:
raise webob.exc.HTTPBadRequest(explanation=error.msg)


class Volume_actions(extensions.ExtensionDescriptor):
"""Enable volume actions."""
Expand Down
2 changes: 2 additions & 0 deletions cinder/api/microversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@

PROJECT_ID_OPTIONAL_IN_URL = '3.67'

SUPPORT_REIMAGE_VOLUME = '3.68'


def get_mv_header(version):
"""Gets a formatted HTTP microversion header.
Expand Down
3 changes: 2 additions & 1 deletion cinder/api/openstack/api_version_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,14 @@
operation.
* 3.66 - Allow snapshotting in-use volumes without force flag.
* 3.67 - API URLs no longer need to include a project_id parameter.
* 3.68 - Support re-image volume
"""

# The minimum and maximum versions of the API supported
# The default api version request is defined to be the
# minimum version of the API supported.
_MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.67"
_MAX_API_VERSION = "3.68"
UPDATED = "2021-11-02T00:00:00Z"


Expand Down
5 changes: 5 additions & 0 deletions cinder/api/openstack/rest_api_version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -513,3 +513,8 @@ route: ``https://$(controller)s/volume/v3/$(project_id)s/volumes`` is
equivalent to ``https://$(controller)s/volume/v3/volumes``. When interacting
with the cinder service as system or domain scoped users, a project_id should
not be specified in the API path.

3.68
----
Support ability to re-image a volume with a specific image. Specify the
``os-reimage`` action in the request body.
17 changes: 17 additions & 0 deletions cinder/api/schemas/volume_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,20 @@
'required': ['os-update_readonly_flag'],
'additionalProperties': False,
}

reimage = {
'type': 'object',
'properties': {
'os-reimage': {
'type': 'object',
'properties': {
'image_id': parameter_types.uuid,
'reimage_reserved': parameter_types.boolean,
},
'required': ['image_id'],
'additionalProperties': False,
},
},
'required': ['os-reimage'],
'additionalProperties': False,
}
18 changes: 18 additions & 0 deletions cinder/compute/nova.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ def _get_volume_extended_event(self, server_id, volume_id):
'server_uuid': server_id,
'tag': volume_id}

def _get_volume_reimaged_event(self, server_id, volume_id):
return {'name': 'volume-reimaged',
'server_uuid': server_id,
'tag': volume_id}

def _send_events(self, context, events, api_version=None):
nova = novaclient(context, privileged_user=True,
api_version=api_version)
Expand Down Expand Up @@ -219,3 +224,16 @@ def extend_volume(self, context, server_ids, volume_id):
resource_uuid=volume_id,
detail=message_field.Detail.NOTIFY_COMPUTE_SERVICE_FAILED)
return result

def reimage_volume(self, context, server_ids, volume_id):
api_version = '2.91'
events = [self._get_volume_reimaged_event(server_id, volume_id)
for server_id in server_ids]
result = self._send_events(context, events, api_version=api_version)
if not result:
self.message_api.create(
context,
message_field.Action.REIMAGE_VOLUME,
resource_uuid=volume_id,
detail=message_field.Detail.NOTIFY_COMPUTE_SERVICE_FAILED)
return result
22 changes: 22 additions & 0 deletions cinder/policies/volume_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
ROLL_DETACHING_POLICY = "volume_extension:volume_actions:roll_detaching"
TERMINATE_POLICY = "volume_extension:volume_actions:terminate_connection"
INITIALIZE_POLICY = "volume_extension:volume_actions:initialize_connection"
REIMAGE_POLICY = "volume:reimage"
REIMAGE_RESERVED_POLICY = "volume:reimage_reserved"

deprecated_extend_policy = base.CinderDeprecatedRule(
name=EXTEND_POLICY,
Expand Down Expand Up @@ -323,6 +325,26 @@
],
deprecated_rule=deprecated_detach_policy,
),
policy.DocumentedRuleDefault(
name=REIMAGE_POLICY,
check_str=base.SYSTEM_ADMIN_OR_PROJECT_MEMBER,
description="Reimage a volume in 'available' or 'error' status.",
operations=[
{
'method': 'POST',
'path': '/volumes/{volume_id}/action (os-reimage)'
}
]),
policy.DocumentedRuleDefault(
name=REIMAGE_RESERVED_POLICY,
check_str=base.SYSTEM_ADMIN_OR_PROJECT_MEMBER,
description="Reimage a volume in 'reserved' status.",
operations=[
{
'method': 'POST',
'path': '/volumes/{volume_id}/action (os-reimage)'
}
]),
]


Expand Down
67 changes: 67 additions & 0 deletions cinder/tests/unit/api/contrib/test_volume_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1552,3 +1552,70 @@ def test_copy_volume_to_image_vhdx(
vol_db = objects.Volume.get_by_id(self.context, volume.id)
self.assertEqual('uploading', vol_db.status)
self.assertEqual('available', vol_db.previous_status)

def _build_reimage_req(self, body, vol_id,
version=mv.SUPPORT_REIMAGE_VOLUME):
req = fakes.HTTPRequest.blank(
'/v3/%s/volumes/%s/action' % (fake.PROJECT_ID, id))
req.method = "POST"
req.body = jsonutils.dump_as_bytes(body)
req.environ['cinder.context'] = self.context
req.api_version_request = mv.get_api_version(version)
req.headers["content-type"] = "application/json"
return req

@ddt.data(None, False, True)
@mock.patch.object(volume_api.API, "reimage")
def test_volume_reimage(self, reimage_reserved, mock_image):
vol = utils.create_volume(self.context)
body = {"os-reimage": {"image_id": fake.IMAGE_ID}}
if reimage_reserved is not None:
body["os-reimage"]["reimage_reserved"] = reimage_reserved
req = self._build_reimage_req(body, vol.id)
self.controller._reimage(req, vol.id, body=body)

@mock.patch.object(volume_api.API, "reimage")
def test_volume_reimage_invaild_params(self, mock_image):
vol = utils.create_volume(self.context)
body = {"os-reimage": {"image_id": fake.IMAGE_ID,
"reimage_reserved": 'wrong'}}
req = self._build_reimage_req(body, vol)
self.assertRaises(exception.ValidationError,
self.controller._reimage, req,
vol.id, body=body)

def test_volume_reimage_before_3_68(self):
vol = utils.create_volume(self.context)
body = {"os-reimage": {"image_id": fake.IMAGE_ID}}

req = self._build_reimage_req(body, vol.id, version="3.67")
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller._reimage, req, vol.id, body=body)

def test_reimage_volume_invalid_status(self):
def fake_reimage_volume(*args, **kwargs):
msg = "Volume status must be available."
raise exception.InvalidVolume(reason=msg)
self.mock_object(volume.api.API, 'reimage',
fake_reimage_volume)

vol = utils.create_volume(self.context)
body = {"os-reimage": {"image_id": fake.IMAGE_ID}}
req = self._build_reimage_req(body, vol)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._reimage, req,
vol.id, body=body)

@mock.patch('cinder.context.RequestContext.authorize')
def test_reimage_volume_attach_more_than_one_server(self, mock_authorize):
vol = utils.create_volume(self.context)
va_objs = [objects.VolumeAttachment(context=self.context, id=i)
for i in [fake.OBJECT_ID, fake.OBJECT2_ID, fake.OBJECT3_ID]]
va_list = objects.VolumeAttachmentList(context=self.context,
objects=va_objs)
vol.volume_attachment = va_list
self.mock_object(volume_api.API, 'get', return_value=vol)
body = {"os-reimage": {"image_id": fake.IMAGE_ID}}
req = self._build_reimage_req(body, vol)
self.assertRaises(webob.exc.HTTPConflict,
self.controller._reimage, req, vol.id, body=body)
9 changes: 9 additions & 0 deletions cinder/tests/unit/volume/test_rpcapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,3 +676,12 @@ def test_list_replication_targets(self):
server=self.fake_group.host,
group=self.fake_group,
version='3.14')

def test_reimage(self):
self._test_rpc_api('reimage', rpc_method='cast',
server=self.fake_volume_obj.host,
volume=self.fake_volume_obj,
image_meta={'id': fake.IMAGE_ID,
'container_format': 'fake_type',
'disk_format': 'fake_format'},
version='3.18')
Loading

0 comments on commit d69e89e

Please sign in to comment.