{"action":"created","comment":{"url":"https://api.github.com/repos/apache/incubator-livy/pulls/comments/213262049","pull_request_review_id":150060003,"id":213262049,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxMzI2MjA0OQ==","diff_hunk":"@@ -0,0 +1,609 @@\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one or more\n+ * contributor license agreements.  See the NOTICE file distributed with\n+ * this work for additional information regarding copyright ownership.\n+ * The ASF licenses this file to You under the Apache License, Version 2.0\n+ * (the \"License\"); you may not use this file except in compliance with\n+ * the License.  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+\n+package org.apache.livy.thriftserver\n+\n+import java.net.URI\n+import java.security.PrivilegedExceptionAction\n+import java.util\n+import java.util.{Date, Map => JMap, UUID}\n+import java.util.concurrent.{ConcurrentHashMap, LinkedBlockingQueue, ThreadPoolExecutor, TimeUnit}\n+import java.util.concurrent.atomic.AtomicLong\n+\n+import scala.collection.JavaConverters._\n+import scala.collection.mutable\n+import scala.concurrent.{Await, Future}\n+import scala.concurrent.duration.Duration\n+import scala.concurrent.ExecutionContext.Implicits.global\n+import scala.util.{Failure, Success, Try}\n+\n+import org.apache.hadoop.hive.conf.HiveConf\n+import org.apache.hadoop.hive.conf.HiveConf.ConfVars\n+import org.apache.hadoop.hive.shims.Utils\n+import org.apache.hive.service.CompositeService\n+import org.apache.hive.service.cli.{HiveSQLException, SessionHandle}\n+import org.apache.hive.service.rpc.thrift.TProtocolVersion\n+import org.apache.hive.service.server.ThreadFactoryWithGarbageCleanup\n+\n+import org.apache.livy.LivyConf\n+import org.apache.livy.Logging\n+import org.apache.livy.server.interactive.{CreateInteractiveRequest, InteractiveSession}\n+import org.apache.livy.sessions.Spark\n+import org.apache.livy.thriftserver.SessionStates._\n+import org.apache.livy.thriftserver.rpc.RpcClient\n+import org.apache.livy.utils.LivySparkUtils\n+\n+class LivyThriftSessionManager(val server: LivyThriftServer, hiveConf: HiveConf)\n+  extends CompositeService(classOf[LivyThriftSessionManager].getName) with Logging {\n+  private[thriftserver] val operationManager = new LivyOperationManager(this)\n+  private val sessionHandleToLivySession =\n+    new ConcurrentHashMap[SessionHandle, Future[InteractiveSession]]()\n+  // A map which returns how many incoming connections are open for a Livy session.\n+  // This map tracks only the sessions created by the Livy thriftserver and not those which have\n+  // been created through the REST API, as those should not be stopped even though there are no\n+  // more active connections.\n+  private val managedLivySessionActiveUsers = new mutable.HashMap[Int, Int]()\n+\n+  // Contains metadata about a session\n+  private val sessionInfo = new ConcurrentHashMap[SessionHandle, SessionInfo]()\n+\n+  // Map the number of incoming connections for IP, user. It is used in order to check\n+  // that the configured limits are not exceeded.\n+  private val connectionsCount = new ConcurrentHashMap[String, AtomicLong]\n+\n+  // Timeout for a Spark session creation\n+  private val maxSessionWait = Duration(\n+    server.livyConf.getTimeAsMs(LivyConf.THRIFT_SESSION_CREATION_TIMEOUT),\n+    scala.concurrent.duration.MILLISECONDS)\n+\n+  // Flag indicating whether the Spark version being used supports the USE database statement\n+  val supportUseDatabase: Boolean = {\n+    val sparkVersion = server.livyConf.get(LivyConf.LIVY_SPARK_VERSION)\n+    val (sparkMajorVersion, _) = LivySparkUtils.formatSparkVersion(sparkVersion)\n+    sparkMajorVersion > 1 || server.livyConf.getBoolean(LivyConf.ENABLE_HIVE_CONTEXT)\n+  }\n+\n+  // Configs from Hive\n+  private val userLimit = hiveConf.getIntVar(ConfVars.HIVE_SERVER2_LIMIT_CONNECTIONS_PER_USER)\n+  private val ipAddressLimit =\n+    hiveConf.getIntVar(ConfVars.HIVE_SERVER2_LIMIT_CONNECTIONS_PER_IPADDRESS)\n+  private val userIpAddressLimit =\n+    hiveConf.getIntVar(ConfVars.HIVE_SERVER2_LIMIT_CONNECTIONS_PER_USER_IPADDRESS)\n+  private val checkInterval = HiveConf.getTimeVar(\n+    hiveConf, ConfVars.HIVE_SERVER2_SESSION_CHECK_INTERVAL, TimeUnit.MILLISECONDS)\n+  private val sessionTimeout = HiveConf.getTimeVar(\n+    hiveConf, ConfVars.HIVE_SERVER2_IDLE_SESSION_TIMEOUT, TimeUnit.MILLISECONDS)\n+  private val checkOperation = HiveConf.getBoolVar(\n+    hiveConf, ConfVars.HIVE_SERVER2_IDLE_SESSION_CHECK_OPERATION)\n+\n+  private var backgroundOperationPool: ThreadPoolExecutor = _\n+\n+\n+  def getLivySession(sessionHandle: SessionHandle): InteractiveSession = {\n+    val future = sessionHandleToLivySession.get(sessionHandle)\n+    assert(future != null, s\"Looking for not existing session: $sessionHandle.\")\n+\n+    if (!future.isCompleted) {\n+      Try(Await.result(future, maxSessionWait)) match {\n+        case Success(session) => session\n+        case Failure(e) => throw e\n+      }\n+    } else {\n+      future.value match {\n+        case Some(Success(session)) => session\n+        case Some(Failure(e)) => throw e\n+        case None => throw new RuntimeException(\"Future cannot be None when it is completed\")\n+      }\n+    }\n+  }\n+\n+  def livySessionId(sessionHandle: SessionHandle): Option[Int] = {\n+    sessionHandleToLivySession.get(sessionHandle).value.filter(_.isSuccess).map(_.get.id)\n+  }\n+\n+  def livySessionState(sessionHandle: SessionHandle): SessionStates = {\n+    sessionHandleToLivySession.get(sessionHandle).value match {\n+      case Some(Success(_)) => CREATION_SUCCESS\n+      case Some(Failure(_)) => CREATION_FAILED\n+      case None => CREATION_IN_PROGRESS\n+    }\n+  }\n+\n+  def numberOfActiveUsers(livySessionId: Int): Int = synchronized[Int] {\n+    managedLivySessionActiveUsers.getOrElse(livySessionId, 0)\n+  }\n+\n+  def onLivySessionOpened(livySession: InteractiveSession): Unit = {\n+    server.livySessionManager.register(livySession)\n+  }\n+\n+  private def incrementManagedSessionActiveUsers(livySessionId: Int): Unit = synchronized {\n+    managedLivySessionActiveUsers(livySessionId) = numberOfActiveUsers(livySessionId) + 1\n+  }\n+\n+  def onUserSessionClosed(sessionHandle: SessionHandle, livySession: InteractiveSession): Unit = {\n+    val closeSession = synchronized[Boolean] {\n+      val activeUsers = managedLivySessionActiveUsers(livySession.id)\n+      if (activeUsers == 1) {\n+        // it was the last user, so we can close the LivySession\n+        managedLivySessionActiveUsers -= livySession.id\n+        true\n+      } else {\n+        managedLivySessionActiveUsers(livySession.id) = activeUsers - 1\n+        false\n+      }\n+    }\n+    if (closeSession) {\n+      server.livySessionManager.delete(livySession)\n+    } else {\n+      // We unregister the session only if we don't close it, as it is unnecessary in that case\n+      val rpcClient = new RpcClient(livySession)\n+      try {\n+        rpcClient.executeUnregisterSession(sessionHandle).get()\n+      } catch {\n+        case e: Exception => warn(s\"Unable to unregister session $sessionHandle\", e)\n+      }\n+    }\n+  }\n+\n+  /**\n+   * If the user specified an existing sessionId to use, the corresponding session is returned,\n+   * otherwise a new session is created and returned.\n+   */\n+  private def getOrCreateLivySession(\n+      sessionHandle: SessionHandle,\n+      sessionId: Option[Int],\n+      username: String,\n+      createLivySession: () => InteractiveSession): InteractiveSession = {\n+    sessionId match {\n+      case Some(id) =>\n+        server.livySessionManager.get(id) match {\n+          case None =>\n+            warn(s\"Session id $id doesn't exist, so we will ignore it.\")\n+            createLivySession()\n+          case Some(session) if !server.isAllowedToUse(username, session) =>\n+            warn(s\"Session id $id doesn't belong to $username, so we will ignore it.\")\n+            createLivySession()\n+          case Some(session) => if (session.state.isActive) {\n+            info(s\"Reusing Session $id for $sessionHandle.\")\n+            session\n+          } else {\n+            warn(s\"Session id $id is not active anymore, so we will ignore it.\")\n+            createLivySession()\n+          }\n+        }\n+      case None =>\n+        createLivySession()\n+    }\n+  }\n+\n+  /**\n+   * Performs the initialization of the new Thriftserver session:\n+   *  - adds the Livy thrifserver JAR to the Spark application;\n+   *  - register the new Thriftserver session in the Spark application;\n+   *  - runs the initialization statements;\n+   */\n+  private def initSession(\n+      sessionHandle: SessionHandle,\n+      livySession: InteractiveSession,\n+      initStatements: List[String]): Unit = {\n+    // Add the thriftserver jar to Spark application as we need to deserialize there the classes\n+    // which handle the job submission.\n+    // Note: if this is an already existing session, adding the JARs multiple times is not a\n+    // problem as Spark ignores JARs which have already been added.\n+    try {\n+      livySession.addJar(LivyThriftSessionManager.thriftserverJarLocation(server.livyConf))\n+    } catch {\n+      case e: java.util.concurrent.ExecutionException\n+          if Option(e.getCause).forall(_.getMessage.contains(\"has already been uploaded\")) =>\n+        // We have already uploaded the jar to this session, we can ignore this error\n+        debug(e.getMessage, e)\n+    }\n+\n+    val rpcClient = new RpcClient(livySession)\n+    rpcClient.executeRegisterSession(sessionHandle).get()\n+    initStatements.foreach { statement =>\n+      val statementId = UUID.randomUUID().toString\n+      try {\n+        rpcClient.executeSql(sessionHandle, statementId, statement).get()\n+      } finally {\n+        Try(rpcClient.cleanupStatement(statementId).get()).failed.foreach { e =>\n+          error(s\"Failed to close init operation $statementId\", e)\n+        }\n+      }\n+    }\n+  }\n+\n+  def openSession(\n+      protocol: TProtocolVersion,\n+      username: String,\n+      password: String,\n+      ipAddress: String,\n+      sessionConf: JMap[String, String],\n+      withImpersonation: Boolean,\n+      delegationToken: String): SessionHandle = {\n+    val sessionHandle = new SessionHandle(protocol)\n+    incrementConnections(username, ipAddress, SessionInfo.getForwardedAddresses)","path":"thriftserver/server/src/main/scala/org/apache/livy/thriftserver/LivyThriftSessionManager.scala","position":240,"original_position":240,"commit_id":"ec7e079866b70786d66cc67068730ca99eb03345","original_commit_id":"ec7e079866b70786d66cc67068730ca99eb03345","user":{"login":"mgaido91","id":8821783,"node_id":"MDQ6VXNlcjg4MjE3ODM=","avatar_url":"https://avatars1.githubusercontent.com/u/8821783?v=4","gravatar_id":"","url":"https://api.github.com/users/mgaido91","html_url":"https://github.com/mgaido91","followers_url":"https://api.github.com/users/mgaido91/followers","following_url":"https://api.github.com/users/mgaido91/following{/other_user}","gists_url":"https://api.github.com/users/mgaido91/gists{/gist_id}","starred_url":"https://api.github.com/users/mgaido91/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mgaido91/subscriptions","organizations_url":"https://api.github.com/users/mgaido91/orgs","repos_url":"https://api.github.com/users/mgaido91/repos","events_url":"https://api.github.com/users/mgaido91/events{/privacy}","received_events_url":"https://api.github.com/users/mgaido91/received_events","type":"User","site_admin":false},"body":"if the things below break, the `Future` is a failure. When the session is closed this is removed. Basically, if anything of the things below break, the user gets an error for any query he/she submits. When he/she closes the connection, this is undone.","created_at":"2018-08-28T10:32:13Z","updated_at":"2018-08-28T10:32:13Z","html_url":"https://github.com/apache/incubator-livy/pull/104#discussion_r213262049","pull_request_url":"https://api.github.com/repos/apache/incubator-livy/pulls/104","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/apache/incubator-livy/pulls/comments/213262049"},"html":{"href":"https://github.com/apache/incubator-livy/pull/104#discussion_r213262049"},"pull_request":{"href":"https://api.github.com/repos/apache/incubator-livy/pulls/104"}},"in_reply_to_id":213133123},"pull_request":{"url":"https://api.github.com/repos/apache/incubator-livy/pulls/104","id":208587477,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA4NTg3NDc3","html_url":"https://github.com/apache/incubator-livy/pull/104","diff_url":"https://github.com/apache/incubator-livy/pull/104.diff","patch_url":"https://github.com/apache/incubator-livy/pull/104.patch","issue_url":"https://api.github.com/repos/apache/incubator-livy/issues/104","number":104,"state":"open","locked":false,"title":"[LIVY-491][LIVY-492][LIVY-493] Add Thriftserver module and implementation","user":{"login":"mgaido91","id":8821783,"node_id":"MDQ6VXNlcjg4MjE3ODM=","avatar_url":"https://avatars1.githubusercontent.com/u/8821783?v=4","gravatar_id":"","url":"https://api.github.com/users/mgaido91","html_url":"https://github.com/mgaido91","followers_url":"https://api.github.com/users/mgaido91/followers","following_url":"https://api.github.com/users/mgaido91/following{/other_user}","gists_url":"https://api.github.com/users/mgaido91/gists{/gist_id}","starred_url":"https://api.github.com/users/mgaido91/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mgaido91/subscriptions","organizations_url":"https://api.github.com/users/mgaido91/orgs","repos_url":"https://api.github.com/users/mgaido91/repos","events_url":"https://api.github.com/users/mgaido91/events{/privacy}","received_events_url":"https://api.github.com/users/mgaido91/received_events","type":"User","site_admin":false},"body":"## What changes were proposed in this pull request?\r\n\r\nThe PR contains an implementation of a JDBC API for Livy server based on the Hive Thriftserver. The implementation is based on the version 3.0 of Hive Thriftserver.\r\n\r\nThis initial PR contains the thriftserver module added to Livy and its implementation. It doesn't contain any binding for starting it, this will be added later.\r\n\r\nSome Hive classes have been ported here because they needed come modifications in order to works properly in Livy. Long term solution is to get of them all by re-implementing the needed parts in the Livy thriftserver itself, without relying on Hive code (other than the PRC interface) anymore. Those classes/changes can be summarized in three categories:\r\n\r\n 1. Changes to make the Hive classes easy to extend: for instance, some visibility modifiers were changes (moving from `private` or `package private` to `protected` or `public`);\r\n 2. Changes in order to reduce the dependencies on Hive modules/classes: for instance all the classes in the `operation` package were modified in order to get rid of the usage of `HiveSession` and the `HiveServer2` was changed in order not to use `CLIService`.\r\n 3. The UGI management which is currently performed in Livy is definitely very different from the Hive one, this required changes to the `HiveAuthFactory` in order not to interfere with the existing codebase.\r\n\r\n\r\n## How was this patch tested?\r\n\r\nadded integration tests as UTs","created_at":"2018-08-15T14:57:30Z","updated_at":"2018-08-28T10:32:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"afa5fe8968b0581843471204183d2cee73dedc96","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/apache/incubator-livy/pulls/104/commits","review_comments_url":"https://api.github.com/repos/apache/incubator-livy/pulls/104/comments","review_comment_url":"https://api.github.com/repos/apache/incubator-livy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/incubator-livy/issues/104/comments","statuses_url":"https://api.github.com/repos/apache/incubator-livy/statuses/ec7e079866b70786d66cc67068730ca99eb03345","head":{"label":"mgaido91:LIVY-491","ref":"LIVY-491","sha":"ec7e079866b70786d66cc67068730ca99eb03345","user":{"login":"mgaido91","id":8821783,"node_id":"MDQ6VXNlcjg4MjE3ODM=","avatar_url":"https://avatars1.githubusercontent.com/u/8821783?v=4","gravatar_id":"","url":"https://api.github.com/users/mgaido91","html_url":"https://github.com/mgaido91","followers_url":"https://api.github.com/users/mgaido91/followers","following_url":"https://api.github.com/users/mgaido91/following{/other_user}","gists_url":"https://api.github.com/users/mgaido91/gists{/gist_id}","starred_url":"https://api.github.com/users/mgaido91/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mgaido91/subscriptions","organizations_url":"https://api.github.com/users/mgaido91/orgs","repos_url":"https://api.github.com/users/mgaido91/repos","events_url":"https://api.github.com/users/mgaido91/events{/privacy}","received_events_url":"https://api.github.com/users/mgaido91/received_events","type":"User","site_admin":false},"repo":{"id":138994555,"node_id":"MDEwOlJlcG9zaXRvcnkxMzg5OTQ1NTU=","name":"incubator-livy","full_name":"mgaido91/incubator-livy","owner":{"login":"mgaido91","id":8821783,"node_id":"MDQ6VXNlcjg4MjE3ODM=","avatar_url":"https://avatars1.githubusercontent.com/u/8821783?v=4","gravatar_id":"","url":"https://api.github.com/users/mgaido91","html_url":"https://github.com/mgaido91","followers_url":"https://api.github.com/users/mgaido91/followers","following_url":"https://api.github.com/users/mgaido91/following{/other_user}","gists_url":"https://api.github.com/users/mgaido91/gists{/gist_id}","starred_url":"https://api.github.com/users/mgaido91/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mgaido91/subscriptions","organizations_url":"https://api.github.com/users/mgaido91/orgs","repos_url":"https://api.github.com/users/mgaido91/repos","events_url":"https://api.github.com/users/mgaido91/events{/privacy}","received_events_url":"https://api.github.com/users/mgaido91/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mgaido91/incubator-livy","description":"Mirror of Apache livy (Incubating)","fork":true,"url":"https://api.github.com/repos/mgaido91/incubator-livy","forks_url":"https://api.github.com/repos/mgaido91/incubator-livy/forks","keys_url":"https://api.github.com/repos/mgaido91/incubator-livy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mgaido91/incubator-livy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mgaido91/incubator-livy/teams","hooks_url":"https://api.github.com/repos/mgaido91/incubator-livy/hooks","issue_events_url":"https://api.github.com/repos/mgaido91/incubator-livy/issues/events{/number}","events_url":"https://api.github.com/repos/mgaido91/incubator-livy/events","assignees_url":"https://api.github.com/repos/mgaido91/incubator-livy/assignees{/user}","branches_url":"https://api.github.com/repos/mgaido91/incubator-livy/branches{/branch}","tags_url":"https://api.github.com/repos/mgaido91/incubator-livy/tags","blobs_url":"https://api.github.com/repos/mgaido91/incubator-livy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mgaido91/incubator-livy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mgaido91/incubator-livy/git/refs{/sha}","trees_url":"https://api.github.com/repos/mgaido91/incubator-livy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mgaido91/incubator-livy/statuses/{sha}","languages_url":"https://api.github.com/repos/mgaido91/incubator-livy/languages","stargazers_url":"https://api.github.com/repos/mgaido91/incubator-livy/stargazers","contributors_url":"https://api.github.com/repos/mgaido91/incubator-livy/contributors","subscribers_url":"https://api.github.com/repos/mgaido91/incubator-livy/subscribers","subscription_url":"https://api.github.com/repos/mgaido91/incubator-livy/subscription","commits_url":"https://api.github.com/repos/mgaido91/incubator-livy/commits{/sha}","git_commits_url":"https://api.github.com/repos/mgaido91/incubator-livy/git/commits{/sha}","comments_url":"https://api.github.com/repos/mgaido91/incubator-livy/comments{/number}","issue_comment_url":"https://api.github.com/repos/mgaido91/incubator-livy/issues/comments{/number}","contents_url":"https://api.github.com/repos/mgaido91/incubator-livy/contents/{+path}","compare_url":"https://api.github.com/repos/mgaido91/incubator-livy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mgaido91/incubator-livy/merges","archive_url":"https://api.github.com/repos/mgaido91/incubator-livy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mgaido91/incubator-livy/downloads","issues_url":"https://api.github.com/repos/mgaido91/incubator-livy/issues{/number}","pulls_url":"https://api.github.com/repos/mgaido91/incubator-livy/pulls{/number}","milestones_url":"https://api.github.com/repos/mgaido91/incubator-livy/milestones{/number}","notifications_url":"https://api.github.com/repos/mgaido91/incubator-livy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mgaido91/incubator-livy/labels{/name}","releases_url":"https://api.github.com/repos/mgaido91/incubator-livy/releases{/id}","deployments_url":"https://api.github.com/repos/mgaido91/incubator-livy/deployments","created_at":"2018-06-28T09:10:17Z","updated_at":"2018-06-28T09:10:20Z","pushed_at":"2018-08-28T08:04:23Z","git_url":"git://github.com/mgaido91/incubator-livy.git","ssh_url":"git@github.com:mgaido91/incubator-livy.git","clone_url":"https://github.com/mgaido91/incubator-livy.git","svn_url":"https://github.com/mgaido91/incubator-livy","homepage":null,"size":3582,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_projects":true,"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":"apache:master","ref":"master","sha":"8027ca708fdc3df9a5b08d2d33d0436018154bcc","user":{"login":"apache","id":47359,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MzU5","avatar_url":"https://avatars0.githubusercontent.com/u/47359?v=4","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":95343930,"node_id":"MDEwOlJlcG9zaXRvcnk5NTM0MzkzMA==","name":"incubator-livy","full_name":"apache/incubator-livy","owner":{"login":"apache","id":47359,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MzU5","avatar_url":"https://avatars0.githubusercontent.com/u/47359?v=4","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/incubator-livy","description":"Mirror of Apache livy (Incubating)","fork":false,"url":"https://api.github.com/repos/apache/incubator-livy","forks_url":"https://api.github.com/repos/apache/incubator-livy/forks","keys_url":"https://api.github.com/repos/apache/incubator-livy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/incubator-livy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/incubator-livy/teams","hooks_url":"https://api.github.com/repos/apache/incubator-livy/hooks","issue_events_url":"https://api.github.com/repos/apache/incubator-livy/issues/events{/number}","events_url":"https://api.github.com/repos/apache/incubator-livy/events","assignees_url":"https://api.github.com/repos/apache/incubator-livy/assignees{/user}","branches_url":"https://api.github.com/repos/apache/incubator-livy/branches{/branch}","tags_url":"https://api.github.com/repos/apache/incubator-livy/tags","blobs_url":"https://api.github.com/repos/apache/incubator-livy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/incubator-livy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/incubator-livy/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/incubator-livy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/incubator-livy/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/incubator-livy/languages","stargazers_url":"https://api.github.com/repos/apache/incubator-livy/stargazers","contributors_url":"https://api.github.com/repos/apache/incubator-livy/contributors","subscribers_url":"https://api.github.com/repos/apache/incubator-livy/subscribers","subscription_url":"https://api.github.com/repos/apache/incubator-livy/subscription","commits_url":"https://api.github.com/repos/apache/incubator-livy/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/incubator-livy/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/incubator-livy/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/incubator-livy/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/incubator-livy/contents/{+path}","compare_url":"https://api.github.com/repos/apache/incubator-livy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/incubator-livy/merges","archive_url":"https://api.github.com/repos/apache/incubator-livy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/incubator-livy/downloads","issues_url":"https://api.github.com/repos/apache/incubator-livy/issues{/number}","pulls_url":"https://api.github.com/repos/apache/incubator-livy/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/incubator-livy/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/incubator-livy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/incubator-livy/labels{/name}","releases_url":"https://api.github.com/repos/apache/incubator-livy/releases{/id}","deployments_url":"https://api.github.com/repos/apache/incubator-livy/deployments","created_at":"2017-06-25T07:00:09Z","updated_at":"2018-08-27T12:10:36Z","pushed_at":"2018-08-26T16:02:13Z","git_url":"git://github.com/apache/incubator-livy.git","ssh_url":"git@github.com:apache/incubator-livy.git","clone_url":"https://github.com/apache/incubator-livy.git","svn_url":"https://github.com/apache/incubator-livy","homepage":null,"size":3012,"stargazers_count":162,"watchers_count":162,"language":"Scala","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":104,"mirror_url":null,"archived":false,"open_issues_count":13,"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":104,"open_issues":13,"watchers":162,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/incubator-livy/pulls/104"},"html":{"href":"https://github.com/apache/incubator-livy/pull/104"},"issue":{"href":"https://api.github.com/repos/apache/incubator-livy/issues/104"},"comments":{"href":"https://api.github.com/repos/apache/incubator-livy/issues/104/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/incubator-livy/pulls/104/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/incubator-livy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/incubator-livy/pulls/104/commits"},"statuses":{"href":"https://api.github.com/repos/apache/incubator-livy/statuses/ec7e079866b70786d66cc67068730ca99eb03345"}},"author_association":"NONE"}}
{"actor":{"display_login":"mgaido91"}}