python-cinderclient/cinderclient/v3/volumes.py
Konrad Gube dd8ba12681 Add os-extend_volume_completion volume action.
Change-Id: Ifdeab1a8cd634bbe63c25fae17448c0789b297c9
2024-02-27 15:24:16 +00:00

323 lines
14 KiB
Python

# Copyright (c) 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Volume interface (v3 extension)."""
from cinderclient import api_versions
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient.v3 import volumes_base
class Volume(volumes_base.Volume):
def upload_to_image(self, force, image_name, container_format,
disk_format, visibility=None,
protected=None):
"""Upload a volume to image service as an image.
:param force: Boolean to enables or disables upload of a volume that
is attached to an instance.
:param image_name: The new image name.
:param container_format: Container format type.
:param disk_format: Disk format type.
:param visibility: The accessibility of image (allowed for
3.1-latest).
:param protected: Boolean to decide whether prevents image from being
deleted (allowed for 3.1-latest).
:returns: tuple (response, body)
"""
if self.manager.api_version >= api_versions.APIVersion("3.1"):
visibility = 'private' if visibility is None else visibility
protected = False if protected is None else protected
return self.manager.upload_to_image(self, force, image_name,
container_format, disk_format,
visibility, protected)
return self.manager.upload_to_image(self, force, image_name,
container_format, disk_format)
def revert_to_snapshot(self, snapshot):
"""Revert a volume to a snapshot."""
self.manager.revert_to_snapshot(self, snapshot)
def migrate_volume(self, host, force_host_copy, lock_volume, cluster=None):
"""Migrate the volume to a new host."""
return self.manager.migrate_volume(self, host, force_host_copy,
lock_volume, cluster)
def manage(self, host, ref, name=None, description=None,
volume_type=None, availability_zone=None, metadata=None,
bootable=False, cluster=None):
"""Manage an existing volume."""
return self.manager.manage(host=host, ref=ref, name=name,
description=description,
volume_type=volume_type,
availability_zone=availability_zone,
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)
def extend_volume_completion(self, volume, error=False):
"""Complete extending an attached volume"""
self.manager.extend_volume_completion(self, volume, error)
class VolumeManager(volumes_base.VolumeManager):
resource_class = Volume
def create(self, size, consistencygroup_id=None,
group_id=None, snapshot_id=None,
source_volid=None, name=None, description=None,
volume_type=None, user_id=None,
project_id=None, availability_zone=None,
metadata=None, imageRef=None, scheduler_hints=None,
backup_id=None):
"""Create a volume.
:param size: Size of volume in GB
:param consistencygroup_id: ID of the consistencygroup
:param group_id: ID of the group
:param snapshot_id: ID of the snapshot
:param name: Name of the volume
:param description: Description of the volume
:param volume_type: Type of volume
:param user_id: User id derived from context (IGNORED)
:param project_id: Project id derived from context (IGNORED)
:param availability_zone: Availability Zone to use
:param metadata: Optional metadata to set on volume creation
:param imageRef: reference to an image stored in glance
:param source_volid: ID of source volume to clone from
:param scheduler_hints: (optional extension) arbitrary key-value pairs
specified by the client to help boot an instance
:param backup_id: ID of the backup
:rtype: :class:`Volume`
"""
if metadata is None:
volume_metadata = {}
else:
volume_metadata = metadata
body = {'volume': {'size': size,
'consistencygroup_id': consistencygroup_id,
'snapshot_id': snapshot_id,
'name': name,
'description': description,
'volume_type': volume_type,
'availability_zone': availability_zone,
'metadata': volume_metadata,
'imageRef': imageRef,
'source_volid': source_volid,
'backup_id': backup_id
}}
if group_id:
body['volume']['group_id'] = group_id
if scheduler_hints:
body['OS-SCH-HNT:scheduler_hints'] = scheduler_hints
return self._create('/volumes', body, 'volume')
@api_versions.wraps('3.40')
def revert_to_snapshot(self, volume, snapshot):
"""Revert a volume to a snapshot.
The snapshot must be the most recent one known to cinder.
:param volume: volume object or volume id.
:param snapshot: snapshot object or snapshot id.
"""
return self._action('revert', volume,
info={'snapshot_id': base.getid(snapshot)})
@api_versions.wraps('3.12')
def summary(self, all_tenants):
"""Get volumes summary."""
url = "/volumes/summary"
if all_tenants:
url += "?all_tenants=True"
_, body = self.api.client.get(url)
return body
@api_versions.wraps("3.0")
def delete_metadata(self, volume, keys):
"""Delete specified keys from volumes metadata.
:param volume: The :class:`Volume`.
:param keys: A list of keys to be removed.
"""
response_list = []
for k in keys:
resp, body = self._delete("/volumes/%s/metadata/%s" %
(base.getid(volume), k))
response_list.append(resp)
return common_base.ListWithMeta([], response_list)
@api_versions.wraps("3.15")
def delete_metadata(self, volume, keys): # noqa: F811
"""Delete specified keys from volumes metadata.
:param volume: The :class:`Volume`.
:param keys: A list of keys to be removed.
"""
# pylint: disable=function-redefined
data = self._get("/volumes/%s/metadata" % base.getid(volume))
metadata = data._info.get("metadata", {})
if set(keys).issubset(metadata.keys()):
for k in keys:
metadata.pop(k)
body = {'metadata': metadata}
kwargs = {'headers': {'If-Match': data._checksum}}
return self._update("/volumes/%s/metadata" % base.getid(volume),
body, **kwargs)
@api_versions.wraps("3.0")
def upload_to_image(self, volume, force, image_name, container_format,
disk_format):
"""Upload volume to image service as image.
:param volume: The :class:`Volume` to upload.
"""
return self._action('os-volume_upload_image',
volume,
{'force': force,
'image_name': image_name,
'container_format': container_format,
'disk_format': disk_format})
@api_versions.wraps("3.1")
def upload_to_image(self, volume, force, image_name, # noqa: F811
container_format, disk_format, visibility, protected):
"""Upload volume to image service as image.
:param volume: The :class:`Volume` to upload.
"""
# pylint: disable=function-redefined
return self._action('os-volume_upload_image',
volume,
{'force': force,
'image_name': image_name,
'container_format': container_format,
'disk_format': disk_format,
'visibility': visibility,
'protected': protected})
def migrate_volume(self, volume, host, force_host_copy, lock_volume,
cluster=None):
"""Migrate volume to new backend.
The new backend is defined by the host or the cluster (not both).
:param volume: The :class:`Volume` to migrate
:param host: The destination host
:param force_host_copy: Skip driver optimizations
:param lock_volume: Lock the volume and guarantee the migration
to finish
:param cluster: The cluster
"""
body = {'host': host, 'force_host_copy': force_host_copy,
'lock_volume': lock_volume}
if self.api_version.matches('3.16'):
if cluster:
body['cluster'] = cluster
del body['host']
return self._action('os-migrate_volume', volume, body)
def manage(self, host, ref, name=None, description=None,
volume_type=None, availability_zone=None, metadata=None,
bootable=False, cluster=None):
"""Manage an existing volume."""
body = {'volume': {'host': host,
'ref': ref,
'name': name,
'description': description,
'volume_type': volume_type,
'availability_zone': availability_zone,
'metadata': metadata,
'bootable': bootable
}}
if self.api_version.matches('3.16') and cluster:
body['volume']['cluster'] = cluster
return self._create('/os-volume-manage', body, 'volume')
@api_versions.wraps('3.0')
def list_manageable(self, host, detailed=True, marker=None,
limit=None, offset=None, sort=None):
url = self._build_list_url("os-volume-manage", detailed=detailed,
search_opts={'host': host}, marker=marker,
limit=limit, offset=offset, sort=sort)
return self._list(url, "manageable-volumes")
@api_versions.wraps('3.8')
def list_manageable(self, host, detailed=True, marker=None, # noqa: F811
limit=None, offset=None, sort=None, cluster=None):
search_opts = {'cluster': cluster} if cluster else {'host': host}
url = self._build_list_url("manageable_volumes", detailed=detailed,
search_opts=search_opts, marker=marker,
limit=limit, offset=offset, sort=sort)
return self._list(url, "manageable-volumes")
@api_versions.wraps("3.0", "3.32")
def get_pools(self, detail):
"""Show pool information for backends."""
query_string = ""
if detail:
query_string = "?detail=True"
return self._get('/scheduler-stats/get_pools%s' % query_string, None)
@api_versions.wraps("3.33")
def get_pools(self, detail, search_opts): # noqa: F811
"""Show pool information for backends."""
# pylint: disable=function-redefined
options = {'detail': detail}
options.update(search_opts)
url = self._build_list_url('scheduler-stats/get_pools', detailed=False,
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})
@api_versions.wraps('3.71')
def extend_volume_completion(self, volume, error=False):
"""Complete extending an attached volume.
:param volume: The UUID of the extended volume
:param error: Used to indicate if an error has occured that requires
Cinder to roll back the extend operation.
"""
return self._action('os-extend_volume_completion',
volume,
{'error': error})