{"action":"created","comment":{"url":"https://api.github.com/repos/twitter/heron/pulls/comments/70895038","id":70895038,"diff_hunk":"@@ -0,0 +1,547 @@\n+// Copyright 2016 Twitter. 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+// Unless required by applicable law or agreed to in writing, software\n+// distributed under the License is distributed on an \"AS IS\" BASIS,\n+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+// See the License for the specific language governing permissions and\n+// limitations under the License.\n+\n+package com.twitter.heron.scheduler.mesos.framework;\n+\n+import java.util.ArrayList;\n+import java.util.Arrays;\n+import java.util.HashMap;\n+import java.util.HashSet;\n+import java.util.Iterator;\n+import java.util.LinkedList;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Queue;\n+import java.util.Set;\n+import java.util.concurrent.ConcurrentHashMap;\n+import java.util.concurrent.CountDownLatch;\n+import java.util.concurrent.LinkedTransferQueue;\n+import java.util.concurrent.TimeUnit;\n+import java.util.logging.Logger;\n+\n+import org.apache.mesos.Protos;\n+import org.apache.mesos.Scheduler;\n+import org.apache.mesos.SchedulerDriver;\n+\n+import com.twitter.heron.spi.common.Config;\n+import com.twitter.heron.spi.common.Context;\n+\n+/**\n+ * Mesos Framework, communicating with Mesos Master, to do the actual scheduling.\n+ */\n+\n+public class MesosFramework implements Scheduler {\n+  private static final Logger LOG = Logger.getLogger(MesosFramework.class.getName());\n+\n+  private final Config heronConfig;\n+  private final Config heronRuntime;\n+\n+  // ContainerIndex -> BaseContainer\n+  private final Map<Integer, BaseContainer> containersInfo;\n+\n+  // ContainerIndex -> TaskId\n+  private final Map<Integer, String> tasksId;\n+\n+  // Thread-safe queue of taskId, representing BaseContainer to launch\n+  private final Queue<String> toScheduleTasks;\n+\n+  // Flag indicates whether terminated\n+  private volatile boolean isTerminated;\n+\n+  // The Mesos' SchedulerDriver\n+  private volatile SchedulerDriver driver;\n+\n+  // Indicates whether the MesosFramework has registered.\n+  // Scheduler.registered(...) method will count down and release the latch\n+  private final CountDownLatch registeredLatch;\n+\n+  private volatile Protos.FrameworkID frameworkId;\n+\n+  public MesosFramework(Config heronConfig, Config heronRuntime) {\n+    this.heronConfig = heronConfig;\n+    this.heronRuntime = heronRuntime;\n+    this.containersInfo = new ConcurrentHashMap<>();\n+    this.tasksId = new ConcurrentHashMap<>();\n+    this.toScheduleTasks = new LinkedTransferQueue<>();\n+\n+    // Initialize it as 1. Will count down and release once got registered\n+    this.registeredLatch = new CountDownLatch(1);\n+    this.isTerminated = false;\n+  }\n+\n+  // Create a topology\n+  public boolean createJob(Map<Integer, BaseContainer> jobDefinition) {\n+    if (isTerminated) {\n+      LOG.severe(\"Job has been killed\");\n+      return false;\n+    }\n+\n+    // Record the jobDefinition\n+    containersInfo.putAll(jobDefinition);\n+\n+    // To scheduler them with 0 attempt\n+    for (Map.Entry<Integer, BaseContainer> entry : jobDefinition.entrySet()) {\n+      Integer containerIndex = entry.getKey();\n+      BaseContainer container = entry.getValue();\n+      String taskId = TaskUtils.getTaskId(container.name, 0);\n+      scheduleNewTask(taskId);\n+    }\n+\n+    return true;\n+  }\n+\n+  // Kill a topology\n+  public boolean killJob() {\n+    if (isTerminated) {\n+      LOG.info(\"Job has been killed\");\n+      return false;\n+    }\n+    isTerminated = true;\n+\n+    LOG.info(String.format(\"Kill job: %s\", Context.topologyName(heronConfig)));\n+    LOG.info(\"Remove all tasks to schedule\");\n+    toScheduleTasks.clear();\n+\n+    // Kill all the tasks\n+    for (String taskId : tasksId.values()) {\n+      driver.killTask(Protos.TaskID.newBuilder().setValue(taskId).build());\n+    }\n+\n+    containersInfo.clear();\n+    tasksId.clear();\n+\n+    return true;\n+  }\n+\n+  // Restart a topology\n+  public boolean restartJob(int containerIndex) {\n+    if (isTerminated) {\n+      LOG.severe(\"Job has been killed\");\n+      return false;\n+    }\n+\n+    // List of tasks to restart\n+    List<String> tasksToRestart = new ArrayList<>();\n+    if (containerIndex == -1) {\n+      // Restart all tasks\n+      tasksToRestart.addAll(tasksId.values());\n+    } else {\n+      tasksToRestart.add(tasksId.get(containerIndex));\n+    }\n+\n+    // Kill the task -- the framework will automatically restart it\n+    for (String taskId : tasksToRestart) {\n+      driver.killTask(Protos.TaskID.newBuilder().setValue(taskId).build());\n+    }\n+\n+    return true;\n+  }\n+\n+  /**\n+   * Check whether the MesosFramework has been terminated\n+   *\n+   * @return true if MesosFramework has been terminated\n+   */\n+  public boolean isTerminated() {\n+    return isTerminated;\n+  }\n+\n+  /**\n+   * Causes the current thread to wait for MesosFramework got registered,\n+   * unless the thread is interrupted, or the specified waiting time elapses.\n+   *\n+   * @param timeout the maximum time to wait\n+   * @param unit the time unit of the timeout argument\n+   * @return true MesosFramework got registered,\n+   * and false if the waiting time elapsed before the count reached zero\n+   */\n+  public boolean waitForRegistered(long timeout, TimeUnit unit) {\n+    try {\n+      if (this.registeredLatch.await(timeout, unit)) {\n+        return true;\n+      }\n+    } catch (InterruptedException e) {\n+      LOG.severe(\"Failed to wait for mesos framework got registered\");\n+      return false;\n+    }\n+\n+    return false;\n+  }\n+\n+  /**\n+   * Get the FrameworkID registered with Mesos Master\n+   *\n+   * @return the FrameworkID\n+   */\n+  public Protos.FrameworkID getFrameworkId() {\n+    return frameworkId;\n+  }\n+\n+\n+  @Override\n+  public void registered(SchedulerDriver schedulerDriver,\n+                         Protos.FrameworkID frameworkID,\n+                         Protos.MasterInfo masterInfo) {\n+    LOG.info(\"Registered! ID = \" + frameworkID.getValue());\n+    this.driver = schedulerDriver;\n+    this.frameworkId = frameworkID;\n+\n+    // Release the latch\n+    this.registeredLatch.countDown();\n+  }\n+\n+  @Override\n+  public void reregistered(SchedulerDriver schedulerDriver, Protos.MasterInfo masterInfo) {\n+    LOG.info(\"Re-registered!\");\n+  }\n+\n+  @Override\n+  public void resourceOffers(SchedulerDriver schedulerDriver, List<Protos.Offer> offers) {\n+    LOG.fine(\"Received Resource Offers: \" + offers.toString());\n+\n+    Map<Protos.Offer, TaskResources> offerResources = new HashMap<>();\n+    // Convert the offers into TasksResources, an utils class.\n+    for (Protos.Offer offer : offers) {\n+      offerResources.put(offer, TaskResources.apply(offer, Context.role(heronConfig)));\n+    }\n+\n+    // Generate launchable tasks\n+    List<LaunchableTask> tasksToLaunch = generateLaunchableTasks(offerResources);\n+\n+    // Launch tasks\n+    launchTasks(tasksToLaunch);\n+\n+    // Decline unused offers\n+    LOG.fine(\"Declining unused offers.\");\n+    Set<String> usedOffers = new HashSet<>();\n+    for (LaunchableTask task : tasksToLaunch) {\n+      usedOffers.add(task.offer.getId().getValue());\n+    }\n+    for (Protos.Offer offer : offers) {\n+      if (!usedOffers.contains(offer.getId().getValue())) {\n+        schedulerDriver.declineOffer(offer.getId());\n+      }\n+    }\n+  }\n+\n+  @Override\n+  public void offerRescinded(SchedulerDriver schedulerDriver, Protos.OfferID offerID) {\n+    LOG.info(\"Offer Rescinded!\");\n+  }\n+\n+  @Override\n+  public void statusUpdate(SchedulerDriver schedulerDriver, Protos.TaskStatus taskStatus) {\n+    LOG.info(String.format(\"Received status update [%s]\", taskStatus));\n+    if (isTerminated) {\n+      LOG.info(\"The framework terminated. Ignoring any status update.\");\n+    }\n+\n+    String taskId = taskStatus.getTaskId().getValue();\n+\n+    BaseContainer container = containersInfo.get(TaskUtils.getContainerIndexForTaskId(taskId));\n+\n+    if (container == null) {\n+      LOG.info(\"Received a status update from unknown container. \");\n+    } else {\n+      handleMesosStatusUpdate(taskId, taskStatus);\n+    }\n+  }\n+\n+  /**\n+   * Handle status update from mesos master\n+   * 1. It does nothing except logging under states:\n+   * - TASK_STAGING,\n+   * - TASK_STARTING,\n+   * - TASK_RUNNING,\n+   * <p>\n+   * 2. It does severe logging under states:\n+   * - TASK_FINISHED\n+   * since heron-executor should be long live in normal production running.\n+   * This state can occur during DEBUG mode. And we would not consider it as failure.\n+   * <p>\n+   * 3. It considers the task a failure and tries to restart it under states:\n+   * - TASK_FAILED\n+   * - TASK_KILLED\n+   * - TASK_LOST\n+   * - TASK_ERROR\n+   *\n+   * @param taskId the taskId of container to handle\n+   * @param status the TasksStatus to handle\n+   */\n+  protected void handleMesosStatusUpdate(String taskId,\n+                                         Protos.TaskStatus status) {\n+    // We would determine what to do basing on current taskId and mesos status update\n+    switch (status.getState()) {\n+      case TASK_STAGING:\n+        LOG.info(String.format(\"Task with id '%s' STAGING\", status.getTaskId().getValue()));\n+        LOG.severe(\"Framework status updates should not use\");\n+\n+        break;\n+      case TASK_STARTING:\n+        LOG.info(String.format(\"Task with id '%s' STARTING\", status.getTaskId().getValue()));\n+\n+        break;\n+      case TASK_RUNNING:\n+        LOG.info(String.format(\"Task with id '%s' RUNNING\", status.getTaskId().getValue()));\n+\n+        break;\n+      case TASK_FINISHED:\n+        LOG.info(String.format(\"Task with id '%s' FINISHED\", status.getTaskId().getValue()));\n+        LOG.severe(\"Heron job should not finish!\");\n+\n+        break;\n+      case TASK_FAILED:\n+        LOG.info(String.format(\"Task with id '%s' FAILED\", status.getTaskId().getValue()));\n+        handleMesosFailure(taskId);\n+\n+        break;\n+      case TASK_KILLED:\n+        LOG.info(\"Task killed which is supported running. \"\n+            + \"It could be triggered by manual restart command\");\n+        handleMesosFailure(taskId);\n+\n+        break;\n+      case TASK_LOST:\n+        LOG.info(String.format(\"Task with id '%s' LOST\", status.getTaskId().getValue()));\n+        handleMesosFailure(taskId);\n+\n+        break;\n+      case TASK_ERROR:\n+        LOG.info(String.format(\"Task with id '%s' ERROR\", status.getTaskId().getValue()));\n+        handleMesosFailure(taskId);\n+\n+        break;\n+      default:\n+        LOG.severe(\"Unknown TaskState:\" + status.getState() + \" for taskId: \"\n+            + status.getTaskId().getValue());\n+\n+        break;\n+    }\n+  }\n+\n+\n+  /**\n+   * Restart a failed task unless exceeding the retires limitation\n+   *\n+   * @param taskId the taskId of container to handle\n+   */\n+  protected void handleMesosFailure(String taskId) {\n+    int attempt = TaskUtils.getAttemptForTaskId(taskId);\n+    BaseContainer container = containersInfo.get(TaskUtils.getContainerIndexForTaskId(taskId));\n+\n+    boolean hasAttemptsLeft = attempt < container.retries;\n+\n+    if (hasAttemptsLeft) {\n+      LOG.warning(String.format(\"Retrying task: %s, attempt: %d\", container.name, attempt + 1));\n+\n+      String newTaskId = TaskUtils.getTaskId(container.name, attempt + 1);\n+      scheduleNewTask(newTaskId);\n+    } else {\n+      LOG.severe(\"Would not restart the job since it is beyond retries: \" + attempt);\n+    }\n+  }\n+\n+  /**\n+   * Schedule a new task\n+   *\n+   * @param taskId the taskId of container to handle\n+   */\n+  protected boolean scheduleNewTask(String taskId) {\n+    // Put it to the pending start queue\n+    LOG.info(String.format(\"We are to schedule task: [%s]\", taskId));\n+\n+    // Update the tasksId\n+    int containerIndex = TaskUtils.getContainerIndexForTaskId(taskId);\n+    tasksId.put(containerIndex, taskId);\n+\n+    // Re-schedule it\n+    toScheduleTasks.add(taskId);\n+    LOG.info(String.format(\"Added task: %s into the to-schedule-tasks queue: \", taskId));\n+    return true;\n+  }\n+\n+  @Override\n+  public void frameworkMessage(SchedulerDriver schedulerDriver,\n+                               Protos.ExecutorID executorID,\n+                               Protos.SlaveID slaveID,\n+                               byte[] data) {\n+    // TODO(mfu): TO handle this message\n+    LOG.info(\"Received framework message.\");\n+  }\n+\n+  @Override\n+  public void disconnected(SchedulerDriver schedulerDriver) {\n+    // TODO(mfu): TO handle this failure\n+    LOG.info(\"Disconnected!\");\n+  }\n+\n+  @Override\n+  public void slaveLost(SchedulerDriver schedulerDriver, Protos.SlaveID slaveID) {\n+    // TODO(mfu): TO handle this failure\n+    LOG.info(\"Slave lost: \" + slaveID.toString());\n+  }\n+\n+  @Override\n+  public void executorLost(SchedulerDriver schedulerDriver,\n+                           Protos.ExecutorID executorID,\n+                           Protos.SlaveID slaveID,\n+                           int status) {\n+    // TODO(mfu): TO handle this failure\n+    LOG.info(String.format(\"Executor lost: %s, slave ID: %s, status: %d\",\n+        executorID.toString(), slaveID.toString(), status));\n+  }\n+\n+  @Override\n+  public void error(SchedulerDriver schedulerDriver, String message) {\n+    // TODO(mfu): TO handle this failure\n+    LOG.info(\"Received error: \" + message);\n+  }\n+\n+\n+  /**\n+   * Generate launchable tasks basing on offer resources\n+   *\n+   * @param offerResources Mapping from Protos.Offer to utils class TaskResources\n+   * @return list of launchable task\n+   */\n+  protected List<LaunchableTask> generateLaunchableTasks(\n+      Map<Protos.Offer, TaskResources> offerResources) {\n+    List<LaunchableTask> tasks = new LinkedList<>();\n+\n+    if (isTerminated) {\n+      LOG.info(\"Job has been killed\");\n+      return tasks;\n+    }\n+\n+    while (!toScheduleTasks.isEmpty()) {\n+      String taskId = toScheduleTasks.poll();","path":"heron/schedulers/src/java/com/twitter/heron/scheduler/mesos/framework/MesosFramework.java","position":428,"original_position":428,"commit_id":"a69d5aaf35c93ca91ddb560a1dc5eb7b083f8130","original_commit_id":"a69d5aaf35c93ca91ddb560a1dc5eb7b083f8130","user":{"login":"ashvina","id":10034060,"avatar_url":"https://avatars.githubusercontent.com/u/10034060?v=3","gravatar_id":"","url":"https://api.github.com/users/ashvina","html_url":"https://github.com/ashvina","followers_url":"https://api.github.com/users/ashvina/followers","following_url":"https://api.github.com/users/ashvina/following{/other_user}","gists_url":"https://api.github.com/users/ashvina/gists{/gist_id}","starred_url":"https://api.github.com/users/ashvina/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ashvina/subscriptions","organizations_url":"https://api.github.com/users/ashvina/orgs","repos_url":"https://api.github.com/users/ashvina/repos","events_url":"https://api.github.com/users/ashvina/events{/privacy}","received_events_url":"https://api.github.com/users/ashvina/received_events","type":"User","site_admin":false},"body":"taskId can still be null. Check for null?","created_at":"2016-07-14T22:24:29Z","updated_at":"2016-07-14T22:24:29Z","html_url":"https://github.com/twitter/heron/pull/1067#discussion_r70895038","pull_request_url":"https://api.github.com/repos/twitter/heron/pulls/1067","_links":{"self":{"href":"https://api.github.com/repos/twitter/heron/pulls/comments/70895038"},"html":{"href":"https://github.com/twitter/heron/pull/1067#discussion_r70895038"},"pull_request":{"href":"https://api.github.com/repos/twitter/heron/pulls/1067"}}},"pull_request":{"url":"https://api.github.com/repos/twitter/heron/pulls/1067","id":77214610,"html_url":"https://github.com/twitter/heron/pull/1067","diff_url":"https://github.com/twitter/heron/pull/1067.diff","patch_url":"https://github.com/twitter/heron/pull/1067.patch","issue_url":"https://api.github.com/repos/twitter/heron/issues/1067","number":1067,"state":"open","locked":false,"title":"Heron native mesos scheduler","user":{"login":"maosongfu","id":3028698,"avatar_url":"https://avatars.githubusercontent.com/u/3028698?v=3","gravatar_id":"","url":"https://api.github.com/users/maosongfu","html_url":"https://github.com/maosongfu","followers_url":"https://api.github.com/users/maosongfu/followers","following_url":"https://api.github.com/users/maosongfu/following{/other_user}","gists_url":"https://api.github.com/users/maosongfu/gists{/gist_id}","starred_url":"https://api.github.com/users/maosongfu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maosongfu/subscriptions","organizations_url":"https://api.github.com/users/maosongfu/orgs","repos_url":"https://api.github.com/users/maosongfu/repos","events_url":"https://api.github.com/users/maosongfu/events{/privacy}","received_events_url":"https://api.github.com/users/maosongfu/received_events","type":"User","site_admin":false},"body":"Add heron native mesos scheduler and unit tests.\r\nThis scheduler aims to help people get started with heron on mesos; it is not for production.\r\nIt has a lot of concerns, for instance, not fully fault tolerant, etc.\r\n\r\nIn this implementation, heron would start the MesosFramework as a background process on the host doing submission, communicate\r\nwith mesos master directly and do the actual scheduling. So:\r\n1. It assumes the host for submission is in the same network as mesos cluster.\r\n2. It assumes the heron mesos scheduler is long running.\r\n\r\nThe heron mesos scheduler would re-schedule any failed executors. But if the heron mesos scheduler fails due to any\r\nunexpected reasons, it would not get recovered and the whole topology would halt.","created_at":"2016-07-13T00:41:25Z","updated_at":"2016-07-14T22:24:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6b8f6572fb7641e608ce45cfb10024e662e7c841","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/twitter/heron/pulls/1067/commits","review_comments_url":"https://api.github.com/repos/twitter/heron/pulls/1067/comments","review_comment_url":"https://api.github.com/repos/twitter/heron/pulls/comments{/number}","comments_url":"https://api.github.com/repos/twitter/heron/issues/1067/comments","statuses_url":"https://api.github.com/repos/twitter/heron/statuses/a69d5aaf35c93ca91ddb560a1dc5eb7b083f8130","head":{"label":"twitter:mfu/mesosScheduler","ref":"mfu/mesosScheduler","sha":"a69d5aaf35c93ca91ddb560a1dc5eb7b083f8130","user":{"login":"twitter","id":50278,"avatar_url":"https://avatars.githubusercontent.com/u/50278?v=3","gravatar_id":"","url":"https://api.github.com/users/twitter","html_url":"https://github.com/twitter","followers_url":"https://api.github.com/users/twitter/followers","following_url":"https://api.github.com/users/twitter/following{/other_user}","gists_url":"https://api.github.com/users/twitter/gists{/gist_id}","starred_url":"https://api.github.com/users/twitter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitter/subscriptions","organizations_url":"https://api.github.com/users/twitter/orgs","repos_url":"https://api.github.com/users/twitter/repos","events_url":"https://api.github.com/users/twitter/events{/privacy}","received_events_url":"https://api.github.com/users/twitter/received_events","type":"Organization","site_admin":false},"repo":{"id":43158694,"name":"heron","full_name":"twitter/heron","owner":{"login":"twitter","id":50278,"avatar_url":"https://avatars.githubusercontent.com/u/50278?v=3","gravatar_id":"","url":"https://api.github.com/users/twitter","html_url":"https://github.com/twitter","followers_url":"https://api.github.com/users/twitter/followers","following_url":"https://api.github.com/users/twitter/following{/other_user}","gists_url":"https://api.github.com/users/twitter/gists{/gist_id}","starred_url":"https://api.github.com/users/twitter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitter/subscriptions","organizations_url":"https://api.github.com/users/twitter/orgs","repos_url":"https://api.github.com/users/twitter/repos","events_url":"https://api.github.com/users/twitter/events{/privacy}","received_events_url":"https://api.github.com/users/twitter/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/twitter/heron","description":"Heron is a realtime, distributed, fault-tolerant stream processing engine from Twitter","fork":false,"url":"https://api.github.com/repos/twitter/heron","forks_url":"https://api.github.com/repos/twitter/heron/forks","keys_url":"https://api.github.com/repos/twitter/heron/keys{/key_id}","collaborators_url":"https://api.github.com/repos/twitter/heron/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/twitter/heron/teams","hooks_url":"https://api.github.com/repos/twitter/heron/hooks","issue_events_url":"https://api.github.com/repos/twitter/heron/issues/events{/number}","events_url":"https://api.github.com/repos/twitter/heron/events","assignees_url":"https://api.github.com/repos/twitter/heron/assignees{/user}","branches_url":"https://api.github.com/repos/twitter/heron/branches{/branch}","tags_url":"https://api.github.com/repos/twitter/heron/tags","blobs_url":"https://api.github.com/repos/twitter/heron/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/twitter/heron/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/twitter/heron/git/refs{/sha}","trees_url":"https://api.github.com/repos/twitter/heron/git/trees{/sha}","statuses_url":"https://api.github.com/repos/twitter/heron/statuses/{sha}","languages_url":"https://api.github.com/repos/twitter/heron/languages","stargazers_url":"https://api.github.com/repos/twitter/heron/stargazers","contributors_url":"https://api.github.com/repos/twitter/heron/contributors","subscribers_url":"https://api.github.com/repos/twitter/heron/subscribers","subscription_url":"https://api.github.com/repos/twitter/heron/subscription","commits_url":"https://api.github.com/repos/twitter/heron/commits{/sha}","git_commits_url":"https://api.github.com/repos/twitter/heron/git/commits{/sha}","comments_url":"https://api.github.com/repos/twitter/heron/comments{/number}","issue_comment_url":"https://api.github.com/repos/twitter/heron/issues/comments{/number}","contents_url":"https://api.github.com/repos/twitter/heron/contents/{+path}","compare_url":"https://api.github.com/repos/twitter/heron/compare/{base}...{head}","merges_url":"https://api.github.com/repos/twitter/heron/merges","archive_url":"https://api.github.com/repos/twitter/heron/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/twitter/heron/downloads","issues_url":"https://api.github.com/repos/twitter/heron/issues{/number}","pulls_url":"https://api.github.com/repos/twitter/heron/pulls{/number}","milestones_url":"https://api.github.com/repos/twitter/heron/milestones{/number}","notifications_url":"https://api.github.com/repos/twitter/heron/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/twitter/heron/labels{/name}","releases_url":"https://api.github.com/repos/twitter/heron/releases{/id}","deployments_url":"https://api.github.com/repos/twitter/heron/deployments","created_at":"2015-09-25T16:09:24Z","updated_at":"2016-07-14T16:36:31Z","pushed_at":"2016-07-14T19:19:40Z","git_url":"git://github.com/twitter/heron.git","ssh_url":"git@github.com:twitter/heron.git","clone_url":"https://github.com/twitter/heron.git","svn_url":"https://github.com/twitter/heron","homepage":"http://heronstreaming.io","size":134267,"stargazers_count":2153,"watchers_count":2153,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":331,"mirror_url":null,"open_issues_count":149,"forks":331,"open_issues":149,"watchers":2153,"default_branch":"master"}},"base":{"label":"twitter:master","ref":"master","sha":"8cea23e3e355c8f25ff143b0f70db6c302b84652","user":{"login":"twitter","id":50278,"avatar_url":"https://avatars.githubusercontent.com/u/50278?v=3","gravatar_id":"","url":"https://api.github.com/users/twitter","html_url":"https://github.com/twitter","followers_url":"https://api.github.com/users/twitter/followers","following_url":"https://api.github.com/users/twitter/following{/other_user}","gists_url":"https://api.github.com/users/twitter/gists{/gist_id}","starred_url":"https://api.github.com/users/twitter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitter/subscriptions","organizations_url":"https://api.github.com/users/twitter/orgs","repos_url":"https://api.github.com/users/twitter/repos","events_url":"https://api.github.com/users/twitter/events{/privacy}","received_events_url":"https://api.github.com/users/twitter/received_events","type":"Organization","site_admin":false},"repo":{"id":43158694,"name":"heron","full_name":"twitter/heron","owner":{"login":"twitter","id":50278,"avatar_url":"https://avatars.githubusercontent.com/u/50278?v=3","gravatar_id":"","url":"https://api.github.com/users/twitter","html_url":"https://github.com/twitter","followers_url":"https://api.github.com/users/twitter/followers","following_url":"https://api.github.com/users/twitter/following{/other_user}","gists_url":"https://api.github.com/users/twitter/gists{/gist_id}","starred_url":"https://api.github.com/users/twitter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitter/subscriptions","organizations_url":"https://api.github.com/users/twitter/orgs","repos_url":"https://api.github.com/users/twitter/repos","events_url":"https://api.github.com/users/twitter/events{/privacy}","received_events_url":"https://api.github.com/users/twitter/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/twitter/heron","description":"Heron is a realtime, distributed, fault-tolerant stream processing engine from Twitter","fork":false,"url":"https://api.github.com/repos/twitter/heron","forks_url":"https://api.github.com/repos/twitter/heron/forks","keys_url":"https://api.github.com/repos/twitter/heron/keys{/key_id}","collaborators_url":"https://api.github.com/repos/twitter/heron/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/twitter/heron/teams","hooks_url":"https://api.github.com/repos/twitter/heron/hooks","issue_events_url":"https://api.github.com/repos/twitter/heron/issues/events{/number}","events_url":"https://api.github.com/repos/twitter/heron/events","assignees_url":"https://api.github.com/repos/twitter/heron/assignees{/user}","branches_url":"https://api.github.com/repos/twitter/heron/branches{/branch}","tags_url":"https://api.github.com/repos/twitter/heron/tags","blobs_url":"https://api.github.com/repos/twitter/heron/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/twitter/heron/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/twitter/heron/git/refs{/sha}","trees_url":"https://api.github.com/repos/twitter/heron/git/trees{/sha}","statuses_url":"https://api.github.com/repos/twitter/heron/statuses/{sha}","languages_url":"https://api.github.com/repos/twitter/heron/languages","stargazers_url":"https://api.github.com/repos/twitter/heron/stargazers","contributors_url":"https://api.github.com/repos/twitter/heron/contributors","subscribers_url":"https://api.github.com/repos/twitter/heron/subscribers","subscription_url":"https://api.github.com/repos/twitter/heron/subscription","commits_url":"https://api.github.com/repos/twitter/heron/commits{/sha}","git_commits_url":"https://api.github.com/repos/twitter/heron/git/commits{/sha}","comments_url":"https://api.github.com/repos/twitter/heron/comments{/number}","issue_comment_url":"https://api.github.com/repos/twitter/heron/issues/comments{/number}","contents_url":"https://api.github.com/repos/twitter/heron/contents/{+path}","compare_url":"https://api.github.com/repos/twitter/heron/compare/{base}...{head}","merges_url":"https://api.github.com/repos/twitter/heron/merges","archive_url":"https://api.github.com/repos/twitter/heron/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/twitter/heron/downloads","issues_url":"https://api.github.com/repos/twitter/heron/issues{/number}","pulls_url":"https://api.github.com/repos/twitter/heron/pulls{/number}","milestones_url":"https://api.github.com/repos/twitter/heron/milestones{/number}","notifications_url":"https://api.github.com/repos/twitter/heron/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/twitter/heron/labels{/name}","releases_url":"https://api.github.com/repos/twitter/heron/releases{/id}","deployments_url":"https://api.github.com/repos/twitter/heron/deployments","created_at":"2015-09-25T16:09:24Z","updated_at":"2016-07-14T16:36:31Z","pushed_at":"2016-07-14T19:19:40Z","git_url":"git://github.com/twitter/heron.git","ssh_url":"git@github.com:twitter/heron.git","clone_url":"https://github.com/twitter/heron.git","svn_url":"https://github.com/twitter/heron","homepage":"http://heronstreaming.io","size":134267,"stargazers_count":2153,"watchers_count":2153,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":331,"mirror_url":null,"open_issues_count":149,"forks":331,"open_issues":149,"watchers":2153,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/twitter/heron/pulls/1067"},"html":{"href":"https://github.com/twitter/heron/pull/1067"},"issue":{"href":"https://api.github.com/repos/twitter/heron/issues/1067"},"comments":{"href":"https://api.github.com/repos/twitter/heron/issues/1067/comments"},"review_comments":{"href":"https://api.github.com/repos/twitter/heron/pulls/1067/comments"},"review_comment":{"href":"https://api.github.com/repos/twitter/heron/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/twitter/heron/pulls/1067/commits"},"statuses":{"href":"https://api.github.com/repos/twitter/heron/statuses/a69d5aaf35c93ca91ddb560a1dc5eb7b083f8130"}}}}
{"actor":{"display_login":"ashvina"}}