{"action":"created","comment":{"url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/comments/80530400","id":80530400,"diff_hunk":"@@ -0,0 +1,248 @@\n+import logging\n+\n+from bubuku.features.rebalance import BaseRebalanceChange\n+from bubuku.features.rebalance.broker import BrokerDescription\n+from bubuku.zookeeper import BukuExhibitor\n+\n+_LOG = logging.getLogger('bubuku.features.rebalance')\n+\n+\n+def distribute(amount, items, weight_key, apply_func):\n+    if not items:\n+        return\n+    items = sorted(items, key=weight_key, reverse=True)\n+    ceil_amount = int(amount / len(items))\n+    amounts = [ceil_amount for _ in range(0, len(items))]\n+    for idx in range(0, min(amount - sum(amounts), len(items))):\n+        amounts[idx] += 1\n+    for idx in range(0, len(items)):\n+        apply_func(items[idx], amounts[idx])\n+\n+\n+class OptimizedRebalanceChange(BaseRebalanceChange):\n+    _LOAD_STATE = 'load_state'\n+    _COMPUTE_LEADERS = 'compute_leaders'\n+    _COMPUTE_REPLICAS = 'compute_replicas'\n+    _SORT_ACTIONS = 'sort_actions'\n+    _BALANCE = 'balance'\n+\n+    def __init__(self, zk: BukuExhibitor, broker_ids: list):\n+        self.zk = zk\n+        self.broker_ids = sorted(int(id_) for id_ in broker_ids)\n+        self.broker_distribution = None\n+        self.source_distribution = None\n+        self.action_queue = None\n+        self.state = OptimizedRebalanceChange._LOAD_STATE\n+\n+    def __str__(self):\n+        return 'OptimizedRebalance state={}, queue_size={}'.format(\n+            self.state, len(self.action_queue) if self.action_queue is not None else None)\n+\n+    def run(self, current_actions) -> bool:\n+        # Stop rebalance if someone is restarting\n+        if self.should_be_paused(current_actions):\n+            _LOG.warning(\"Rebalance paused, because other blocking events running: {}\".format(current_actions))\n+            return True\n+        if self.zk.is_rebalancing():\n+            return True\n+        new_broker_ids = sorted(int(id_) for id_ in self.zk.get_broker_ids())\n+        if new_broker_ids != self.broker_ids:\n+            _LOG.warning(\"Rebalance stopped because of broker list change from {} to {}\".format(\n+                self.broker_ids, new_broker_ids))\n+            return False\n+        if self.state == OptimizedRebalanceChange._LOAD_STATE:\n+            self._load_data()\n+            self.state = OptimizedRebalanceChange._COMPUTE_LEADERS\n+        elif self.state == OptimizedRebalanceChange._COMPUTE_LEADERS:\n+            self.action_queue = self.rebalance_leaders(self.broker_distribution)\n+            self.state = OptimizedRebalanceChange._COMPUTE_REPLICAS\n+        elif self.state == OptimizedRebalanceChange._COMPUTE_REPLICAS:\n+            self._rebalance_replicas()\n+            self.state = OptimizedRebalanceChange._SORT_ACTIONS\n+        elif self.state == OptimizedRebalanceChange._SORT_ACTIONS:\n+            self.action_queue = self._sort_actions()\n+            self.state = OptimizedRebalanceChange._BALANCE\n+        elif self.state == OptimizedRebalanceChange._BALANCE:\n+            return not self._balance()\n+        return True\n+\n+    def _balance(self):\n+        if not self.action_queue:\n+            return True\n+        key, replicas = self.action_queue.popitem()\n+        topic, partition = key\n+        if not self.zk.reallocate_partition(topic, partition, replicas):\n+            self.action_queue[key] = replicas\n+        return False\n+\n+    def _rebalance_replicas(self):\n+        # Remove duplicates\n+        self._remove_replica_copies()\n+        if not self._rebalance_replicas_template(False):\n+            # It may happen, that data can not be copied, because there is leader of this topic already there.\n+            self._rebalance_replicas_template(True)\n+            if not self._rebalance_replicas_template(False):\n+                _LOG.error('Failed to rebalance replicas... Reason is unclear, will just stop the process')\n+                raise Exception('Failed to perform replica rebalance {}, {}, {}'.format(\n+                    self.broker_distribution, self.broker_ids, self.action_queue))\n+\n+    def _sort_actions(self):\n+        result = {}\n+        for topic_partition, source_broker_id, target_broker_id in self.action_queue:\n+            # Here comes small trick. Each action includes only one replacement.\n+            if topic_partition not in result:\n+                result[topic_partition] = list(self.source_distribution[topic_partition])\n+            tmp_result = result[topic_partition]\n+            for i in range(len(tmp_result) - 1, -1, -1):\n+                if tmp_result[i] == source_broker_id:\n+                    tmp_result[i] = target_broker_id\n+                    break\n+        return result\n+\n+    def _list_active_brokers_with_skip(self, skip_id: int):\n+        # Method was created only in optimization purposes\n+        return [b for b in self.broker_distribution.values() if\n+                b.broker_id != skip_id and b.broker_id in self.broker_ids]\n+\n+    def _remove_replica_copies(self):\n+        for broker in self.broker_distribution.values():\n+            # List all brokers, without free_replica_slots optimization.\n+            for topic_partition in broker.list_replica_copies():\n+                success = self._move_partition_to_replica(\n+                    broker, topic_partition,\n+                    [b for b in self._list_active_brokers_with_skip(broker.broker_id) if b.has_free_replica_slots()])\n+                if not success:\n+                    self._move_partition_to_replica(\n+                        broker, topic_partition, self._list_active_brokers_with_skip(broker.broker_id))\n+\n+    def _move_partition_to_replica(self, broker, topic_partition, broker_iterable):\n+        for target in broker_iterable:\n+            if target.accept_replica(broker, topic_partition):\n+                self.action_queue.append((topic_partition, broker.broker_id, target.broker_id))\n+                return True\n+        return False\n+\n+    def _rebalance_replicas_template(self, force: bool):\n+        for broker in self.broker_distribution.values():\n+            to_move = broker.get_replica_overload()\n+            if to_move <= 0:\n+                continue\n+            targets = self._list_active_brokers_with_skip(broker.broker_id)\n+            if not force:\n+                targets = [t for t in targets if t.has_free_replica_slots()]\n+            for _ in range(0, to_move):\n+                copied = False\n+                for topic_partition in broker.list_replicas():\n+                    if self._move_partition_to_replica(broker, topic_partition, targets):\n+                        copied = True\n+                        break\n+                if not copied:\n+                    return False\n+                if not force:\n+                    targets = [t for t in targets if t.has_free_replica_slots()]\n+        return True\n+\n+    @staticmethod\n+    def _build_leader_distribution_weights(brokers: dict):\n+        candidates = {}\n+        for source_broker_id, source_broker in brokers.items():\n+            if not source_broker.have_extra_leaders():\n+                continue\n+            for target_broker_id, target_broker in brokers.items():\n+                if not target_broker.have_less_leaders():\n+                    continue\n+                weight_list = []\n+                for topic in source_broker.topic_cardinality.keys():\n+                    delta = source_broker.topic_cardinality[topic] - target_broker.topic_cardinality.get(topic, 0)\n+                    weight_list.append((topic, delta))\n+                # Now the first element is the first to rebalance.\n+                if weight_list:\n+                    candidates[(source_broker_id, target_broker_id)] = sorted(\n+                        weight_list, key=lambda x: x[1], reverse=True)\n+        return candidates\n+\n+    @staticmethod\n+    def _place_to_cardinalities(array: list, topic: str, cardinality_change: int):\n+        old_cardinality = None\n+        for item in [(a, b) for a, b in array if a == topic]:\n+            old_cardinality = item[1]\n+            array.remove(item)\n+        if old_cardinality is None:\n+            return\n+        new_cardinality = old_cardinality + cardinality_change\n+        idx = len(array)\n+        for i in range(idx - 1, -1, -1):\n+            if array[i][1] >= new_cardinality:\n+                idx = i + 1\n+                break\n+        array.insert(idx, (topic, new_cardinality))\n+\n+    @staticmethod\n+    def rebalance_leaders(brokers: dict) -> list:\n+        candidates = OptimizedRebalanceChange._build_leader_distribution_weights(brokers)\n+        queue_list = []\n+        while any([broker.have_extra_leaders() for broker in brokers.values()]):\n+            top_candidate = None\n+            for broker_pair, weight_list in candidates.items():\n+                if not top_candidate:\n+                    top_candidate = broker_pair\n+                else:\n+                    if weight_list[0][1] > candidates[top_candidate][0][1]:\n+                        top_candidate = broker_pair\n+            # Taking best topic to move\n+            topic, _ = candidates[top_candidate][0]\n+            # Updating cardinality map\n+            for broker_pair, weight_list in candidates.items():\n+                if top_candidate == broker_pair:\n+                    OptimizedRebalanceChange._place_to_cardinalities(weight_list, topic, -2)\n+                elif top_candidate[0] == broker_pair[0] or top_candidate[1] == broker_pair[1]:  # src or dst matches\n+                    OptimizedRebalanceChange._place_to_cardinalities(weight_list, topic, -1)\n+            # Get broker objects\n+            source_broker, target_broker = brokers[top_candidate[0]], brokers[top_candidate[1]]\n+\n+            # Selecting best partition to move (It is better to just swap leadership, instead of copying data)","path":"bubuku/features/rebalance/change.py","position":203,"original_position":203,"commit_id":"f0cb48e35650e24d067f4952155d808b393ccd90","original_commit_id":"f0cb48e35650e24d067f4952155d808b393ccd90","user":{"login":"rcillo","id":344648,"avatar_url":"https://avatars.githubusercontent.com/u/344648?v=3","gravatar_id":"","url":"https://api.github.com/users/rcillo","html_url":"https://github.com/rcillo","followers_url":"https://api.github.com/users/rcillo/followers","following_url":"https://api.github.com/users/rcillo/following{/other_user}","gists_url":"https://api.github.com/users/rcillo/gists{/gist_id}","starred_url":"https://api.github.com/users/rcillo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rcillo/subscriptions","organizations_url":"https://api.github.com/users/rcillo/orgs","repos_url":"https://api.github.com/users/rcillo/repos","events_url":"https://api.github.com/users/rcillo/events{/privacy}","received_events_url":"https://api.github.com/users/rcillo/received_events","type":"User","site_admin":false},"body":"This is smart :neckbeard: ","created_at":"2016-09-26T17:44:04Z","updated_at":"2016-09-26T17:52:37Z","html_url":"https://github.com/zalando-incubator/bubuku/pull/66#discussion_r80530400","pull_request_url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66","_links":{"self":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/comments/80530400"},"html":{"href":"https://github.com/zalando-incubator/bubuku/pull/66#discussion_r80530400"},"pull_request":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66"}}},"pull_request":{"url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66","id":85992829,"html_url":"https://github.com/zalando-incubator/bubuku/pull/66","diff_url":"https://github.com/zalando-incubator/bubuku/pull/66.diff","patch_url":"https://github.com/zalando-incubator/bubuku/pull/66.patch","issue_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues/66","number":66,"state":"open","locked":false,"title":"#63 Rebalance optimization","user":{"login":"antban","id":2656223,"avatar_url":"https://avatars.githubusercontent.com/u/2656223?v=3","gravatar_id":"","url":"https://api.github.com/users/antban","html_url":"https://github.com/antban","followers_url":"https://api.github.com/users/antban/followers","following_url":"https://api.github.com/users/antban/following{/other_user}","gists_url":"https://api.github.com/users/antban/gists{/gist_id}","starred_url":"https://api.github.com/users/antban/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/antban/subscriptions","organizations_url":"https://api.github.com/users/antban/orgs","repos_url":"https://api.github.com/users/antban/repos","events_url":"https://api.github.com/users/antban/events{/privacy}","received_events_url":"https://api.github.com/users/antban/received_events","type":"User","site_admin":false},"body":"Fully optimized rebalancing. At the end:\r\nmax(replica difference) = 1,\r\nmax(leader_difference) = 1\r\nmax(leader count for any topic)=1 (this part works only if all was balanced or all brokers was restarted one by one)\r\n\r\nAdded tests for that","created_at":"2016-09-20T14:57:30Z","updated_at":"2016-09-26T17:52:37Z","closed_at":null,"merged_at":null,"merge_commit_sha":"00164d8a630cef266028093f8adeeecaf9e2400d","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66/commits","review_comments_url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66/comments","review_comment_url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/comments{/number}","comments_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues/66/comments","statuses_url":"https://api.github.com/repos/zalando-incubator/bubuku/statuses/f0cb48e35650e24d067f4952155d808b393ccd90","head":{"label":"zalando-incubator:I63","ref":"I63","sha":"f0cb48e35650e24d067f4952155d808b393ccd90","user":{"login":"zalando-incubator","id":18533786,"avatar_url":"https://avatars.githubusercontent.com/u/18533786?v=3","gravatar_id":"","url":"https://api.github.com/users/zalando-incubator","html_url":"https://github.com/zalando-incubator","followers_url":"https://api.github.com/users/zalando-incubator/followers","following_url":"https://api.github.com/users/zalando-incubator/following{/other_user}","gists_url":"https://api.github.com/users/zalando-incubator/gists{/gist_id}","starred_url":"https://api.github.com/users/zalando-incubator/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zalando-incubator/subscriptions","organizations_url":"https://api.github.com/users/zalando-incubator/orgs","repos_url":"https://api.github.com/users/zalando-incubator/repos","events_url":"https://api.github.com/users/zalando-incubator/events{/privacy}","received_events_url":"https://api.github.com/users/zalando-incubator/received_events","type":"Organization","site_admin":false},"repo":{"id":64120851,"name":"bubuku","full_name":"zalando-incubator/bubuku","owner":{"login":"zalando-incubator","id":18533786,"avatar_url":"https://avatars.githubusercontent.com/u/18533786?v=3","gravatar_id":"","url":"https://api.github.com/users/zalando-incubator","html_url":"https://github.com/zalando-incubator","followers_url":"https://api.github.com/users/zalando-incubator/followers","following_url":"https://api.github.com/users/zalando-incubator/following{/other_user}","gists_url":"https://api.github.com/users/zalando-incubator/gists{/gist_id}","starred_url":"https://api.github.com/users/zalando-incubator/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zalando-incubator/subscriptions","organizations_url":"https://api.github.com/users/zalando-incubator/orgs","repos_url":"https://api.github.com/users/zalando-incubator/repos","events_url":"https://api.github.com/users/zalando-incubator/events{/privacy}","received_events_url":"https://api.github.com/users/zalando-incubator/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zalando-incubator/bubuku","description":null,"fork":false,"url":"https://api.github.com/repos/zalando-incubator/bubuku","forks_url":"https://api.github.com/repos/zalando-incubator/bubuku/forks","keys_url":"https://api.github.com/repos/zalando-incubator/bubuku/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zalando-incubator/bubuku/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zalando-incubator/bubuku/teams","hooks_url":"https://api.github.com/repos/zalando-incubator/bubuku/hooks","issue_events_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues/events{/number}","events_url":"https://api.github.com/repos/zalando-incubator/bubuku/events","assignees_url":"https://api.github.com/repos/zalando-incubator/bubuku/assignees{/user}","branches_url":"https://api.github.com/repos/zalando-incubator/bubuku/branches{/branch}","tags_url":"https://api.github.com/repos/zalando-incubator/bubuku/tags","blobs_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/refs{/sha}","trees_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zalando-incubator/bubuku/statuses/{sha}","languages_url":"https://api.github.com/repos/zalando-incubator/bubuku/languages","stargazers_url":"https://api.github.com/repos/zalando-incubator/bubuku/stargazers","contributors_url":"https://api.github.com/repos/zalando-incubator/bubuku/contributors","subscribers_url":"https://api.github.com/repos/zalando-incubator/bubuku/subscribers","subscription_url":"https://api.github.com/repos/zalando-incubator/bubuku/subscription","commits_url":"https://api.github.com/repos/zalando-incubator/bubuku/commits{/sha}","git_commits_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/commits{/sha}","comments_url":"https://api.github.com/repos/zalando-incubator/bubuku/comments{/number}","issue_comment_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues/comments{/number}","contents_url":"https://api.github.com/repos/zalando-incubator/bubuku/contents/{+path}","compare_url":"https://api.github.com/repos/zalando-incubator/bubuku/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zalando-incubator/bubuku/merges","archive_url":"https://api.github.com/repos/zalando-incubator/bubuku/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zalando-incubator/bubuku/downloads","issues_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues{/number}","pulls_url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls{/number}","milestones_url":"https://api.github.com/repos/zalando-incubator/bubuku/milestones{/number}","notifications_url":"https://api.github.com/repos/zalando-incubator/bubuku/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zalando-incubator/bubuku/labels{/name}","releases_url":"https://api.github.com/repos/zalando-incubator/bubuku/releases{/id}","deployments_url":"https://api.github.com/repos/zalando-incubator/bubuku/deployments","created_at":"2016-07-25T09:18:17Z","updated_at":"2016-09-05T09:24:26Z","pushed_at":"2016-09-23T07:25:15Z","git_url":"git://github.com/zalando-incubator/bubuku.git","ssh_url":"git@github.com:zalando-incubator/bubuku.git","clone_url":"https://github.com/zalando-incubator/bubuku.git","svn_url":"https://github.com/zalando-incubator/bubuku","homepage":null,"size":218,"stargazers_count":4,"watchers_count":4,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":3,"forks":1,"open_issues":3,"watchers":4,"default_branch":"master"}},"base":{"label":"zalando-incubator:master","ref":"master","sha":"3b8c8f3e873b3fd86b5e6f748ffb73458307dbf2","user":{"login":"zalando-incubator","id":18533786,"avatar_url":"https://avatars.githubusercontent.com/u/18533786?v=3","gravatar_id":"","url":"https://api.github.com/users/zalando-incubator","html_url":"https://github.com/zalando-incubator","followers_url":"https://api.github.com/users/zalando-incubator/followers","following_url":"https://api.github.com/users/zalando-incubator/following{/other_user}","gists_url":"https://api.github.com/users/zalando-incubator/gists{/gist_id}","starred_url":"https://api.github.com/users/zalando-incubator/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zalando-incubator/subscriptions","organizations_url":"https://api.github.com/users/zalando-incubator/orgs","repos_url":"https://api.github.com/users/zalando-incubator/repos","events_url":"https://api.github.com/users/zalando-incubator/events{/privacy}","received_events_url":"https://api.github.com/users/zalando-incubator/received_events","type":"Organization","site_admin":false},"repo":{"id":64120851,"name":"bubuku","full_name":"zalando-incubator/bubuku","owner":{"login":"zalando-incubator","id":18533786,"avatar_url":"https://avatars.githubusercontent.com/u/18533786?v=3","gravatar_id":"","url":"https://api.github.com/users/zalando-incubator","html_url":"https://github.com/zalando-incubator","followers_url":"https://api.github.com/users/zalando-incubator/followers","following_url":"https://api.github.com/users/zalando-incubator/following{/other_user}","gists_url":"https://api.github.com/users/zalando-incubator/gists{/gist_id}","starred_url":"https://api.github.com/users/zalando-incubator/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zalando-incubator/subscriptions","organizations_url":"https://api.github.com/users/zalando-incubator/orgs","repos_url":"https://api.github.com/users/zalando-incubator/repos","events_url":"https://api.github.com/users/zalando-incubator/events{/privacy}","received_events_url":"https://api.github.com/users/zalando-incubator/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zalando-incubator/bubuku","description":null,"fork":false,"url":"https://api.github.com/repos/zalando-incubator/bubuku","forks_url":"https://api.github.com/repos/zalando-incubator/bubuku/forks","keys_url":"https://api.github.com/repos/zalando-incubator/bubuku/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zalando-incubator/bubuku/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zalando-incubator/bubuku/teams","hooks_url":"https://api.github.com/repos/zalando-incubator/bubuku/hooks","issue_events_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues/events{/number}","events_url":"https://api.github.com/repos/zalando-incubator/bubuku/events","assignees_url":"https://api.github.com/repos/zalando-incubator/bubuku/assignees{/user}","branches_url":"https://api.github.com/repos/zalando-incubator/bubuku/branches{/branch}","tags_url":"https://api.github.com/repos/zalando-incubator/bubuku/tags","blobs_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/refs{/sha}","trees_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zalando-incubator/bubuku/statuses/{sha}","languages_url":"https://api.github.com/repos/zalando-incubator/bubuku/languages","stargazers_url":"https://api.github.com/repos/zalando-incubator/bubuku/stargazers","contributors_url":"https://api.github.com/repos/zalando-incubator/bubuku/contributors","subscribers_url":"https://api.github.com/repos/zalando-incubator/bubuku/subscribers","subscription_url":"https://api.github.com/repos/zalando-incubator/bubuku/subscription","commits_url":"https://api.github.com/repos/zalando-incubator/bubuku/commits{/sha}","git_commits_url":"https://api.github.com/repos/zalando-incubator/bubuku/git/commits{/sha}","comments_url":"https://api.github.com/repos/zalando-incubator/bubuku/comments{/number}","issue_comment_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues/comments{/number}","contents_url":"https://api.github.com/repos/zalando-incubator/bubuku/contents/{+path}","compare_url":"https://api.github.com/repos/zalando-incubator/bubuku/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zalando-incubator/bubuku/merges","archive_url":"https://api.github.com/repos/zalando-incubator/bubuku/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zalando-incubator/bubuku/downloads","issues_url":"https://api.github.com/repos/zalando-incubator/bubuku/issues{/number}","pulls_url":"https://api.github.com/repos/zalando-incubator/bubuku/pulls{/number}","milestones_url":"https://api.github.com/repos/zalando-incubator/bubuku/milestones{/number}","notifications_url":"https://api.github.com/repos/zalando-incubator/bubuku/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zalando-incubator/bubuku/labels{/name}","releases_url":"https://api.github.com/repos/zalando-incubator/bubuku/releases{/id}","deployments_url":"https://api.github.com/repos/zalando-incubator/bubuku/deployments","created_at":"2016-07-25T09:18:17Z","updated_at":"2016-09-05T09:24:26Z","pushed_at":"2016-09-23T07:25:15Z","git_url":"git://github.com/zalando-incubator/bubuku.git","ssh_url":"git@github.com:zalando-incubator/bubuku.git","clone_url":"https://github.com/zalando-incubator/bubuku.git","svn_url":"https://github.com/zalando-incubator/bubuku","homepage":null,"size":218,"stargazers_count":4,"watchers_count":4,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":3,"forks":1,"open_issues":3,"watchers":4,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66"},"html":{"href":"https://github.com/zalando-incubator/bubuku/pull/66"},"issue":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/issues/66"},"comments":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/issues/66/comments"},"review_comments":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66/comments"},"review_comment":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/pulls/66/commits"},"statuses":{"href":"https://api.github.com/repos/zalando-incubator/bubuku/statuses/f0cb48e35650e24d067f4952155d808b393ccd90"}}}}
{"actor":{"display_login":"rcillo"}}