{"action":"created","comment":{"url":"https://api.github.com/repos/pravega/pravega/pulls/comments/241477893","pull_request_review_id":184719061,"id":241477893,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI0MTQ3Nzg5Mw==","diff_hunk":"@@ -0,0 +1,326 @@\n+/**\n+ * Copyright (c) 2017 Dell Inc., or its subsidiaries. All Rights Reserved.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ */\n+package io.pravega.segmentstore.server.tables;\n+\n+import com.google.common.base.Preconditions;\n+import com.google.common.collect.Maps;\n+import io.pravega.common.concurrent.Futures;\n+import io.pravega.common.util.AsyncIterator;\n+import io.pravega.segmentstore.contracts.Attributes;\n+import io.pravega.segmentstore.server.AttributeIterator;\n+import io.pravega.segmentstore.server.DirectSegmentAccess;\n+import java.time.Duration;\n+import java.util.ArrayDeque;\n+import java.util.ArrayList;\n+import java.util.Comparator;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.UUID;\n+import java.util.concurrent.CompletableFuture;\n+import java.util.concurrent.CompletionException;\n+import java.util.concurrent.Executor;\n+import java.util.concurrent.ForkJoinPool;\n+import java.util.concurrent.ScheduledExecutorService;\n+import java.util.concurrent.atomic.AtomicBoolean;\n+import java.util.concurrent.atomic.AtomicReference;\n+import java.util.stream.Collectors;\n+import javax.annotation.concurrent.ThreadSafe;\n+import lombok.AccessLevel;\n+import lombok.NonNull;\n+import lombok.RequiredArgsConstructor;\n+import lombok.val;\n+\n+/**\n+ * Iterates through {@link TableBucket}s in a Segment.\n+ * @param <T> Type of the final, converted result.\n+ */\n+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)\n+@ThreadSafe\n+class TableIterator<T> implements AsyncIterator<T> {\n+    //region Members\n+\n+    private final AttributeIterator indexHashIterator;\n+    private final ConvertResult<T> resultConverter;\n+    private final ArrayDeque<Map.Entry<UUID, Long>> cacheHashes;\n+    private final Executor executor;\n+    private final AtomicReference<Iterator<TableBucket>> currentBatch = new AtomicReference<>();\n+    private final AtomicBoolean inProgress = new AtomicBoolean(false);\n+\n+    //endregion\n+\n+    //region AsyncIterator Implementation\n+\n+    @Override\n+    public CompletableFuture<T> getNext() {\n+        // Verify no other call to getNext() is currently executing.\n+        Preconditions.checkState(this.inProgress.compareAndSet(false, true), \"Another call to getNext() is in progress.\");\n+        return getNextBucket()\n+                .thenCompose(bucket -> {\n+                    this.inProgress.set(false);\n+                    if (bucket == null) {\n+                        // We are done.\n+                        return CompletableFuture.completedFuture(null);\n+                    } else {\n+                        // Convert the TableBucket into the desired result.\n+                        return this.resultConverter.apply(bucket);\n+                    }\n+                })\n+                .exceptionally(ex -> {\n+                    this.inProgress.set(false);\n+                    throw new CompletionException(ex);\n+                });\n+    }\n+\n+    /**\n+     * Gets the next {@link TableBucket} in the iteration. This will either be served directly from the cached batch\n+     * of {@link TableBucket}s or a new invocation to the underlying indexHashIterator will be performed to fetch the next\n+     * {@link TableBucket}.\n+     */\n+    private CompletableFuture<TableBucket> getNextBucket() {\n+        val fromBatch = getNextBucketFromExistingBatch();\n+        if (fromBatch != null) {\n+            return CompletableFuture.completedFuture(fromBatch);\n+        }\n+\n+        val canContinue = new AtomicBoolean(true);\n+        return Futures\n+                .loop(\n+                        canContinue::get,\n+                        this::fetchNextTableBuckets,\n+                        canContinue::set,\n+                        this.executor)\n+                .thenApply(v -> getNextBucketFromExistingBatch());\n+    }\n+\n+    /**\n+     * Gets the next {@link TableBucket} in the iteration from the cached batch. If the batch is exhausted, returns null.\n+     */\n+    private TableBucket getNextBucketFromExistingBatch() {\n+        val batch = this.currentBatch.get();\n+        if (batch != null) {\n+            TableBucket next = batch.next();\n+            if (!batch.hasNext()) {\n+                this.currentBatch.set(null);\n+            }\n+\n+            return next;\n+        }\n+\n+        return null;\n+    }\n+\n+    /**\n+     * Fetches the next set of {@link TableBucket}s from the indexHashIterator.\n+     */\n+    private CompletableFuture<Boolean> fetchNextTableBuckets() {\n+        return this.indexHashIterator\n+                .getNext()\n+                .thenApplyAsync(indexHashes -> {\n+                    List<TableBucket> buckets = toBuckets(indexHashes);\n+                    if (buckets == null) {\n+                        // End of iteration.\n+                        return false;\n+                    } else if (!buckets.isEmpty()) {\n+                        // Got something. Stop here for now.\n+                        this.currentBatch.set(buckets.iterator());\n+                        return false;\n+                    }\n+                    return true;\n+                }, this.executor);\n+    }\n+\n+    /**\n+     * Merges the given list of Index Hashes with the available Cache Hashes and generates the associated {@link TableBucket}s.\n+     */\n+    private List<TableBucket> toBuckets(List<Map.Entry<UUID, Long>> indexHashes) {\n+        val buckets = new ArrayList<TableBucket>();\n+        if (indexHashes == null) {\n+            // Nothing more in the index. Add whatever is in the cache attributes.\n+            while (!this.cacheHashes.isEmpty()) {\n+                add(this.cacheHashes.removeFirst(), buckets);\n+            }\n+\n+            return buckets.isEmpty() ? null : buckets;\n+        } else {\n+            // Transform every eligible Attribute into a TableBucket and add it to the result.\n+            for (val indexHash : indexHashes) {\n+                if (KeyHasher.isValid(indexHash.getKey()) && indexHash.getValue() != Attributes.NULL_ATTRIBUTE_VALUE) {\n+                    // For each bucket returned above, include all Buckets/hashes from the ContainerKeyIndex which are equal to or\n+                    // below it. (this is very similar to the AttributeMixer - maybe reuse that methodology).\n+                    boolean overridden = false;\n+                    while (!this.cacheHashes.isEmpty()) {\n+                        val cacheHash = this.cacheHashes.peekFirst();\n+                        int cmp = indexHash.getKey().compareTo(this.cacheHashes.peekFirst().getKey());\n+                        if (cmp < 0) {\n+                            // Cache Hash is after Index Hash. We are done here.\n+                            break;\n+                        }\n+\n+                        // The value we got from the Index was overridden by the one in the cache.\n+                        overridden = overridden || cmp == 0;\n+                        add(cacheHash, buckets);\n+                        this.cacheHashes.removeFirst();\n+                    }\n+\n+                    if (!overridden) {\n+                        add(indexHash, buckets);\n+                    }\n+                }\n+            }\n+        }\n+\n+        return buckets;\n+    }\n+\n+    private void add(Map.Entry<UUID, Long> bucketInfo, List<TableBucket> buckets) {\n+        buckets.add(new TableBucket(bucketInfo.getKey(), bucketInfo.getValue()));\n+    }\n+\n+    //endregion\n+\n+    //region Builder\n+\n+    /**\n+     * Creates a new {@link TableIterator.Builder} that can be used to construct {@link TableIterator} instances.\n+     *\n+     * @param <T> Type of the elements returned at each iteration.\n+     * @return A new instance of the {@link TableIterator.Builder} class.\n+     */\n+    static <T> Builder<T> builder() {\n+        return new Builder<>();\n+    }\n+\n+    /**\n+     * Creates a new {@link TableIterator} that contains no elements.\n+     *\n+     * @param <T> Type of elements returned at each iteration.\n+     * @return A new instance of the {@link TableIterator.Builder} class.\n+     */\n+    static <T> TableIterator<T> empty() {\n+        return new TableIterator<>(\n+                () -> CompletableFuture.completedFuture(null),\n+                ignored -> CompletableFuture.completedFuture(null),\n+                new ArrayDeque<>(),\n+                ForkJoinPool.commonPool());\n+    }\n+\n+    /**\n+     * Builder for the {@link TableIterator} class.\n+     */\n+    static class Builder<T> {\n+        private DirectSegmentAccess segment;\n+        private Map<UUID, CacheBucketOffset> cacheHashes;\n+        private UUID firstHash;\n+        private ConvertResult<T> resultConverter;\n+        private ScheduledExecutorService executor;\n+        private Duration fetchTimeout;\n+\n+        /**\n+         * Sets a {@link DirectSegmentAccess} representing a Table Segment that the iterator will iterate over.\n+         *\n+         * @param segment The {@link DirectSegmentAccess} to associate.\n+         * @return This object.\n+         */\n+        Builder<T> segment(@NonNull DirectSegmentAccess segment) {","path":"segmentstore/server/src/main/java/io/pravega/segmentstore/server/tables/TableIterator.java","position":232,"original_position":232,"commit_id":"9df9db7029bc675b266448b1a5f6a6b18c74abe7","original_commit_id":"9df9db7029bc675b266448b1a5f6a6b18c74abe7","user":{"login":"fpj","id":572920,"node_id":"MDQ6VXNlcjU3MjkyMA==","avatar_url":"https://avatars3.githubusercontent.com/u/572920?v=4","gravatar_id":"","url":"https://api.github.com/users/fpj","html_url":"https://github.com/fpj","followers_url":"https://api.github.com/users/fpj/followers","following_url":"https://api.github.com/users/fpj/following{/other_user}","gists_url":"https://api.github.com/users/fpj/gists{/gist_id}","starred_url":"https://api.github.com/users/fpj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fpj/subscriptions","organizations_url":"https://api.github.com/users/fpj/orgs","repos_url":"https://api.github.com/users/fpj/repos","events_url":"https://api.github.com/users/fpj/events{/privacy}","received_events_url":"https://api.github.com/users/fpj/received_events","type":"User","site_admin":false},"body":"If you want to follow some of the practices we have been following in other builders across the project, I suggest you use:\r\n\r\n```\r\nforSegment()\r\nwithCacheHashes()\r\nwithFirstHash()\r\n<the other ones are all with*>\r\n```","created_at":"2018-12-13T16:47:59Z","updated_at":"2018-12-13T16:54:26Z","html_url":"https://github.com/pravega/pravega/pull/3172#discussion_r241477893","pull_request_url":"https://api.github.com/repos/pravega/pravega/pulls/3172","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/pravega/pravega/pulls/comments/241477893"},"html":{"href":"https://github.com/pravega/pravega/pull/3172#discussion_r241477893"},"pull_request":{"href":"https://api.github.com/repos/pravega/pravega/pulls/3172"}}},"pull_request":{"url":"https://api.github.com/repos/pravega/pravega/pulls/3172","id":236280122,"node_id":"MDExOlB1bGxSZXF1ZXN0MjM2MjgwMTIy","html_url":"https://github.com/pravega/pravega/pull/3172","diff_url":"https://github.com/pravega/pravega/pull/3172.diff","patch_url":"https://github.com/pravega/pravega/pull/3172.patch","issue_url":"https://api.github.com/repos/pravega/pravega/issues/3172","number":3172,"state":"open","locked":false,"title":"Issue 2881: (Segment Store) TableSegment Key/Entry Iterator","user":{"login":"andreipaduroiu","id":14354377,"node_id":"MDQ6VXNlcjE0MzU0Mzc3","avatar_url":"https://avatars2.githubusercontent.com/u/14354377?v=4","gravatar_id":"","url":"https://api.github.com/users/andreipaduroiu","html_url":"https://github.com/andreipaduroiu","followers_url":"https://api.github.com/users/andreipaduroiu/followers","following_url":"https://api.github.com/users/andreipaduroiu/following{/other_user}","gists_url":"https://api.github.com/users/andreipaduroiu/gists{/gist_id}","starred_url":"https://api.github.com/users/andreipaduroiu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andreipaduroiu/subscriptions","organizations_url":"https://api.github.com/users/andreipaduroiu/orgs","repos_url":"https://api.github.com/users/andreipaduroiu/repos","events_url":"https://api.github.com/users/andreipaduroiu/events{/privacy}","received_events_url":"https://api.github.com/users/andreipaduroiu/received_events","type":"User","site_admin":false},"body":"**Change log description**  \r\n- Implemented Table Segment Key and Entry iterators, which allow iterating through all the Keys or Entries in a Table.\r\n\r\n**Purpose of the change**  \r\nFixes #3172.\r\n\r\n**What the code does**  \r\nImplemented Table Iterators which allow looping through all the Keys or Table Entries in a Table Segment. This makes use of Segment Attribute Iterators, which was implemented as part of a previous PR (#3052).\r\n\r\nTop-to-bottom description:\r\n- `TableStore` API defines `keyIterator` and `entryIterator`, which return an `AsyncIterator` that can be used to traverse the entire Table. `keyIterator` will return the Table Keys only, while `entryIterator` will also return the values.\r\n    - This allows an optional parameter `serializedState` that can be used to resume a previously interrupted iteration. This value can be fetched from each `IteratorItem` that is returned from the `AsyncIterator`\r\n    - `TableService` delegates the iterator request down to the `ContainerTableExtension` that handles that particular Table Segment.\r\n- `ContainerTableExtensionImpl`\r\n    - Deserializes the supplied state (if any) and determines the next `TableBucket` that should be fetched. The state itself contains the `TableBucket` Hash (16-byte UUID) of the last `TableBucket` that was served by the iterator.\r\n    - Creates a `TableIterator` (which loops through all `TableBuckets`) and for each result, returns all Keys or all Entries associated with that TableBucket.\r\n- `TableIterator`\r\n    - Uses the `SegmentAttributeIterator` to loop through all the Segment's Extended Attributes that are Table Bucket Hashes. If a State was provided (see above) then it iterates only from the next attribute starting at the provided one.\r\n    - Each value from the `SegmentAttributeIterator` is a `TableBucket`, and is eligible for returning.\r\n    - This also merges with the unindexed TableBucket values in `ContainerKeyIndex` - these are Table Bucket updates/removals that have been accepted, written to the Segment, but not yet indexed by the `WriterTableProcessor`.  \r\n\r\n**How to verify it**  \r\nAll existing unit tests must pass.\r\nNewly added unit tests (for iterators) must pass as well.\r\n","created_at":"2018-12-05T18:37:24Z","updated_at":"2018-12-13T16:54:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a9598e67c5ffd537000664f9f9fc1488750785f8","assignee":{"login":"fpj","id":572920,"node_id":"MDQ6VXNlcjU3MjkyMA==","avatar_url":"https://avatars3.githubusercontent.com/u/572920?v=4","gravatar_id":"","url":"https://api.github.com/users/fpj","html_url":"https://github.com/fpj","followers_url":"https://api.github.com/users/fpj/followers","following_url":"https://api.github.com/users/fpj/following{/other_user}","gists_url":"https://api.github.com/users/fpj/gists{/gist_id}","starred_url":"https://api.github.com/users/fpj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fpj/subscriptions","organizations_url":"https://api.github.com/users/fpj/orgs","repos_url":"https://api.github.com/users/fpj/repos","events_url":"https://api.github.com/users/fpj/events{/privacy}","received_events_url":"https://api.github.com/users/fpj/received_events","type":"User","site_admin":false},"assignees":[{"login":"fpj","id":572920,"node_id":"MDQ6VXNlcjU3MjkyMA==","avatar_url":"https://avatars3.githubusercontent.com/u/572920?v=4","gravatar_id":"","url":"https://api.github.com/users/fpj","html_url":"https://github.com/fpj","followers_url":"https://api.github.com/users/fpj/followers","following_url":"https://api.github.com/users/fpj/following{/other_user}","gists_url":"https://api.github.com/users/fpj/gists{/gist_id}","starred_url":"https://api.github.com/users/fpj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fpj/subscriptions","organizations_url":"https://api.github.com/users/fpj/orgs","repos_url":"https://api.github.com/users/fpj/repos","events_url":"https://api.github.com/users/fpj/events{/privacy}","received_events_url":"https://api.github.com/users/fpj/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/pravega/pravega/pulls/3172/commits","review_comments_url":"https://api.github.com/repos/pravega/pravega/pulls/3172/comments","review_comment_url":"https://api.github.com/repos/pravega/pravega/pulls/comments{/number}","comments_url":"https://api.github.com/repos/pravega/pravega/issues/3172/comments","statuses_url":"https://api.github.com/repos/pravega/pravega/statuses/9df9db7029bc675b266448b1a5f6a6b18c74abe7","head":{"label":"andreipaduroiu:issue-2881-table-iterator","ref":"issue-2881-table-iterator","sha":"9df9db7029bc675b266448b1a5f6a6b18c74abe7","user":{"login":"andreipaduroiu","id":14354377,"node_id":"MDQ6VXNlcjE0MzU0Mzc3","avatar_url":"https://avatars2.githubusercontent.com/u/14354377?v=4","gravatar_id":"","url":"https://api.github.com/users/andreipaduroiu","html_url":"https://github.com/andreipaduroiu","followers_url":"https://api.github.com/users/andreipaduroiu/followers","following_url":"https://api.github.com/users/andreipaduroiu/following{/other_user}","gists_url":"https://api.github.com/users/andreipaduroiu/gists{/gist_id}","starred_url":"https://api.github.com/users/andreipaduroiu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andreipaduroiu/subscriptions","organizations_url":"https://api.github.com/users/andreipaduroiu/orgs","repos_url":"https://api.github.com/users/andreipaduroiu/repos","events_url":"https://api.github.com/users/andreipaduroiu/events{/privacy}","received_events_url":"https://api.github.com/users/andreipaduroiu/received_events","type":"User","site_admin":false},"repo":{"id":136227276,"node_id":"MDEwOlJlcG9zaXRvcnkxMzYyMjcyNzY=","name":"pravega","full_name":"andreipaduroiu/pravega","private":false,"owner":{"login":"andreipaduroiu","id":14354377,"node_id":"MDQ6VXNlcjE0MzU0Mzc3","avatar_url":"https://avatars2.githubusercontent.com/u/14354377?v=4","gravatar_id":"","url":"https://api.github.com/users/andreipaduroiu","html_url":"https://github.com/andreipaduroiu","followers_url":"https://api.github.com/users/andreipaduroiu/followers","following_url":"https://api.github.com/users/andreipaduroiu/following{/other_user}","gists_url":"https://api.github.com/users/andreipaduroiu/gists{/gist_id}","starred_url":"https://api.github.com/users/andreipaduroiu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andreipaduroiu/subscriptions","organizations_url":"https://api.github.com/users/andreipaduroiu/orgs","repos_url":"https://api.github.com/users/andreipaduroiu/repos","events_url":"https://api.github.com/users/andreipaduroiu/events{/privacy}","received_events_url":"https://api.github.com/users/andreipaduroiu/received_events","type":"User","site_admin":false},"html_url":"https://github.com/andreipaduroiu/pravega","description":"Pravega - Streaming as a new software defined storage primitive","fork":true,"url":"https://api.github.com/repos/andreipaduroiu/pravega","forks_url":"https://api.github.com/repos/andreipaduroiu/pravega/forks","keys_url":"https://api.github.com/repos/andreipaduroiu/pravega/keys{/key_id}","collaborators_url":"https://api.github.com/repos/andreipaduroiu/pravega/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/andreipaduroiu/pravega/teams","hooks_url":"https://api.github.com/repos/andreipaduroiu/pravega/hooks","issue_events_url":"https://api.github.com/repos/andreipaduroiu/pravega/issues/events{/number}","events_url":"https://api.github.com/repos/andreipaduroiu/pravega/events","assignees_url":"https://api.github.com/repos/andreipaduroiu/pravega/assignees{/user}","branches_url":"https://api.github.com/repos/andreipaduroiu/pravega/branches{/branch}","tags_url":"https://api.github.com/repos/andreipaduroiu/pravega/tags","blobs_url":"https://api.github.com/repos/andreipaduroiu/pravega/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/andreipaduroiu/pravega/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/andreipaduroiu/pravega/git/refs{/sha}","trees_url":"https://api.github.com/repos/andreipaduroiu/pravega/git/trees{/sha}","statuses_url":"https://api.github.com/repos/andreipaduroiu/pravega/statuses/{sha}","languages_url":"https://api.github.com/repos/andreipaduroiu/pravega/languages","stargazers_url":"https://api.github.com/repos/andreipaduroiu/pravega/stargazers","contributors_url":"https://api.github.com/repos/andreipaduroiu/pravega/contributors","subscribers_url":"https://api.github.com/repos/andreipaduroiu/pravega/subscribers","subscription_url":"https://api.github.com/repos/andreipaduroiu/pravega/subscription","commits_url":"https://api.github.com/repos/andreipaduroiu/pravega/commits{/sha}","git_commits_url":"https://api.github.com/repos/andreipaduroiu/pravega/git/commits{/sha}","comments_url":"https://api.github.com/repos/andreipaduroiu/pravega/comments{/number}","issue_comment_url":"https://api.github.com/repos/andreipaduroiu/pravega/issues/comments{/number}","contents_url":"https://api.github.com/repos/andreipaduroiu/pravega/contents/{+path}","compare_url":"https://api.github.com/repos/andreipaduroiu/pravega/compare/{base}...{head}","merges_url":"https://api.github.com/repos/andreipaduroiu/pravega/merges","archive_url":"https://api.github.com/repos/andreipaduroiu/pravega/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/andreipaduroiu/pravega/downloads","issues_url":"https://api.github.com/repos/andreipaduroiu/pravega/issues{/number}","pulls_url":"https://api.github.com/repos/andreipaduroiu/pravega/pulls{/number}","milestones_url":"https://api.github.com/repos/andreipaduroiu/pravega/milestones{/number}","notifications_url":"https://api.github.com/repos/andreipaduroiu/pravega/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/andreipaduroiu/pravega/labels{/name}","releases_url":"https://api.github.com/repos/andreipaduroiu/pravega/releases{/id}","deployments_url":"https://api.github.com/repos/andreipaduroiu/pravega/deployments","created_at":"2018-06-05T19:49:10Z","updated_at":"2018-06-05T19:55:27Z","pushed_at":"2018-12-13T00:19:16Z","git_url":"git://github.com/andreipaduroiu/pravega.git","ssh_url":"git@github.com:andreipaduroiu/pravega.git","clone_url":"https://github.com/andreipaduroiu/pravega.git","svn_url":"https://github.com/andreipaduroiu/pravega","homepage":"http://pravega.io","size":27629,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"pravega:master","ref":"master","sha":"8e2f29248e25d3f2f4d34910db8fb72a33bd66b3","user":{"login":"pravega","id":25698199,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI1Njk4MTk5","avatar_url":"https://avatars1.githubusercontent.com/u/25698199?v=4","gravatar_id":"","url":"https://api.github.com/users/pravega","html_url":"https://github.com/pravega","followers_url":"https://api.github.com/users/pravega/followers","following_url":"https://api.github.com/users/pravega/following{/other_user}","gists_url":"https://api.github.com/users/pravega/gists{/gist_id}","starred_url":"https://api.github.com/users/pravega/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pravega/subscriptions","organizations_url":"https://api.github.com/users/pravega/orgs","repos_url":"https://api.github.com/users/pravega/repos","events_url":"https://api.github.com/users/pravega/events{/privacy}","received_events_url":"https://api.github.com/users/pravega/received_events","type":"Organization","site_admin":false},"repo":{"id":63096822,"node_id":"MDEwOlJlcG9zaXRvcnk2MzA5NjgyMg==","name":"pravega","full_name":"pravega/pravega","private":false,"owner":{"login":"pravega","id":25698199,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI1Njk4MTk5","avatar_url":"https://avatars1.githubusercontent.com/u/25698199?v=4","gravatar_id":"","url":"https://api.github.com/users/pravega","html_url":"https://github.com/pravega","followers_url":"https://api.github.com/users/pravega/followers","following_url":"https://api.github.com/users/pravega/following{/other_user}","gists_url":"https://api.github.com/users/pravega/gists{/gist_id}","starred_url":"https://api.github.com/users/pravega/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pravega/subscriptions","organizations_url":"https://api.github.com/users/pravega/orgs","repos_url":"https://api.github.com/users/pravega/repos","events_url":"https://api.github.com/users/pravega/events{/privacy}","received_events_url":"https://api.github.com/users/pravega/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/pravega/pravega","description":"Pravega - Streaming as a new software defined storage primitive","fork":false,"url":"https://api.github.com/repos/pravega/pravega","forks_url":"https://api.github.com/repos/pravega/pravega/forks","keys_url":"https://api.github.com/repos/pravega/pravega/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pravega/pravega/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pravega/pravega/teams","hooks_url":"https://api.github.com/repos/pravega/pravega/hooks","issue_events_url":"https://api.github.com/repos/pravega/pravega/issues/events{/number}","events_url":"https://api.github.com/repos/pravega/pravega/events","assignees_url":"https://api.github.com/repos/pravega/pravega/assignees{/user}","branches_url":"https://api.github.com/repos/pravega/pravega/branches{/branch}","tags_url":"https://api.github.com/repos/pravega/pravega/tags","blobs_url":"https://api.github.com/repos/pravega/pravega/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pravega/pravega/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pravega/pravega/git/refs{/sha}","trees_url":"https://api.github.com/repos/pravega/pravega/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pravega/pravega/statuses/{sha}","languages_url":"https://api.github.com/repos/pravega/pravega/languages","stargazers_url":"https://api.github.com/repos/pravega/pravega/stargazers","contributors_url":"https://api.github.com/repos/pravega/pravega/contributors","subscribers_url":"https://api.github.com/repos/pravega/pravega/subscribers","subscription_url":"https://api.github.com/repos/pravega/pravega/subscription","commits_url":"https://api.github.com/repos/pravega/pravega/commits{/sha}","git_commits_url":"https://api.github.com/repos/pravega/pravega/git/commits{/sha}","comments_url":"https://api.github.com/repos/pravega/pravega/comments{/number}","issue_comment_url":"https://api.github.com/repos/pravega/pravega/issues/comments{/number}","contents_url":"https://api.github.com/repos/pravega/pravega/contents/{+path}","compare_url":"https://api.github.com/repos/pravega/pravega/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pravega/pravega/merges","archive_url":"https://api.github.com/repos/pravega/pravega/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pravega/pravega/downloads","issues_url":"https://api.github.com/repos/pravega/pravega/issues{/number}","pulls_url":"https://api.github.com/repos/pravega/pravega/pulls{/number}","milestones_url":"https://api.github.com/repos/pravega/pravega/milestones{/number}","notifications_url":"https://api.github.com/repos/pravega/pravega/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pravega/pravega/labels{/name}","releases_url":"https://api.github.com/repos/pravega/pravega/releases{/id}","deployments_url":"https://api.github.com/repos/pravega/pravega/deployments","created_at":"2016-07-11T19:41:03Z","updated_at":"2018-12-13T15:02:57Z","pushed_at":"2018-12-13T15:02:53Z","git_url":"git://github.com/pravega/pravega.git","ssh_url":"git@github.com:pravega/pravega.git","clone_url":"https://github.com/pravega/pravega.git","svn_url":"https://github.com/pravega/pravega","homepage":"http://pravega.io","size":27215,"stargazers_count":289,"watchers_count":289,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":94,"mirror_url":null,"archived":false,"open_issues_count":315,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":94,"open_issues":315,"watchers":289,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/pravega/pravega/pulls/3172"},"html":{"href":"https://github.com/pravega/pravega/pull/3172"},"issue":{"href":"https://api.github.com/repos/pravega/pravega/issues/3172"},"comments":{"href":"https://api.github.com/repos/pravega/pravega/issues/3172/comments"},"review_comments":{"href":"https://api.github.com/repos/pravega/pravega/pulls/3172/comments"},"review_comment":{"href":"https://api.github.com/repos/pravega/pravega/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/pravega/pravega/pulls/3172/commits"},"statuses":{"href":"https://api.github.com/repos/pravega/pravega/statuses/9df9db7029bc675b266448b1a5f6a6b18c74abe7"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"fpj"}}