[KYUUBI #1206][FEATURE] Support GROUP for engine.share.level
<!-- Thanks for sending a pull request! Here are some tips for you: 1. If this is your first time, please read our contributor guidelines: https://kyuubi.readthedocs.io/en/latest/community/contributions.html 2. If the PR is related to an issue in https://github.com/apache/incubator-kyuubi/issues, add '[KYUUBI #XXXX]' in your PR title, e.g., '[KYUUBI #XXXX] Your PR title ...'. 3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP][KYUUBI #XXXX] Your PR title ...'. --> ### _Why are the changes needed?_ <!-- Please clarify why the changes are needed. For instance, 1. If you add a feature, you can talk about the use case of it. 2. If you fix a bug, you can clarify why it is a bug. --> The detail locates #1206 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/latest/develop_tools/testing.html#running-tests) locally before make a pull request Closes #1207 from yaooqinn/1206. Closes #1206 99e324a9 [Kent Yao] [KYUUBI #1206][FEATURE] Support GROUP for engine.share.level f41fd944 [Kent Yao] Merge branch 'master' into 1206 a7cb80da [Kent Yao] [KYUUBI #1206][FEATURE] Support GROUP for engine.share.level 6ecef063 [Kent Yao] [KYUUBI #1206][FEATURE] Support GROUP for engine.share.level d7ff7583 [Kent Yao] [KYUUBI #1206][FEATURE] Support GROUP for engine.share.level 7b3b2f53 [Kent Yao] [KYUUBI #1206][FEATURE] Support GROUP for engine.share.level Authored-by: Kent Yao <yao@apache.org> Signed-off-by: ulysses-you <ulyssesyou@apache.org>
This commit is contained in:
parent
6160b9de30
commit
bf629291bf
@ -184,7 +184,7 @@ kyuubi\.engine<br>\.operation\.log\.dir<br>\.root|<div style='width: 65pt;word-w
|
||||
kyuubi\.engine\.pool<br>\.size|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>-1</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>The size of engine pool. Note that, if the size is less than 1, the engine pool will not be enabled; otherwise, the size of the engine pool will be min(this, kyuubi.engine.pool.size.threshold).</div>|<div style='width: 30pt'>int</div>|<div style='width: 20pt'>1.4.0</div>
|
||||
kyuubi\.engine\.pool<br>\.size\.threshold|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>9</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>This parameter is introduced as a server-side parameter, and controls the upper limit of the engine pool.</div>|<div style='width: 30pt'>int</div>|<div style='width: 20pt'>1.4.0</div>
|
||||
kyuubi\.engine\.session<br>\.initialize\.sql|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>SHOW DATABASES</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>SemiColon-separated list of SQL statements to be initialized in the newly created engine session before queries. This configuration can not be used in JDBC url due to the limitation of Beeline/JDBC driver.</div>|<div style='width: 30pt'>seq</div>|<div style='width: 20pt'>1.3.0</div>
|
||||
kyuubi\.engine\.share<br>\.level|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>USER</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>Engines will be shared in different levels, available configs are: <ul> <li>CONNECTION: engine will not be shared but only used by the current client connection</li> <li>USER: engine will be shared by all sessions created by a unique username, see also kyuubi.engine.share.level.subdomain</li> <li>SERVER: the App will be shared by Kyuubi servers</li></ul></div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.2.0</div>
|
||||
kyuubi\.engine\.share<br>\.level|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>USER</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>Engines will be shared in different levels, available configs are: <ul> <li>CONNECTION: engine will not be shared but only used by the current client connection</li> <li>USER: engine will be shared by all sessions created by a unique username, see also kyuubi.engine.share.level.subdomain</li> <li>GROUP: engine will be shared by all sessions created by all users belong to the same primary group name. The engine will be launched by the group name as the effective username, so here the group name is kind of special user who is able to visit the compute resources/data of a team. It follows the [Hadoop GroupsMapping](https://reurl.cc/xE61Y5) to map user to a primary group. If the primary group is not found, it fallback to the USER level. <li>SERVER: the App will be shared by Kyuubi servers</li></ul></div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.2.0</div>
|
||||
kyuubi\.engine\.share<br>\.level\.sub\.domain|<div style='width: 65pt;word-wrap: break-word;white-space: normal'><undefined></div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>(deprecated) - Using kyuubi.engine.share.level.subdomain instead</div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.2.0</div>
|
||||
kyuubi\.engine\.share<br>\.level\.subdomain|<div style='width: 65pt;word-wrap: break-word;white-space: normal'><undefined></div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>Allow end-users to create a subdomain for the share level of an engine. A subdomain is a case-insensitive string values in `^[a-zA-Z_-]{1,14}$` form. For example, for `USER` share level, an end-user can share a certain engine within a subdomain, not for all of its clients. End-users are free to create multiple engines in the `USER` share level</div>|<div style='width: 30pt'>string</div>|<div style='width: 20pt'>1.4.0</div>
|
||||
kyuubi\.engine\.single<br>\.spark\.session|<div style='width: 65pt;word-wrap: break-word;white-space: normal'>false</div>|<div style='width: 170pt;word-wrap: break-word;white-space: normal'>When set to true, this engine is running in a single session mode. All the JDBC/ODBC connections share the temporary views, function registries, SQL configuration and the current database.</div>|<div style='width: 30pt'>boolean</div>|<div style='width: 20pt'>1.3.0</div>
|
||||
|
||||
@ -740,6 +740,12 @@ object KyuubiConf {
|
||||
" connection</li>" +
|
||||
" <li>USER: engine will be shared by all sessions created by a unique username," +
|
||||
s" see also ${ENGINE_SHARE_LEVEL_SUBDOMAIN.key}</li>" +
|
||||
" <li>GROUP: engine will be shared by all sessions created by all users belong to the same" +
|
||||
" primary group name. The engine will be launched by the group name as the effective" +
|
||||
" username, so here the group name is kind of special user who is able to visit the" +
|
||||
" compute resources/data of a team. It follows the" +
|
||||
" [Hadoop GroupsMapping](https://reurl.cc/xE61Y5) to map user to a primary group. If the" +
|
||||
" primary group is not found, it fallback to the USER level." +
|
||||
" <li>SERVER: the App will be shared by Kyuubi servers</li></ul>")
|
||||
.version("1.2.0")
|
||||
.fallbackConf(LEGACY_ENGINE_SHARE_LEVEL)
|
||||
|
||||
@ -27,6 +27,8 @@ object ShareLevel extends Enumeration {
|
||||
CONNECTION,
|
||||
/** DEFAULT level, An APP will be shared for all sessions created by a user */
|
||||
USER,
|
||||
/** In this level, An APP will be shared for all sessions created by a user's default group */
|
||||
GROUP,
|
||||
/** In this level, All sessions from one or more Kyuubi server's will share one single APP */
|
||||
SERVER = Value
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ import org.apache.kyuubi.service.authentication.PlainSASLHelper
|
||||
trait JDBCTestUtils extends KyuubiFunSuite {
|
||||
|
||||
protected val dftSchema = "default"
|
||||
protected val user: String = Utils.currentUser
|
||||
protected lazy val user: String = Utils.currentUser
|
||||
protected val patterns = Seq("", "*", "%", null, ".*", "_*", "_%", ".%")
|
||||
protected def jdbcUrl: String
|
||||
private var _sessionConfs: Map[String, String] = Map.empty
|
||||
|
||||
@ -27,11 +27,12 @@ import com.google.common.annotations.VisibleForTesting
|
||||
import org.apache.curator.framework.CuratorFramework
|
||||
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex
|
||||
import org.apache.curator.utils.ZKPaths
|
||||
import org.apache.hadoop.security.UserGroupInformation
|
||||
|
||||
import org.apache.kyuubi.{KyuubiSQLException, Logging, Utils}
|
||||
import org.apache.kyuubi.config.KyuubiConf
|
||||
import org.apache.kyuubi.config.KyuubiConf._
|
||||
import org.apache.kyuubi.engine.ShareLevel.{CONNECTION, SERVER, ShareLevel}
|
||||
import org.apache.kyuubi.engine.ShareLevel.{CONNECTION, GROUP, SERVER, ShareLevel}
|
||||
import org.apache.kyuubi.engine.spark.SparkProcessBuilder
|
||||
import org.apache.kyuubi.ha.HighAvailabilityConf.HA_ZK_ENGINE_REF_ID
|
||||
import org.apache.kyuubi.ha.HighAvailabilityConf.HA_ZK_NAMESPACE
|
||||
@ -87,6 +88,16 @@ private[kyuubi] class EngineRef(
|
||||
// Launcher of the engine
|
||||
private[kyuubi] val appUser: String = shareLevel match {
|
||||
case SERVER => Utils.currentUser
|
||||
case GROUP =>
|
||||
val clientUGI = UserGroupInformation.createRemoteUser(user)
|
||||
// Similar to `clientUGI.getPrimaryGroupName` (avoid IOE) to get the Primary GroupName of
|
||||
// the client user mapping to
|
||||
clientUGI.getGroupNames.headOption match {
|
||||
case Some(primaryGroup) => primaryGroup
|
||||
case None =>
|
||||
warn(s"There is no primary group for $user, use the client user name as group directly")
|
||||
user
|
||||
}
|
||||
case _ => user
|
||||
}
|
||||
|
||||
@ -109,7 +120,10 @@ private[kyuubi] class EngineRef(
|
||||
* /`serverSpace_CONNECTION`/`user`/`engineRefId`
|
||||
* For `USER` share level:
|
||||
* /`serverSpace_USER`/`user`[/`subdomain`]
|
||||
*
|
||||
* For `GROUP` share level:
|
||||
* /`serverSpace_GROUP`/`primary group name`[/`subdomain`]
|
||||
* For `SERVER` share level:
|
||||
* /`serverSpace_SERVER`/`kyuubi server user`[/`subdomain`]
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private[kyuubi] lazy val engineSpace: String = shareLevel match {
|
||||
|
||||
@ -34,6 +34,7 @@ import org.apache.hadoop.hive.metastore.{HiveMetaException, HiveMetaStore}
|
||||
import org.apache.hadoop.hive.thrift.{DelegationTokenIdentifier, HadoopThriftAuthBridge, HadoopThriftAuthBridge23}
|
||||
import org.apache.hadoop.io.Text
|
||||
import org.apache.hadoop.security.{Credentials, UserGroupInformation}
|
||||
import org.apache.hadoop.security.authorize.ProxyUsers
|
||||
import org.apache.thrift.TProcessor
|
||||
import org.apache.thrift.protocol.TProtocol
|
||||
import org.scalatest.concurrent.Eventually._
|
||||
@ -82,6 +83,7 @@ class HiveDelegationTokenProviderSuite extends KerberizedTestHelper {
|
||||
hiveConf.setVar(METASTORE_USE_THRIFT_SASL, "true")
|
||||
hiveConf.setVar(METASTORE_KERBEROS_PRINCIPAL, testPrincipal)
|
||||
hiveConf.setVar(METASTORE_KERBEROS_KEYTAB_FILE, testKeytab)
|
||||
ProxyUsers.refreshSuperUserGroupsConfiguration(hiveConf)
|
||||
val metaServer = new LocalMetaServer(hiveConf, classloader)
|
||||
metaServer.start()
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ package org.apache.kyuubi.engine
|
||||
import java.util.UUID
|
||||
|
||||
import org.apache.curator.utils.ZKPaths
|
||||
import org.apache.hadoop.security.UserGroupInformation
|
||||
import org.scalatest.time.SpanSugar.convertIntToGrainOfTime
|
||||
|
||||
import org.apache.kyuubi.{KyuubiFunSuite, Utils}
|
||||
@ -34,7 +35,7 @@ class EngineRefSuite extends KyuubiFunSuite {
|
||||
import ShareLevel._
|
||||
private val zkServer = new EmbeddedZookeeper
|
||||
private val conf = KyuubiConf()
|
||||
val user = Utils.currentUser
|
||||
private val user = Utils.currentUser
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
val zkData = Utils.createTempDir()
|
||||
@ -87,6 +88,32 @@ class EngineRefSuite extends KyuubiFunSuite {
|
||||
}
|
||||
}
|
||||
|
||||
test("GROUP shared level engine name") {
|
||||
val id = UUID.randomUUID().toString
|
||||
conf.set(KyuubiConf.ENGINE_SHARE_LEVEL, GROUP.toString)
|
||||
val engineRef = new EngineRef(conf, user, id)
|
||||
val primaryGroupName = UserGroupInformation.createRemoteUser(user).getPrimaryGroupName
|
||||
assert(engineRef.engineSpace === ZKPaths.makePath(s"kyuubi_GROUP", primaryGroupName))
|
||||
assert(engineRef.defaultEngineName === s"kyuubi_GROUP_${primaryGroupName}_$id")
|
||||
|
||||
Seq(KyuubiConf.ENGINE_SHARE_LEVEL_SUBDOMAIN,
|
||||
KyuubiConf.ENGINE_SHARE_LEVEL_SUB_DOMAIN).foreach { k =>
|
||||
conf.unset(k)
|
||||
conf.set(k.key, "abc")
|
||||
val engineRef2 = new EngineRef(conf, user, id)
|
||||
assert(engineRef2.engineSpace ===
|
||||
ZKPaths.makePath(s"kyuubi_$GROUP", primaryGroupName, "abc"))
|
||||
assert(engineRef2.defaultEngineName === s"kyuubi_${GROUP}_${primaryGroupName}_abc_$id")
|
||||
}
|
||||
|
||||
val userName = "Iamauserwithoutgroup"
|
||||
val newUGI = UserGroupInformation.createRemoteUser(userName)
|
||||
assert(newUGI.getGroupNames.isEmpty)
|
||||
val engineRef3 = new EngineRef(conf, userName, id)
|
||||
assert(engineRef3.engineSpace === ZKPaths.makePath(s"kyuubi_GROUP", userName, "abc"))
|
||||
assert(engineRef3.defaultEngineName === s"kyuubi_GROUP_${userName}_abc_$id")
|
||||
}
|
||||
|
||||
test("SERVER shared level engine name") {
|
||||
val id = UUID.randomUUID().toString
|
||||
conf.set(KyuubiConf.ENGINE_SHARE_LEVEL, SERVER.toString)
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.kyuubi.operation
|
||||
|
||||
import org.apache.hadoop.security.UserGroupInformation
|
||||
|
||||
import org.apache.kyuubi.WithKyuubiServer
|
||||
import org.apache.kyuubi.config.KyuubiConf
|
||||
import org.apache.kyuubi.util.KyuubiHadoopUtils
|
||||
|
||||
class KyuubiOperationGroupSuite extends WithKyuubiServer with JDBCTests {
|
||||
|
||||
override protected def jdbcUrl: String = getJdbcUrl
|
||||
|
||||
override protected val conf: KyuubiConf = {
|
||||
val c = KyuubiConf().set(KyuubiConf.ENGINE_SHARE_LEVEL, "group")
|
||||
.set("hadoop.user.group.static.mapping.overrides",
|
||||
s"user1=testGG,group_tt;user2=testGG")
|
||||
UserGroupInformation.setConfiguration(KyuubiHadoopUtils.newHadoopConf(c))
|
||||
c.set(s"hadoop.proxyuser.$user.groups", "*")
|
||||
.set(s"hadoop.proxyuser.$user.hosts", "*")
|
||||
}
|
||||
|
||||
test("ensure two connections in group mode share the same engine started by primary group") {
|
||||
var r1: String = null
|
||||
var r2: String = null
|
||||
withSessionConf(Map("hive.server2.proxy.user" -> "user1"))(Map.empty)(Map.empty) {
|
||||
withJdbcStatement() { statement =>
|
||||
val res = statement.executeQuery("set spark.app.name")
|
||||
assert(res.next())
|
||||
r1 = res.getString("value")
|
||||
}
|
||||
}
|
||||
|
||||
withSessionConf(Map("hive.server2.proxy.user" -> "user2"))(Map.empty)(Map.empty) {
|
||||
withJdbcStatement() { statement =>
|
||||
val res = statement.executeQuery("set spark.app.name")
|
||||
assert(res.next())
|
||||
r2 = res.getString("value")
|
||||
}
|
||||
}
|
||||
assert(r1 != null && r2 != null)
|
||||
assert(r1 === r2)
|
||||
assert(r1.startsWith(s"kyuubi_GROUP_testGG"))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user