def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] update_dict = body['volume'] self.validate_name_and_description(update_dict, check_length=False) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in update_dict: update_dict['display_name'] = update_dict.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in update_dict: update_dict['display_description'] = update_dict.pop('description') # Not found and Invalid exceptions will be handled at the wsgi level try: volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) except exception.InvalidVolumeMetadataSize as error: raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg) volume.update(update_dict) utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return self._view_builder.detail(req, volume)
def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] if not body: raise exc.HTTPUnprocessableEntity() if 'volume' not in body: raise exc.HTTPUnprocessableEntity() volume = body['volume'] update_dict = {} valid_update_keys = ( 'display_name', 'display_description', 'metadata', ) for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] # Not found exception will be handled at the wsgi level volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) volume.update(update_dict) utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return {'volume': _translate_volume_detail_view(context, volume)}
def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] update_dict = body['volume'] self.validate_name_and_description(update_dict, check_length=False) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in update_dict: update_dict['display_name'] = update_dict.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in update_dict: update_dict['display_description'] = update_dict.pop('description') # Not found and Invalid exceptions will be handled at the wsgi level try: volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) except exception.InvalidVolumeMetadataSize as error: raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg) volume.update(update_dict) api_utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return self._view_builder.detail(req, volume)
def update(self, req, id, body): """Update a volume.""" context = req.environ["cinder.context"] if not body: raise exc.HTTPUnprocessableEntity() if "volume" not in body: raise exc.HTTPUnprocessableEntity() volume = body["volume"] update_dict = {} valid_update_keys = ("display_name", "display_description", "metadata") for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] try: volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, "update.start") self.volume_api.update(context, volume, update_dict) except exception.NotFound: raise exc.HTTPNotFound() volume.update(update_dict) utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, "update.end") return {"volume": _translate_volume_detail_view(context, volume)}
def create(self, context, volume_id, display_name): """Creates an entry in the transfers table.""" volume_api.check_policy(context, 'create_transfer') LOG.info(_LI("Generating transfer record for volume %s"), volume_id) volume_ref = self.db.volume_get(context, volume_id) if volume_ref['status'] != "available": raise exception.InvalidVolume(reason=_("status must be available")) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.start") # The salt is just a short random string. salt = self._get_random_string(CONF.volume_transfer_salt_length) auth_key = self._get_random_string(CONF.volume_transfer_key_length) crypt_hash = self._get_crypt_hash(salt, auth_key) # TODO(ollie): Transfer expiry needs to be implemented. transfer_rec = {'volume_id': volume_id, 'display_name': display_name, 'salt': salt, 'crypt_hash': crypt_hash, 'expires_at': None} try: transfer = self.db.transfer_create(context, transfer_rec) except Exception: LOG.error(_LE("Failed to create transfer record " "for %s"), volume_id) raise volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.end") return {'id': transfer['id'], 'volume_id': transfer['volume_id'], 'display_name': transfer['display_name'], 'auth_key': auth_key, 'created_at': transfer['created_at']}
def create(self, context, volume_id, display_name, no_snapshots=False): """Creates an entry in the transfers table.""" LOG.info("Generating transfer record for volume %s", volume_id) volume_ref = objects.Volume.get_by_id(context, volume_id) context.authorize(policy.CREATE_POLICY, target_obj=volume_ref) if volume_ref['status'] != "available": raise exception.InvalidVolume(reason=_("status must be available")) if volume_ref['encryption_key_id'] is not None: raise exception.InvalidVolume( reason=_("transferring encrypted volume is not supported")) if not no_snapshots: snapshots = self.db.snapshot_get_all_for_volume(context, volume_id) for snapshot in snapshots: if snapshot['status'] != "available": msg = _("snapshot: %s status must be " "available") % snapshot['id'] raise exception.InvalidSnapshot(reason=msg) if snapshot.get('encryption_key_id'): msg = _("snapshot: %s encrypted snapshots cannot be " "transferred") % snapshot['id'] raise exception.InvalidSnapshot(reason=msg) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.start") # The salt is just a short random string. salt = self._get_random_string(CONF.volume_transfer_salt_length) auth_key = self._get_random_string(CONF.volume_transfer_key_length) crypt_hash = self._get_crypt_hash(salt, auth_key) # TODO(ollie): Transfer expiry needs to be implemented. transfer_rec = { 'volume_id': volume_id, 'display_name': display_name, 'salt': salt, 'crypt_hash': crypt_hash, 'expires_at': None, 'no_snapshots': no_snapshots, 'source_project_id': volume_ref['project_id'] } try: transfer = self.db.transfer_create(context, transfer_rec) except Exception: LOG.error("Failed to create transfer record for %s", volume_id) raise volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.end") return { 'id': transfer['id'], 'volume_id': transfer['volume_id'], 'display_name': transfer['display_name'], 'auth_key': auth_key, 'created_at': transfer['created_at'], 'no_snapshots': transfer['no_snapshots'], 'source_project_id': transfer['source_project_id'], 'destination_project_id': transfer['destination_project_id'], 'accepted': transfer['accepted'] }
def _notify_about_volume_usage(self, context, volume, event_suffix, extra_usage_info=None): volume_utils.notify_about_volume_usage( context, volume, event_suffix, extra_usage_info=extra_usage_info, host=self.host)
def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] if not body: msg = _("Missing request body") raise exc.HTTPBadRequest(explanation=msg) if 'volume' not in body: msg = _("Missing required element '%s' in request body") % 'volume' raise exc.HTTPBadRequest(explanation=msg) volume = body['volume'] update_dict = {} valid_update_keys = ( 'name', 'description', 'display_name', 'display_description', 'metadata', ) for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] self.validate_name_and_description(update_dict) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in update_dict: update_dict['display_name'] = update_dict.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in update_dict: update_dict['display_description'] = update_dict.pop('description') try: volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) except exception.InvalidVolumeMetadata as error: raise webob.exc.HTTPBadRequest(explanation=error.msg) except exception.InvalidVolumeMetadataSize as error: raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg) volume.update(update_dict) utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return self._view_builder.detail(req, volume)
def accept(self, context, transfer_id, auth_key): """Accept a volume that has been offered for transfer.""" # We must use an elevated context to see the volume that is still # owned by the donor. volume_api.check_policy(context, "accept_transfer") transfer = self.db.transfer_get(context.elevated(), transfer_id) crypt_hash = self._get_crypt_hash(transfer["salt"], auth_key) if crypt_hash != transfer["crypt_hash"]: msg = _("Attempt to transfer %s with invalid auth key.") % transfer_id LOG.error(msg) raise exception.InvalidAuthKey(reason=msg) volume_id = transfer["volume_id"] vol_ref = self.db.volume_get(context.elevated(), volume_id) if vol_ref["consistencygroup_id"]: msg = _("Volume %s must not be part of a consistency " "group.") % vol_ref["id"] LOG.error(msg) raise exception.InvalidVolume(reason=msg) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.start") try: reserve_opts = {"volumes": 1, "gigabytes": vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) reservations = QUOTAS.reserve(context, **reserve_opts) except exception.OverQuota as e: quota_utils.process_reserve_over_quota(context, e, resource="volumes", size=vol_ref.size) try: donor_id = vol_ref["project_id"] reserve_opts = {"volumes": -1, "gigabytes": -vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) donor_reservations = QUOTAS.reserve(context.elevated(), project_id=donor_id, **reserve_opts) except Exception: donor_reservations = None LOG.exception(_LE("Failed to update quota donating volume" " transfer id %s"), transfer_id) try: # Transfer ownership of the volume now, must use an elevated # context. self.volume_api.accept_transfer(context, vol_ref, context.user_id, context.project_id) self.db.transfer_accept(context.elevated(), transfer_id, context.user_id, context.project_id) QUOTAS.commit(context, reservations) if donor_reservations: QUOTAS.commit(context, donor_reservations, project_id=donor_id) LOG.info(_LI("Volume %s has been transferred."), volume_id) except Exception: with excutils.save_and_reraise_exception(): QUOTAS.rollback(context, reservations) if donor_reservations: QUOTAS.rollback(context, donor_reservations, project_id=donor_id) vol_ref = self.db.volume_get(context, volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.end") return {"id": transfer_id, "display_name": transfer["display_name"], "volume_id": vol_ref["id"]}
def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] if not body: msg = _("Missing request body") raise exc.HTTPBadRequest(explanation=msg) if 'volume' not in body: msg = _("Missing required element '%s' in request body") % 'volume' raise exc.HTTPBadRequest(explanation=msg) volume = body['volume'] update_dict = {} valid_update_keys = ( 'name', 'description', 'display_name', 'display_description', 'metadata', ) for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] self.validate_name_and_description(update_dict) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in update_dict: update_dict['display_name'] = update_dict.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in update_dict: update_dict['display_description'] = update_dict.pop('description') # Not found and Invalid exceptions will be handled at the wsgi level try: volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) except exception.InvalidVolumeMetadataSize as error: raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg) volume.update(update_dict) utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return self._view_builder.detail(req, volume)
def create(self, context, volume_id, display_name, no_snapshots=False): """Creates an entry in the transfers table.""" LOG.info("Generating transfer record for volume %s", volume_id) volume_ref = objects.Volume.get_by_id(context, volume_id) context.authorize(policy.CREATE_POLICY, target_obj=volume_ref) if volume_ref['status'] != "available": raise exception.InvalidVolume(reason=_("status must be available")) if volume_ref['encryption_key_id'] is not None: raise exception.InvalidVolume( reason=_("transferring encrypted volume is not supported")) if not no_snapshots: snapshots = self.db.snapshot_get_all_for_volume(context, volume_id) for snapshot in snapshots: if snapshot['status'] != "available": msg = _("snapshot: %s status must be " "available") % snapshot['id'] raise exception.InvalidSnapshot(reason=msg) if snapshot.get('encryption_key_id'): msg = _("snapshot: %s encrypted snapshots cannot be " "transferred") % snapshot['id'] raise exception.InvalidSnapshot(reason=msg) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.start") # The salt is just a short random string. salt = self._get_random_string(CONF.volume_transfer_salt_length) auth_key = self._get_random_string(CONF.volume_transfer_key_length) crypt_hash = self._get_crypt_hash(salt, auth_key) # TODO(ollie): Transfer expiry needs to be implemented. transfer_rec = {'volume_id': volume_id, 'display_name': display_name, 'salt': salt, 'crypt_hash': crypt_hash, 'expires_at': None, 'no_snapshots': no_snapshots, 'source_project_id': volume_ref['project_id']} try: transfer = self.db.transfer_create(context, transfer_rec) except Exception: LOG.error("Failed to create transfer record for %s", volume_id) raise volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.end") return {'id': transfer['id'], 'volume_id': transfer['volume_id'], 'display_name': transfer['display_name'], 'auth_key': auth_key, 'created_at': transfer['created_at'], 'no_snapshots': transfer['no_snapshots'], 'source_project_id': transfer['source_project_id'], 'destination_project_id': transfer['destination_project_id'], 'accepted': transfer['accepted']}
def delete(self, context, transfer_id): """Make the RPC call to delete a volume transfer.""" volume_api.check_policy(context, "delete_transfer") transfer = self.db.transfer_get(context, transfer_id) volume_ref = self.db.volume_get(context, transfer.volume_id) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.start") if volume_ref["status"] != "awaiting-transfer": LOG.error(_LE("Volume in unexpected state")) self.db.transfer_destroy(context, transfer_id) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.end")
def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] if not body: msg = _("Missing request body") raise exc.HTTPBadRequest(explanation=msg) if 'volume' not in body: msg = _("Missing required element '%s' in request body") % 'volume' raise exc.HTTPBadRequest(explanation=msg) volume = body['volume'] update_dict = {} valid_update_keys = ( 'name', 'description', 'display_name', 'display_description', 'metadata', ) for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] # NOTE(thingee): v2 API allows name instead of display_name if 'name' in update_dict: update_dict['display_name'] = update_dict['name'] del update_dict['name'] # NOTE(thingee): v2 API allows name instead of display_name if 'description' in update_dict: update_dict['display_description'] = update_dict['description'] del update_dict['description'] try: volume = self.volume_api.get(context, id) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) except exception.NotFound: msg = _("Volume could not be found") raise exc.HTTPNotFound(explanation=msg) volume.update(update_dict) utils.add_visible_admin_metadata(context, volume, self.volume_api) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return self._view_builder.detail(req, volume)
def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] if not body: msg = _("Missing request body") raise exc.HTTPBadRequest(explanation=msg) if 'volume' not in body: msg = _("Missing required element '%s' in request body") % 'volume' raise exc.HTTPBadRequest(explanation=msg) volume = body['volume'] update_dict = {} valid_update_keys = ( 'name', 'description', 'display_name', 'display_description', 'metadata', ) for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] # NOTE(thingee): v2 API allows name instead of display_name if 'name' in update_dict: update_dict['display_name'] = update_dict['name'] del update_dict['name'] # NOTE(thingee): v2 API allows name instead of display_name if 'description' in update_dict: update_dict['display_description'] = update_dict['description'] del update_dict['description'] try: volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) except exception.NotFound: msg = _("Volume could not be found") raise exc.HTTPNotFound(explanation=msg) volume.update(update_dict) utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return self._view_builder.detail(req, volume)
def delete(self, context, volume, force=False, unmanage_only=False): if context.is_admin and context.project_id != volume["project_id"]: project_id = volume["project_id"] else: project_id = context.project_id volume_id = volume["id"] if not volume["host"]: volume_utils.notify_about_volume_usage(context, volume, "delete.start") # NOTE(vish): scheduling failed, so delete it # Note(zhiteng): update volume quota reservation try: reserve_opts = {"volumes": -1, "gigabytes": -volume["size"]} QUOTAS.add_volume_type_opts(context, reserve_opts, volume["volume_type_id"]) reservations = QUOTAS.reserve(context, project_id=project_id, **reserve_opts) except Exception: reservations = None LOG.exception(_("Failed to update quota for deleting volume")) self.db.volume_destroy(context.elevated(), volume_id) if reservations: QUOTAS.commit(context, reservations, project_id=project_id) volume_utils.notify_about_volume_usage(context, volume, "delete.end") return if not force and volume["status"] not in ["available", "error", "error_restoring", "error_extending"]: msg = _("Volume status must be available or error, " "but current status is: %s") % volume["status"] raise exception.InvalidVolume(reason=msg) if volume["attach_status"] == "attached": # Volume is still attached, need to detach first raise exception.VolumeAttached(volume_id=volume_id) if volume["migration_status"] is not None: # Volume is migrating, wait until done msg = _("Volume cannot be deleted while migrating") raise exception.InvalidVolume(reason=msg) snapshots = self.db.snapshot_get_all_for_volume(context, volume_id) if len(snapshots): msg = _("Volume still has %d dependent snapshots") % len(snapshots) raise exception.InvalidVolume(reason=msg) # If the volume is encrypted, delete its encryption key from the key # manager. This operation makes volume deletion an irreversible process # because the volume cannot be decrypted without its key. encryption_key_id = volume.get("encryption_key_id", None) if encryption_key_id is not None: self.key_manager.delete_key(context, encryption_key_id) now = timeutils.utcnow() self.db.volume_update(context, volume_id, {"status": "deleting", "terminated_at": now}) self.volume_rpcapi.delete_volume(context, volume, unmanage_only)
def execute(self, context, volume): try: volume_utils.notify_about_volume_usage(context, volume, self.event_suffix, host=volume.host) except exception.CinderException: # If notification sending of volume database entry reading fails # then we shouldn't error out the whole workflow since this is # not always information that must be sent for volumes to operate LOG.exception("Failed notifying about the volume" " action %(event)s for volume %(volume_id)s", {'event': self.event_suffix, 'volume_id': volume.id})
def execute(self, context, volume_ref): volume_id = volume_ref["id"] try: volume_utils.notify_about_volume_usage(context, volume_ref, self.event_suffix, host=volume_ref["host"]) except exception.CinderException: # If notification sending of volume database entry reading fails # then we shouldn't error out the whole workflow since this is # not always information that must be sent for volumes to operate LOG.exception( _LE("Failed notifying about the volume" " action %(event)s for volume %(volume_id)s"), {"event": self.event_suffix, "volume_id": volume_id}, )
def delete(self, context, transfer_id): """Make the RPC call to delete a volume transfer.""" transfer = self.db.transfer_get(context, transfer_id) volume_ref = objects.Volume.get_by_id(context, transfer.volume_id) context.authorize(policy.DELETE_POLICY, target_obj=volume_ref) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.start") if volume_ref['status'] != 'awaiting-transfer': LOG.error("Volume in unexpected state") self.db.transfer_destroy(context, transfer_id) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.end")
def delete(self, context, transfer_id): """Make the RPC call to delete a volume transfer.""" volume_api.check_policy(context, 'delete_transfer') transfer = self.db.transfer_get(context, transfer_id) volume_ref = self.db.volume_get(context, transfer.volume_id) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.start") if volume_ref['status'] != 'awaiting-transfer': LOG.error(_LE("Volume in unexpected state")) self.db.transfer_destroy(context, transfer_id) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.end")
def delete(self, context, transfer_id): """Make the RPC call to delete a volume transfer.""" transfer = self.db.transfer_get(context, transfer_id) volume_ref = self.db.volume_get(context, transfer.volume_id) context.authorize(policy.DELETE_POLICY, target_obj=volume_ref) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.start") if volume_ref['status'] != 'awaiting-transfer': LOG.error("Volume in unexpected state") self.db.transfer_destroy(context, transfer_id) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.delete.end")
def complete(self, req, id, body): """Mark a volume attachment process as completed (in-use).""" context = req.environ['cinder.context'] attachment_ref = (objects.VolumeAttachment.get_by_id(context, id)) volume_ref = objects.Volume.get_by_id(context, attachment_ref.volume_id) context.authorize(attachment_policy.COMPLETE_POLICY, target_obj=attachment_ref) attachment_ref.update( {'attach_status': fields.VolumeAttachStatus.ATTACHED}) attachment_ref.save() volume_ref.update({'status': 'in-use', 'attach_status': 'attached'}) volume_ref.save() volume_utils.notify_about_volume_usage(context, volume_ref, "attach.end")
def complete(self, req, id, body): """Mark a volume attachment process as completed (in-use).""" context = req.environ['cinder.context'] attachment_ref = ( objects.VolumeAttachment.get_by_id(context, id)) volume_ref = objects.Volume.get_by_id( context, attachment_ref.volume_id) context.authorize(attachment_policy.COMPLETE_POLICY, target_obj=attachment_ref) attachment_ref.update( {'attach_status': fields.VolumeAttachStatus.ATTACHED}) attachment_ref.save() volume_ref.update({'status': 'in-use', 'attach_status': 'attached'}) volume_ref.save() volume_utils.notify_about_volume_usage(context, volume_ref, "attach.end")
def update(self, req, id, body): """Update a volume.""" context = req.environ["cinder.context"] if not body: msg = _("Missing request body") raise exc.HTTPBadRequest(explanation=msg) if "volume" not in body: msg = _("Missing required element '%s' in request body") % "volume" raise exc.HTTPBadRequest(explanation=msg) volume = body["volume"] update_dict = {} valid_update_keys = ("name", "description", "display_name", "display_description", "metadata") for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] self.validate_name_and_description(update_dict) # NOTE(thingee): v2 API allows name instead of display_name if "name" in update_dict: update_dict["display_name"] = update_dict.pop("name") # NOTE(thingee): v2 API allows description instead of # display_description if "description" in update_dict: update_dict["display_description"] = update_dict.pop("description") try: volume = self.volume_api.get(context, id, viewable_admin_meta=True) volume_utils.notify_about_volume_usage(context, volume, "update.start") self.volume_api.update(context, volume, update_dict) except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) volume.update(update_dict) utils.add_visible_admin_metadata(volume) volume_utils.notify_about_volume_usage(context, volume, "update.end") return self._view_builder.detail(req, volume)
def test_notify_about_volume_usage(self, mock_rpc, mock_conf, mock_usage): mock_conf.host = "host1" output = volume_utils.notify_about_volume_usage(mock.sentinel.context, mock.sentinel.volume, "test_suffix") self.assertIsNone(output) mock_usage.assert_called_once_with(mock.sentinel.context, mock.sentinel.volume) mock_rpc.get_notifier.assert_called_once_with("volume", "host1") mock_rpc.get_notifier.return_value.info.assert_called_once_with( mock.sentinel.context, "volume.test_suffix", mock_usage.return_value )
def test_notify_about_volume_usage(self, mock_rpc, mock_conf, mock_usage): mock_conf.host = 'host1' output = volume_utils.notify_about_volume_usage( mock.sentinel.context, mock.sentinel.volume, 'test_suffix') self.assertIsNone(output) mock_usage.assert_called_once_with(mock.sentinel.context, mock.sentinel.volume) mock_rpc.get_notifier.assert_called_once_with('volume', 'host1') mock_rpc.get_notifier.return_value.info.assert_called_once_with( mock.sentinel.context, 'volume.test_suffix', mock_usage.return_value)
def test_notify_about_volume_usage(self, mock_rpc, mock_conf, mock_usage): mock_conf.host = 'host1' output = volume_utils.notify_about_volume_usage(mock.sentinel.context, mock.sentinel.volume, 'test_suffix') self.assertIsNone(output) mock_usage.assert_called_once_with(mock.sentinel.volume) mock_rpc.get_notifier.assert_called_once_with('volume', 'host1') mock_rpc.get_notifier.return_value.info.assert_called_once_with( mock.sentinel.context, 'volume.test_suffix', mock_usage.return_value)
def update(self, req, id, body): """Update a volume.""" context = req.environ['cinder.context'] if not body: raise exc.HTTPUnprocessableEntity() if 'volume' not in body: raise exc.HTTPUnprocessableEntity() volume = body['volume'] update_dict = {} valid_update_keys = ( 'display_name', 'display_description', 'metadata', ) for key in valid_update_keys: if key in volume: update_dict[key] = volume[key] try: volume = self.volume_api.get(context, id) volume_utils.notify_about_volume_usage(context, volume, 'update.start') self.volume_api.update(context, volume, update_dict) except exception.NotFound: raise exc.HTTPNotFound() volume.update(update_dict) self._add_visible_admin_metadata(context, volume) volume_utils.notify_about_volume_usage(context, volume, 'update.end') return {'volume': _translate_volume_detail_view(context, volume)}
def create(self, context, volume_id, display_name): """Creates an entry in the transfers table.""" volume_api.check_policy(context, "create_transfer") LOG.info(_LI("Generating transfer record for volume %s"), volume_id) volume_ref = self.db.volume_get(context, volume_id) if volume_ref["status"] != "available": raise exception.InvalidVolume(reason=_("status must be available")) volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.start") # The salt is just a short random string. salt = self._get_random_string(CONF.volume_transfer_salt_length) auth_key = self._get_random_string(CONF.volume_transfer_key_length) crypt_hash = self._get_crypt_hash(salt, auth_key) # TODO(ollie): Transfer expiry needs to be implemented. transfer_rec = { "volume_id": volume_id, "display_name": display_name, "salt": salt, "crypt_hash": crypt_hash, "expires_at": None, } try: transfer = self.db.transfer_create(context, transfer_rec) except Exception: LOG.error(_LE("Failed to create transfer record " "for %s"), volume_id) raise volume_utils.notify_about_volume_usage(context, volume_ref, "transfer.create.end") return { "id": transfer["id"], "volume_id": transfer["volume_id"], "display_name": transfer["display_name"], "auth_key": auth_key, "created_at": transfer["created_at"], }
def test_notify_about_volume_usage_with_kwargs(self, mock_rpc, mock_conf, mock_usage): mock_conf.host = "host1" output = volume_utils.notify_about_volume_usage( mock.sentinel.context, mock.sentinel.volume, "test_suffix", extra_usage_info={"a": "b", "c": "d"}, host="host2", ) self.assertIsNone(output) mock_usage.assert_called_once_with(mock.sentinel.context, mock.sentinel.volume, a="b", c="d") mock_rpc.get_notifier.assert_called_once_with("volume", "host2") mock_rpc.get_notifier.return_value.info.assert_called_once_with( mock.sentinel.context, "volume.test_suffix", mock_usage.return_value )
def test_notify_about_volume_usage_with_kwargs(self, mock_rpc, mock_conf, mock_usage): mock_conf.host = 'host1' output = volume_utils.notify_about_volume_usage( mock.sentinel.context, mock.sentinel.volume, 'test_suffix', extra_usage_info={'a': 'b', 'c': 'd'}, host='host2') self.assertIsNone(output) mock_usage.assert_called_once_with(mock.sentinel.volume, a='b', c='d') mock_rpc.get_notifier.assert_called_once_with('volume', 'host2') mock_rpc.get_notifier.return_value.info.assert_called_once_with( mock.sentinel.context, 'volume.test_suffix', mock_usage.return_value)
def test_notify_about_volume_usage_with_kwargs(self, mock_rpc, mock_conf, mock_usage): mock_conf.host = 'host1' output = volume_utils.notify_about_volume_usage( mock.sentinel.context, mock.sentinel.volume, 'test_suffix', extra_usage_info={'a': 'b', 'c': 'd'}, host='host2') self.assertIsNone(output) mock_usage.assert_called_once_with(mock.sentinel.context, mock.sentinel.volume, a='b', c='d') mock_rpc.get_notifier.assert_called_once_with('volume', 'host2') mock_rpc.get_notifier.return_value.info.assert_called_once_with( mock.sentinel.context, 'volume.test_suffix', mock_usage.return_value)
def accept(self, context, transfer_id, auth_key): """Accept a volume that has been offered for transfer.""" # We must use an elevated context to see the volume that is still # owned by the donor. volume_api.check_policy(context, 'accept_transfer') transfer = self.db.transfer_get(context.elevated(), transfer_id) crypt_hash = self._get_crypt_hash(transfer['salt'], auth_key) if crypt_hash != transfer['crypt_hash']: msg = (_("Attempt to transfer %s with invalid auth key.") % transfer_id) LOG.error(msg) raise exception.InvalidAuthKey(reason=msg) volume_id = transfer['volume_id'] vol_ref = self.db.volume_get(context.elevated(), volume_id) if vol_ref['consistencygroup_id']: msg = _("Volume %s must not be part of a consistency " "group.") % vol_ref['id'] LOG.error(msg) raise exception.InvalidVolume(reason=msg) try: values = {'per_volume_gigabytes': vol_ref.size} QUOTAS.limit_check(context, project_id=context.project_id, **values) except exception.OverQuota as e: quotas = e.kwargs['quotas'] raise exception.VolumeSizeExceedsLimit( size=vol_ref.size, limit=quotas['per_volume_gigabytes']) try: reserve_opts = {'volumes': 1, 'gigabytes': vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) reservations = QUOTAS.reserve(context, **reserve_opts) except exception.OverQuota as e: quota_utils.process_reserve_over_quota(context, e, resource='volumes', size=vol_ref.size) try: donor_id = vol_ref['project_id'] reserve_opts = {'volumes': -1, 'gigabytes': -vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) donor_reservations = QUOTAS.reserve(context.elevated(), project_id=donor_id, **reserve_opts) except Exception: donor_reservations = None LOG.exception(_LE("Failed to update quota donating volume" " transfer id %s"), transfer_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.start") try: # Transfer ownership of the volume now, must use an elevated # context. self.volume_api.accept_transfer(context, vol_ref, context.user_id, context.project_id) self.db.transfer_accept(context.elevated(), transfer_id, context.user_id, context.project_id) QUOTAS.commit(context, reservations) if donor_reservations: QUOTAS.commit(context, donor_reservations, project_id=donor_id) LOG.info(_LI("Volume %s has been transferred."), volume_id) except Exception: with excutils.save_and_reraise_exception(): QUOTAS.rollback(context, reservations) if donor_reservations: QUOTAS.rollback(context, donor_reservations, project_id=donor_id) vol_ref = self.db.volume_get(context, volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.end") return {'id': transfer_id, 'display_name': transfer['display_name'], 'volume_id': vol_ref['id']}
def accept(self, context, transfer_id, auth_key): """Accept a volume that has been offered for transfer.""" # We must use an elevated context to see the volume that is still # owned by the donor. volume_api.check_policy(context, 'accept_transfer') transfer = self.db.transfer_get(context.elevated(), transfer_id) crypt_hash = self._get_crypt_hash(transfer['salt'], auth_key) if crypt_hash != transfer['crypt_hash']: msg = (_("Attempt to transfer %s with invalid auth key.") % transfer_id) LOG.error(msg) raise exception.InvalidAuthKey(reason=msg) volume_id = transfer['volume_id'] vol_ref = self.db.volume_get(context.elevated(), volume_id) if vol_ref['consistencygroup_id']: msg = _("Volume %s must not be part of a consistency " "group.") % vol_ref['id'] LOG.error(msg) raise exception.InvalidVolume(reason=msg) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.start") try: reserve_opts = {'volumes': 1, 'gigabytes': vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) reservations = QUOTAS.reserve(context, **reserve_opts) except exception.OverQuota as e: quota_utils.process_reserve_over_quota(context, e, resource='volumes', size=vol_ref.size) try: donor_id = vol_ref['project_id'] reserve_opts = {'volumes': -1, 'gigabytes': -vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) donor_reservations = QUOTAS.reserve(context.elevated(), project_id=donor_id, **reserve_opts) except Exception: donor_reservations = None LOG.exception( _LE("Failed to update quota donating volume" " transfer id %s"), transfer_id) try: # Transfer ownership of the volume now, must use an elevated # context. self.volume_api.accept_transfer(context, vol_ref, context.user_id, context.project_id) self.db.transfer_accept(context.elevated(), transfer_id, context.user_id, context.project_id) QUOTAS.commit(context, reservations) if donor_reservations: QUOTAS.commit(context, donor_reservations, project_id=donor_id) LOG.info(_LI("Volume %s has been transferred."), volume_id) except Exception: with excutils.save_and_reraise_exception(): QUOTAS.rollback(context, reservations) if donor_reservations: QUOTAS.rollback(context, donor_reservations, project_id=donor_id) vol_ref = self.db.volume_get(context, volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.end") return { 'id': transfer_id, 'display_name': transfer['display_name'], 'volume_id': vol_ref['id'] }
def accept(self, context, transfer_id, auth_key): """Accept a volume that has been offered for transfer.""" # We must use an elevated context to see the volume that is still # owned by the donor. context.authorize(policy.ACCEPT_POLICY) transfer = self.db.transfer_get(context.elevated(), transfer_id) crypt_hash = self._get_crypt_hash(transfer['salt'], auth_key) if crypt_hash != transfer['crypt_hash']: msg = (_("Attempt to transfer %s with invalid auth key.") % transfer_id) LOG.error(msg) raise exception.InvalidAuthKey(reason=msg) volume_id = transfer['volume_id'] vol_ref = objects.Volume.get_by_id(context.elevated(), volume_id) if vol_ref['consistencygroup_id']: msg = _("Volume %s must not be part of a consistency " "group.") % vol_ref['id'] LOG.error(msg) raise exception.InvalidVolume(reason=msg) try: values = {'per_volume_gigabytes': vol_ref.size} QUOTAS.limit_check(context, project_id=context.project_id, **values) except exception.OverQuota as e: quotas = e.kwargs['quotas'] raise exception.VolumeSizeExceedsLimit( size=vol_ref.size, limit=quotas['per_volume_gigabytes']) try: reserve_opts = {'volumes': 1, 'gigabytes': vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) reservations = QUOTAS.reserve(context, **reserve_opts) except exception.OverQuota as e: quota_utils.process_reserve_over_quota(context, e, resource='volumes', size=vol_ref.size) try: donor_id = vol_ref['project_id'] reserve_opts = {'volumes': -1, 'gigabytes': -vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) donor_reservations = QUOTAS.reserve(context.elevated(), project_id=donor_id, **reserve_opts) except Exception: donor_reservations = None LOG.exception( "Failed to update quota donating volume" " transfer id %s", transfer_id) snap_res = None snap_donor_res = None if transfer['no_snapshots'] is False: snapshots = objects.SnapshotList.get_all_for_volume( context.elevated(), volume_id) volume_type_id = vol_ref.volume_type_id snap_res, snap_donor_res = self._handle_snapshot_quota( context, snapshots, volume_type_id, vol_ref['project_id']) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.start") try: # Transfer ownership of the volume now, must use an elevated # context. self.volume_api.accept_transfer(context, vol_ref, context.user_id, context.project_id, transfer['no_snapshots']) self.db.transfer_accept(context.elevated(), transfer_id, context.user_id, context.project_id, transfer['no_snapshots']) QUOTAS.commit(context, reservations) if snap_res: QUOTAS.commit(context, snap_res) if donor_reservations: QUOTAS.commit(context, donor_reservations, project_id=donor_id) if snap_donor_res: QUOTAS.commit(context, snap_donor_res, project_id=donor_id) LOG.info("Volume %s has been transferred.", volume_id) except Exception: with excutils.save_and_reraise_exception(): QUOTAS.rollback(context, reservations) if snap_res: QUOTAS.rollback(context, snap_res) if donor_reservations: QUOTAS.rollback(context, donor_reservations, project_id=donor_id) if snap_donor_res: QUOTAS.rollback(context, snap_donor_res, project_id=donor_id) vol_ref = objects.Volume.get_by_id(context.elevated(), volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.end") return { 'id': transfer_id, 'display_name': transfer['display_name'], 'volume_id': vol_ref['id'] }
def accept(self, context, transfer_id, auth_key): """Accept a volume that has been offered for transfer.""" # We must use an elevated context to see the volume that is still # owned by the donor. volume_api.check_policy(context, 'accept_transfer') transfer = self.db.transfer_get(context.elevated(), transfer_id) crypt_hash = self._get_crypt_hash(transfer['salt'], auth_key) if crypt_hash != transfer['crypt_hash']: msg = (_("Attempt to transfer %s with invalid auth key.") % transfer_id) LOG.error(msg) raise exception.InvalidAuthKey(reason=msg) volume_id = transfer['volume_id'] vol_ref = self.db.volume_get(context.elevated(), volume_id) if vol_ref['consistencygroup_id']: msg = _("Volume %s must not be part of a consistency " "group.") % vol_ref['id'] LOG.error(msg) raise exception.InvalidVolume(reason=msg) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.start") try: reserve_opts = {'volumes': 1, 'gigabytes': vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) reservations = QUOTAS.reserve(context, **reserve_opts) except exception.OverQuota as e: overs = e.kwargs['overs'] usages = e.kwargs['usages'] quotas = e.kwargs['quotas'] def _consumed(name): return (usages[name]['reserved'] + usages[name]['in_use']) for over in overs: if 'gigabytes' in over: msg = _LW("Quota exceeded for %(s_pid)s, tried to create " "%(s_size)sG volume (%(d_consumed)dG of " "%(d_quota)dG already consumed)") LOG.warning( msg, { 's_pid': context.project_id, 's_size': vol_ref['size'], 'd_consumed': _consumed(over), 'd_quota': quotas[over] }) raise exception.VolumeSizeExceedsAvailableQuota( requested=vol_ref['size'], consumed=_consumed(over), quota=quotas[over]) elif 'volumes' in over: msg = _LW("Quota exceeded for %(s_pid)s, tried to create " "volume (%(d_consumed)d volumes " "already consumed)") LOG.warning(msg, { 's_pid': context.project_id, 'd_consumed': _consumed(over) }) raise exception.VolumeLimitExceeded(allowed=quotas[over], name=over) try: donor_id = vol_ref['project_id'] reserve_opts = {'volumes': -1, 'gigabytes': -vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) donor_reservations = QUOTAS.reserve(context.elevated(), project_id=donor_id, **reserve_opts) except Exception: donor_reservations = None LOG.exception( _LE("Failed to update quota donating volume" " transfer id %s"), transfer_id) try: # Transfer ownership of the volume now, must use an elevated # context. self.volume_api.accept_transfer(context, vol_ref, context.user_id, context.project_id) self.db.transfer_accept(context.elevated(), transfer_id, context.user_id, context.project_id) QUOTAS.commit(context, reservations) if donor_reservations: QUOTAS.commit(context, donor_reservations, project_id=donor_id) LOG.info(_LI("Volume %s has been transferred."), volume_id) except Exception: with excutils.save_and_reraise_exception(): QUOTAS.rollback(context, reservations) if donor_reservations: QUOTAS.rollback(context, donor_reservations, project_id=donor_id) vol_ref = self.db.volume_get(context, volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.end") return { 'id': transfer_id, 'display_name': transfer['display_name'], 'volume_id': vol_ref['id'] }
def delete(self, context, volume, force=False, unmanage_only=False): if context.is_admin and context.project_id != volume['project_id']: project_id = volume['project_id'] else: project_id = context.project_id volume_id = volume['id'] if not volume['host']: volume_utils.notify_about_volume_usage(context, volume, "delete.start") # NOTE(vish): scheduling failed, so delete it # Note(zhiteng): update volume quota reservation try: reserve_opts = {'volumes': -1, 'gigabytes': -volume['size']} QUOTAS.add_volume_type_opts(context, reserve_opts, volume['volume_type_id']) reservations = QUOTAS.reserve(context, project_id=project_id, **reserve_opts) except Exception: reservations = None LOG.exception(_("Failed to update quota for deleting volume")) self.db.volume_destroy(context.elevated(), volume_id) if reservations: QUOTAS.commit(context, reservations, project_id=project_id) volume_utils.notify_about_volume_usage(context, volume, "delete.end") return if not force and volume['status'] not in [ "available", "error", "error_restoring", "error_extending" ]: msg = _("Volume status must be available or error, " "but current status is: %s") % volume['status'] raise exception.InvalidVolume(reason=msg) if volume['attach_status'] == "attached": # Volume is still attached, need to detach first raise exception.VolumeAttached(volume_id=volume_id) if volume['migration_status'] is not None: # Volume is migrating, wait until done msg = _("Volume cannot be deleted while migrating") raise exception.InvalidVolume(reason=msg) snapshots = self.db.snapshot_get_all_for_volume(context, volume_id) if len(snapshots): msg = _("Volume still has %d dependent snapshots") % len(snapshots) raise exception.InvalidVolume(reason=msg) # If the volume is encrypted, delete its encryption key from the key # manager. This operation makes volume deletion an irreversible process # because the volume cannot be decrypted without its key. encryption_key_id = volume.get('encryption_key_id', None) if encryption_key_id is not None: self.key_manager.delete_key(context, encryption_key_id) now = timeutils.utcnow() self.db.volume_update(context, volume_id, { 'status': 'deleting', 'terminated_at': now }) self.volume_rpcapi.delete_volume(context, volume, unmanage_only)
def accept(self, context, transfer_id, auth_key): """Accept a volume that has been offered for transfer.""" # We must use an elevated context to see the volume that is still # owned by the donor. volume_api.check_policy(context, 'accept_transfer') transfer = self.db.transfer_get(context.elevated(), transfer_id) crypt_hash = self._get_crypt_hash(transfer['salt'], auth_key) if crypt_hash != transfer['crypt_hash']: msg = (_("Attempt to transfer %s with invalid auth key.") % transfer_id) LOG.error(msg) raise exception.InvalidAuthKey(reason=msg) volume_id = transfer['volume_id'] vol_ref = self.db.volume_get(context.elevated(), volume_id) if vol_ref['consistencygroup_id']: msg = _("Volume %s must not be part of a consistency " "group.") % vol_ref['id'] LOG.error(msg) raise exception.InvalidVolume(reason=msg) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.start") try: reserve_opts = {'volumes': 1, 'gigabytes': vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) reservations = QUOTAS.reserve(context, **reserve_opts) except exception.OverQuota as e: overs = e.kwargs['overs'] usages = e.kwargs['usages'] quotas = e.kwargs['quotas'] def _consumed(name): return (usages[name]['reserved'] + usages[name]['in_use']) for over in overs: if 'gigabytes' in over: msg = _LW("Quota exceeded for %(s_pid)s, tried to create " "%(s_size)sG volume (%(d_consumed)dG of " "%(d_quota)dG already consumed)") LOG.warning(msg, {'s_pid': context.project_id, 's_size': vol_ref['size'], 'd_consumed': _consumed(over), 'd_quota': quotas[over]}) raise exception.VolumeSizeExceedsAvailableQuota( requested=vol_ref['size'], consumed=_consumed(over), quota=quotas[over]) elif 'volumes' in over: msg = _LW("Quota exceeded for %(s_pid)s, tried to create " "volume (%(d_consumed)d volumes " "already consumed)") LOG.warning(msg, {'s_pid': context.project_id, 'd_consumed': _consumed(over)}) raise exception.VolumeLimitExceeded(allowed=quotas[over], name=over) try: donor_id = vol_ref['project_id'] reserve_opts = {'volumes': -1, 'gigabytes': -vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) donor_reservations = QUOTAS.reserve(context.elevated(), project_id=donor_id, **reserve_opts) except Exception: donor_reservations = None LOG.exception(_LE("Failed to update quota donating volume" " transfer id %s"), transfer_id) try: # Transfer ownership of the volume now, must use an elevated # context. self.volume_api.accept_transfer(context, vol_ref, context.user_id, context.project_id) self.db.transfer_accept(context.elevated(), transfer_id, context.user_id, context.project_id) QUOTAS.commit(context, reservations) if donor_reservations: QUOTAS.commit(context, donor_reservations, project_id=donor_id) LOG.info(_LI("Volume %s has been transferred."), volume_id) except Exception: with excutils.save_and_reraise_exception(): QUOTAS.rollback(context, reservations) if donor_reservations: QUOTAS.rollback(context, donor_reservations, project_id=donor_id) vol_ref = self.db.volume_get(context, volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.end") return {'id': transfer_id, 'display_name': transfer['display_name'], 'volume_id': vol_ref['id']}
def accept(self, context, transfer_id, auth_key): """Accept a volume that has been offered for transfer.""" # We must use an elevated context to see the volume that is still # owned by the donor. volume_api.check_policy(context, "accept_transfer") transfer = self.db.transfer_get(context.elevated(), transfer_id) crypt_hash = self._get_crypt_hash(transfer["salt"], auth_key) if crypt_hash != transfer["crypt_hash"]: msg = _("Attempt to transfer %s with invalid auth key.") % transfer_id LOG.error(msg) raise exception.InvalidAuthKey(reason=msg) volume_id = transfer["volume_id"] vol_ref = self.db.volume_get(context.elevated(), volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.start") try: reserve_opts = {"volumes": 1, "gigabytes": vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) reservations = QUOTAS.reserve(context, **reserve_opts) except exception.OverQuota as e: overs = e.kwargs["overs"] usages = e.kwargs["usages"] quotas = e.kwargs["quotas"] def _consumed(name): return usages[name]["reserved"] + usages[name]["in_use"] if "gigabytes" in overs: msg = _LW( "Quota exceeded for %(s_pid)s, tried to create " "%(s_size)sG volume (%(d_consumed)dG of " "%(d_quota)dG already consumed)" ) LOG.warning( msg, { "s_pid": context.project_id, "s_size": vol_ref["size"], "d_consumed": _consumed("gigabytes"), "d_quota": quotas["gigabytes"], }, ) raise exception.VolumeSizeExceedsAvailableQuota( requested=vol_ref["size"], consumed=_consumed("gigabytes"), quota=quotas["gigabytes"] ) elif "volumes" in overs: msg = _LW( "Quota exceeded for %(s_pid)s, tried to create " "volume (%(d_consumed)d volumes " "already consumed)" ) LOG.warning(msg, {"s_pid": context.project_id, "d_consumed": _consumed("volumes")}) raise exception.VolumeLimitExceeded(allowed=quotas["volumes"]) try: donor_id = vol_ref["project_id"] reserve_opts = {"volumes": -1, "gigabytes": -vol_ref.size} QUOTAS.add_volume_type_opts(context, reserve_opts, vol_ref.volume_type_id) donor_reservations = QUOTAS.reserve(context.elevated(), project_id=donor_id, **reserve_opts) except Exception: donor_reservations = None LOG.exception(_LE("Failed to update quota donating volume" " transfer id %s"), transfer_id) try: # Transfer ownership of the volume now, must use an elevated # context. self.volume_api.accept_transfer(context, vol_ref, context.user_id, context.project_id) self.db.transfer_accept(context.elevated(), transfer_id, context.user_id, context.project_id) QUOTAS.commit(context, reservations) if donor_reservations: QUOTAS.commit(context, donor_reservations, project_id=donor_id) LOG.info(_LI("Volume %s has been transferred."), volume_id) except Exception: with excutils.save_and_reraise_exception(): QUOTAS.rollback(context, reservations) if donor_reservations: QUOTAS.rollback(context, donor_reservations, project_id=donor_id) vol_ref = self.db.volume_get(context, volume_id) volume_utils.notify_about_volume_usage(context, vol_ref, "transfer.accept.end") return {"id": transfer_id, "display_name": transfer["display_name"], "volume_id": vol_ref["id"]}