package io.fathom.cloud.openstack.client.storage; import io.fathom.cloud.openstack.client.OpenstackServiceClientBase; import io.fathom.cloud.openstack.client.RestClientException; import io.fathom.cloud.openstack.client.identity.TokenProvider; import io.fathom.cloud.openstack.client.storage.model.StorageListChunk; import io.fathom.cloud.openstack.client.storage.model.StorageObjectInfo; import io.fathom.http.HttpClient; import io.fathom.http.HttpRequest; import io.fathom.http.HttpResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.io.ByteSource; import com.google.common.io.Files; import com.google.common.net.HttpHeaders; public class OpenstackStorageClient extends OpenstackServiceClientBase { public static final String SERVICE_TYPE = "object-store"; public static abstract class GetFileOption { public abstract void modifyRequest(HttpRequest request); } public static class Range extends GetFileOption { private final Long from; private final Long to; public Range(Long from, Long to) { if (from == null && to == null) { throw new IllegalArgumentException(); } this.from = from; this.to = to; } @Override public void modifyRequest(HttpRequest request) { // Note than -123 means the last 123 bytes in a range header, so we // want 0-123 if ((from == null || from == 0) && to == null) { return; } StringBuilder sb = new StringBuilder(); sb.append("bytes="); if (from != null) { sb.append(from); } sb.append('-'); if (to != null) { sb.append(to); } request.setHeader("Range", sb.toString()); } } private static final Logger log = LoggerFactory.getLogger(OpenstackStorageClient.class); public OpenstackStorageClient(HttpClient httpClient, URI uri, TokenProvider tokenProvider) { super(httpClient, uri, tokenProvider); } public StorageObjectInfo findStorageObjectInfo(String path) throws RestClientException { HttpRequest request = buildHead(path); HttpResponse response = null; try { response = executeRawRequest(request); StorageObjectInfo info = new StorageObjectInfo(); { String header = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); if (header != null) { info.length = Long.valueOf(header); } } { String header = response.getFirstHeader(HttpHeaders.LAST_MODIFIED); if (header != null) { info.lastModified = new Date(header); } } return info; } catch (RestClientException e) { if (e.is(404)) { return null; } throw e; } finally { closeQuietly(response); } } public void putFile(String path, File src) throws RestClientException { HttpRequest request = buildPut(path); ByteSource entity = Files.asByteSource(src); setRequestContent(request, entity); doByteArrayRequest(request); } public void appendToFile(String path, ByteSource entity) throws RestClientException { HttpRequest request = buildPost(path); setRequestContent(request, entity); doByteArrayRequest(request); } public void putFile(String path, ByteSource entity) throws RestClientException { // TODO: Support metadata HttpRequest request = buildPut(path); setRequestContent(request, entity); doByteArrayRequest(request); } public void delete(String path) throws RestClientException { HttpRequest request = buildDelete(path); doStringRequest(request); } public StorageListChunk listObjectsChunked(String bucket, String prefix, String delimiter, int maxListingLength, String priorLastKey) throws RestClientException { String relativeUri = bucket + "?format=json"; if (prefix != null) { relativeUri += "&prefix=" + urlEscape(prefix); } if (delimiter != null) { relativeUri += "&delimiter=" + urlEscape(delimiter); } if (priorLastKey != null) { throw new UnsupportedOperationException(); } if (maxListingLength != 0) { relativeUri += "&limit=" + maxListingLength; } HttpRequest get = buildGet(relativeUri); List<StorageObjectInfo> objects = doListRequest(get, StorageObjectInfo.class); String nextKey = null; if (maxListingLength != 0 && objects.size() == maxListingLength) { // set nextKey throw new UnsupportedOperationException(); } List<String> subdirs = new ArrayList<String>(); List<StorageObjectInfo> files = new ArrayList<StorageObjectInfo>(); for (StorageObjectInfo object : objects) { if (object.subdir == null) { files.add(object); } else { subdirs.add(object.subdir); } } return new StorageListChunk(objects, subdirs, nextKey); } public StorageObject getObject(String path, GetFileOption... options) throws RestClientException { return getObject(path, Arrays.asList(options)); } public StorageObject getObject(String path, List<GetFileOption> options) throws RestClientException { HttpRequest request = buildGet(path); for (GetFileOption option : options) { option.modifyRequest(request); } HttpResponse response = null; try { response = executeRawRequest(request); InputStream is; try { is = response.getInputStream(); } catch (IOException e) { throw new RestClientException("Error reading response", e); } if (is == null) { throw new IllegalStateException(); } is = new CloseHttpInputStream(is, response); StorageObject object = new StorageObject(is); response = null; // Don't close return object; } catch (RestClientException e) { if (e.is(404)) { return null; } throw e; } finally { closeQuietly(response); } } @Override protected boolean shouldRetry(int attempt, int statusCode) { if (attempt == 1 && statusCode == 401) { tokenProvider.reset(); return true; } return false; } public List<StorageObjectInfo> listChildren(String bucket, String prefix, String delimiter) throws RestClientException { List<StorageObjectInfo> ret = new ArrayList<StorageObjectInfo>(); int maxListingLength = 2000; try { String marker = null; while (true) { StorageListChunk chunk = listObjectsChunked(bucket, prefix, delimiter, maxListingLength, marker); for (StorageObjectInfo o : chunk.getObjects()) { ret.add(o); } marker = chunk.getPriorLastKey(); if (marker == null) { break; } } } catch (RestClientException e) { if (ret.isEmpty() && e.is(404)) { return null; } throw new RestClientException("Error listing children", e); } return ret; } public void createBucket(String bucket) throws RestClientException { HttpRequest request = buildPost(bucket); doStringRequest(request); } }