-
Notifications
You must be signed in to change notification settings - Fork 275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
POC for S3 API experiments (Not to be checked in) #2664
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,14 +13,19 @@ | |
*/ | ||
package com.github.ambry.rest; | ||
|
||
import com.github.ambry.account.Container; | ||
import com.github.ambry.frontend.Operations; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.URLDecoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import static com.github.ambry.rest.RestUtils.*; | ||
import static com.github.ambry.rest.RestUtils.InternalKeys.*; | ||
import static com.github.ambry.router.GetBlobOptions.*; | ||
|
||
|
||
|
@@ -35,6 +40,8 @@ public class RequestPath { | |
private static final char PATH_SEPARATOR_CHAR = '/'; | ||
private static final String PATH_SEPARATOR_STRING = String.valueOf(PATH_SEPARATOR_CHAR); | ||
private static final String SEGMENT = SubResource.Segment.toString(); | ||
private static final Logger logger = LoggerFactory.getLogger(RequestPath.class); | ||
private static final String S3_PATH = PATH_SEPARATOR_CHAR + Operations.S3; | ||
|
||
/** | ||
* Parse the request path (and additional headers in some cases). The path will match the following regex-like | ||
|
@@ -74,6 +81,14 @@ public static RequestPath parse(RestRequest restRequest, List<String> prefixesTo | |
} catch (IllegalArgumentException e) { | ||
throw new RestServiceException("Invalid URI path", e, RestServiceErrorCode.BadRequest); | ||
} | ||
|
||
logger.info("S3 API | Input path: {}", path); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As another comment, probably we can have one function to parse all S3 command and generate context for each S3 commands. |
||
if (path.startsWith(S3_PATH)) { | ||
// Convert to named blob request internally | ||
path = getNamedBlobPath(path); | ||
restRequest.setArg(S3_REQUEST, "true"); | ||
} | ||
|
||
return parse(path, restRequest.getArgs(), prefixesToRemove, clusterName); | ||
} | ||
|
||
|
@@ -94,6 +109,10 @@ public static RequestPath parse(String path, Map<String, Object> args, List<Stri | |
String clusterName) throws RestServiceException { | ||
int offset = 0; | ||
|
||
for (String arg : args.keySet()) { | ||
System.out.println(arg + "=" + args.get(arg)); | ||
} | ||
|
||
// remove prefix. | ||
String prefixFound = ""; | ||
if (prefixesToRemove != null) { | ||
|
@@ -298,4 +317,29 @@ private static int matchPathSegments(String path, int pathOffset, String pathSeg | |
} | ||
return nextPathSegmentOffset; | ||
} | ||
|
||
/** | ||
* Get named blob path from S3 request path | ||
* @param path s3 request path | ||
* @return named blob request path | ||
*/ | ||
private static String getNamedBlobPath(String path) { | ||
// S3 requests to Ambry are in the form "/s3/account-name/key-name". We convert it to named blob path | ||
// "/named/account-name/container-name/key-name" internally. | ||
// For ex: for S3 path, "/s3/named-blob-sandbox/checkpoints/87833badf879a3fc7bf151adfe928eac/chk-1/_metadata", | ||
// the corresponding named blob path is "/named/named-blob-sandbox/container-a/checkpoints/87833badf879a3fc7bf151adfe928eac/chk-1/_metadata". | ||
// Please note that we hardcode container-name to 'container-a'. | ||
|
||
int accountStart = S3_PATH.length() + PATH_SEPARATOR_STRING.length(); | ||
int accountEnd = path.indexOf(PATH_SEPARATOR_CHAR, accountStart); | ||
if (accountEnd == -1) { | ||
accountEnd = path.length(); | ||
} | ||
String accountName = path.substring(accountStart, accountEnd); | ||
String containerName = Container.DEFAULT_S3_CONTAINER_NAME; | ||
String keyName = path.substring(accountEnd); | ||
String namedBlobPath = "/named/" + accountName + "/" + containerName + (keyName.length() > 0 ? keyName : ""); | ||
logger.info("S3 API | Converted S3 request path {} to NamedBlob path {}", path, namedBlobPath); | ||
return namedBlobPath; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,8 @@ | |
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import static com.github.ambry.rest.RestUtils.InternalKeys.*; | ||
|
||
|
||
/** | ||
* Common utility functions that will be used across implementations of REST interfaces. | ||
|
@@ -252,6 +254,8 @@ public static final class Headers { | |
* stitched together. | ||
*/ | ||
public static final String CHUNK_UPLOAD = "x-ambry-chunk-upload"; | ||
|
||
public static final String S3_CHUNK_UPLOAD = "x-ambry-chunk-upload-s3"; | ||
/** | ||
* The reserved blobid for metadata chunk of a stitched upload. | ||
*/ | ||
|
@@ -452,6 +456,21 @@ public static final class InternalKeys { | |
* The version for the NamedBlob record in MySQL DB | ||
*/ | ||
public static final String NAMED_BLOB_VERSION = KEY_PREFIX + "named-blob-version"; | ||
|
||
/** | ||
* Boolean field set to "true" if this is a S3 request. | ||
*/ | ||
public static final String S3_REQUEST = KEY_PREFIX + "is-s3-request"; | ||
|
||
/** | ||
* Stores the S3 bucket-name, i.e. Ambry account name. | ||
*/ | ||
public static final String S3_BUCKET = KEY_PREFIX + "s3-bucket"; | ||
|
||
/** | ||
* Stores the file name. | ||
*/ | ||
public static final String S3_KEY = KEY_PREFIX + "s3-key"; | ||
} | ||
|
||
/** | ||
|
@@ -541,7 +560,7 @@ public static BlobProperties buildBlobProperties(Map<String, Object> args) throw | |
* @throws RestServiceException if required arguments aren't present or if they aren't in the format expected. | ||
*/ | ||
public static long getTtlFromRequestHeader(Map<String, Object> args) throws RestServiceException { | ||
long ttl = Utils.Infinite_Time; | ||
long ttl = 86400; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did we ever discuss the life cycle management with flink and TiKV team? |
||
Long ttlFromHeader = getLongHeader(args, Headers.TTL, false); | ||
if (ttlFromHeader != null) { | ||
if (ttlFromHeader < -1) { | ||
|
@@ -807,6 +826,15 @@ public static boolean isNamedBlobStitchRequest(RestRequest restRequest) { | |
} | ||
} | ||
|
||
/** | ||
* Determines if the input is a S3 API request | ||
* @param restRequest rest request | ||
* @return {@code true} if the request is a S3 API request. | ||
*/ | ||
public static boolean isS3Request(RestRequest restRequest) { | ||
return restRequest.getArgs().containsKey(S3_REQUEST); | ||
} | ||
|
||
/** | ||
* Fetch time in ms for the {@code dateString} passed in, since epoch | ||
* @param dateString the String representation of the date that needs to be parsed | ||
|
@@ -885,6 +913,16 @@ public static boolean isChunkUpload(Map<String, Object> args) throws RestService | |
return getBooleanHeader(args, Headers.CHUNK_UPLOAD, false); | ||
} | ||
|
||
/** | ||
* Determine if this is an chunk upload for S3 | ||
* @param args | ||
* @return | ||
* @throws RestServiceException | ||
*/ | ||
public static boolean isS3ChunkUpload(Map<String, Object> args) throws RestServiceException { | ||
return getBooleanHeader(args, Headers.S3_CHUNK_UPLOAD, false); | ||
} | ||
|
||
/** | ||
* Return the reserved metadata id if set in the request args. Return {@code null} if not set in args. | ||
* @param args The request arguments. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the case we hit the exception?
After the naming translation from S3 to "named",
One small issue I saw was that for listing, the path is "/named/named-blob-sandbox/container-a/". With the ending "/", we split it to four parts. But if we trim the ending "/", it works fine.