def delete_snapshot(self, snapshot): ldev = self.get_ldev(snapshot) if ldev is None: LOG.warning( basic_lib.set_msg(304, method='delete_snapshot', id=snapshot['id'])) return self.add_volinfo(ldev, id=snapshot['id'], type='snapshot') if not self.volume_info[ldev]['in_use'].lock.acquire(False): desc = self.volume_info[ldev]['in_use'].desc basic_lib.output_err(660, desc=desc) raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) try: is_vvol = self.get_snapshot_is_vvol(snapshot) try: self.delete_ldev(ldev, is_vvol) except exception.HBSDNotFound: with self.volinfo_lock: if ldev in self.volume_info: self.volume_info.pop(ldev) LOG.warning( basic_lib.set_msg(305, type='snapshot', id=snapshot['id'])) except exception.HBSDBusy: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) finally: if ldev in self.volume_info: self.volume_info[ldev]['in_use'].lock.release()
def test_delete_no_dev_fails(self): """Test delete snapshot with no dev file fails.""" self.mock_object(os.path, 'exists', lambda x: False) self.volume.driver.vg = fake_lvm.FakeBrickLVM('cinder-volumes', False, None, 'default') volume = tests_utils.create_volume(self.context, **self.volume_params) volume_id = volume['id'] self.volume.create_volume(self.context, volume) snapshot = create_snapshot(volume_id) snapshot_id = snapshot.id self.volume.create_snapshot(self.context, snapshot) with mock.patch.object(self.volume.driver, 'delete_snapshot', side_effect=exception.SnapshotIsBusy( snapshot_name='fake')) as mock_del_snap: self.volume.delete_snapshot(self.context, snapshot) snapshot_ref = objects.Snapshot.get_by_id(self.context, snapshot_id) self.assertEqual(snapshot_id, snapshot_ref.id) self.assertEqual(fields.SnapshotStatus.AVAILABLE, snapshot_ref.status) mock_del_snap.assert_called_once_with(snapshot)
def delete_vol_or_snap(self, volume_url, volume_name='', ignore_non_exist=False): try: try: volume = self._get_volume(volume_url) except sushy_exceptions.ResourceNotFoundError: if ignore_non_exist: LOG.warning("Deleted non existent vol/snap %s", volume_url) else: raise if volume.links.endpoints: LOG.warning("Delete vol/snap failed, attached: %s", volume_url) raise exception.VolumeIsBusy(_("Volume is already attached"), volume_name=volume_name) volume.delete() except sushy_exceptions.BadRequestError as e: try: msg = e.body['@Message.ExtendedInfo'][0]['Message'] if (msg == "Cannot delete source snapshot volume when " "other clone volumes are based on this snapshot."): LOG.warning("Delete vol/snap failed, has-deps: %s", volume_url) raise exception.SnapshotIsBusy(snapshot_name=volume_name) except Exception: LOG.exception("Delete vol/snap failed") raise exception.VolumeBackendAPIException( data=(_('Unable to delete volume %s.') % volume_url)) except Exception: LOG.exception("Delete vol/snap failed") raise exception.VolumeBackendAPIException( data=(_('Unable to delete volume %s.') % volume_url)) LOG.info("RSD volume deleted: %s", volume_url)
def delete_snapshot(self, snapshot): """Deletes an rbd snapshot.""" # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are # utf-8 otherwise librbd will barf. volume_name = utils.convert_str(snapshot['volume_name']) snap_name = utils.convert_str(snapshot['name']) with RBDVolumeProxy(self, volume_name) as volume: try: volume.unprotect_snap(snap_name) except self.rbd.ImageBusy: children_list = self._get_children_info(volume, snap_name) if children_list: for (pool, image) in children_list: LOG.info( _LI('Image %(pool)s/%(image)s is dependent ' 'on the snapshot %(snap)s.'), { 'pool': pool, 'image': image, 'snap': snap_name }) raise exception.SnapshotIsBusy(snapshot_name=snap_name) volume.remove_snap(snap_name)
def test_create_cgsnapshot_busy_snapshot(self): snapshot = fake.CG_SNAPSHOT snapshot['volume'] = fake.CG_VOLUME mock_extract_host = self.mock_object(volume_utils, 'extract_host', return_value=fake.POOL_NAME) mock_clone_lun = self.mock_object(self.library, '_clone_lun') mock_busy = self.mock_object(self.zapi_client, 'wait_for_busy_snapshot') mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name']) mock_delete_snapshot = self.mock_object(self.zapi_client, 'delete_snapshot') mock_mark_snapshot_for_deletion = self.mock_object( self.zapi_client, 'mark_snapshot_for_deletion') self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot]) mock_extract_host.assert_called_once_with(fake.CG_VOLUME['host'], level='pool') self.zapi_client.create_cg_snapshot.assert_called_once_with( set([fake.POOL_NAME]), fake.CG_SNAPSHOT_ID) mock_clone_lun.assert_called_once_with( fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME, source_snapshot=fake.CG_SNAPSHOT_ID) mock_delete_snapshot.assert_not_called() mock_mark_snapshot_for_deletion.assert_called_once_with( fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
def _wait_for_snap_delete(snapshot, start_time): # defining CLI command snapshotname = snapshot['name'] volumename = snapshot['volume_name'] snap_destroy = ('snap', '-destroy', '-id', snapshotname, '-o') # executing CLI command out, rc = self._cli_execute(*snap_destroy) LOG.debug('Delete Snapshot: Volume: %(volumename)s Snapshot: ' '%(snapshotname)s Output: %(out)s' % {'volumename': volumename, 'snapshotname': snapshotname, 'out': out}) if rc not in [0, 9, 5]: if rc == 13: if int(time.time()) - start_time < \ self.timeout * 60: LOG.info(_('Snapshot %s is in use'), snapshotname) else: msg = (_('Failed to destroy %s ' ' because snapshot is in use.'), snapshotname) LOG.error(msg) raise exception.SnapshotIsBusy(data=msg) else: msg = (_('Failed to destroy %s'), snapshotname) LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) else: raise loopingcall.LoopingCallDone()
def test_delete_busy_snapshot(self, mock_clean): """Test snapshot can be created and deleted.""" self.volume.driver.vg = fake_lvm.FakeBrickLVM('cinder-volumes', False, None, 'default') volume = tests_utils.create_volume(self.context, **self.volume_params) volume_id = volume['id'] self.volume.create_volume(self.context, volume) snapshot = create_snapshot(volume_id, size=volume['size']) self.volume.create_snapshot(self.context, snapshot) with mock.patch.object(self.volume.driver, 'delete_snapshot', side_effect=exception.SnapshotIsBusy( snapshot_name='fake')) as mock_del_snap: snapshot_id = snapshot.id self.volume.delete_snapshot(self.context, snapshot) snapshot_ref = objects.Snapshot.get_by_id(self.context, snapshot_id) self.assertEqual(snapshot_id, snapshot_ref.id) self.assertEqual(fields.SnapshotStatus.AVAILABLE, snapshot_ref.status) mock_del_snap.assert_called_once_with(snapshot) mock_clean.assert_not_called()
def test_create_cgsnapshot_busy_snapshot(self): snapshot = fake.CG_SNAPSHOT snapshot['volume'] = fake.CG_VOLUME mock_get_snapshot_flexvols = self.mock_object( self.driver, '_get_flexvol_names_from_hosts') mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME])) mock_clone_backing_file = self.mock_object( self.driver, '_clone_backing_file_for_volume') mock_busy = self.mock_object( self.driver.zapi_client, 'wait_for_busy_snapshot') mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name']) mock_mark_snapshot_for_deletion = self.mock_object( self.driver.zapi_client, 'mark_snapshot_for_deletion') self.driver.create_cgsnapshot( fake.CG_CONTEXT, fake.CG_SNAPSHOT, [snapshot]) mock_get_snapshot_flexvols.assert_called_once_with( [snapshot['volume']['host']]) self.driver.zapi_client.create_cg_snapshot.assert_called_once_with( set([fake.CG_POOL_NAME]), fake.CG_SNAPSHOT_ID) mock_clone_backing_file.assert_called_once_with( snapshot['volume']['name'], snapshot['name'], snapshot['volume']['id'], source_snapshot=fake.CG_SNAPSHOT_ID) mock_busy.assert_called_once_with( fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID) self.driver.zapi_client.delete_snapshot.assert_not_called() mock_mark_snapshot_for_deletion.assert_called_once_with( fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
def _gc_delete(self, vname): """Delete volume and its hidden parents Deletes volume by going recursively to the first active parent and cals recursive deletion on storage side """ vol = None try: vol = self.ra.get_lun(vname) except jexc.JDSSResourceNotFoundException: LOG.debug('volume %s does not exist, it was already ' 'deleted.', vname) return except jexc.JDSSException as err: raise exception.VolumeBackendAPIException(err) if vol['is_clone']: self._delete_back_recursively(jcom.origin_volume(vol['origin']), jcom.origin_snapshot(vol['origin'])) else: try: self.ra.delete_lun(vname) except jexc.JDSSRESTException as err: LOG.debug( "Unable to delete physical volume %(volume)s " "with error %(err)s.", { "volume": vname, "err": err}) raise exception.SnapshotIsBusy(err)
def delete_snapshot(self, snapshot): """Deletes an rbd snapshot.""" with RBDVolumeProxy(self, snapshot['volume_name']) as volume: snap = str(snapshot['name']) if self._supports_layering(): try: volume.unprotect_snap(snap) except self.rbd.ImageBusy: raise exception.SnapshotIsBusy(snapshot_name=snap) volume.remove_snap(snap)
def delete_snapshot(self, snapshot): """Deletes an rbd snapshot""" if self._supports_layering(): try: self._try_execute('rbd', 'snap', 'unprotect', '--pool', FLAGS.rbd_pool, '--snap', snapshot['name'], snapshot['volume_name']) except exception.ProcessExecutionError: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) self._try_execute('rbd', 'snap', 'rm', '--pool', FLAGS.rbd_pool, '--snap', snapshot['name'], snapshot['volume_name'])
def delete_snapshot(self, snapshot): """Deletes an rbd snapshot.""" # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are # utf-8 otherwise librbd will barf. volume_name = encodeutils.safe_encode(snapshot['volume_name']) snap_name = encodeutils.safe_encode(snapshot['name']) with RBDVolumeProxy(self, volume_name) as volume: try: volume.unprotect_snap(snap_name) except self.rbd.ImageBusy: raise exception.SnapshotIsBusy(snapshot_name=snap_name) volume.remove_snap(snap_name)
def delete_snapshot(self, snapshot): """Delete the specified snapshot.""" ldev = utils.get_ldev(snapshot) if ldev is None: utils.output_log(MSG.INVALID_LDEV_FOR_DELETION, method='delete_snapshot', id=snapshot['id']) return try: self.delete_ldev(ldev) except utils.HBSDBusy: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
def delete_snapshot(self, snapshot): """Deletes a snapshot.""" root_helper = utils.get_root_helper() zvol_snapshot = self._zfs_snapshot(snapshot) try: self._execute('zfs', 'list', '-H', zvol_snapshot, root_helper=root_helper, run_as_root=True) except processutils.ProcessExecutionError: # If the snapshot isn't present, then don't attempt to delete LOG.warning( _("snapshot: %s not found, " "skipping delete operations"), snapshot['name']) LOG.info(_('Successfully deleted snapshot: %s'), snapshot['id']) return True try: out, err = self._execute('zfs', 'list', '-H', '-t', 'volume', '-o', 'name,origin', root_helper=root_helper, run_as_root=True) except processutils.ProcessExecutionError as exc: exception_message = (_("Failed to list origins, " "error message was: %s") % six.text_type(exc.stderr)) raise exception.VolumeBackendAPIException(data=exception_message) for i in out.splitlines(): name, origin = i.strip().split('\t') if origin == zvol_snapshot: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) try: self._execute('zfs', 'destroy', zvol_snapshot, root_helper=root_helper, run_as_root=True) except processutils.ProcessExecutionError as exc: exception_message = (_("Failed to delete snapshot, " "error message was: %s") % six.text_type(exc.stderr)) raise exception.VolumeBackendAPIException(data=exception_message)
def delete_snapshot(self, snapshot): """Deletes a snapshot.""" LOG.debug('zfssa.delete_snapshot: snapshot=' + snapshot['name']) lcfg = self.configuration has_clones = self.zfssa.has_clones(lcfg.zfssa_pool, lcfg.zfssa_project, snapshot['volume_name'], snapshot['name']) if has_clones: LOG.error(_LE('Snapshot %s: has clones') % snapshot['name']) raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) self.zfssa.delete_snapshot(lcfg.zfssa_pool, lcfg.zfssa_project, snapshot['volume_name'], snapshot['name'])
def delete_snapshot(self, snapshot): """Delete volume's snapshot on appliance. :param snapshot: shapshot reference """ try: self.nms.snapshot.destroy( '%s@%s' % (self._get_zvol_name( snapshot['volume_name']), snapshot['name']), '') except nexenta.NexentaException as exc: if "snapshot has dependent clones" in exc.args[1]: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) else: raise
def delete_snapshot(self, snapshot): """Delete the specified snapshot.""" ldev = utils.get_ldev(snapshot) # When 'ldev' is 0, it should be true. # Therefore, it cannot remove 'is None'. if ldev is None: utils.output_log(MSG.INVALID_LDEV_FOR_DELETION, method='delete_snapshot', id=snapshot['id']) return try: self._delete_ldev(ldev) except exception.VSPBusy: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
def delete_snapshot(self, volume, snapshot_uuid, user, api_version=None): if api_version is None: api_version = self.default_api_version kwargs = dict( Version=api_version, User=user, File=volume, UUID=snapshot_uuid ) try: return self._do_request('DeleteSnapshot', **kwargs) except ImageBusy: raise exception.SnapshotIsBusy(snapshot_name=snapshot_uuid)
def delete_snapshot(self, snapshot): """Delete the specified snapshot.""" ldev = utils.get_ldev(snapshot) if ldev is None: utils.output_log(MSG.INVALID_LDEV_FOR_DELETION, method='delete_snapshot', id=snapshot['id']) return try: self.delete_ldev(ldev) except exception.VolumeDriverException as ex: if ex.msg == utils.BUSY_MESSAGE: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) else: raise ex
def delete_snapshot(self, snapshot): """Deletes a snapshot.""" meta = snapshot.get('metadata') if 'force' in meta.keys(): LOG.debug("Found force flag for snapshot metadata." " Not sending call to datanode ") LOG.debug('snapshot metadata %s', meta) return if 'is_busy' in meta.keys(): LOG.warning("Snapshot %s is being used, skipping delete", snapshot['id']) raise exception.SnapshotIsBusy(snapshot_name=snapshot['id']) else: LOG.warning("Snapshot %s is being deleted," " is_busy key not present", snapshot['id']) message_body = {} message_body['volume_guid'] = ( util.get_guid_with_curly_brackets(snapshot['volume_id'])) message_body['snapshot_id'] = ( util.get_guid_with_curly_brackets(snapshot['id'])) # HyperScale snapshots whether Episodic or User initiated, all resides # in the data plane. # Hence delete snapshot operation will go to datanode rt_key = None # Get metadata for volume snapshot_volume = snapshot.get('volume') metadata = snapshot_volume['metadata'] rt_key = self._get_volume_metadata_value(metadata, 'current_dn_owner') if rt_key is None: rt_key = self.dn_routing_key try: # send message to data node util.message_data_plane( rt_key, 'hyperscale.storage.dm.version.delete', **message_body) except (exception.UnableToExecuteHyperScaleCmd, exception.UnableToProcessHyperScaleCmdOutput): with excutils.save_and_reraise_exception(): LOG.exception('Exception in delete snapshot')
def _delete_snapshot(self, snapshot_name): svol_name = {'scSnapName': snapshot_name} ret = self._call_method('DelSvol', svol_name) if ret['returncode'] not in [zte_pub.ZTE_ERR_CLONE_OR_SNAP_NOT_EXIST, zte_pub.ZTE_ERR_VAS_OBJECT_NOT_EXIST, zte_pub.ZTE_SUCCESS]: err_msg = (_('_delete_snapshot:Failed to delete snap.' 'snap name: %(snapname)s with Return code: ' '%(ret)s.') % {'snapname': snapshot_name, 'ret': ret['returncode']}) if ret['returncode'] == zte_pub.ZTE_ERR_SNAP_EXIST_CLONE: raise exception.SnapshotIsBusy(snapshot_name=snapshot_name) else: raise exception.VolumeBackendAPIException(data=err_msg)
def delete_snapshot(self, snapshot): """Deletes a snapshot.""" try: snap_info = self.client.getSnapshotByName(snapshot['name']) self.client.deleteSnapshot(snap_info['id']) except hpexceptions.HTTPNotFound: LOG.error(_("Snapshot did not exist. It will not be deleted")) except hpexceptions.HTTPServerError as ex: in_use_msg = 'cannot be deleted because it is a clone point' if in_use_msg in ex.get_description(): raise exception.SnapshotIsBusy(ex) raise exception.VolumeBackendAPIException(ex) except Exception as ex: raise exception.VolumeBackendAPIException(ex)
def _delete_cvol(self, cloned_name, issnapshot): cvol_name = {'scCvolName': cloned_name} ret = self._call_method('SyncForceDelCvol', cvol_name) if ret['returncode'] not in [zte_pub.ZTE_ERR_CLONE_OR_SNAP_NOT_EXIST, zte_pub.ZTE_ERR_VAS_OBJECT_NOT_EXIST, zte_pub.ZTE_SUCCESS]: err_msg = (_('_delete_cvol: Failed to delete clone vol. ' 'cloned name: %(name)s with Return code: ' '%(ret)s.') % {'name': cloned_name, 'ret': ret['returncode']}) if ret['returncode'] == zte_pub.ZTE_VOLUME_TASK_NOT_FINISHED: if issnapshot: raise exception.SnapshotIsBusy(snapshot_name=cloned_name) else: raise exception.VolumeIsBusy(volume_name=cloned_name) else: raise exception.VolumeBackendAPIException(data=err_msg)
def delete_snapshot(self, snapshot): """Delete volume's snapshot on appliance. :param snapshot: snapshot reference """ volume_name = self._get_zvol_name(snapshot['volume_name']) snapshot_name = '%s@%s' % (volume_name, snapshot['name']) try: self.nms.snapshot.destroy(snapshot_name, '') except nexenta.NexentaException as exc: if "does not exist" in exc.args[0]: LOG.info(_('Snapshot %s does not exist, it seems it was ' 'already deleted.'), snapshot_name) return if "snapshot has dependent clones" in exc.args[0]: raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) raise
def wait_for_busy_snapshot(self, flexvol, snapshot_name): """Checks for and handles a busy snapshot. If a snapshot is busy, for reasons other than cloning, an exception is raised immediately. Otherwise, wait for a period of time for the clone dependency to finish before giving up. If the snapshot is not busy then no action is taken and the method exits. """ snapshot = self.get_snapshot(flexvol, snapshot_name) if not snapshot['busy']: LOG.debug("Backing consistency group snapshot %s available for " "deletion.", snapshot_name) return else: LOG.debug("Snapshot %(snap)s for vol %(vol)s is busy, waiting " "for volume clone dependency to clear.", {"snap": snapshot_name, "vol": flexvol}) raise exception.SnapshotIsBusy(snapshot_name=snapshot_name)
def delete_snapshot(self, snapshot): """Deletes a snapshot.""" cliq_args = {} cliq_args['snapshotName'] = snapshot['name'] cliq_args['prompt'] = 'false' # Don't confirm try: volume_info = self._cliq_get_snapshot_info(snapshot['name']) except processutils.ProcessExecutionError: LOG.error(_("Snapshot did not exist. It will not be deleted")) return try: self._cliq_run_xml("deleteSnapshot", cliq_args) except Exception as ex: in_use_msg = 'cannot be deleted because it is a clone point' if in_use_msg in ex.message: raise exception.SnapshotIsBusy(str(ex)) raise exception.VolumeBackendAPIException(str(ex))
def test_delete_snapshot_when_busy_generates_user_message( self, fake_notify, fake_init, fake_msg_create): manager = vol_manager.VolumeManager() fake_snapshot = mock.MagicMock(id='0', project_id='1') fake_context = mock.MagicMock() fake_context.elevated.return_value = fake_context fake_exp = exception.SnapshotIsBusy(snapshot_name='Fred') fake_init.side_effect = fake_exp manager.delete_snapshot(fake_context, fake_snapshot) # make sure a user message was generated fake_msg_create.assert_called_once_with( fake_context, action=message_field.Action.SNAPSHOT_DELETE, resource_type=message_field.Resource.VOLUME_SNAPSHOT, resource_uuid=fake_snapshot['id'], exception=fake_exp)
def _handle_busy_snapshot(self, flexvol, snapshot_name): """Checks for and handles a busy snapshot. If a snapshot is not busy, take no action. If a snapshot is busy for reasons other than a clone dependency, raise immediately. Otherwise, since we always start a clone split operation after cloning a share, wait up to a minute for a clone dependency to clear before giving up. """ snapshot = self.zapi_client.get_snapshot(flexvol, snapshot_name) if not snapshot['busy']: LOG.info(_LI("Backing consistency group snapshot %s " "available for deletion"), snapshot_name) return else: LOG.debug('Snapshot %(snap)s for vol %(vol)s is busy, waiting ' 'for volume clone dependency to clear.', {'snap': snapshot_name, 'vol': flexvol}) raise exception.SnapshotIsBusy(snapshot_name=snapshot_name)
def delete_snapshot(self, snapshot): LOG.debug("Delete Snapshot id %s %s" % (snapshot['id'], pprint.pformat(snapshot))) try: snap_name = self._get_3par_snap_name(snapshot['id']) self.client.deleteVolume(snap_name) except hpexceptions.HTTPForbidden as ex: LOG.error(str(ex)) raise exception.NotAuthorized() except hpexceptions.HTTPNotFound as ex: # We'll let this act as if it worked # it helps clean up the cinder entries. msg = _("Delete Snapshot id not found. Removing from cinder: " "%(id)s Ex: %(msg)s") % {'id': snapshot['id'], 'msg': ex} LOG.warning(msg) except hpexceptions.HTTPConflict as ex: LOG.error(str(ex)) raise exception.SnapshotIsBusy(snapshot_name=snapshot['id'])
def test_delete_busy_snapshot(self): """Test snapshot can be created and deleted.""" volume = self._create_volume() volume_id = volume['id'] self.volume.create_volume(self.context, volume_id) snapshot_id = self._create_snapshot(volume_id)['id'] self.volume.create_snapshot(self.context, volume_id, snapshot_id) self.mox.StubOutWithMock(self.volume.driver, 'delete_snapshot') self.volume.driver.delete_snapshot(mox.IgnoreArg()).AndRaise( exception.SnapshotIsBusy(snapshot_name='fake')) self.mox.ReplayAll() self.volume.delete_snapshot(self.context, snapshot_id) snapshot_ref = db.snapshot_get(self.context, snapshot_id) self.assertEqual(snapshot_id, snapshot_ref.id) self.assertEqual("available", snapshot_ref.status) self.mox.UnsetStubs() self.volume.delete_snapshot(self.context, snapshot_id) self.volume.delete_volume(self.context, volume_id)