
org.duracloud.openstackstorage.OpenStackStorageProvider Maven / Gradle / Ivy
The newest version!
/*
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://duracloud.org/license/
*/
package org.duracloud.openstackstorage;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Module;
import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;
import org.duracloud.common.stream.ChecksumInputStream;
import org.duracloud.common.util.ChecksumUtil;
import org.duracloud.common.util.DateUtil;
import org.duracloud.storage.domain.ContentIterator;
import org.duracloud.storage.error.ChecksumMismatchException;
import org.duracloud.storage.error.NotFoundException;
import org.duracloud.storage.error.StorageException;
import org.duracloud.storage.provider.StorageProvider;
import org.duracloud.storage.provider.StorageProviderBase;
import org.duracloud.storage.util.StorageProviderUtil;
import org.jclouds.Constants;
import org.jclouds.ContextBuilder;
import org.jclouds.blobstore.ContainerNotFoundException;
import org.jclouds.blobstore.domain.PageSet;
import org.jclouds.enterprise.config.EnterpriseConfigurationModule;
import org.jclouds.openstack.swift.CopyObjectException;
import org.jclouds.openstack.swift.SwiftApiMetadata;
import org.jclouds.openstack.swift.SwiftClient;
import org.jclouds.openstack.swift.domain.ContainerMetadata;
import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata;
import org.jclouds.openstack.swift.domain.ObjectInfo;
import org.jclouds.openstack.swift.domain.SwiftObject;
import org.jclouds.openstack.swift.options.CreateContainerOptions;
import org.jclouds.openstack.swift.options.ListContainerOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.duracloud.storage.error.StorageException.NO_RETRY;
import static org.duracloud.storage.error.StorageException.RETRY;
import static org.duracloud.storage.util.StorageProviderUtil.compareChecksum;
/**
* Provides content storage access to OpenStack storage providers.
*
* @author Bill Branan
*/
public abstract class OpenStackStorageProvider extends StorageProviderBase {
private static final Logger log =
LoggerFactory.getLogger(OpenStackStorageProvider.class);
private SwiftClient swiftClient = null;
public OpenStackStorageProvider(String username,
String apiAccessKey,
String authUrl) {
if (null == authUrl) {
authUrl = getAuthUrl();
}
try {
String trimmedAuthUrl = // JClouds expects authURL with no version
authUrl.substring(0, authUrl.lastIndexOf("/"));
ListeningExecutorService useExecutor = createThreadPool();
ListeningExecutorService ioExecutor = createThreadPool();
Iterable modules = ImmutableSet. of(
new EnterpriseConfigurationModule(useExecutor, ioExecutor));
Properties properties = new Properties();
properties.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER,
"true");
swiftClient = ContextBuilder.newBuilder(new SwiftApiMetadata())
.endpoint(trimmedAuthUrl)
.credentials(username, apiAccessKey)
.modules(modules)
.overrides(properties)
.buildApi(SwiftClient.class);
} catch (Exception e) {
String err = "Could not connect to " + getProviderName() +
" due to error: " + e.getMessage();
throw new StorageException(err, e, RETRY);
}
}
protected ListeningExecutorService createThreadPool() {
return MoreExecutors.listeningDecorator(
new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
5L,
TimeUnit.SECONDS,
new SynchronousQueue()));
}
public OpenStackStorageProvider(String username, String apiAccessKey) {
this(username, apiAccessKey, null);
}
public OpenStackStorageProvider(SwiftClient swiftClient) {
this.swiftClient = swiftClient;
}
public abstract String getAuthUrl();
public abstract String getProviderName();
/**
* {@inheritDoc}
*/
public Iterator getSpaces() {
log.debug("getSpace()");
Set containers =
swiftClient.listContainers(ListContainerOptions.NONE);
List spaces = new ArrayList();
for (ContainerMetadata container : containers) {
String containerName = container.getName();
spaces.add(containerName);
}
return spaces.iterator();
}
/**
* {@inheritDoc}
*/
public Iterator getSpaceContents(String spaceId,
String prefix) {
log.debug("getSpaceContents(" + spaceId + ", " + prefix);
throwIfSpaceNotExist(spaceId);
return new ContentIterator(this, spaceId, prefix);
}
/**
* {@inheritDoc}
*/
public List getSpaceContentsChunked(String spaceId,
String prefix,
long maxResults,
String marker) {
log.debug("getSpaceContentsChunked(" + spaceId + ", " + prefix + ", " +
maxResults + ", " + marker + ")");
throwIfSpaceNotExist(spaceId);
if(maxResults <= 0) {
maxResults = StorageProvider.DEFAULT_MAX_RESULTS;
}
List spaceContents =
getCompleteSpaceContents(spaceId, prefix, maxResults, marker);
return spaceContents;
}
private List getCompleteSpaceContents(String spaceId,
String prefix,
long maxResults,
String marker) {
String containerName = getContainerName(spaceId);
PageSet objects = listObjects(containerName,
prefix,
maxResults,
marker);
List contentItems = new ArrayList();
for (ObjectInfo object : objects) {
contentItems.add(object.getName());
}
return contentItems;
}
private PageSet listObjects(String containerName,
String prefix,
long maxResults,
String marker) {
int limit = new Long(maxResults).intValue();
ListContainerOptions containerOptions = ListContainerOptions.Builder.maxResults(limit);
if(marker != null) containerOptions.afterMarker(sanitizeForURI(marker));
if(prefix != null) containerOptions.withPrefix(sanitizeForURI(prefix));
return swiftClient.listObjects(containerName, containerOptions);
}
private void throwIfContentNotExist(String spaceId, String contentId) {
log.debug("throwIfContentNotExist({}, {})", spaceId, contentId);
String containerName = getContainerName(spaceId);
boolean exists = false;
try {
String encContentId = sanitizeForURI(contentId);
exists = swiftClient.objectExists(containerName, encContentId);
} catch (ContainerNotFoundException e) {
log.debug("object does not exist: {}, {}", containerName, contentId);
String errMsg = createNotFoundMsg(containerName, contentId);
throw new NotFoundException(errMsg, e);
}
if(! exists) {
log.debug("object does not exist: {}, {}", containerName, contentId);
String errMsg = createNotFoundMsg(containerName, contentId);
throw new NotFoundException(errMsg);
}
log.debug("object does exist: {}, {}", containerName, contentId);
}
protected boolean spaceExists(String spaceId) {
String containerName = getContainerName(spaceId);
return swiftClient.containerExists(containerName);
}
/**
* {@inheritDoc}
*/
public void createSpace(String spaceId) {
log.debug("getCreateSpace(" + spaceId + ")");
throwIfSpaceExists(spaceId);
// Add space properties
// Note: According to Rackspace support (ticket #13597) there are no
// dates recorded for containers, so store our own created date
Map spaceProperties = new HashMap();
spaceProperties.put(PROPERTIES_SPACE_CREATED,
DateUtil.convertToString(System.currentTimeMillis()));
CreateContainerOptions createContainerOptions =
CreateContainerOptions.Builder.withMetadata(spaceProperties);
String containerName = getContainerName(spaceId);
swiftClient.createContainer(containerName, createContainerOptions);
}
protected void doSetSpaceProperties(String spaceId,
Map spaceProperties) {
log.debug("doSetSpaceProperties(" + spaceId + ")");
throwIfSpaceNotExist(spaceId);
// Ensure that space created date is included in the new properties
String created = getCreationTimestamp(spaceId, spaceProperties);
if (created != null) {
spaceProperties.put(PROPERTIES_SPACE_CREATED, created);
}
String containerName = getContainerName(spaceId);
swiftClient.setContainerMetadata(containerName, spaceProperties);
}
/**
* {@inheritDoc}
*/
public void removeSpace(String spaceId) {
String containerName = getContainerName(spaceId);
boolean successful = swiftClient.deleteContainerIfEmpty(containerName);
if(!successful) {
StringBuilder err = new StringBuilder(
"Could not delete " + getProviderName() + " container with name " +
containerName + " due to error: container not empty");
throw new StorageException(err.toString(), NO_RETRY);
}
}
/**
* {@inheritDoc}
*/
protected Map getAllSpaceProperties(String spaceId) {
log.debug("getAllSpaceProperties(" + spaceId + ")");
throwIfSpaceNotExist(spaceId);
Map spaceProperties = new HashMap<>();
String containerName = getContainerName(spaceId);
ContainerMetadata containerMetadata = swiftClient.getContainerMetadata(containerName);
if(null != containerMetadata) {
Map metadata = containerMetadata.getMetadata();
spaceProperties.putAll(metadata);
// Add count and size properties
spaceProperties.put(PROPERTIES_SPACE_COUNT,
String.valueOf(containerMetadata.getCount()));
spaceProperties.put(PROPERTIES_SPACE_SIZE,
String.valueOf(containerMetadata.getBytes()));
} else {
spaceProperties.put(PROPERTIES_SPACE_COUNT, String.valueOf(0));
spaceProperties.put(PROPERTIES_SPACE_SIZE, String.valueOf(0));
}
return spaceProperties;
}
private String getCreationTimestamp(String spaceId,
Map spaceProperties) {
String creationTime = null;
if (!spaceProperties.containsKey(PROPERTIES_SPACE_CREATED)) {
Map spaceMd = getAllSpaceProperties(spaceId);
creationTime = spaceMd.get(PROPERTIES_SPACE_CREATED);
} else {
creationTime = spaceProperties.get(PROPERTIES_SPACE_CREATED);
}
if (creationTime == null) {
StringBuffer msg = new StringBuffer("Error: ");
msg.append("No ").append(PROPERTIES_SPACE_CREATED).append(" found ");
msg.append("for spaceId: ").append(spaceId);
log.error(msg.toString());
creationTime = ISO8601_DATE_FORMAT.format(new Date());
}
return creationTime;
}
/**
* {@inheritDoc}
*/
public String addContent(String spaceId,
String contentId,
String contentMimeType,
Map userProperties,
long contentSize,
String contentChecksum,
InputStream content) {
log.debug("addContent("+ spaceId +", "+ contentId +", "+
contentMimeType +", "+ contentSize +", "+ contentChecksum +")");
throwIfSpaceNotExist(spaceId);
if(contentMimeType == null || contentMimeType.equals("")) {
contentMimeType = DEFAULT_MIMETYPE;
}
Map properties = new HashMap();
properties.put(PROPERTIES_CONTENT_MIMETYPE, contentMimeType);
if(userProperties != null) {
userProperties = removeCalculatedProperties(userProperties);
for (String key : userProperties.keySet()) {
if (log.isDebugEnabled()) {
log.debug("[" + key + "|" + userProperties.get(key) + "]");
}
properties.put(getSpaceFree(key),
userProperties.get(key));
}
}
// Wrap the content in order to be able to retrieve a checksum
ChecksumInputStream wrappedContent =
new ChecksumInputStream(content, contentChecksum);
String containerName = getContainerName(spaceId);
SwiftObject swiftObject = swiftClient.newSwiftObject();
MutableObjectInfoWithMetadata objectInfoMetadata = swiftObject.getInfo();
String encContentId = sanitizeForURI(contentId);
objectInfoMetadata.setName(encContentId);
// if(contentSize > 0) { ******** THIS BROKE THINGS ON SDSC *********
// objectInfoMetadata.setBytes(contentSize);
// }
objectInfoMetadata.setContentType(contentMimeType); // This doesn't seem to do anything, set in metadata!
objectInfoMetadata.getMetadata().putAll(properties);
swiftObject.setPayload(wrappedContent);
String providerChecksum = swiftClient.putObject(containerName, swiftObject);
// Compare checksum
try {
String checksum = wrappedContent.getMD5();
compareChecksum(providerChecksum, spaceId, contentId, checksum);
} catch(ChecksumMismatchException e) {
// Clean up object
if(swiftClient.objectExists(containerName, encContentId)) {
swiftClient.removeObject(containerName, encContentId);
}
throw e;
}
return providerChecksum;
}
@Override
public String copyContent(String sourceSpaceId,
String sourceContentId,
String destSpaceId,
String destContentId) {
log.debug("copyContent({}, {}, {}, {})",
new Object[]{sourceSpaceId,
sourceContentId,
destSpaceId,
destContentId});
throwIfContentNotExist(sourceSpaceId, sourceContentId);
throwIfSpaceNotExist(destSpaceId);
if(doCopyContent(sourceSpaceId,
sourceContentId,
destSpaceId,
destContentId)) {
MutableObjectInfoWithMetadata objectInfoWithMetadata =
getObjectProperties(destSpaceId, destContentId);
byte[] hash = objectInfoWithMetadata.getHash();
String md5 = null;
if(hash != null) {
md5 = ChecksumUtil.checksumBytesToString(hash);
}
return StorageProviderUtil.compareChecksum(this,
sourceSpaceId,
sourceContentId,
md5);
} else {
throw new StorageException("failed to copy object - " +
"srcSpaceId: " +sourceSpaceId+ ", " +
"sourceContentId: " +sourceContentId+ ", " +
"destSpaceId: " +destSpaceId+ ", " +
"destContentId: " +destContentId);
}
}
private boolean doCopyContent(String sourceSpaceId,
String sourceContentId,
String destSpaceId,
String destContentId) {
try {
String encSourceContentId = sanitizeForURI(sourceContentId);
String encDestContentId = sanitizeForURI(destContentId);
return swiftClient.copyObject(sourceSpaceId,
encSourceContentId,
destSpaceId,
encDestContentId);
} catch (CopyObjectException e) {
StringBuilder err = new StringBuilder("Could not copy content from: ");
err.append(sourceSpaceId);
err.append(" / ");
err.append(sourceContentId);
err.append(", to: ");
err.append(destSpaceId);
err.append(" / ");
err.append(destContentId);
err.append(", due to error: ");
err.append(e.getMessage());
throw new StorageException(err.toString(), e, RETRY);
}
}
/**
* {@inheritDoc}
*/
public InputStream getContent(String spaceId, String contentId) {
log.debug("getContent(" + spaceId + ", " + contentId + ")");
throwIfSpaceNotExist(spaceId);
String containerName = getContainerName(spaceId);
String encContentId = sanitizeForURI(contentId);
SwiftObject swiftObject = swiftClient.getObject(containerName, encContentId);
if(swiftObject == null) {
String errMsg = createNotFoundMsg(spaceId, contentId);
throw new NotFoundException(errMsg);
}
InputStream content = swiftObject.getPayload().getInput();
return content;
}
private String createNotFoundMsg(String spaceId,
String contentId) {
StringBuilder msg = new StringBuilder(getProviderName());
msg.append(": Could not find content item with ID ");
msg.append(contentId);
msg.append(" in space ");
msg.append(spaceId);
return msg.toString();
}
/**
* {@inheritDoc}
*/
public void deleteContent(String spaceId, String contentId) {
log.debug("deleteContent({}, {})", spaceId, contentId);
throwIfContentNotExist(spaceId, contentId);
log.debug("after check exist: {}, {}", spaceId, contentId);
String containerName = getContainerName(spaceId);
log.debug("before swiftClient.removeObject({}, {})", spaceId, contentId);
String encContentId = sanitizeForURI(contentId);
swiftClient.removeObject(containerName, encContentId);
}
/**
* {@inheritDoc}
*/
public void setContentProperties(String spaceId,
String contentId,
Map contentProperties) {
log.debug("setContentProperties(" + spaceId + ", " + contentId + ")");
throwIfSpaceNotExist(spaceId);
throwIfContentNotExist(spaceId, contentId);
// Remove calculated properties
contentProperties = removeCalculatedProperties(contentProperties);
// Set mimetype
String contentMimeType =
contentProperties.remove(PROPERTIES_CONTENT_MIMETYPE);
if(contentMimeType == null || contentMimeType.equals("")) {
contentMimeType = getContentProperties(spaceId, contentId)
.get(PROPERTIES_CONTENT_MIMETYPE);
}
Map newContentProperties = new HashMap();
for (String key : contentProperties.keySet()) {
if (log.isDebugEnabled()) {
log.debug("[" + key + "|" + contentProperties.get(key) + "]");
}
newContentProperties.put(getSpaceFree(key),
contentProperties.get(key));
}
// Set Content-Type
if (contentMimeType != null && !contentMimeType.equals("")) {
newContentProperties.put(PROPERTIES_CONTENT_MIMETYPE, contentMimeType);
}
String containerName = getContainerName(spaceId);
log.debug("Calling swiftClient.setObjectInfo for spaceId: {} and contentId: {}",
spaceId, contentId);
String encContentId = sanitizeForURI(contentId);
if(! swiftClient.setObjectInfo(containerName,
encContentId,
newContentProperties)) {
String errMsg = createNotFoundMsg(spaceId, contentId);
throw new StorageException("Error setting content properties");
}
}
/**
* {@inheritDoc}
*/
public Map getContentProperties(String spaceId,
String contentId) {
log.debug("getContentProperties(" + spaceId + ", " + contentId + ")");
throwIfSpaceNotExist(spaceId);
MutableObjectInfoWithMetadata objectInfoWithMetadata =
getObjectProperties(spaceId, contentId);
if (objectInfoWithMetadata == null) {
String err = "No properties are available for item " + contentId +
" in " + getProviderName() + " space " + spaceId;
throw new StorageException(err, RETRY);
}
Map propertiesMap = objectInfoWithMetadata.getMetadata();
// Set expected property values
// MIMETYPE
// PROPERTIES_CONTENT_MIMETYPE value is set directly by add/update content
// SIZE
Long contentLength = objectInfoWithMetadata.getBytes();
if (contentLength != null) {
propertiesMap.put(PROPERTIES_CONTENT_SIZE, contentLength.toString());
}
// CHECKSUM
byte[] hash = objectInfoWithMetadata.getHash();
if (hash != null) {
String checksum = ChecksumUtil.checksumBytesToString(hash);
propertiesMap.put(PROPERTIES_CONTENT_CHECKSUM, checksum);
}
// MODIFIED DATE
Date modified = objectInfoWithMetadata.getLastModified();
if (modified != null) {
String formatted = DateUtil.convertToString(modified.getTime());
propertiesMap.put(PROPERTIES_CONTENT_MODIFIED, formatted);
}
// Normalize properties keys to lowercase.
Map resultMap = new HashMap();
Iterator keys = propertiesMap.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
String val = propertiesMap.get(key);
resultMap.put(getWithSpace(key.toLowerCase()), val);
}
return resultMap;
}
private MutableObjectInfoWithMetadata getObjectProperties(String spaceId,
String contentId) {
String containerName = getContainerName(spaceId);
String encContentId = sanitizeForURI(contentId);
MutableObjectInfoWithMetadata objectInfoWithMetadata =
swiftClient.getObjectInfo(containerName, encContentId);
if(objectInfoWithMetadata == null) {
String errMsg = createNotFoundMsg(spaceId, contentId);
throw new NotFoundException(errMsg);
}
return objectInfoWithMetadata;
}
/**
* Converts a provided space ID into a valid Rackspace container name. From
* Cloud Files Docs: The only restrictions on Container names is that they
* cannot contain a forward slash (/) character or a question mark (?)
* character and they must be less than 64 characters in length (after URL
* encoding).
*
* @param spaceId user preferred ID of the space
* @return spaceId converted to valid Rackspace container name
*/
protected String getContainerName(String spaceId) {
String containerName = spaceId;
containerName = containerName.replaceAll("/", "-");
containerName = containerName.replaceAll("[?]", "-");
containerName = containerName.replaceAll("[-]+", "-");
containerName = sanitizeForURI(containerName);
if (containerName.length() > 63) {
containerName = containerName.substring(0, 63);
}
return containerName;
}
/**
* Encode any unicode characters that will cause us problems.
*
* @param str
* @return The string encoded for a URI
*/
public static String sanitizeForURI(String str) {
URLCodec codec = new URLCodec();
try {
return codec.encode(str).replaceAll("\\+", "%20");
} catch (EncoderException ee) {
log.warn("Error trying to encode string for URI", ee);
return str;
}
}
/**
* Replaces all spaces with "%20"
*
* @param name string with possible space
* @return converted to string without spaces
*/
protected String getSpaceFree(String name) {
return name.replaceAll(" ", "%20");
}
/**
* Converts "%20" back to spaces
*
* @param name string
* @return converted to spaces
*/
protected String getWithSpace(String name) {
return name.replaceAll("%20", " ");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy