/*************************************************************************
* 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;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import com.eucalyptus.auth.principal.User;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.objectstorage.entities.Bucket;
import com.eucalyptus.objectstorage.exceptions.IllegalResourceStateException;
import com.eucalyptus.objectstorage.exceptions.MetadataOperationFailureException;
import com.eucalyptus.objectstorage.exceptions.NoSuchEntityException;
import com.eucalyptus.objectstorage.exceptions.s3.BucketAlreadyExistsException;
import com.eucalyptus.objectstorage.exceptions.s3.InternalErrorException;
import com.eucalyptus.objectstorage.exceptions.s3.NoSuchBucketException;
import com.eucalyptus.objectstorage.exceptions.s3.S3Exception;
import com.eucalyptus.objectstorage.msgs.CreateBucketResponseType;
import com.eucalyptus.objectstorage.msgs.CreateBucketType;
import com.eucalyptus.objectstorage.msgs.DeleteBucketResponseType;
import com.eucalyptus.objectstorage.msgs.DeleteBucketType;
import com.eucalyptus.objectstorage.providers.ObjectStorageProviderClient;
import com.eucalyptus.storage.msgs.s3.AccessControlList;
import com.eucalyptus.util.EucalyptusCloudException;
import com.google.common.base.Predicate;
public class BucketFactoryImpl implements BucketFactory {
private static final Logger LOG = Logger.getLogger(BucketFactoryImpl.class);
@Override
public Bucket createBucket(@Nonnull ObjectStorageProviderClient backendProvider, @Nonnull Bucket bucket, @Nullable String correlationId,
@Nullable User requestUser) throws S3Exception {
// Ensure not null for logging. CorrelationId is not required, just used to track user requests.
if (correlationId == null) {
correlationId = "unknown";
}
// Initiate metadata creation. State = 'creating'
try {
// The bucket must either be created in 'creating' state or exception thrown.
bucket = BucketMetadataManagers.getInstance().transitionBucketToState(bucket, BucketState.creating);
} catch (IllegalResourceStateException e) {
throw new BucketAlreadyExistsException(bucket.getBucketName());
} catch (MetadataOperationFailureException | ConstraintViolationException e) {
throw new BucketAlreadyExistsException(bucket.getBucketName());
} catch (Exception e) {
// Failed metadata operation, not an expected error
LOG.error("Error initiating bucket creation in metadata. Failing operation", e);
InternalErrorException ex = new InternalErrorException(bucket.getBucketName());
ex.initCause(e);
throw ex;
}
if (bucket == null) {
LOG.error("CorrelationId: " + correlationId
+ "Unexpected internal error. Got null bucket when not expected. Cannot continue bucket creation for " + bucket.getBucketName());
throw new InternalErrorException();
}
if (BucketState.creating.equals(bucket.getState())) {
// Do backend operation
CreateBucketResponseType backendResponse = null;
try {
CreateBucketType request = new CreateBucketType();
request.setAccessControlList(new AccessControlList());
request.setBucket(bucket.getBucketUuid());
request.setUser(requestUser);
backendResponse = backendProvider.createBucket(request);
// Finalize bucket state. Set State = 'extant'
return BucketMetadataManagers.getInstance().transitionBucketToState(bucket, BucketState.extant);
} catch (EucalyptusCloudException e) {
LOG.error("Bucket creation failed due to error response from backend.", e);
if (e instanceof S3Exception) {
// TODO: need to translate this error to ensure it is not raw backend response
LOG.error("Error creating bucket " + bucket.getBucketName(), e);
throw (S3Exception) e;
} else {
InternalErrorException ex = new InternalErrorException();
ex.initCause(e);
throw ex;
}
} catch (Exception e) {
LOG.warn("Unknown exception caused failure of CreateBucket for bucket " + bucket.getBucketName(), e);
InternalErrorException ex = new InternalErrorException(bucket.getBucketName());
ex.initCause(e);
throw ex;
}
} else {
// In some other state that we don't expect. Bail, it already exists
throw new BucketAlreadyExistsException(bucket.getBucketName());
}
}
@Override
public void deleteBucket(@Nonnull final ObjectStorageProviderClient backendProvider, @Nonnull Bucket bucketToDelete,
@Nullable final String correlationId, @Nullable User requestUser) throws S3Exception {
Bucket deletingBucket;
try {
deletingBucket = BucketMetadataManagers.getInstance().transitionBucketToState(bucketToDelete, BucketState.deleting);
} catch (IllegalResourceStateException e) {
LOG.trace("CorrelationId: " + correlationId + " Unexpected resource state on delete update.", e);
throw e;
} catch (MetadataOperationFailureException e) {
LOG.trace("CorrelationId: " + correlationId + " Could not transition bucket " + bucketToDelete.toString() + " to 'deleting' state.", e);
throw e;
}
/*
* Remove the entities that may refer to the bucket record. Parts cannot be explicitly deleted in S3, they are removed when the bucket is deleted.
* So we flush the metadata here
*/
try {
// Remove all pending uploads
ObjectMetadataManagers.getInstance().flushUploads(bucketToDelete);
// Delete all parts and pending MPU uploads
MpuPartMetadataManagers.getInstance().flushAllParts(bucketToDelete);
// Delete the lifecycle configuration
BucketLifecycleManagers.getInstance().deleteLifecycleRules(bucketToDelete.getBucketUuid());
// Delete the bucket tagging
BucketTaggingManagers.getInstance().deleteBucketTagging(bucketToDelete.getBucketUuid());
} catch (Exception e) {
LOG.warn("Error flushing MPU parts during bucket deletion operation", e);
throw new InternalErrorException(e);
}
Predicate<Bucket> deleteBucket = new Predicate<Bucket>() {
public boolean apply(Bucket bucket) {
DeleteBucketResponseType response;
DeleteBucketType deleteRequest = new DeleteBucketType();
deleteRequest.setBucket(bucket.getBucketUuid());
try {
backendProvider.deleteBucket(deleteRequest); // should throw an exception on any failure
} catch (NoSuchEntityException | NoSuchBucketException e) {
// Ok, fall through
} catch (S3Exception e) {
if (!HttpResponseStatus.NOT_FOUND.equals(e.getStatus())) {
LOG.warn("Got error during bucket cleanup. Will retry", e);
return false;
}
}
try {
BucketMetadataManagers.getInstance().deleteBucketMetadata(bucket);
return true;
} catch (Exception e) {
LOG.warn("Error removing bucket metadata for bucket " + bucket.getBucketUuid() + " Will retry later", e);
}
return false;
}
};
try {
Entities.asTransaction(Bucket.class, deleteBucket).apply(deletingBucket);
} catch (Exception e) {
try {
Bucket foundBucket = BucketMetadataManagers.getInstance().lookupBucket(bucketToDelete.getBucketName());
LOG.trace("CorrelationId: " + correlationId + " Error deleting bucket " + bucketToDelete.toString(), e);
throw new InternalErrorException(bucketToDelete.getBucketName());
} catch (Exception ex) {
// Bucket not found. Success!
}
}
}
}