/* * 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 java.util.Random; import org.apache.log4j.Logger; import com.eucalyptus.objectstorage.BucketMetadataManagers; import com.eucalyptus.objectstorage.BucketState; import com.eucalyptus.objectstorage.ObjectMetadataManagers; import com.eucalyptus.objectstorage.OsgBucketFactory; import com.eucalyptus.objectstorage.PaginatedResult; import com.eucalyptus.objectstorage.entities.Bucket; import com.eucalyptus.objectstorage.entities.ObjectEntity; import com.eucalyptus.objectstorage.entities.ObjectStorageGlobalConfiguration; import com.eucalyptus.objectstorage.providers.ObjectStorageProviders; import com.eucalyptus.storage.config.ConfigurationCache; /** * Scans metadata for each objects in a bucket and cleans history for each Many of these may be running concurrently. Has a self-imposed timeout of 30 * seconds. * * This should be more than sufficient given that it only manipulates metadata and never interacts with the backend. * */ public class BucketReaperTask implements Runnable { private static final Logger LOG = Logger.getLogger(BucketReaperTask.class); private long startTime; private static final long MAX_TASK_DURATION = 30 * 1000; // 30 seconds private static final Random rand = new Random(System.currentTimeMillis()); private boolean interrupted = false; public BucketReaperTask() {} // Does a single scan of all objects in the bucket and does history cleanup on each @Override public void run() { startTime = System.currentTimeMillis(); try { LOG.trace("Initiating bucket cleanup task"); final List<Bucket> buckets = BucketMetadataManagers.getInstance().lookupBucketsByState(null); if (buckets == null || buckets.size() <= 0) { LOG.trace("No buckets found to clean. Cleanup task complete"); return; } Bucket b; // Randomly iterate through int idx; while (buckets.size() > 0 && !isTimedOut() && !interrupted) { idx = rand.nextInt(buckets.size()); b = buckets.get(idx); cleanObjectHistoriesInBucket(b); resolveBucketState(b); buckets.remove(idx); } } catch (final Throwable f) { LOG.error("Error during bucket cleanup execution. Will retry later", f); } finally { try { long endTime = System.currentTimeMillis(); LOG.trace("Bucket cleanup execution task took " + Long.toString(endTime - startTime) + "ms to complete"); } catch (final Throwable f) { // Do nothing, but don't allow exceptions out } } } public void interrupt() { this.interrupted = true; } public void resume() { this.interrupted = false; } /** * Fixes the state of the bucket. If in 'deleting' state, will issue deletion to backend. And remove the bucket. If in 'creating' state that is * expired (by timestamp), will issue delete to backend and update state * * @param bucket */ private void resolveBucketState(Bucket bucket) { LOG.trace("Resolving bucket state for bucket uuid " + bucket.getBucketUuid()); if (BucketState.deleting.equals(bucket.getState()) || !bucket.stateStillValid(ConfigurationCache.getConfiguration(ObjectStorageGlobalConfiguration.class) .getBucket_creation_wait_interval_seconds())) { // Clean-up a bucket marked for deletion. This usually indicates a failed delete operation previously LOG.trace("Deleting backend bucket for bucket uuid " + bucket.getBucketUuid() + " during bucket cleanup"); try { OsgBucketFactory.getFactory().deleteBucket(ObjectStorageProviders.getInstance(), bucket, null, null); } catch (Exception e) { LOG.error("Error cleaning deletion marked bucketuuid " + bucket.getBucketUuid(), e); } } } private boolean isTimedOut() { return System.currentTimeMillis() - startTime >= MAX_TASK_DURATION; } protected void cleanObjectHistoriesInBucket(Bucket b) { String nextKey = null; final int chunkSize = 1000; PaginatedResult<ObjectEntity> result = null; LOG.trace("Cleaning object histories for bucket uuid " + b.getBucketUuid()); do { try { result = ObjectMetadataManagers.getInstance().listPaginated(b, chunkSize, null, null, nextKey); } catch (final Throwable f) { LOG.error("Could not get object listing for bucket " + b.getBucketName() + " with next marker: " + nextKey); nextKey = null; result = null; break; } INNER: for (ObjectEntity obj : result.getEntityList()) { try { ObjectMetadataManagers.getInstance().cleanupInvalidObjects(b, obj.getObjectKey()); } catch (final Throwable f) { LOG.error("Error doing async repair of object " + b.getBucketName() + "/" + obj.getObjectKey() + " Continuing to next object", f); } if (interrupted) { break INNER; } } if (!interrupted && result.getIsTruncated()) { nextKey = ((ObjectEntity) result.getLastEntry()).getObjectKey(); } else { nextKey = null; } } while (nextKey != null && !isTimedOut()); } }