/*************************************************************************
* Copyright 2009-2013 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.metadata;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityTransaction;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.PersistentObjectException;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.exception.ConstraintViolationException;
import com.eucalyptus.auth.PolicyParseException;
import com.eucalyptus.auth.policy.PolicyParser;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.objectstorage.BucketState;
import com.eucalyptus.objectstorage.entities.Bucket;
import com.eucalyptus.objectstorage.entities.Bucket_;
import com.eucalyptus.objectstorage.exceptions.IllegalResourceStateException;
import com.eucalyptus.objectstorage.exceptions.InvalidMetadataException;
import com.eucalyptus.objectstorage.exceptions.MetadataOperationFailureException;
import com.eucalyptus.objectstorage.exceptions.NoSuchEntityException;
import com.eucalyptus.objectstorage.exceptions.s3.NoSuchBucketException;
import com.eucalyptus.objectstorage.exceptions.s3.S3Exception;
import com.eucalyptus.objectstorage.util.ObjectStorageProperties.VersioningStatus;
import com.eucalyptus.storage.msgs.s3.AccessControlPolicy;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import javaslang.control.Option;
/**
* Three types of failures on a metadata operation: IllegalResourceState - entity is not in a state where the update is a valid change (e.g.
* deleting->creating) EntityNotFound - the entity is no longer found on the backend (e.g. deleted while other update pending)
* MetadataOperationFailureException - the operation could not complete, db failure, etc
*
*/
public class DbBucketMetadataManagerImpl implements BucketMetadataManager {
private static final Logger LOG = Logger.getLogger(DbBucketMetadataManagerImpl.class);
public void start() throws Exception {}
public void stop() throws Exception {}
@Override
public Bucket persistBucketInCreatingState(@Nonnull String bucketName, @Nonnull AccessControlPolicy acp, @Nullable String iamUserId,
@Nullable String location) throws IllegalResourceStateException, MetadataOperationFailureException, NoSuchEntityException {
Bucket initialized;
try {
initialized = Bucket.getInitializedBucket(bucketName, iamUserId, acp, location);
} catch (Exception e) {
throw new MetadataOperationFailureException(e);
}
return transitionBucketToState(initialized, BucketState.creating);
}
@Override
public Bucket lookupExtantBucket(@Nonnull String bucketName) throws NoSuchEntityException, MetadataOperationFailureException {
try {
Bucket searchExample = new Bucket(bucketName).withState(BucketState.extant);
return Transactions.find(searchExample);
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(bucketName);
} catch (Exception e) {
LOG.warn("Error querying bucket existence in db", e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public Bucket lookupBucket(@Nonnull String bucketName) throws NoSuchEntityException, MetadataOperationFailureException {
try {
Bucket searchExample = new Bucket(bucketName);
return Transactions.find(searchExample);
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(bucketName);
} catch (Exception e) {
LOG.warn("Error querying bucket existence in db", e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public Bucket lookupBucketByUuid(@Nonnull String bucketUuid) throws NoSuchEntityException, MetadataOperationFailureException {
try {
Bucket searchExample = new Bucket().withUuid(bucketUuid);
return Transactions.find(searchExample);
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(bucketUuid);
} catch (Exception e) {
LOG.warn("Error querying bucket existence in db", e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public List<Bucket> getBucketsForDeletion() throws Exception {
try {
// Rely on uniqueness contraint that only one record can be in
// either creating or extant state with that bucket name
Bucket searchBucket = new Bucket().withState(BucketState.deleting);
return Transactions.findAll(searchBucket);
} catch (NoSuchElementException e) {
throw e;
} catch (Exception e) {
LOG.error("Error querying bucket existence in db", e);
throw e;
}
}
@Override
public Bucket transitionBucketToState(@Nonnull final Bucket bucket, @Nonnull BucketState destState) throws NoSuchEntityException,
IllegalResourceStateException, MetadataOperationFailureException {
Function<Bucket, Bucket> transitionFunction = null;
switch (destState) {
case creating:
transitionFunction = BucketStateTransitions.TRANSITION_TO_CREATING;
break;
case extant:
transitionFunction = BucketStateTransitions.TRANSITION_TO_EXTANT;
break;
case deleting:
transitionFunction = BucketStateTransitions.TRANSITION_TO_DELETING;
break;
default:
LOG.error("Unexpected destination state: " + destState);
throw new IllegalArgumentException();
}
try {
return Entities.asTransaction(Bucket.class, transitionFunction).apply(bucket);
} catch (IllegalResourceStateException e) {
throw e;
} catch (ConstraintViolationException e) {
IllegalResourceStateException ex = new IllegalResourceStateException();
ex.initCause(e);
throw ex;
} catch (PersistentObjectException e) {
// Object passed for merge is not found on the db.
throw new NoSuchEntityException("Bucket entity not found for merge", e);
} catch (Exception e) {
throw new MetadataOperationFailureException(e);
}
}
@Override
public void deleteBucketMetadata(@Nonnull final Bucket bucket) throws Exception {
try (TransactionResource trans = Entities.transactionFor(Bucket.class)) {
Bucket bucketToDelete = Entities.uniqueResult(bucket);
if (BucketState.deleting.equals(bucketToDelete.getState())) {
// Remove the record.
Entities.delete(bucketToDelete);
} else {
throw new IllegalResourceStateException("Bucket not in deleting state, no valid transition to deleted", null,
BucketState.deleting.toString(), bucketToDelete.getState().toString());
}
trans.commit();
} catch (NoSuchElementException e) {
// Ok, continue.
LOG.trace("Bucket deletion finalization for (bucket uuid) " + bucket.getBucketUuid() + " failed to find entity record. Returning normally");
}
}
@Override
public List<Bucket> lookupBucketsByOwner(String ownerCanonicalId) throws MetadataOperationFailureException {
Bucket searchBucket = new Bucket().withState(BucketState.extant);
searchBucket.setOwnerCanonicalId(ownerCanonicalId);
List<Bucket> buckets = null;
try (TransactionResource trans = Entities.transactionFor(Bucket.class)) {
Criteria searchCriteria = Entities.createCriteria(Bucket.class);
Example example = Example.create(searchBucket);
searchCriteria.add(example);
searchCriteria.addOrder(Order.asc("bucketName"));
searchCriteria.setReadOnly(true);
buckets = searchCriteria.list();
trans.commit();
return buckets;
} catch (Exception e) {
LOG.error("Error listing buckets for user " + ownerCanonicalId + " due to DB transaction error", e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public List<Bucket> lookupBucketsByState(BucketState state) throws TransactionException {
Bucket searchBucket = new Bucket().withState(state);
List<Bucket> buckets = null;
try (TransactionResource trans = Entities.transactionFor(Bucket.class)) {
Criteria searchCriteria = Entities.createCriteria(Bucket.class);
Example example = Example.create(searchBucket);
searchCriteria.add(example);
searchCriteria.addOrder(Order.asc("bucketName"));
searchCriteria.setReadOnly(true);
buckets = searchCriteria.list();
trans.commit();
return buckets;
} catch (Exception e) {
LOG.error("Error listing buckets in the state: " + state + " due to DB transaction error", e);
throw e;
}
}
@Override
public List<Bucket> lookupBucketsByUser(String userIamId) throws MetadataOperationFailureException {
Bucket searchBucket = new Bucket().withState(BucketState.extant);
searchBucket.setOwnerIamUserId(userIamId);
List<Bucket> buckets = null;
try {
buckets = Transactions.findAll(searchBucket);
return buckets;
} catch (TransactionException e) {
LOG.error("Error listing buckets for user " + userIamId + " due to DB transaction error", e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public long countBucketsByUser(String userIamId) throws MetadataOperationFailureException {
Bucket searchBucket = new Bucket();
searchBucket.setOwnerIamUserId(userIamId);
try (TransactionResource db = Entities.transactionFor(Bucket.class)) {
return Entities.count(searchBucket, Restrictions.ne("state", BucketState.deleting), new HashMap<String, String>());
} catch (Exception e) {
LOG.warn("Error counting buckets for user " + userIamId + " due to DB transaction error", e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public long countBucketsByAccount(String canonicalId) throws MetadataOperationFailureException {
Bucket searchBucket = new Bucket();
searchBucket.setOwnerCanonicalId(canonicalId);
try (TransactionResource db = Entities.transactionFor(Bucket.class)) {
return Entities.count(searchBucket, Restrictions.ne("state", BucketState.deleting), new HashMap<String, String>());
} catch (Exception e) {
LOG.warn("Error counting buckets for account canonicalId " + canonicalId + " due to DB transaction error", e);
throw new MetadataOperationFailureException(e);
}
}
/**
* For internal use only (copying, etc)
*
* @param bucketEntity
* @param jsonMarshalledAcl
* @return
* @throws MetadataOperationFailureException
* @throws NoSuchEntityException
*/
protected Bucket setAcp(@Nonnull Bucket bucketEntity, @Nonnull String jsonMarshalledAcl) throws MetadataOperationFailureException,
NoSuchEntityException {
try (TransactionResource trans = Entities.transactionFor(Bucket.class)) {
Bucket bucket = Entities.merge(bucketEntity);
bucket.setAcl(jsonMarshalledAcl);
trans.commit();
return bucket;
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(bucketEntity.getBucketName());
} catch (Exception e) {
LOG.error("Error updating acl for bucket " + bucketEntity.getBucketName(), e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public Bucket setAcp(@Nonnull Bucket bucketEntity, @Nonnull AccessControlPolicy acp) throws MetadataOperationFailureException,
NoSuchEntityException {
try (TransactionResource trans = Entities.transactionFor(Bucket.class)) {
Bucket bucket = Entities.merge(bucketEntity);
bucket.setAcl(acp);
trans.commit();
return bucket;
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(bucketEntity.getBucketName());
} catch (Exception e) {
LOG.error("Error updating acl for bucket " + bucketEntity.getBucketName(), e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public Bucket setLoggingStatus(@Nonnull Bucket bucketEntity, @Nonnull Boolean loggingEnabled, @Nullable String destBucket,
@Nullable String destPrefix) throws TransactionException, S3Exception {
EntityTransaction db = Entities.get(Bucket.class);
try {
Bucket bucket = Entities.uniqueResult(bucketEntity);
bucket.setLoggingEnabled(loggingEnabled);
bucket.setTargetBucket(destBucket);
bucket.setTargetPrefix(destPrefix);
db.commit();
return bucket;
} catch (NoSuchElementException e) {
throw new NoSuchBucketException(bucketEntity.getBucketName());
} catch (TransactionException e) {
LOG.error("Transaction error updating acl for bucket " + bucketEntity.getBucketName(), e);
throw e;
} finally {
if (db != null && db.isActive()) {
db.rollback();
}
}
}
@Override
public Bucket setVersioning(@Nonnull Bucket bucketEntity, @Nonnull VersioningStatus newState) throws IllegalResourceStateException,
MetadataOperationFailureException, NoSuchEntityException {
try (TransactionResource trans = Entities.transactionFor(Bucket.class)) {
Bucket bucket = Entities.uniqueResult(new Bucket().withUuid(bucketEntity.getBucketUuid()));
if (VersioningStatus.Disabled.equals(newState)) {
// The user cannot ever set 'Disabled'.
throw new IllegalResourceStateException("Invalid versioning state transition");
}
bucket.setVersioning(newState);
trans.commit();
return bucket;
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(bucketEntity.getBucketName());
} catch (TransactionException e) {
LOG.error("Transaction error updating versioning state for bucket " + bucketEntity.getBucketName(), e);
throw new MetadataOperationFailureException(e);
}
}
@Override
public Bucket setPolicy(
@Nonnull Bucket bucketEntity,
@Nonnull Option<String> policy
) throws NoSuchEntityException, InvalidMetadataException {
// validate policy
final String policyText;
if ( policy.isDefined( ) ) try {
PolicyParser.getResourceInstance( ).parse( policy.get( ) );
policyText = PolicyParser.getResourceInstance( ).normalize( policy.get( ) );
} catch ( final PolicyParseException e ) {
throw new InvalidMetadataException( e.getMessage( ), e );
} else {
policyText = null;
}
// update bucket
try ( final TransactionResource trans = Entities.transactionFor( Bucket.class ) ) {
final Bucket bucket = Entities.criteriaQuery( Bucket.class )
.whereEqual( Bucket_.bucketUuid, bucketEntity.getBucketUuid( ) ).uniqueResult( );
bucket.setPolicy( policyText );
trans.commit();
return bucket;
} catch ( final NoSuchElementException e ) {
throw new NoSuchEntityException(bucketEntity.getBucketName());
}
}
@Override
public long totalSizeOfAllBuckets() throws MetadataOperationFailureException {
long size = -1;
try (TransactionResource db = Entities.transactionFor(Bucket.class)) {
size =
Objects.firstNonNull(
(Number) Entities.createCriteria(Bucket.class).setProjection(Projections.sum("bucketSize")).setReadOnly(true).uniqueResult(), 0)
.longValue();
db.commit();
} catch (Exception e) {
LOG.warn("Error getting buckets cumulative size", e);
throw new MetadataOperationFailureException(e);
}
return size;
}
}