/* * Copyright 2009-2014 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. */ package com.eucalyptus.objectstorage.asynctask; import java.util.List; import org.apache.log4j.Logger; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.Transactions; import com.eucalyptus.objectstorage.MpuPartMetadataManagers; import com.eucalyptus.objectstorage.ObjectMetadataManagers; import com.eucalyptus.objectstorage.ObjectState; import com.eucalyptus.objectstorage.OsgObjectFactory; import com.eucalyptus.objectstorage.entities.ObjectEntity; import com.eucalyptus.objectstorage.entities.PartEntity; import com.eucalyptus.objectstorage.providers.ObjectStorageProviders; import com.eucalyptus.util.EucalyptusCloudException; /** * Scans metadata for "deleted" objects and removes them from the backend. Many of these may be running concurrently. * */ public class ObjectReaperTask implements Runnable { private static final Logger LOG = Logger.getLogger(ObjectReaperTask.class); private boolean interrupted = false; public ObjectReaperTask() {} public void interrupt() { this.interrupted = true; } public void resume() { this.interrupted = false; } public void reapObject(final ObjectEntity obj) throws Exception { LOG.trace("Reaping object " + obj.getObjectUuid()); try { OsgObjectFactory.getFactory().actuallyDeleteObject(ObjectStorageProviders.getInstance(), obj, null); } catch (EucalyptusCloudException ex) { // Failed. Keep record so we can retry later LOG.trace("Reaping failed due to error for object: " + obj.getBucket().getBucketUuid() + "/" + obj.getObjectUuid() + " Will retry", ex); } } // Does a single scan of the DB and reclaims objects it finds in the 'deleting' state @Override public void run() { long startTime = System.currentTimeMillis(); try { LOG.debug("Initiating object-storage object reaper task"); cleanDeleting(); cleanFailed(); cleanParts(); } catch (final Throwable f) { LOG.error("Error during object reaper execution. Will retry later", f); } finally { try { long endTime = System.currentTimeMillis(); LOG.debug("Object reaper execution task took " + Long.toString(endTime - startTime) + "ms to complete"); } catch (final Throwable f) { // Do nothing, but don't allow exceptions out } } } private void cleanDeleting() { try { List<ObjectEntity> entitiesToClean = ObjectMetadataManagers.getInstance().lookupObjectsInState(null, null, null, ObjectState.deleting); LOG.trace("Reaping " + entitiesToClean.size() + " objects from backend"); for (ObjectEntity obj : entitiesToClean) { try { reapObject(obj); } catch (final Throwable f) { LOG.error("Error during object reaper cleanup for object: " + " uuid= " + obj.getObjectUuid(), f); } if (interrupted) { break; } } } catch (Exception e) { LOG.warn("Error encountered during reaping of deleting-state object. Will retry on next cycle", e); } } private void cleanFailed() { try { List<ObjectEntity> entitiesToClean = ObjectMetadataManagers.getInstance().lookupFailedObjects(); LOG.trace("Reaping " + entitiesToClean.size() + " objects with expired creation time from backend"); for (ObjectEntity obj : entitiesToClean) { try { reapObject(obj); } catch (final Throwable f) { LOG.error("Error during object reaper cleanup for object: " + " uuid= " + obj.getObjectUuid(), f); } if (interrupted) { break; } } } catch (Exception e) { LOG.warn("Error encountered during reaping of deleting-state object. Will retry on next cycle", e); } } private void cleanParts() { // For multipart upload. These are parts that are duplicates or are not the latest according to timestamp and have been marked for deletion. try { List<PartEntity> partsToClean = MpuPartMetadataManagers.getInstance().lookupPartsInState(null, null, null, ObjectState.deleting); LOG.trace("Reaping " + partsToClean.size() + " parts from backend"); for (PartEntity part : partsToClean) { try { reapPart(part); } catch (final Throwable f) { LOG.error("Error during part reaper cleanup for part: " + part.getBucket().getBucketName() + " uploadId: " + part.getUploadId() + " partNumber: " + part.getPartNumber() + " uuid= " + part.getPartUuid(), f); } if (interrupted) { break; } } } catch (Exception e) { LOG.warn("Error cleaning parts. Will retry later.", e); } } public void reapPart(final PartEntity part) throws Exception { // we don't care about the backend here, because the backend will handle GC'ing parts // on its own. try { Transactions.delete(part); } catch (TransactionException e) { LOG.error("Unable to drop part: " + part.getBucket().getBucketName() + " uploadId: " + part.getUploadId() + " partNumber: " + part.getPartNumber() + " uuid: " + part.getPartUuid()); } } }