# 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 snapshot interface (v3 extension).""" from oslo_utils import strutils from cinderclient import api_versions from cinderclient.apiclient import base as common_base from cinderclient import base MV_3_66_FORCE_FLAG_ERROR = ( "Since microversion 3.66 of the Block Storage API, the 'force' option is " "invalid for this request. For backward compatibility, however, when the " "'force' flag is passed with a value evaluating to True, it is silently " "ignored.") class Snapshot(base.Resource): """A Snapshot is a point-in-time snapshot of an openstack volume.""" def __repr__(self): return "" % self.id def delete(self, force=False): """Delete this snapshot.""" return self.manager.delete(self, force) def update(self, **kwargs): """Update the name or description for this snapshot.""" return self.manager.update(self, **kwargs) @property def progress(self): return self._info.get('os-extended-snapshot-attributes:progress') @property def project_id(self): return self._info.get('os-extended-snapshot-attributes:project_id') def reset_state(self, state): """Update the snapshot with the provided state.""" return self.manager.reset_state(self, state) def set_metadata(self, metadata): """Set metadata of this snapshot.""" return self.manager.set_metadata(self, metadata) def delete_metadata(self, keys): """Delete metadata of this snapshot.""" return self.manager.delete_metadata(self, keys) def update_all_metadata(self, metadata): """Update_all metadata of this snapshot.""" return self.manager.update_all_metadata(self, metadata) def manage(self, volume_id, ref, name=None, description=None, metadata=None): """Manage an existing snapshot.""" self.manager.manage(volume_id=volume_id, ref=ref, name=name, description=description, metadata=metadata) def list_manageable(self, host, detailed=True, marker=None, limit=None, offset=None, sort=None, cluster=None): return self.manager.list_manageable(host, detailed=detailed, marker=marker, limit=limit, offset=offset, sort=sort, cluster=cluster) def unmanage(self, snapshot): """Unmanage a snapshot.""" self.manager.unmanage(snapshot) class SnapshotManager(base.ManagerWithFind): """Manage :class:`Snapshot` resources.""" resource_class = Snapshot @api_versions.wraps("3.0", "3.65") def create(self, volume_id, force=False, name=None, description=None, metadata=None): """Creates a snapshot of the given volume. :param volume_id: The ID of the volume to snapshot. :param force: If force is True, create a snapshot even if the volume is attached to an instance. Default is False. :param name: Name of the snapshot :param description: Description of the snapshot :param metadata: Metadata of the snapshot :rtype: :class:`Snapshot` """ if metadata is None: snapshot_metadata = {} else: snapshot_metadata = metadata # Bug #1995883: it's possible for the shell to use the user- # specified 3.66 do_snapshot_create function, but if the server # only supports < 3.66, the client will have been downgraded and # will use this function. In that case, the 'force' parameter will # be None, which means that the user didn't specify a value for it, # so we set it to the pre-3.66 default value of False. # # NOTE: we know this isn't a problem for current client consumers # because a null value for 'force' has never been allowed by the # Block Storage API v3, so there's no reason for anyone to directly # call this method passing force=None. if force is None: force = False body = {'snapshot': {'volume_id': volume_id, 'force': force, 'name': name, 'description': description, 'metadata': snapshot_metadata}} return self._create('/snapshots', body, 'snapshot') @api_versions.wraps("3.66") def create(self, volume_id, force=None, # noqa: F811 name=None, description=None, metadata=None): """Creates a snapshot of the given volume. :param volume_id: The ID of the volume to snapshot. :param force: This is technically not valid after mv 3.66, but the API silently accepts force=True for backward compatibility, so this function will, too :param name: Name of the snapshot :param description: Description of the snapshot :param metadata: Metadata of the snapshot :raises: ValueError if 'force' is not passed with a value that evaluates to true :rtype: :class:`Snapshot` """ if metadata is None: snapshot_metadata = {} else: snapshot_metadata = metadata body = {'snapshot': {'volume_id': volume_id, 'name': name, 'description': description, 'metadata': snapshot_metadata}} if force is not None: try: force = strutils.bool_from_string(force, strict=True) if not force: raise ValueError() except ValueError: raise ValueError(MV_3_66_FORCE_FLAG_ERROR) return self._create('/snapshots', body, 'snapshot') def get(self, snapshot_id): """Shows snapshot details. :param snapshot_id: The ID of the snapshot to get. :rtype: :class:`Snapshot` """ return self._get("/snapshots/%s" % snapshot_id, "snapshot") def list(self, detailed=True, search_opts=None, marker=None, limit=None, sort=None): """Get a list of all snapshots. :rtype: list of :class:`Snapshot` """ resource_type = "snapshots" url = self._build_list_url(resource_type, detailed=detailed, search_opts=search_opts, marker=marker, limit=limit, sort=sort) return self._list(url, resource_type, limit=limit) def delete(self, snapshot, force=False): """Delete a snapshot. :param snapshot: The :class:`Snapshot` to delete. :param force: Allow delete in state other than error or available. """ if force: return self._action('os-force_delete', snapshot) else: return self._delete("/snapshots/%s" % base.getid(snapshot)) def update(self, snapshot, **kwargs): """Update the name or description for a snapshot. :param snapshot: The :class:`Snapshot` to update. """ if not kwargs: return body = {"snapshot": kwargs} return self._update("/snapshots/%s" % base.getid(snapshot), body) def reset_state(self, snapshot, state): """Update the specified snapshot with the provided state.""" return self._action('os-reset_status', snapshot, {'status': state} if state else {}) def _action(self, action, snapshot, info=None, **kwargs): """Perform a snapshot action.""" body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) url = '/snapshots/%s/action' % base.getid(snapshot) resp, body = self.api.client.post(url, body=body) return common_base.TupleWithMeta((resp, body), resp) def update_snapshot_status(self, snapshot, update_dict): return self._action('os-update_snapshot_status', base.getid(snapshot), update_dict) def set_metadata(self, snapshot, metadata): """Update/Set a snapshots metadata. :param snapshot: The :class:`Snapshot`. :param metadata: A list of keys to be set. """ body = {'metadata': metadata} return self._create("/snapshots/%s/metadata" % base.getid(snapshot), body, "metadata") def delete_metadata(self, snapshot, keys): """Delete specified keys from snapshot metadata. :param snapshot: The :class:`Snapshot`. :param keys: A list of keys to be removed. """ response_list = [] snapshot_id = base.getid(snapshot) for k in keys: resp, body = self._delete("/snapshots/%s/metadata/%s" % (snapshot_id, k)) response_list.append(resp) return common_base.ListWithMeta([], response_list) def update_all_metadata(self, snapshot, metadata): """Update_all snapshot metadata. :param snapshot: The :class:`Snapshot`. :param metadata: A list of keys to be updated. """ body = {'metadata': metadata} return self._update("/snapshots/%s/metadata" % base.getid(snapshot), body) def manage(self, volume_id, ref, name=None, description=None, metadata=None): """Manage an existing snapshot.""" body = {'snapshot': {'volume_id': volume_id, 'ref': ref, 'name': name, 'description': description, 'metadata': metadata } } return self._create('/os-snapshot-manage', body, 'snapshot') @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-snapshot-manage", detailed=detailed, search_opts={'host': host}, marker=marker, limit=limit, offset=offset, sort=sort) return self._list(url, "manageable-snapshots") @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_snapshots", detailed=detailed, search_opts=search_opts, marker=marker, limit=limit, offset=offset, sort=sort) return self._list(url, "manageable-snapshots") def unmanage(self, snapshot): """Unmanage a snapshot.""" return self._action('os-unmanage', snapshot, None)