{"action":"created","comment":{"url":"https://api.github.com/repos/Alluxio/alluxio/pulls/comments/70172052","id":70172052,"diff_hunk":"@@ -0,0 +1,725 @@\n+/*\n+ * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0\n+ * (the \"License\"). You may not use this work except in compliance with the License, which is\n+ * available at www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * This software is distributed on an \"AS IS\" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,\n+ * either express or implied, as more fully set forth in the License.\n+ *\n+ * See the NOTICE file distributed with this work for information regarding copyright ownership.\n+ */\n+\n+package alluxio.underfs.s3a;\n+\n+import alluxio.AlluxioURI;\n+import alluxio.Configuration;\n+import alluxio.Constants;\n+import alluxio.underfs.UnderFileSystem;\n+import alluxio.underfs.options.CreateOptions;\n+import alluxio.underfs.options.MkdirsOptions;\n+import alluxio.util.io.PathUtils;\n+\n+import com.amazonaws.AmazonClientException;\n+import com.amazonaws.ClientConfiguration;\n+import com.amazonaws.Protocol;\n+import com.amazonaws.auth.AWSCredentialsProvider;\n+import com.amazonaws.auth.AWSCredentialsProviderChain;\n+import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;\n+import com.amazonaws.services.s3.AmazonS3Client;\n+import com.amazonaws.services.s3.internal.Mimetypes;\n+import com.amazonaws.services.s3.model.CopyObjectRequest;\n+import com.amazonaws.services.s3.model.ListObjectsV2Request;\n+import com.amazonaws.services.s3.model.ListObjectsV2Result;\n+import com.amazonaws.services.s3.model.ObjectListing;\n+import com.amazonaws.services.s3.model.ObjectMetadata;\n+import com.amazonaws.services.s3.model.PutObjectRequest;\n+import com.amazonaws.services.s3.model.S3ObjectSummary;\n+import com.amazonaws.services.s3.transfer.TransferManager;\n+import com.amazonaws.services.s3.transfer.TransferManagerConfiguration;\n+import com.amazonaws.util.Base64;\n+import com.google.common.base.Preconditions;\n+import org.apache.commons.codec.digest.DigestUtils;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import java.io.ByteArrayInputStream;\n+import java.io.FileNotFoundException;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.OutputStream;\n+import java.util.HashSet;\n+import java.util.List;\n+import java.util.Set;\n+import javax.annotation.concurrent.ThreadSafe;\n+\n+/**\n+ * S3 {@link alluxio.underfs.UnderFileSystem} implementation based on the aws-java-sdk-s3 library.\n+ */\n+@ThreadSafe\n+public class S3AUnderFileSystem extends UnderFileSystem {\n+  private static final Logger LOG = LoggerFactory.getLogger(Constants.LOGGER_TYPE);\n+\n+  /** Suffix for an empty file to flag it as a directory. */\n+  private static final String FOLDER_SUFFIX = \"_$folder$\";\n+\n+  /** Value used to indicate folder structure in S3. */\n+  private static final String PATH_SEPARATOR = \"/\";\n+\n+  /** Static hash for a directory's empty contents. */\n+  private static final String DIR_HASH;\n+\n+  /** Length of each list request in S3. */\n+  private static final int LISTING_LENGTH = 1000;\n+\n+  /** Threshold to do multipart copy. */\n+  private static final long MULTIPART_COPY_THRESHOLD = 100 * Constants.MB;\n+\n+  /** AWS-SDK S3 client. */\n+  private final AmazonS3Client mClient;\n+\n+  /** Bucket name of user's configured Alluxio bucket. */\n+  private final String mBucketName;\n+\n+  /** Prefix of the bucket, for example s3a://my-bucket-name/. */\n+  private final String mBucketPrefix;\n+\n+  /** Transfer Manager for efficient I/O to s3. */\n+  private final TransferManager mManager;\n+\n+  static {\n+    byte[] dirByteHash = DigestUtils.md5(new byte[0]);\n+    DIR_HASH = new String(Base64.encode(dirByteHash));\n+  }\n+\n+  /**\n+   * Constructs a new instance of {@link S3AUnderFileSystem}.\n+   *\n+   * @param uri the {@link AlluxioURI} for this UFS\n+   */\n+  public S3AUnderFileSystem(AlluxioURI uri) {\n+    super(uri);\n+    mBucketName = uri.getHost();\n+    mBucketPrefix = PathUtils.normalizePath(Constants.HEADER_S3A + mBucketName, PATH_SEPARATOR);\n+\n+    // Checks, in order, env variables, system properties, profile file, and instance profile\n+    AWSCredentialsProvider credentials =\n+        new AWSCredentialsProviderChain(new DefaultAWSCredentialsProviderChain());\n+\n+    // Set the client configuration based on Alluxio configuration values\n+    ClientConfiguration clientConf = new ClientConfiguration();\n+\n+    // Socket timeout\n+    clientConf.setSocketTimeout(Configuration.getInt(Constants.UNDERFS_S3A_SOCKET_TIMEOUT_MS));\n+\n+    // HTTP protocol\n+    if (Configuration.getBoolean(Constants.UNDERFS_S3A_SECURE_HTTP_ENABLED)) {\n+      clientConf.setProtocol(Protocol.HTTPS);\n+    } else {\n+      clientConf.setProtocol(Protocol.HTTP);\n+    }\n+\n+    // Proxy host\n+    if (Configuration.containsKey(Constants.UNDERFS_S3_PROXY_HOST)) {\n+      clientConf.setProxyHost(Configuration.get(Constants.UNDERFS_S3_PROXY_HOST));\n+    }\n+\n+    // Proxy port\n+    if (Configuration.containsKey(Constants.UNDERFS_S3_PROXY_PORT)) {\n+      clientConf.setProxyPort(Configuration.getInt(Constants.UNDERFS_S3_PROXY_PORT));\n+    }\n+\n+\n+    mClient = new AmazonS3Client(credentials, clientConf);\n+    mManager = new TransferManager(mClient);\n+\n+    TransferManagerConfiguration transferConf = new TransferManagerConfiguration();\n+    transferConf.setMultipartCopyThreshold(MULTIPART_COPY_THRESHOLD);\n+    mManager.setConfiguration(transferConf);\n+  }\n+\n+  @Override\n+  public UnderFSType getUnderFSType() {\n+    return UnderFSType.S3;\n+  }\n+\n+  @Override\n+  public void close() throws IOException {\n+  }\n+\n+  @Override\n+  public void connectFromMaster(String hostname) {\n+    // Authentication is taken care of in the constructor\n+  }\n+\n+  @Override\n+  public void connectFromWorker(String hostname) {\n+    // Authentication is taken care of in the constructor\n+  }\n+\n+  @Override\n+  public OutputStream create(String path) throws IOException {\n+    return create(path, new CreateOptions());\n+  }\n+\n+  @Override\n+  public OutputStream create(String path, CreateOptions options) throws IOException {\n+    if (mkdirs(getParentKey(path), true)) {\n+      return new S3AOutputStream(mBucketName, stripPrefixIfPresent(path), mManager);\n+    }\n+    return null;\n+  }\n+\n+  @Override\n+  public boolean delete(String path, boolean recursive) throws IOException {\n+    if (!recursive) {\n+      if (isFolder(path) && listInternal(path, false).length != 0) {\n+        LOG.error(\"Unable to delete \" + path + \" because it is a non empty directory. Specify \"\n+            + \"recursive as true in order to delete non empty directories.\");\n+        return false;\n+      }\n+      return deleteInternal(path);\n+    }\n+    // Get all relevant files\n+    String[] pathsToDelete = listInternal(path, true);\n+    for (String pathToDelete : pathsToDelete) {\n+      // If we fail to deleteInternal one file, stop\n+      if (!deleteInternal(PathUtils.concatPath(path, pathToDelete))) {\n+        LOG.error(\"Failed to delete path {}, aborting delete.\", pathToDelete);\n+        return false;\n+      }\n+    }\n+    return deleteInternal(path);\n+  }\n+\n+  @Override\n+  public boolean exists(String path) throws IOException {\n+    // Root path always exists.\n+    return isRoot(path) || getObjectDetails(path) != null;\n+  }\n+\n+  /**\n+   * Gets the block size in bytes. There is no concept of a block in S3 and the maximum size of\n+   * one put is 5 GB, and the max size of a multi-part upload is 5 TB. This method defaults to the\n+   * default user block size in Alluxio.\n+   *\n+   * @param path the file name\n+   * @return the default Alluxio user block size\n+   * @throws IOException this implementation will not throw this exception, but subclasses may\n+   */\n+  @Override\n+  public long getBlockSizeByte(String path) throws IOException {\n+    return Configuration.getBytes(Constants.USER_BLOCK_SIZE_BYTES_DEFAULT);\n+  }\n+\n+  // Not supported\n+  @Override\n+  public Object getConf() {\n+    LOG.warn(\"getConf is not supported when using S3UnderFileSystem, returning null.\");\n+    return null;\n+  }\n+\n+  // Not supported\n+  @Override\n+  public List<String> getFileLocations(String path) throws IOException {\n+    LOG.warn(\"getFileLocations is not supported when using S3UnderFileSystem, returning null.\");\n+    return null;\n+  }\n+\n+  // Not supported\n+  @Override\n+  public List<String> getFileLocations(String path, long offset) throws IOException {\n+    LOG.warn(\"getFileLocations is not supported when using S3UnderFileSystem, returning null.\");\n+    return null;\n+  }\n+\n+  @Override\n+  public long getFileSize(String path) throws IOException {\n+    ObjectMetadata details = getObjectDetails(path);\n+    if (details != null) {\n+      return details.getContentLength();\n+    } else {\n+      throw new FileNotFoundException(path);\n+    }\n+  }\n+\n+  @Override\n+  public long getModificationTimeMs(String path) throws IOException {\n+    ObjectMetadata details = getObjectDetails(path);\n+    if (details != null) {\n+      return details.getLastModified().getTime();\n+    } else {\n+      throw new FileNotFoundException(path);\n+    }\n+  }\n+\n+  // This call is currently only used for the web ui, where a negative value implies unknown.\n+  @Override\n+  public long getSpace(String path, SpaceType type) throws IOException {\n+    return -1;\n+  }\n+\n+  @Override\n+  public boolean isFile(String path) throws IOException {\n+    // Directly try to get the file metadata, if we fail it either is a folder or does not exist\n+    try {\n+      mClient.getObjectMetadata(mBucketName, stripPrefixIfPresent(path));\n+      return true;\n+    } catch (AmazonClientException e) {\n+      return false;\n+    }\n+  }\n+\n+  @Override\n+  public String[] list(String path) throws IOException {\n+    // if the path not exists, or it is a file, then should return null\n+    if (!exists(path) || isFile(path)) {\n+      return null;\n+    }\n+    // Non recursive list\n+    path = PathUtils.normalizePath(path, PATH_SEPARATOR);\n+    return listInternal(path, false);\n+  }\n+\n+  @Override\n+  public boolean mkdirs(String path, boolean createParent) throws IOException {\n+    return mkdirs(path, new MkdirsOptions().setCreateParent(createParent));\n+  }\n+\n+  @Override\n+  public boolean mkdirs(String path, MkdirsOptions options) throws IOException {\n+    if (path == null) {\n+      return false;\n+    }\n+    if (isFolder(path)) {\n+      return true;\n+    }\n+    if (exists(path)) {\n+      LOG.error(\"Cannot create directory {} because it is already a file.\", path);\n+      return false;\n+    }\n+    if (!options.getCreateParent()) {\n+      if (parentExists(path)) {\n+        // Parent directory exists\n+        return mkdirsInternal(path);\n+      } else {\n+        LOG.error(\"Cannot create directory {} because parent does not exist\", path);\n+        return false;\n+      }\n+    }\n+    // Parent directories should be created\n+    if (parentExists(path)) {\n+      // Parent directory exists\n+      return mkdirsInternal(path);\n+    } else {\n+      String parentKey = getParentKey(path);\n+      // Recursively make the parent folders\n+      return mkdirs(parentKey, true) && mkdirsInternal(path);\n+    }\n+  }\n+\n+  @Override\n+  public InputStream open(String path) throws IOException {\n+    try {\n+      path = stripPrefixIfPresent(path);\n+      return new S3AInputStream(mBucketName, path, mClient);\n+    } catch (AmazonClientException e) {\n+      LOG.error(\"Failed to open file: {}\", path, e);\n+      return null;\n+    }\n+  }\n+\n+  /**\n+   * Opens a S3 object at given position and returns the opened input stream.\n+   *\n+   * @param path the S3 object path\n+   * @param pos the position to open at\n+   * @return the opened input stream\n+   * @throws java.io.IOException if failed to open file at position\n+   */\n+  public InputStream openAtPosition(String path, long pos) throws IOException {\n+    try {\n+      path = stripPrefixIfPresent(path);\n+      return new S3AInputStream(mBucketName, path, mClient, pos);\n+    } catch (AmazonClientException e) {\n+      LOG.error(\"Failed to open file {} at position {}:\", path, pos, e);\n+      return null;\n+    }\n+  }\n+\n+  @Override\n+  public boolean rename(String src, String dst) throws IOException {\n+    if (!exists(src)) {\n+      LOG.error(\"Unable to rename {} to {} because source does not exist.\", src, dst);\n+      return false;\n+    }\n+    if (exists(dst)) {\n+      LOG.error(\"Unable to rename {} to {} because destination already exists.\", src, dst);\n+      return false;\n+    }\n+    // Source exists and destination does not exist\n+    if (isFolder(src)) {\n+      // Rename the source folder first\n+      if (!copy(convertToFolderName(src), convertToFolderName(dst))) {\n+        return false;\n+      }\n+      // Rename each child in the src folder to destination/child\n+      String [] children = list(src);\n+      for (String child: children) {\n+        if (!rename(PathUtils.concatPath(src, child), PathUtils.concatPath(dst, child))) {\n+          return false;\n+        }\n+      }\n+      // Delete src and everything under src\n+      return delete(src, true);\n+    }\n+    // Source is a file and Destination does not exist\n+    return copy(src, dst) && deleteInternal(src);\n+  }\n+\n+  // Not supported\n+  @Override\n+  public void setConf(Object conf) {}\n+\n+  // Not supported\n+  @Override\n+  public void setOwner(String path, String user, String group) {}\n+\n+  // Not supported\n+  @Override\n+  public void setMode(String path, short mode) throws IOException {}\n+\n+  // Not supported\n+  @Override\n+  public String getOwner(String path) throws IOException {\n+    return \"\";\n+  }\n+\n+  // Not supported\n+  @Override\n+  public String getGroup(String path) throws IOException {\n+    return \"\";\n+  }\n+\n+  // Not supported\n+  @Override\n+  public short getMode(String path) throws IOException {\n+    return Constants.DEFAULT_FILE_SYSTEM_MODE;\n+  }\n+\n+  /**\n+   * Appends the directory suffix to the key.\n+   *\n+   * @param key the key to convert\n+   * @return key as a directory path\n+   */\n+  private String convertToFolderName(String key) {\n+    // Strips the slash if it is the end of the key string. This is because the slash at\n+    // the end of the string is not part of the Object key in S3.\n+    if (key.endsWith(PATH_SEPARATOR)) {","path":"underfs/s3a/src/main/java/alluxio/underfs/s3a/S3AUnderFileSystem.java","position":418,"original_position":418,"commit_id":"ed9fb56606c9182d175dd68214092eb3a7cebffd","original_commit_id":"ed9fb56606c9182d175dd68214092eb3a7cebffd","user":{"login":"apc999","id":1413748,"avatar_url":"https://avatars.githubusercontent.com/u/1413748?v=3","gravatar_id":"","url":"https://api.github.com/users/apc999","html_url":"https://github.com/apc999","followers_url":"https://api.github.com/users/apc999/followers","following_url":"https://api.github.com/users/apc999/following{/other_user}","gists_url":"https://api.github.com/users/apc999/gists{/gist_id}","starred_url":"https://api.github.com/users/apc999/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apc999/subscriptions","organizations_url":"https://api.github.com/users/apc999/orgs","repos_url":"https://api.github.com/users/apc999/repos","events_url":"https://api.github.com/users/apc999/events{/privacy}","received_events_url":"https://api.github.com/users/apc999/received_events","type":"User","site_admin":false},"body":"@madanadit has added `CommonUtils.stripeSuffixIfPresent` which you may want to use when his PR gets merged","created_at":"2016-07-09T21:06:22Z","updated_at":"2016-07-09T21:06:22Z","html_url":"https://github.com/Alluxio/alluxio/pull/3656#discussion_r70172052","pull_request_url":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656","_links":{"self":{"href":"https://api.github.com/repos/Alluxio/alluxio/pulls/comments/70172052"},"html":{"href":"https://github.com/Alluxio/alluxio/pull/3656#discussion_r70172052"},"pull_request":{"href":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656"}}},"pull_request":{"url":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656","id":76689913,"html_url":"https://github.com/Alluxio/alluxio/pull/3656","diff_url":"https://github.com/Alluxio/alluxio/pull/3656.diff","patch_url":"https://github.com/Alluxio/alluxio/pull/3656.patch","issue_url":"https://api.github.com/repos/Alluxio/alluxio/issues/3656","number":3656,"state":"open","locked":false,"title":"[ALLUXIO-2066] AWS-SDK implementation of S3 UFS","user":{"login":"calvinjia","id":2223813,"avatar_url":"https://avatars.githubusercontent.com/u/2223813?v=3","gravatar_id":"","url":"https://api.github.com/users/calvinjia","html_url":"https://github.com/calvinjia","followers_url":"https://api.github.com/users/calvinjia/followers","following_url":"https://api.github.com/users/calvinjia/following{/other_user}","gists_url":"https://api.github.com/users/calvinjia/gists{/gist_id}","starred_url":"https://api.github.com/users/calvinjia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/calvinjia/subscriptions","organizations_url":"https://api.github.com/users/calvinjia/orgs","repos_url":"https://api.github.com/users/calvinjia/repos","events_url":"https://api.github.com/users/calvinjia/events{/privacy}","received_events_url":"https://api.github.com/users/calvinjia/received_events","type":"User","site_admin":false},"body":"https://alluxio.atlassian.net/browse/ALLUXIO-2066","created_at":"2016-07-08T01:23:30Z","updated_at":"2016-07-09T21:06:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"da5cb2c02094402d48dd1b0f00ad4314418d5197","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656/commits","review_comments_url":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656/comments","review_comment_url":"https://api.github.com/repos/Alluxio/alluxio/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Alluxio/alluxio/issues/3656/comments","statuses_url":"https://api.github.com/repos/Alluxio/alluxio/statuses/ed9fb56606c9182d175dd68214092eb3a7cebffd","head":{"label":"calvinjia:aws-sdk-master","ref":"aws-sdk-master","sha":"ed9fb56606c9182d175dd68214092eb3a7cebffd","user":{"login":"calvinjia","id":2223813,"avatar_url":"https://avatars.githubusercontent.com/u/2223813?v=3","gravatar_id":"","url":"https://api.github.com/users/calvinjia","html_url":"https://github.com/calvinjia","followers_url":"https://api.github.com/users/calvinjia/followers","following_url":"https://api.github.com/users/calvinjia/following{/other_user}","gists_url":"https://api.github.com/users/calvinjia/gists{/gist_id}","starred_url":"https://api.github.com/users/calvinjia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/calvinjia/subscriptions","organizations_url":"https://api.github.com/users/calvinjia/orgs","repos_url":"https://api.github.com/users/calvinjia/repos","events_url":"https://api.github.com/users/calvinjia/events{/privacy}","received_events_url":"https://api.github.com/users/calvinjia/received_events","type":"User","site_admin":false},"repo":{"id":9278135,"name":"tachyon","full_name":"calvinjia/tachyon","owner":{"login":"calvinjia","id":2223813,"avatar_url":"https://avatars.githubusercontent.com/u/2223813?v=3","gravatar_id":"","url":"https://api.github.com/users/calvinjia","html_url":"https://github.com/calvinjia","followers_url":"https://api.github.com/users/calvinjia/followers","following_url":"https://api.github.com/users/calvinjia/following{/other_user}","gists_url":"https://api.github.com/users/calvinjia/gists{/gist_id}","starred_url":"https://api.github.com/users/calvinjia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/calvinjia/subscriptions","organizations_url":"https://api.github.com/users/calvinjia/orgs","repos_url":"https://api.github.com/users/calvinjia/repos","events_url":"https://api.github.com/users/calvinjia/events{/privacy}","received_events_url":"https://api.github.com/users/calvinjia/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/calvinjia/tachyon","description":"Tachyon","fork":true,"url":"https://api.github.com/repos/calvinjia/tachyon","forks_url":"https://api.github.com/repos/calvinjia/tachyon/forks","keys_url":"https://api.github.com/repos/calvinjia/tachyon/keys{/key_id}","collaborators_url":"https://api.github.com/repos/calvinjia/tachyon/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/calvinjia/tachyon/teams","hooks_url":"https://api.github.com/repos/calvinjia/tachyon/hooks","issue_events_url":"https://api.github.com/repos/calvinjia/tachyon/issues/events{/number}","events_url":"https://api.github.com/repos/calvinjia/tachyon/events","assignees_url":"https://api.github.com/repos/calvinjia/tachyon/assignees{/user}","branches_url":"https://api.github.com/repos/calvinjia/tachyon/branches{/branch}","tags_url":"https://api.github.com/repos/calvinjia/tachyon/tags","blobs_url":"https://api.github.com/repos/calvinjia/tachyon/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/calvinjia/tachyon/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/calvinjia/tachyon/git/refs{/sha}","trees_url":"https://api.github.com/repos/calvinjia/tachyon/git/trees{/sha}","statuses_url":"https://api.github.com/repos/calvinjia/tachyon/statuses/{sha}","languages_url":"https://api.github.com/repos/calvinjia/tachyon/languages","stargazers_url":"https://api.github.com/repos/calvinjia/tachyon/stargazers","contributors_url":"https://api.github.com/repos/calvinjia/tachyon/contributors","subscribers_url":"https://api.github.com/repos/calvinjia/tachyon/subscribers","subscription_url":"https://api.github.com/repos/calvinjia/tachyon/subscription","commits_url":"https://api.github.com/repos/calvinjia/tachyon/commits{/sha}","git_commits_url":"https://api.github.com/repos/calvinjia/tachyon/git/commits{/sha}","comments_url":"https://api.github.com/repos/calvinjia/tachyon/comments{/number}","issue_comment_url":"https://api.github.com/repos/calvinjia/tachyon/issues/comments{/number}","contents_url":"https://api.github.com/repos/calvinjia/tachyon/contents/{+path}","compare_url":"https://api.github.com/repos/calvinjia/tachyon/compare/{base}...{head}","merges_url":"https://api.github.com/repos/calvinjia/tachyon/merges","archive_url":"https://api.github.com/repos/calvinjia/tachyon/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/calvinjia/tachyon/downloads","issues_url":"https://api.github.com/repos/calvinjia/tachyon/issues{/number}","pulls_url":"https://api.github.com/repos/calvinjia/tachyon/pulls{/number}","milestones_url":"https://api.github.com/repos/calvinjia/tachyon/milestones{/number}","notifications_url":"https://api.github.com/repos/calvinjia/tachyon/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/calvinjia/tachyon/labels{/name}","releases_url":"https://api.github.com/repos/calvinjia/tachyon/releases{/id}","deployments_url":"https://api.github.com/repos/calvinjia/tachyon/deployments","created_at":"2013-04-07T14:56:38Z","updated_at":"2016-01-28T08:06:47Z","pushed_at":"2016-07-09T20:40:09Z","git_url":"git://github.com/calvinjia/tachyon.git","ssh_url":"git@github.com:calvinjia/tachyon.git","clone_url":"https://github.com/calvinjia/tachyon.git","svn_url":"https://github.com/calvinjia/tachyon","homepage":null,"size":57425,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"Alluxio:master","ref":"master","sha":"5c6d31dc6bbec0314c6fbd0809b2013ad10a5f27","user":{"login":"Alluxio","id":16203694,"avatar_url":"https://avatars.githubusercontent.com/u/16203694?v=3","gravatar_id":"","url":"https://api.github.com/users/Alluxio","html_url":"https://github.com/Alluxio","followers_url":"https://api.github.com/users/Alluxio/followers","following_url":"https://api.github.com/users/Alluxio/following{/other_user}","gists_url":"https://api.github.com/users/Alluxio/gists{/gist_id}","starred_url":"https://api.github.com/users/Alluxio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Alluxio/subscriptions","organizations_url":"https://api.github.com/users/Alluxio/orgs","repos_url":"https://api.github.com/users/Alluxio/repos","events_url":"https://api.github.com/users/Alluxio/events{/privacy}","received_events_url":"https://api.github.com/users/Alluxio/received_events","type":"Organization","site_admin":false},"repo":{"id":7276954,"name":"alluxio","full_name":"Alluxio/alluxio","owner":{"login":"Alluxio","id":16203694,"avatar_url":"https://avatars.githubusercontent.com/u/16203694?v=3","gravatar_id":"","url":"https://api.github.com/users/Alluxio","html_url":"https://github.com/Alluxio","followers_url":"https://api.github.com/users/Alluxio/followers","following_url":"https://api.github.com/users/Alluxio/following{/other_user}","gists_url":"https://api.github.com/users/Alluxio/gists{/gist_id}","starred_url":"https://api.github.com/users/Alluxio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Alluxio/subscriptions","organizations_url":"https://api.github.com/users/Alluxio/orgs","repos_url":"https://api.github.com/users/Alluxio/repos","events_url":"https://api.github.com/users/Alluxio/events{/privacy}","received_events_url":"https://api.github.com/users/Alluxio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Alluxio/alluxio","description":"Alluxio, formerly Tachyon, Memory Speed Virtual Distributed Storage System","fork":false,"url":"https://api.github.com/repos/Alluxio/alluxio","forks_url":"https://api.github.com/repos/Alluxio/alluxio/forks","keys_url":"https://api.github.com/repos/Alluxio/alluxio/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Alluxio/alluxio/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Alluxio/alluxio/teams","hooks_url":"https://api.github.com/repos/Alluxio/alluxio/hooks","issue_events_url":"https://api.github.com/repos/Alluxio/alluxio/issues/events{/number}","events_url":"https://api.github.com/repos/Alluxio/alluxio/events","assignees_url":"https://api.github.com/repos/Alluxio/alluxio/assignees{/user}","branches_url":"https://api.github.com/repos/Alluxio/alluxio/branches{/branch}","tags_url":"https://api.github.com/repos/Alluxio/alluxio/tags","blobs_url":"https://api.github.com/repos/Alluxio/alluxio/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Alluxio/alluxio/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Alluxio/alluxio/git/refs{/sha}","trees_url":"https://api.github.com/repos/Alluxio/alluxio/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Alluxio/alluxio/statuses/{sha}","languages_url":"https://api.github.com/repos/Alluxio/alluxio/languages","stargazers_url":"https://api.github.com/repos/Alluxio/alluxio/stargazers","contributors_url":"https://api.github.com/repos/Alluxio/alluxio/contributors","subscribers_url":"https://api.github.com/repos/Alluxio/alluxio/subscribers","subscription_url":"https://api.github.com/repos/Alluxio/alluxio/subscription","commits_url":"https://api.github.com/repos/Alluxio/alluxio/commits{/sha}","git_commits_url":"https://api.github.com/repos/Alluxio/alluxio/git/commits{/sha}","comments_url":"https://api.github.com/repos/Alluxio/alluxio/comments{/number}","issue_comment_url":"https://api.github.com/repos/Alluxio/alluxio/issues/comments{/number}","contents_url":"https://api.github.com/repos/Alluxio/alluxio/contents/{+path}","compare_url":"https://api.github.com/repos/Alluxio/alluxio/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Alluxio/alluxio/merges","archive_url":"https://api.github.com/repos/Alluxio/alluxio/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Alluxio/alluxio/downloads","issues_url":"https://api.github.com/repos/Alluxio/alluxio/issues{/number}","pulls_url":"https://api.github.com/repos/Alluxio/alluxio/pulls{/number}","milestones_url":"https://api.github.com/repos/Alluxio/alluxio/milestones{/number}","notifications_url":"https://api.github.com/repos/Alluxio/alluxio/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Alluxio/alluxio/labels{/name}","releases_url":"https://api.github.com/repos/Alluxio/alluxio/releases{/id}","deployments_url":"https://api.github.com/repos/Alluxio/alluxio/deployments","created_at":"2012-12-21T17:43:46Z","updated_at":"2016-07-09T01:15:51Z","pushed_at":"2016-07-09T20:40:10Z","git_url":"git://github.com/Alluxio/alluxio.git","ssh_url":"git@github.com:Alluxio/alluxio.git","clone_url":"https://github.com/Alluxio/alluxio.git","svn_url":"https://github.com/Alluxio/alluxio","homepage":"http://www.alluxio.org/","size":89140,"stargazers_count":2445,"watchers_count":2445,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1009,"mirror_url":null,"open_issues_count":16,"forks":1009,"open_issues":16,"watchers":2445,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656"},"html":{"href":"https://github.com/Alluxio/alluxio/pull/3656"},"issue":{"href":"https://api.github.com/repos/Alluxio/alluxio/issues/3656"},"comments":{"href":"https://api.github.com/repos/Alluxio/alluxio/issues/3656/comments"},"review_comments":{"href":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656/comments"},"review_comment":{"href":"https://api.github.com/repos/Alluxio/alluxio/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Alluxio/alluxio/pulls/3656/commits"},"statuses":{"href":"https://api.github.com/repos/Alluxio/alluxio/statuses/ed9fb56606c9182d175dd68214092eb3a7cebffd"}}}}
{"actor":{"display_login":"apc999"}}