/*************************************************************************
* 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.entities;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.PersistenceContext;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.apache.log4j.Logger;
import org.hibernate.annotations.Type;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.configurable.ConfigurableFieldType;
import com.eucalyptus.configurable.ConfigurableInit;
import com.eucalyptus.entities.AbstractPersistent;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.objectstorage.ObjectStorage;
import com.eucalyptus.objectstorage.util.OSGUtil;
import com.eucalyptus.storage.config.CacheableConfiguration;
import com.eucalyptus.upgrade.Upgrades.EntityUpgrade;
import com.eucalyptus.upgrade.Upgrades.Version;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.LockResource;
import com.google.common.base.Predicate;
@Entity
@PersistenceContext(name = "eucalyptus_osg")
@Table(name = "s3provider_config")
@ConfigurableClass(root = "objectstorage.s3provider", alias = "backendconfig", description = "Configuration for S3-compatible backend",
singleton = true)
public class S3ProviderConfiguration extends AbstractPersistent implements CacheableConfiguration<S3ProviderConfiguration> {
@Transient
private static final String DEFAULT_S3_ENDPOINT = "uninitialized-s3-endpoint";
@Transient
private static final Logger LOG = Logger.getLogger(S3ProviderConfiguration.class);
@Transient
private static final boolean DEFAULT_BACKEND_DNS = false;
@Transient
private static final Boolean DEFAULT_BACKEND_HTTPS = false;
@Transient
private static final String DEFAULT_S3_HEAD_RESPONSE = "405";
@ConfigurableField(description = "External S3 endpoint.", displayName = "s3_endpoint", initial = DEFAULT_S3_ENDPOINT)
@Column(name = "endpoint")
protected String S3Endpoint;
@ConfigurableField(description = "Local Store S3 Access Key.", displayName = "s3_access_key", type = ConfigurableFieldType.KEYVALUEHIDDEN)
@Column(name = "access_key")
protected String S3AccessKey;
@ConfigurableField(description = "Local Store S3 Secret Key.", displayName = "s3_secret_key", type = ConfigurableFieldType.KEYVALUEHIDDEN)
@Column(name = "secret_key")
@Lob
@Type(type = "org.hibernate.type.StringClobType")
protected String S3SecretKey;
@Transient
private String decryptedS3SecretKey = null;
@Transient
private ReentrantReadWriteLock S3SecretKeyLock = new ReentrantReadWriteLock();
@ConfigurableField(description = "Use HTTPS for communication to service backend.", displayName = "use_https", initial = "false",
type = ConfigurableFieldType.BOOLEAN)
@Column(name = "use_https")
protected Boolean S3UseHttps;
@ConfigurableField(description = "Use DNS virtual-hosted-style bucket names for communication to service backend.", displayName = "use_backend_dns",
initial = "false", type = ConfigurableFieldType.BOOLEAN)
@Column(name = "use_backend_dns")
protected Boolean S3UseBackendDns;
@ConfigurableField(description = "HTTP response code for HEAD operation on S3 endpoint. Default value is 405",
displayName = "s3_endpoint_heartbeat_response", initial = DEFAULT_S3_HEAD_RESPONSE)
@Column(name = "endpoint_head_response")
protected Integer S3EndpointHeadResponse;
public Boolean getS3UseBackendDns() {
return S3UseBackendDns;
}
public void setS3UseBackendDns(Boolean useDns) {
S3UseBackendDns = useDns;
}
public String getS3AccessKey() {
return S3AccessKey;
}
public void setS3AccessKey(String s3AccessKey) {
S3AccessKey = s3AccessKey;
}
/* Returns decrypted S3SecretKey */
public String getS3SecretKey() throws Exception {
if (this.S3SecretKey != null) {
try(final LockResource rlock = LockResource.lock(S3SecretKeyLock.readLock())) {
if (decryptedS3SecretKey == null) {
rlock.close();
LOG.trace("There is no stored decrypted S3 Secret Key. Decrypting...");
try(final LockResource lock = LockResource.lock(S3SecretKeyLock.writeLock())) {
decryptedS3SecretKey = OSGUtil.decryptWithComponentPrivateKey(ObjectStorage.class, this.S3SecretKey);
return decryptedS3SecretKey;
} catch (EucalyptusCloudException ex) {
LOG.error(ex);
throw ex;
}
}
return decryptedS3SecretKey;
}
} else {
return null;
}
}
public void setS3SecretKey(String s3SecretKey) throws Exception {
if (s3SecretKey != null) {
try(final LockResource lock = LockResource.lock(S3SecretKeyLock.writeLock())) {
String val = s3SecretKey;
s3SecretKey = OSGUtil.encryptWithComponentPublicKey(ObjectStorage.class, s3SecretKey);
decryptedS3SecretKey = val;
} catch (EucalyptusCloudException ex) {
LOG.error(ex);
throw ex;
}
}
this.S3SecretKey = s3SecretKey;
}
public Boolean getS3UseHttps() {
return S3UseHttps;
}
public void setS3UseHttps(Boolean s3UseHttps) {
S3UseHttps = s3UseHttps;
}
public String getS3Endpoint() {
return S3Endpoint;
}
public void setS3Endpoint(String endPoint) {
S3Endpoint = endPoint;
}
public Integer getS3EndpointHeadResponse() {
return S3EndpointHeadResponse;
}
public void setS3EndpointHeadResponse(Integer s3EndpointHeadResponse) {
S3EndpointHeadResponse = s3EndpointHeadResponse;
}
@ConfigurableInit
public S3ProviderConfiguration initializeDefaults() {
this.setS3Endpoint(DEFAULT_S3_ENDPOINT);
this.setS3UseBackendDns(DEFAULT_BACKEND_DNS);
this.setS3UseHttps(DEFAULT_BACKEND_HTTPS);
this.setS3EndpointHeadResponse(Integer.valueOf(DEFAULT_S3_HEAD_RESPONSE));
return this;
}
@PrePersist
@PreUpdate
public void updateDefaults() {
if (this.S3EndpointHeadResponse == null) {
this.S3EndpointHeadResponse = Integer.valueOf(DEFAULT_S3_HEAD_RESPONSE);
}
}
public static S3ProviderConfiguration getS3ProviderConfiguration() {
try {
try {
return Transactions.find(new S3ProviderConfiguration());
} catch (NoSuchElementException enf) {
LOG.info("No extant S3 provider configuration found. Initializing defaults");
return Transactions.saveDirect(new S3ProviderConfiguration().initializeDefaults());
}
} catch (Throwable f) {
LOG.error("exception occurred while retrieving S3 provider configuration", f);
throw Exceptions.toUndeclared("Error getting/initializing s3 provider configuration", f);
}
}
@Override
public S3ProviderConfiguration getLatest() {
return getS3ProviderConfiguration();
}
@EntityUpgrade( entities = S3ProviderConfiguration.class, since = Version.v4_3_0, value = ObjectStorage.class )
public enum S3ProviderConfigurationUpgrade430 implements Predicate<Class> {
INSTANCE;
private static final Logger LOG = Logger.getLogger(S3ProviderConfigurationUpgrade430.class);
@Override
public boolean apply( final Class entityClass ) {
try ( final TransactionResource tx = Entities.transactionFor( S3ProviderConfiguration.class ) ) {
final S3ProviderConfiguration configuration = S3ProviderConfiguration.getS3ProviderConfiguration( );
if ( configuration.getS3EndpointHeadResponse( ) == null ) {
LOG.info( "Initializing S3EndpointHeadResponse to default value " + DEFAULT_S3_HEAD_RESPONSE );
configuration.updateDefaults( );
tx.commit( );
}
return true;
} catch (final Exception ex) {
throw Exceptions.toUndeclared(ex);
}
}
}
}