|
@@ -32,6 +32,7 @@ import java.nio.charset.CharacterCodingException;
|
|
|
import java.nio.charset.Charset;
|
|
|
import java.nio.charset.CharsetDecoder;
|
|
|
import java.nio.charset.CharsetEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.text.ParseException;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.util.ArrayList;
|
|
@@ -47,6 +48,7 @@ import java.util.Set;
|
|
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
|
import com.google.common.base.Preconditions;
|
|
|
+import com.google.common.base.Strings;
|
|
|
|
|
|
import org.apache.hadoop.classification.InterfaceAudience;
|
|
|
import org.apache.hadoop.classification.InterfaceStability;
|
|
@@ -81,6 +83,7 @@ import org.apache.hadoop.fs.azurebfs.services.AuthType;
|
|
|
import org.apache.hadoop.fs.azurebfs.services.ExponentialRetryPolicy;
|
|
|
import org.apache.hadoop.fs.azurebfs.services.SharedKeyCredentials;
|
|
|
import org.apache.hadoop.fs.azurebfs.utils.Base64;
|
|
|
+import org.apache.hadoop.fs.azurebfs.utils.CRC64;
|
|
|
import org.apache.hadoop.fs.azurebfs.utils.UriUtils;
|
|
|
import org.apache.hadoop.fs.permission.AclEntry;
|
|
|
import org.apache.hadoop.fs.permission.AclStatus;
|
|
@@ -92,7 +95,17 @@ import org.apache.http.client.utils.URIBuilder;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CHAR_EQUALS;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CHAR_FORWARD_SLASH;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CHAR_HYPHEN;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CHAR_PLUS;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CHAR_STAR;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CHAR_UNDERSCORE;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.ROOT_PATH;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.SINGLE_WHITE_SPACE;
|
|
|
+import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.TOKEN_VERSION;
|
|
|
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.AZURE_ABFS_ENDPOINT;
|
|
|
+
|
|
|
/**
|
|
|
* Provides the bridging logic between Hadoop's abstract filesystem and Azure Storage.
|
|
|
*/
|
|
@@ -106,6 +119,7 @@ public class AzureBlobFileSystemStore implements Closeable {
|
|
|
private String userName;
|
|
|
private String primaryUserGroup;
|
|
|
private static final String DATE_TIME_PATTERN = "E, dd MMM yyyy HH:mm:ss 'GMT'";
|
|
|
+ private static final String TOKEN_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'";
|
|
|
private static final String XMS_PROPERTIES_ENCODING = "ISO-8859-1";
|
|
|
private static final int LIST_MAX_RESULTS = 500;
|
|
|
|
|
@@ -522,15 +536,43 @@ public class AzureBlobFileSystemStore implements Closeable {
|
|
|
eTag);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param path The list path.
|
|
|
+ * @return the entries in the path.
|
|
|
+ * */
|
|
|
public FileStatus[] listStatus(final Path path) throws IOException {
|
|
|
- LOG.debug("listStatus filesystem: {} path: {}",
|
|
|
+ return listStatus(path, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param path Path the list path.
|
|
|
+ * @param startFrom the entry name that list results should start with.
|
|
|
+ * For example, if folder "/folder" contains four files: "afile", "bfile", "hfile", "ifile".
|
|
|
+ * Then listStatus(Path("/folder"), "hfile") will return "/folder/hfile" and "folder/ifile"
|
|
|
+ * Notice that if startFrom is a non-existent entry name, then the list response contains
|
|
|
+ * all entries after this non-existent entry in lexical order:
|
|
|
+ * listStatus(Path("/folder"), "cfile") will return "/folder/hfile" and "/folder/ifile".
|
|
|
+ *
|
|
|
+ * @return the entries in the path start from "startFrom" in lexical order.
|
|
|
+ * */
|
|
|
+ @InterfaceStability.Unstable
|
|
|
+ public FileStatus[] listStatus(final Path path, final String startFrom) throws IOException {
|
|
|
+ LOG.debug("listStatus filesystem: {} path: {}, startFrom: {}",
|
|
|
client.getFileSystem(),
|
|
|
- path);
|
|
|
+ path,
|
|
|
+ startFrom);
|
|
|
|
|
|
- String relativePath = path.isRoot() ? AbfsHttpConstants.EMPTY_STRING : getRelativePath(path);
|
|
|
+ final String relativePath = path.isRoot() ? AbfsHttpConstants.EMPTY_STRING : getRelativePath(path);
|
|
|
String continuation = null;
|
|
|
- ArrayList<FileStatus> fileStatuses = new ArrayList<>();
|
|
|
|
|
|
+ // generate continuation token if a valid startFrom is provided.
|
|
|
+ if (startFrom != null && !startFrom.isEmpty()) {
|
|
|
+ continuation = getIsNamespaceEnabled()
|
|
|
+ ? generateContinuationTokenForXns(startFrom)
|
|
|
+ : generateContinuationTokenForNonXns(path.isRoot() ? ROOT_PATH : relativePath, startFrom);
|
|
|
+ }
|
|
|
+
|
|
|
+ ArrayList<FileStatus> fileStatuses = new ArrayList<>();
|
|
|
do {
|
|
|
AbfsRestOperation op = client.listPath(relativePath, false, LIST_MAX_RESULTS, continuation);
|
|
|
continuation = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_CONTINUATION);
|
|
@@ -583,6 +625,61 @@ public class AzureBlobFileSystemStore implements Closeable {
|
|
|
return fileStatuses.toArray(new FileStatus[fileStatuses.size()]);
|
|
|
}
|
|
|
|
|
|
+ // generate continuation token for xns account
|
|
|
+ private String generateContinuationTokenForXns(final String firstEntryName) {
|
|
|
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(firstEntryName)
|
|
|
+ && !firstEntryName.startsWith(AbfsHttpConstants.ROOT_PATH),
|
|
|
+ "startFrom must be a dir/file name and it can not be a full path");
|
|
|
+
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ sb.append(firstEntryName).append("#$").append("0");
|
|
|
+
|
|
|
+ CRC64 crc64 = new CRC64();
|
|
|
+ StringBuilder token = new StringBuilder();
|
|
|
+ token.append(crc64.compute(sb.toString().getBytes(StandardCharsets.UTF_8)))
|
|
|
+ .append(SINGLE_WHITE_SPACE)
|
|
|
+ .append("0")
|
|
|
+ .append(SINGLE_WHITE_SPACE)
|
|
|
+ .append(firstEntryName);
|
|
|
+
|
|
|
+ return Base64.encode(token.toString().getBytes(StandardCharsets.UTF_8));
|
|
|
+ }
|
|
|
+
|
|
|
+ // generate continuation token for non-xns account
|
|
|
+ private String generateContinuationTokenForNonXns(final String path, final String firstEntryName) {
|
|
|
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(firstEntryName)
|
|
|
+ && !firstEntryName.startsWith(AbfsHttpConstants.ROOT_PATH),
|
|
|
+ "startFrom must be a dir/file name and it can not be a full path");
|
|
|
+
|
|
|
+ // Notice: non-xns continuation token requires full path (first "/" is not included) for startFrom
|
|
|
+ final String startFrom = (path.isEmpty() || path.equals(ROOT_PATH))
|
|
|
+ ? firstEntryName
|
|
|
+ : path + ROOT_PATH + firstEntryName;
|
|
|
+
|
|
|
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TOKEN_DATE_PATTERN, Locale.US);
|
|
|
+ String date = simpleDateFormat.format(new Date());
|
|
|
+ String token = String.format("%06d!%s!%06d!%s!%06d!%s!",
|
|
|
+ path.length(), path, startFrom.length(), startFrom, date.length(), date);
|
|
|
+ String base64EncodedToken = Base64.encode(token.getBytes(StandardCharsets.UTF_8));
|
|
|
+
|
|
|
+ StringBuilder encodedTokenBuilder = new StringBuilder(base64EncodedToken.length() + 5);
|
|
|
+ encodedTokenBuilder.append(String.format("%s!%d!", TOKEN_VERSION, base64EncodedToken.length()));
|
|
|
+
|
|
|
+ for (int i = 0; i < base64EncodedToken.length(); i++) {
|
|
|
+ char current = base64EncodedToken.charAt(i);
|
|
|
+ if (CHAR_FORWARD_SLASH == current) {
|
|
|
+ current = CHAR_UNDERSCORE;
|
|
|
+ } else if (CHAR_PLUS == current) {
|
|
|
+ current = CHAR_STAR;
|
|
|
+ } else if (CHAR_EQUALS == current) {
|
|
|
+ current = CHAR_HYPHEN;
|
|
|
+ }
|
|
|
+ encodedTokenBuilder.append(current);
|
|
|
+ }
|
|
|
+
|
|
|
+ return encodedTokenBuilder.toString();
|
|
|
+ }
|
|
|
+
|
|
|
public void setOwner(final Path path, final String owner, final String group) throws
|
|
|
AzureBlobFileSystemException {
|
|
|
if (!getIsNamespaceEnabled()) {
|
|
@@ -1002,7 +1099,7 @@ public class AzureBlobFileSystemStore implements Closeable {
|
|
|
|
|
|
FileStatus other = (FileStatus) obj;
|
|
|
|
|
|
- if (!other.equals(this)) {// compare the path
|
|
|
+ if (!this.getPath().equals(other.getPath())) {// compare the path
|
|
|
return false;
|
|
|
}
|
|
|
|