Allow multiple account providers

This allows different backend blobstores to be configured at
runtime, based on the request access key. As a result different
frontend auth can also be used. Currently this can only be done
programmatically and not through properties config file.
pull/51/merge
Ka-Hing Cheung 2015-03-24 11:51:50 -07:00 zatwierdzone przez Andrew Gaul
rodzic 2dc2cd60eb
commit 87bed283cb
2 zmienionych plików z 173 dodań i 123 usunięć

Wyświetl plik

@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import java.net.URI; import java.net.URI;
import java.util.Map;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Strings; import com.google.common.base.Strings;
@ -39,6 +40,7 @@ import org.jclouds.blobstore.BlobStore;
*/ */
public final class S3Proxy { public final class S3Proxy {
private final Server server; private final Server server;
private final S3ProxyHandler handler;
static { static {
// Prevent Jetty from rewriting headers: // Prevent Jetty from rewriting headers:
@ -81,8 +83,9 @@ public final class S3Proxy {
connector.setHost(endpoint.getHost()); connector.setHost(endpoint.getHost());
connector.setPort(endpoint.getPort()); connector.setPort(endpoint.getPort());
server.addConnector(connector); server.addConnector(connector);
server.setHandler(new S3ProxyHandler(blobStore, identity, credential, handler = new S3ProxyHandler(blobStore, identity, credential,
virtualHost)); virtualHost);
server.setHandler(handler);
} }
public static final class Builder { public static final class Builder {
@ -150,4 +153,9 @@ public final class S3Proxy {
public String getState() { public String getState() {
return server.getState(); return server.getState();
} }
public void setProviders(
Map<String, Map.Entry<String, BlobStore>> providers) {
handler.setProviders(providers);
}
} }

Wyświetl plik

@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -60,6 +61,7 @@ import com.google.common.base.Strings;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap; import com.google.common.collect.TreeMultimap;
import com.google.common.hash.HashCode; import com.google.common.hash.HashCode;
@ -149,10 +151,9 @@ final class S3ProxyHandler extends AbstractHandler {
"log-delivery-write" "log-delivery-write"
); );
private final BlobStore blobStore; private Map<String, Map.Entry<String, BlobStore>> providers =
private final String blobStoreType; new HashMap<>();
private final String identity; private final BlobStore defaultBlobStore;
private final String credential;
private final Optional<String> virtualHost; private final Optional<String> virtualHost;
private final XMLInputFactory xmlInputFactory = private final XMLInputFactory xmlInputFactory =
XMLInputFactory.newInstance(); XMLInputFactory.newInstance();
@ -161,16 +162,22 @@ final class S3ProxyHandler extends AbstractHandler {
S3ProxyHandler(BlobStore blobStore, String identity, String credential, S3ProxyHandler(BlobStore blobStore, String identity, String credential,
Optional<String> virtualHost) { Optional<String> virtualHost) {
this.blobStore = requireNonNull(blobStore); requireNonNull(blobStore);
this.blobStoreType = if (identity != null) {
blobStore.getContext().unwrap().getProviderMetadata().getId(); providers.put(identity, Maps.immutableEntry(credential, blobStore));
this.identity = identity; defaultBlobStore = null;
this.credential = credential; } else {
defaultBlobStore = blobStore;
}
this.virtualHost = requireNonNull(virtualHost); this.virtualHost = requireNonNull(virtualHost);
xmlOutputFactory.setProperty("javax.xml.stream.isRepairingNamespaces", xmlOutputFactory.setProperty("javax.xml.stream.isRepairingNamespaces",
Boolean.FALSE); Boolean.FALSE);
} }
private static String getBlobStoreType(BlobStore blobStore) {
return blobStore.getContext().unwrap().getProviderMetadata().getId();
}
@Override @Override
public void handle(String target, Request baseRequest, public void handle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response) HttpServletRequest request, HttpServletResponse response)
@ -220,7 +227,7 @@ final class S3ProxyHandler extends AbstractHandler {
} }
} }
if (identity != null && !hasDateHeader && !hasXAmzDateHeader && if (defaultBlobStore == null && !hasDateHeader && !hasXAmzDateHeader &&
request.getParameter("Expires") == null) { request.getParameter("Expires") == null) {
sendSimpleErrorResponse(response, S3ErrorCode.ACCESS_DENIED, sendSimpleErrorResponse(response, S3ErrorCode.ACCESS_DENIED,
"AWS authentication requires a valid Date or" + "AWS authentication requires a valid Date or" +
@ -247,44 +254,45 @@ final class S3ProxyHandler extends AbstractHandler {
} }
} }
if (identity != null) { BlobStore blobStore;
String expectedSignature = createAuthorizationSignature(request, String requestIdentity = null;
uri, identity, credential); String requestSignature = null;
String headerAuthorization = request.getHeader( String headerAuthorization = request.getHeader(
HttpHeaders.AUTHORIZATION); HttpHeaders.AUTHORIZATION);
String headerIdentity = null;
String headerSignature = null; if (headerAuthorization != null) {
if (headerAuthorization != null && if (headerAuthorization.startsWith("AWS ")) {
headerAuthorization.startsWith("AWS ")) {
String[] values = String[] values =
headerAuthorization.substring(4).split(":", 2); headerAuthorization.substring(4).split(":", 2);
if (values.length != 2) { if (values.length != 2) {
throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
} }
headerIdentity = values[0]; requestIdentity = values[0];
headerSignature = values[1]; requestSignature = values[1];
} else if (headerAuthorization != null && } else if (headerAuthorization.startsWith("AWS4-HMAC-SHA256 ")) {
headerAuthorization.startsWith("AWS4-HMAC-SHA256 ")) {
// Fail V4 signature requests to allow clients to retry with V2. // Fail V4 signature requests to allow clients to retry with V2.
throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
} }
String parameterIdentity = request.getParameter("AWSAccessKeyId"); } else {
String parameterSignature = request.getParameter("Signature"); requestIdentity = request.getParameter("AWSAccessKeyId");
requestSignature = request.getParameter("Signature");
}
if (headerIdentity != null && headerSignature != null) { if (requestIdentity != null) {
if (!identity.equals(headerIdentity)) { Map.Entry<String, BlobStore> provider =
providers.get(requestIdentity);
if (provider == null) {
throw new S3Exception(S3ErrorCode.INVALID_ACCESS_KEY_ID); throw new S3Exception(S3ErrorCode.INVALID_ACCESS_KEY_ID);
} else if (!expectedSignature.equals(headerSignature)) { }
throw new S3Exception(S3ErrorCode.SIGNATURE_DOES_NOT_MATCH);
} String expectedSignature = createAuthorizationSignature(request,
} else if (parameterIdentity != null && uri, requestIdentity, provider.getKey());
parameterSignature != null) { if (!expectedSignature.equals(requestSignature)) {
if (!identity.equals(parameterIdentity)) {
throw new S3Exception(S3ErrorCode.INVALID_ACCESS_KEY_ID);
} else if (!expectedSignature.equals(parameterSignature)) {
throw new S3Exception(S3ErrorCode.SIGNATURE_DOES_NOT_MATCH); throw new S3Exception(S3ErrorCode.SIGNATURE_DOES_NOT_MATCH);
} }
blobStore = provider.getValue();
String expiresString = request.getParameter("Expires"); String expiresString = request.getParameter("Expires");
if (expiresString != null) { if (expiresString != null) {
long expires = Long.parseLong(expiresString); long expires = Long.parseLong(expiresString);
@ -294,7 +302,10 @@ final class S3ProxyHandler extends AbstractHandler {
} }
} }
} else { } else {
if (defaultBlobStore == null) {
throw new S3Exception(S3ErrorCode.ACCESS_DENIED); throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
} else {
blobStore = defaultBlobStore;
} }
} }
@ -316,79 +327,82 @@ final class S3ProxyHandler extends AbstractHandler {
switch (method) { switch (method) {
case "DELETE": case "DELETE":
if (path.length <= 2 || path[2].isEmpty()) { if (path.length <= 2 || path[2].isEmpty()) {
handleContainerDelete(response, path[1]); handleContainerDelete(response, blobStore, path[1]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if (uploadId != null) { } else if (uploadId != null) {
handleAbortMultipartUpload(request, response, path[1], path[2], handleAbortMultipartUpload(request, response, blobStore,
uploadId); path[1], path[2], uploadId);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else { } else {
handleBlobRemove(response, path[1], path[2]); handleBlobRemove(response, blobStore, path[1], path[2]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
case "GET": case "GET":
if (uri.equals("/")) { if (uri.equals("/")) {
handleContainerList(response); handleContainerList(response, blobStore);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if (path.length <= 2 || path[2].isEmpty()) { } else if (path.length <= 2 || path[2].isEmpty()) {
if ("".equals(request.getParameter("acl"))) { if ("".equals(request.getParameter("acl"))) {
handleGetContainerAcl(response, path[1]); handleGetContainerAcl(response, blobStore, path[1]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if ("".equals(request.getParameter("location"))) { } else if ("".equals(request.getParameter("location"))) {
handleContainerLocation(response, path[1]); handleContainerLocation(response, blobStore, path[1]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if ("".equals(request.getParameter("uploads"))) { } else if ("".equals(request.getParameter("uploads"))) {
handleListMultipartUploads(response, uploadId); handleListMultipartUploads(response, blobStore,
baseRequest.setHandled(true);
return;
}
handleBlobList(request, response, path[1]);
baseRequest.setHandled(true);
return;
} else {
if ("".equals(request.getParameter("acl"))) {
handleGetBlobAcl(response, path[1], path[2]);
baseRequest.setHandled(true);
return;
} else if (uploadId != null) {
handleListParts(request, response, path[1], path[2],
uploadId); uploadId);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
handleGetBlob(request, response, path[1], path[2]); handleBlobList(request, response, blobStore, path[1]);
baseRequest.setHandled(true);
return;
} else {
if ("".equals(request.getParameter("acl"))) {
handleGetBlobAcl(response, path[1], blobStore,
path[2]);
baseRequest.setHandled(true);
return;
} else if (uploadId != null) {
handleListParts(request, response, blobStore, path[1],
path[2], uploadId);
baseRequest.setHandled(true);
return;
}
handleGetBlob(request, response, blobStore, path[1],
path[2]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
case "HEAD": case "HEAD":
if (path.length <= 2 || path[2].isEmpty()) { if (path.length <= 2 || path[2].isEmpty()) {
handleContainerExists(response, path[1]); handleContainerExists(response, blobStore, path[1]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else { } else {
handleBlobMetadata(response, path[1], path[2]); handleBlobMetadata(response, blobStore, path[1], path[2]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
case "POST": case "POST":
if ("".equals(request.getParameter("delete"))) { if ("".equals(request.getParameter("delete"))) {
handleMultiBlobRemove(request, response, path[1]); handleMultiBlobRemove(request, response, blobStore, path[1]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if ("".equals(request.getParameter("uploads"))) { } else if ("".equals(request.getParameter("uploads"))) {
handleInitiateMultipartUpload(request, response, path[1], handleInitiateMultipartUpload(request, response, blobStore,
path[2]); path[1], path[2]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if (uploadId != null) { } else if (uploadId != null) {
handleCompleteMultipartUpload(request, response, path[1], handleCompleteMultipartUpload(request, response, blobStore,
path[2], uploadId); path[1], path[2], uploadId);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
@ -396,29 +410,31 @@ final class S3ProxyHandler extends AbstractHandler {
case "PUT": case "PUT":
if (path.length <= 2 || path[2].isEmpty()) { if (path.length <= 2 || path[2].isEmpty()) {
if ("".equals(request.getParameter("acl"))) { if ("".equals(request.getParameter("acl"))) {
handleSetContainerAcl(request, response, path[1]); handleSetContainerAcl(request, response, blobStore,
path[1]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
handleContainerCreate(request, response, path[1]); handleContainerCreate(request, response, blobStore, path[1]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if (uploadId != null) { } else if (uploadId != null) {
handleUploadPart(request, response, path[1], path[2], handleUploadPart(request, response, blobStore, path[1],
uploadId); path[2], uploadId);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else if (request.getHeader("x-amz-copy-source") != null) { } else if (request.getHeader("x-amz-copy-source") != null) {
handleCopyBlob(request, response, path[1], path[2]); handleCopyBlob(request, response, blobStore, path[1], path[2]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} else { } else {
if ("".equals(request.getParameter("acl"))) { if ("".equals(request.getParameter("acl"))) {
handleSetBlobAcl(request, response, path[1], path[2]); handleSetBlobAcl(request, response, blobStore, path[1],
path[2]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
handlePutBlob(request, response, path[1], path[2]); handlePutBlob(request, response, blobStore, path[1], path[2]);
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
@ -430,8 +446,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleGetContainerAcl(HttpServletResponse response, private void handleGetContainerAcl(HttpServletResponse response,
String containerName) throws IOException { BlobStore blobStore, String containerName) throws IOException {
ContainerAccess access; ContainerAccess access;
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("filesystem") || if (blobStoreType.equals("filesystem") ||
blobStoreType.equals("transient")) { blobStoreType.equals("transient")) {
access = ContainerAccess.PRIVATE; access = ContainerAccess.PRIVATE;
@ -505,8 +522,8 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleSetContainerAcl(HttpServletRequest request, private void handleSetContainerAcl(HttpServletRequest request,
HttpServletResponse response, String containerName) HttpServletResponse response, BlobStore blobStore,
throws IOException, S3Exception { String containerName) throws IOException, S3Exception {
ContainerAccess access; ContainerAccess access;
String cannedAcl = request.getHeader("x-amz-acl"); String cannedAcl = request.getHeader("x-amz-acl");
@ -521,6 +538,7 @@ final class S3ProxyHandler extends AbstractHandler {
return; return;
} }
String blobStoreType = getBlobStoreType(blobStore);
if (!(blobStoreType.equals("filesystem") || if (!(blobStoreType.equals("filesystem") ||
blobStoreType.equals("transient"))) { blobStoreType.equals("transient"))) {
blobStore.setContainerAccess(containerName, access); blobStore.setContainerAccess(containerName, access);
@ -528,8 +546,10 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleGetBlobAcl(HttpServletResponse response, private void handleGetBlobAcl(HttpServletResponse response,
String containerName, String blobName) throws IOException { String containerName, BlobStore blobStore,
String blobName) throws IOException {
BlobAccess access; BlobAccess access;
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("filesystem") || if (blobStoreType.equals("filesystem") ||
blobStoreType.equals("transient")) { blobStoreType.equals("transient")) {
access = BlobAccess.PRIVATE; access = BlobAccess.PRIVATE;
@ -603,8 +623,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleSetBlobAcl(HttpServletRequest request, private void handleSetBlobAcl(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName) throws IOException, S3Exception { String containerName, String blobName)
throws IOException, S3Exception {
BlobAccess access; BlobAccess access;
String cannedAcl = request.getHeader("x-amz-acl"); String cannedAcl = request.getHeader("x-amz-acl");
@ -619,14 +640,15 @@ final class S3ProxyHandler extends AbstractHandler {
return; return;
} }
String blobStoreType = getBlobStoreType(blobStore);
if (!(blobStoreType.equals("filesystem") || if (!(blobStoreType.equals("filesystem") ||
blobStoreType.equals("transient"))) { blobStoreType.equals("transient"))) {
blobStore.setBlobAccess(containerName, blobName, access); blobStore.setBlobAccess(containerName, blobName, access);
} }
} }
private void handleContainerList(HttpServletResponse response) private void handleContainerList(HttpServletResponse response,
throws IOException { BlobStore blobStore) throws IOException {
try (Writer writer = response.getWriter()) { try (Writer writer = response.getWriter()) {
XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter( XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter(
writer); writer);
@ -667,7 +689,7 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleContainerLocation(HttpServletResponse response, private void handleContainerLocation(HttpServletResponse response,
String containerName) throws IOException { BlobStore blobStore, String containerName) throws IOException {
try (Writer writer = response.getWriter()) { try (Writer writer = response.getWriter()) {
XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter( XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter(
writer); writer);
@ -683,21 +705,23 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleListMultipartUploads(HttpServletResponse response, private void handleListMultipartUploads(HttpServletResponse response,
String uploadId) throws IOException, S3Exception { BlobStore blobStore, String uploadId)
throws IOException, S3Exception {
// TODO: list all blobs starting with uploadId // TODO: list all blobs starting with uploadId
throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED); throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
} }
private void handleContainerExists(HttpServletResponse response, private void handleContainerExists(HttpServletResponse response,
String containerName) throws IOException, S3Exception { BlobStore blobStore, String containerName)
throws IOException, S3Exception {
if (!blobStore.containerExists(containerName)) { if (!blobStore.containerExists(containerName)) {
throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET);
} }
} }
private void handleContainerCreate(HttpServletRequest request, private void handleContainerCreate(HttpServletRequest request,
HttpServletResponse response, String containerName) HttpServletResponse response, BlobStore blobStore,
throws IOException, S3Exception { String containerName) throws IOException, S3Exception {
if (containerName.isEmpty()) { if (containerName.isEmpty()) {
throw new S3Exception(S3ErrorCode.METHOD_NOT_ALLOWED); throw new S3Exception(S3ErrorCode.METHOD_NOT_ALLOWED);
} }
@ -771,7 +795,8 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleContainerDelete(HttpServletResponse response, private void handleContainerDelete(HttpServletResponse response,
String containerName) throws IOException, S3Exception { BlobStore blobStore, String containerName)
throws IOException, S3Exception {
if (!blobStore.containerExists(containerName)) { if (!blobStore.containerExists(containerName)) {
throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET);
} }
@ -782,8 +807,8 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleBlobList(HttpServletRequest request, private void handleBlobList(HttpServletRequest request,
HttpServletResponse response, String containerName) HttpServletResponse response, BlobStore blobStore,
throws IOException, S3Exception { String containerName) throws IOException, S3Exception {
ListContainerOptions options = new ListContainerOptions(); ListContainerOptions options = new ListContainerOptions();
String delimiter = request.getParameter("delimiter"); String delimiter = request.getParameter("delimiter");
if (!(delimiter != null && delimiter.equals("/"))) { if (!(delimiter != null && delimiter.equals("/"))) {
@ -903,6 +928,7 @@ final class S3ProxyHandler extends AbstractHandler {
String eTag = metadata.getETag(); String eTag = metadata.getETag();
if (eTag != null) { if (eTag != null) {
xml.writeStartElement("ETag"); xml.writeStartElement("ETag");
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("google-cloud-storage")) { if (blobStoreType.equals("google-cloud-storage")) {
eTag = BaseEncoding.base16().lowerCase().encode( eTag = BaseEncoding.base16().lowerCase().encode(
BaseEncoding.base64().decode(eTag)); BaseEncoding.base64().decode(eTag));
@ -942,15 +968,15 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleBlobRemove(HttpServletResponse response, private void handleBlobRemove(HttpServletResponse response,
String containerName, String blobName) BlobStore blobStore, String containerName,
throws IOException, S3Exception { String blobName) throws IOException, S3Exception {
blobStore.removeBlob(containerName, blobName); blobStore.removeBlob(containerName, blobName);
response.sendError(HttpServletResponse.SC_NO_CONTENT); response.sendError(HttpServletResponse.SC_NO_CONTENT);
} }
private void handleMultiBlobRemove(HttpServletRequest request, private void handleMultiBlobRemove(HttpServletRequest request,
HttpServletResponse response, String containerName) HttpServletResponse response, BlobStore blobStore,
throws IOException { String containerName) throws IOException {
try (InputStream is = request.getInputStream(); try (InputStream is = request.getInputStream();
Writer writer = new OutputStreamWriter(response.getOutputStream(), Writer writer = new OutputStreamWriter(response.getOutputStream(),
StandardCharsets.UTF_8)) { StandardCharsets.UTF_8)) {
@ -978,8 +1004,8 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleBlobMetadata(HttpServletResponse response, private void handleBlobMetadata(HttpServletResponse response,
String containerName, String blobName) BlobStore blobStore, String containerName,
throws IOException, S3Exception { String blobName) throws IOException, S3Exception {
BlobMetadata metadata = blobStore.blobMetadata(containerName, blobName); BlobMetadata metadata = blobStore.blobMetadata(containerName, blobName);
if (metadata == null) { if (metadata == null) {
throw new S3Exception(S3ErrorCode.NO_SUCH_KEY); throw new S3Exception(S3ErrorCode.NO_SUCH_KEY);
@ -990,8 +1016,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleGetBlob(HttpServletRequest request, private void handleGetBlob(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName) throws IOException, S3Exception { String containerName, String blobName)
throws IOException, S3Exception {
int status = HttpServletResponse.SC_OK; int status = HttpServletResponse.SC_OK;
GetOptions options = new GetOptions(); GetOptions options = new GetOptions();
String range = request.getHeader(HttpHeaders.RANGE); String range = request.getHeader(HttpHeaders.RANGE);
@ -1026,8 +1053,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleCopyBlob(HttpServletRequest request, private void handleCopyBlob(HttpServletRequest request,
HttpServletResponse response, String destContainerName, HttpServletResponse response, BlobStore blobStore,
String destBlobName) throws IOException, S3Exception { String destContainerName, String destBlobName)
throws IOException, S3Exception {
String copySourceHeader = request.getHeader("x-amz-copy-source"); String copySourceHeader = request.getHeader("x-amz-copy-source");
if (copySourceHeader.startsWith("/")) { if (copySourceHeader.startsWith("/")) {
// Some clients like boto do not include the leading slash // Some clients like boto do not include the leading slash
@ -1071,6 +1099,7 @@ final class S3ProxyHandler extends AbstractHandler {
} }
PutOptions options = new PutOptions(); PutOptions options = new PutOptions();
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("azureblob") && if (blobStoreType.equals("azureblob") &&
contentLength > 64 * 1024 * 1024) { contentLength > 64 * 1024 * 1024) {
options.multipart(true); options.multipart(true);
@ -1103,8 +1132,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handlePutBlob(HttpServletRequest request, private void handlePutBlob(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName) throws IOException, S3Exception { String containerName, String blobName)
throws IOException, S3Exception {
// Flag headers present since HttpServletResponse.getHeader returns // Flag headers present since HttpServletResponse.getHeader returns
// null for empty headers values. // null for empty headers values.
String contentLengthString = null; String contentLengthString = null;
@ -1156,6 +1186,7 @@ final class S3ProxyHandler extends AbstractHandler {
} }
PutOptions options = new PutOptions(); PutOptions options = new PutOptions();
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("azureblob") && if (blobStoreType.equals("azureblob") &&
contentLength > 64 * 1024 * 1024) { contentLength > 64 * 1024 * 1024) {
options.multipart(true); options.multipart(true);
@ -1199,13 +1230,14 @@ final class S3ProxyHandler extends AbstractHandler {
// TODO: jclouds should include this in PutOptions // TODO: jclouds should include this in PutOptions
String cannedAcl = request.getHeader("x-amz-acl"); String cannedAcl = request.getHeader("x-amz-acl");
if (cannedAcl != null && !cannedAcl.equals("private")) { if (cannedAcl != null && !cannedAcl.equals("private")) {
handleSetBlobAcl(request, response, containerName, blobName); handleSetBlobAcl(request, response, blobStore, containerName,
blobName);
} }
} }
private void handleInitiateMultipartUpload(HttpServletRequest request, private void handleInitiateMultipartUpload(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName) throws IOException { String containerName, String blobName) throws IOException {
String uploadId = FAKE_UPLOAD_ID + UUID.randomUUID().toString(); String uploadId = FAKE_UPLOAD_ID + UUID.randomUUID().toString();
ByteSource payload = ByteSource.empty(); ByteSource payload = ByteSource.empty();
BlobBuilder.PayloadBlobBuilder builder = blobStore BlobBuilder.PayloadBlobBuilder builder = blobStore
@ -1242,8 +1274,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleCompleteMultipartUpload(HttpServletRequest request, private void handleCompleteMultipartUpload(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName, String uploadId) throws IOException, S3Exception { String containerName, String blobName, String uploadId)
throws IOException, S3Exception {
Collection<String> partNames = new ArrayList<>(); Collection<String> partNames = new ArrayList<>();
long totalContentLength = 0; long totalContentLength = 0;
try (InputStream is = request.getInputStream()) { try (InputStream is = request.getInputStream()) {
@ -1317,6 +1350,7 @@ final class S3ProxyHandler extends AbstractHandler {
if (eTag != null) { if (eTag != null) {
xml.writeStartElement("ETag"); xml.writeStartElement("ETag");
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("google-cloud-storage")) { if (blobStoreType.equals("google-cloud-storage")) {
eTag = BaseEncoding.base16().lowerCase().encode( eTag = BaseEncoding.base16().lowerCase().encode(
BaseEncoding.base64().decode(eTag)); BaseEncoding.base64().decode(eTag));
@ -1333,8 +1367,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleAbortMultipartUpload(HttpServletRequest request, private void handleAbortMultipartUpload(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName, String uploadId) throws IOException, S3Exception { String containerName, String blobName, String uploadId)
throws IOException, S3Exception {
if (!blobStore.blobExists(containerName, uploadId)) { if (!blobStore.blobExists(containerName, uploadId)) {
throw new S3Exception(S3ErrorCode.NO_SUCH_UPLOAD); throw new S3Exception(S3ErrorCode.NO_SUCH_UPLOAD);
} }
@ -1353,8 +1388,9 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleListParts(HttpServletRequest request, private void handleListParts(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName, String uploadId) throws IOException { String containerName, String blobName, String uploadId)
throws IOException {
try (Writer writer = response.getWriter()) { try (Writer writer = response.getWriter()) {
XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter( XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter(
writer); writer);
@ -1441,6 +1477,7 @@ final class S3ProxyHandler extends AbstractHandler {
String eTag = sm.getETag(); String eTag = sm.getETag();
if (eTag != null) { if (eTag != null) {
xml.writeStartElement("ETag"); xml.writeStartElement("ETag");
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("google-cloud-storage")) { if (blobStoreType.equals("google-cloud-storage")) {
eTag = BaseEncoding.base16().lowerCase().encode( eTag = BaseEncoding.base16().lowerCase().encode(
BaseEncoding.base64().decode(eTag)); BaseEncoding.base64().decode(eTag));
@ -1465,8 +1502,8 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleUploadPart(HttpServletRequest request, private void handleUploadPart(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, BlobStore blobStore,
String blobName, String uploadId) String containerName, String blobName, String uploadId)
throws IOException, S3Exception { throws IOException, S3Exception {
// TODO: duplicated from handlePutBlob // TODO: duplicated from handlePutBlob
String contentLengthString = null; String contentLengthString = null;
@ -1610,6 +1647,11 @@ final class S3ProxyHandler extends AbstractHandler {
} }
} }
public void setProviders(
Map<String, Map.Entry<String, BlobStore>> providers) {
this.providers = ImmutableMap.copyOf(providers);
}
static class S3Exception extends Exception { static class S3Exception extends Exception {
private final S3ErrorCode error; private final S3ErrorCode error;