diff --git a/docs/configuration/settings.md b/docs/configuration/settings.md index d939e9122cb..f4ce59d8248 100644 --- a/docs/configuration/settings.md +++ b/docs/configuration/settings.md @@ -161,6 +161,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co | kyuubi.engine.jdbc.connection.provider | <undefined> | A JDBC connection provider plugin for the Kyuubi Server to establish a connection to the JDBC URL. The configuration value should be a subclass of `org.apache.kyuubi.engine.jdbc.connection.JdbcConnectionProvider`. Kyuubi provides the following built-in implementations:
  • doris: For establishing Doris connections.
  • mysql: For establishing MySQL connections.
  • phoenix: For establishing Phoenix connections.
  • postgresql: For establishing PostgreSQL connections.
  • starrocks: For establishing StarRocks connections.
  • impala: For establishing Impala connections.
  • clickhouse: For establishing clickhouse connections.
  • | string | 1.6.0 | | kyuubi.engine.jdbc.connection.url | <undefined> | The server url that engine will connect to | string | 1.6.0 | | kyuubi.engine.jdbc.connection.user | <undefined> | The user is used for connecting to server | string | 1.6.0 | +| kyuubi.engine.jdbc.deploy.mode | LOCAL | Configures the jdbc engine deploy mode, The value can be 'local', 'yarn'. In local mode, the engine operates on the same node as the KyuubiServer. In YARN mode, the engine runs within the Application Master (AM) container of YARN. | string | 1.10.0 | | kyuubi.engine.jdbc.driver.class | <undefined> | The driver class for JDBC engine connection | string | 1.6.0 | | kyuubi.engine.jdbc.extra.classpath | <undefined> | The extra classpath for the JDBC query engine, for configuring the location of the JDBC driver and etc. | string | 1.6.0 | | kyuubi.engine.jdbc.fetch.size | 1000 | The fetch size of JDBC engine | int | 1.9.0 | diff --git a/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/deploy/HiveYarnModeSubmitter.scala b/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/deploy/HiveYarnModeSubmitter.scala index a717f9dfd01..793fcdc7a4a 100644 --- a/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/deploy/HiveYarnModeSubmitter.scala +++ b/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/deploy/HiveYarnModeSubmitter.scala @@ -26,6 +26,7 @@ import org.apache.kyuubi.Utils import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf.ENGINE_HIVE_EXTRA_CLASSPATH import org.apache.kyuubi.engine.deploy.yarn.EngineYarnModeSubmitter +import org.apache.kyuubi.engine.deploy.yarn.EngineYarnModeSubmitter.KYUUBI_ENGINE_DEPLOY_YARN_MODE_HIVE_CONF_KEY import org.apache.kyuubi.engine.hive.HiveSQLEngine object HiveYarnModeSubmitter extends EngineYarnModeSubmitter { @@ -60,18 +61,10 @@ object HiveYarnModeSubmitter extends EngineYarnModeSubmitter { jars.toSeq } - private[hive] def parseClasspath(classpath: String, jars: ListBuffer[File]): Unit = { - classpath.split(":").filter(_.nonEmpty).foreach { cp => - if (cp.endsWith("/*")) { - val dir = cp.substring(0, cp.length - 2) - new File(dir) match { - case f if f.isDirectory => - f.listFiles().filter(_.getName.endsWith(".jar")).foreach(jars += _) - case _ => - } - } else { - jars += new File(cp) - } - } + override def listConfFiles(): Seq[File] = { + // respect the following priority loading configuration, and distinct files + // hive configuration -> hadoop configuration -> yarn configuration + val hiveConf = kyuubiConf.getOption(KYUUBI_ENGINE_DEPLOY_YARN_MODE_HIVE_CONF_KEY) + listDistinctFiles(hiveConf.get) ++ super.listConfFiles() } } diff --git a/externals/kyuubi-hive-sql-engine/src/test/scala/org/apache/kyuubi/engine/hive/deploy/HiveYarnModeSubmitterSuite.scala b/externals/kyuubi-hive-sql-engine/src/test/scala/org/apache/kyuubi/engine/hive/deploy/HiveYarnModeSubmitterSuite.scala deleted file mode 100644 index b04fefa0afd..00000000000 --- a/externals/kyuubi-hive-sql-engine/src/test/scala/org/apache/kyuubi/engine/hive/deploy/HiveYarnModeSubmitterSuite.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.engine.hive.deploy - -import java.io.File - -import scala.collection.mutable.ListBuffer - -import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiFunSuite, SCALA_COMPILE_VERSION} -import org.apache.kyuubi.util.JavaUtils - -class HiveYarnModeSubmitterSuite extends KyuubiFunSuite { - val hiveEngineHome: String = JavaUtils.getCodeSourceLocation(getClass).split("/target")(0) - - test("hadoop class path") { - val jars = new ListBuffer[File] - val classpath = - s"$hiveEngineHome/target/scala-$SCALA_COMPILE_VERSION/jars/*:" + - s"$hiveEngineHome/target/kyuubi-hive-sql-engine-$SCALA_COMPILE_VERSION-$KYUUBI_VERSION.jar" - HiveYarnModeSubmitter.parseClasspath(classpath, jars) - assert(jars.nonEmpty) - assert(jars.exists( - _.getName == s"kyuubi-hive-sql-engine-$SCALA_COMPILE_VERSION-$KYUUBI_VERSION.jar")) - } - -} diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcSQLEngine.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcSQLEngine.scala index 6e0647f6c7a..ca620207c11 100644 --- a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcSQLEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcSQLEngine.scala @@ -16,10 +16,15 @@ */ package org.apache.kyuubi.engine.jdbc +import java.security.PrivilegedExceptionAction + +import org.apache.hadoop.security.UserGroupInformation + import org.apache.kyuubi.{Logging, Utils} import org.apache.kyuubi.Utils.{addShutdownHook, JDBC_ENGINE_SHUTDOWN_PRIORITY} -import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.config.KyuubiConf.ENGINE_JDBC_INITIALIZE_SQL +import org.apache.kyuubi.config.{KyuubiConf, KyuubiReservedKeys} +import org.apache.kyuubi.config.KyuubiConf.{ENGINE_JDBC_DEPLOY_MODE, ENGINE_JDBC_INITIALIZE_SQL, ENGINE_KEYTAB, ENGINE_PRINCIPAL} +import org.apache.kyuubi.engine.deploy.DeployMode import org.apache.kyuubi.engine.jdbc.JdbcSQLEngine.currentEngine import org.apache.kyuubi.engine.jdbc.util.KyuubiJdbcUtils import org.apache.kyuubi.ha.HighAvailabilityConf.HA_ZK_CONN_RETRY_POLICY @@ -38,6 +43,7 @@ class JdbcSQLEngine extends Serverable("JdbcSQLEngine") { // Start engine self-terminating checker after all services are ready and it can be reached by // all servers in engine spaces. backendService.sessionManager.startTerminatingChecker(() => { + selfExited = true currentEngine.foreach(_.stop()) }) } @@ -71,10 +77,32 @@ object JdbcSQLEngine extends Logging { Utils.fromCommandLineArgs(args, kyuubiConf) kyuubiConf.setIfMissing(KyuubiConf.FRONTEND_THRIFT_BINARY_BIND_PORT, 0) kyuubiConf.setIfMissing(HA_ZK_CONN_RETRY_POLICY, RetryPolicies.N_TIME.toString) + val proxyUser = kyuubiConf.getOption(KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY) + require(proxyUser.isDefined, s"${KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY} is not set") + val realUser = UserGroupInformation.getLoginUser + val principal = kyuubiConf.get(ENGINE_PRINCIPAL) + val keytab = kyuubiConf.get(ENGINE_KEYTAB) - startEngine() + val ugi = DeployMode.withName(kyuubiConf.get(ENGINE_JDBC_DEPLOY_MODE)) match { + case DeployMode.LOCAL + if UserGroupInformation.isSecurityEnabled && principal.isDefined && keytab.isDefined => + UserGroupInformation.loginUserFromKeytab(principal.get, keytab.get) + UserGroupInformation.getCurrentUser + case DeployMode.LOCAL if proxyUser.get != realUser.getShortUserName => + kyuubiConf.unset(KyuubiReservedKeys.KYUUBI_ENGINE_CREDENTIALS_KEY) + UserGroupInformation.createProxyUser(proxyUser.get, realUser) + case _ => + UserGroupInformation.getCurrentUser + } - KyuubiJdbcUtils.initializeJdbcSession(kyuubiConf, kyuubiConf.get(ENGINE_JDBC_INITIALIZE_SQL)) + ugi.doAs(new PrivilegedExceptionAction[Unit] { + override def run(): Unit = { + startEngine() + KyuubiJdbcUtils.initializeJdbcSession( + kyuubiConf, + kyuubiConf.get(ENGINE_JDBC_INITIALIZE_SQL)) + } + }) } catch { case t: Throwable if currentEngine.isDefined => currentEngine.foreach { engine => diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/deploy/JdbcYarnModeSubmitter.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/deploy/JdbcYarnModeSubmitter.scala new file mode 100644 index 00000000000..a585a9a6f46 --- /dev/null +++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/deploy/JdbcYarnModeSubmitter.scala @@ -0,0 +1,60 @@ +/* + * 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.engine.jdbc.deploy + +import java.io.File + +import scala.collection.mutable.ListBuffer + +import org.apache.hadoop.security.UserGroupInformation + +import org.apache.kyuubi.Utils +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiConf.ENGINE_JDBC_EXTRA_CLASSPATH +import org.apache.kyuubi.engine.deploy.yarn.EngineYarnModeSubmitter +import org.apache.kyuubi.engine.jdbc.JdbcSQLEngine + +object JdbcYarnModeSubmitter extends EngineYarnModeSubmitter { + + def main(args: Array[String]): Unit = { + Utils.fromCommandLineArgs(args, kyuubiConf) + + if (UserGroupInformation.isSecurityEnabled) { + require( + kyuubiConf.get(KyuubiConf.ENGINE_PRINCIPAL).isDefined + && kyuubiConf.get(KyuubiConf.ENGINE_KEYTAB).isDefined, + s"${KyuubiConf.ENGINE_PRINCIPAL.key} and " + + s"${KyuubiConf.ENGINE_KEYTAB.key} must be set when submit " + + s"${JdbcSQLEngine.getClass.getSimpleName.stripSuffix("$")} to YARN") + } + run() + } + + override var engineType: String = "jdbc" + + override def engineMainClass(): String = JdbcSQLEngine.getClass.getName + + /** + * Jar list for the JDBC engine. + */ + override def engineExtraJars(): Seq[File] = { + val extraCp = kyuubiConf.get(ENGINE_JDBC_EXTRA_CLASSPATH) + val jars = new ListBuffer[File] + extraCp.foreach(cp => parseClasspath(cp, jars)) + jars.toSeq + } +} diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala index 513e61303fd..101a06e7183 100644 --- a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala +++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala @@ -61,6 +61,9 @@ class JdbcSessionManager(name: String) } private def stopSession(): Unit = { - JdbcSQLEngine.currentEngine.foreach(_.stop()) + JdbcSQLEngine.currentEngine.foreach { engine => + engine.selfExited = true + engine.stop() + } } } diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/clickhouse/WithClickHouseEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/clickhouse/WithClickHouseEngine.scala index 2cf695d28d3..bb834cd70af 100644 --- a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/clickhouse/WithClickHouseEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/clickhouse/WithClickHouseEngine.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.engine.jdbc.clickhouse import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.jdbc.WithJdbcEngine trait WithClickHouseEngine extends WithJdbcEngine with WithClickHouseContainer { @@ -29,6 +30,7 @@ trait WithClickHouseEngine extends WithJdbcEngine with WithClickHouseContainer { ENGINE_JDBC_CONNECTION_PASSWORD.key -> container.password, ENGINE_TYPE.key -> "jdbc", ENGINE_JDBC_SHORT_NAME.key -> "clickhouse", + KYUUBI_SESSION_USER_KEY -> "kyuubi", ENGINE_JDBC_DRIVER_CLASS.key -> container.driverClassName) } } diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/doris/WithDorisEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/doris/WithDorisEngine.scala index 692f37b9515..47e5a4ab96e 100644 --- a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/doris/WithDorisEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/doris/WithDorisEngine.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.engine.jdbc.doris import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.jdbc.WithJdbcEngine trait WithDorisEngine extends WithJdbcEngine with WithDorisContainer { @@ -28,5 +29,6 @@ trait WithDorisEngine extends WithJdbcEngine with WithDorisContainer { ENGINE_JDBC_CONNECTION_PASSWORD.key -> "", ENGINE_TYPE.key -> "jdbc", ENGINE_JDBC_SHORT_NAME.key -> "doris", + KYUUBI_SESSION_USER_KEY -> "kyuubi", ENGINE_JDBC_DRIVER_CLASS.key -> "com.mysql.cj.jdbc.Driver") } diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/impala/WithImpalaEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/impala/WithImpalaEngine.scala index 9b31dd245bb..7dc12d56ba0 100644 --- a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/impala/WithImpalaEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/impala/WithImpalaEngine.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.engine.jdbc.impala import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.jdbc.WithJdbcEngine trait WithImpalaEngine extends WithJdbcEngine with WithImpalaContainer { @@ -26,5 +27,6 @@ trait WithImpalaEngine extends WithJdbcEngine with WithImpalaContainer { ENGINE_JDBC_CONNECTION_URL.key -> hiveServerJdbcUrl, ENGINE_TYPE.key -> "jdbc", ENGINE_JDBC_SHORT_NAME.key -> "impala", + KYUUBI_SESSION_USER_KEY -> "kyuubi", ENGINE_JDBC_DRIVER_CLASS.key -> ImpalaConnectionProvider.driverClass) } diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/WithMySQLEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/WithMySQLEngine.scala index 39d2e0a59ec..09c9066a630 100644 --- a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/WithMySQLEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/mysql/WithMySQLEngine.scala @@ -21,6 +21,7 @@ import com.dimafeng.testcontainers.scalatest.TestContainerForAll import org.testcontainers.utility.DockerImageName import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.jdbc.WithJdbcEngine trait WithMySQLEngine extends WithJdbcEngine with TestContainerForAll { @@ -40,6 +41,7 @@ trait WithMySQLEngine extends WithJdbcEngine with TestContainerForAll { ENGINE_JDBC_CONNECTION_PASSWORD.key -> mysqlContainer.password, ENGINE_TYPE.key -> "jdbc", ENGINE_JDBC_SHORT_NAME.key -> "mysql", + KYUUBI_SESSION_USER_KEY -> "kyuubi", ENGINE_JDBC_DRIVER_CLASS.key -> "com.mysql.cj.jdbc.Driver") } } diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/phoenix/WithPhoenixEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/phoenix/WithPhoenixEngine.scala index adf328d7b0c..e1584341577 100644 --- a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/phoenix/WithPhoenixEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/phoenix/WithPhoenixEngine.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.engine.jdbc.phoenix import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.jdbc.WithJdbcEngine trait WithPhoenixEngine extends WithJdbcEngine with WithPhoenixContainer { @@ -32,6 +33,7 @@ trait WithPhoenixEngine extends WithJdbcEngine with WithPhoenixContainer { ENGINE_JDBC_CONNECTION_PASSWORD.key -> "", ENGINE_TYPE.key -> "jdbc", ENGINE_JDBC_SHORT_NAME.key -> "phoenix", + KYUUBI_SESSION_USER_KEY -> "kyuubi", ENGINE_JDBC_DRIVER_CLASS.key -> "org.apache.phoenix.queryserver.client.Driver") private def getConnectString: String = s"$jdbcUrlPrefix=http://$queryServerUrl;$serialization" diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/postgresql/WithPostgreSQLEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/postgresql/WithPostgreSQLEngine.scala index 6d453934e6f..74c42f188f9 100644 --- a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/postgresql/WithPostgreSQLEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/postgresql/WithPostgreSQLEngine.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.engine.jdbc.postgresql import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.jdbc.WithJdbcEngine trait WithPostgreSQLEngine extends WithJdbcEngine with WithPostgreSQLContainer { @@ -29,6 +30,7 @@ trait WithPostgreSQLEngine extends WithJdbcEngine with WithPostgreSQLContainer { ENGINE_JDBC_CONNECTION_PASSWORD.key -> container.password, ENGINE_TYPE.key -> "jdbc", ENGINE_JDBC_SHORT_NAME.key -> "postgresql", + KYUUBI_SESSION_USER_KEY -> "kyuubi", ENGINE_JDBC_DRIVER_CLASS.key -> container.driverClassName) } diff --git a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/starrocks/WithStarRocksEngine.scala b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/starrocks/WithStarRocksEngine.scala index 6423186c050..3787bc2f176 100644 --- a/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/starrocks/WithStarRocksEngine.scala +++ b/externals/kyuubi-jdbc-engine/src/test/scala/org/apache/kyuubi/engine/jdbc/starrocks/WithStarRocksEngine.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.engine.jdbc.starrocks import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.jdbc.WithJdbcEngine import org.apache.kyuubi.engine.jdbc.mysql.MySQL8ConnectionProvider @@ -32,5 +33,6 @@ trait WithStarRocksEngine extends WithJdbcEngine with WithStarRocksContainer { ENGINE_JDBC_CONNECTION_PASSWORD.key -> password, ENGINE_TYPE.key -> "jdbc", ENGINE_JDBC_SHORT_NAME.key -> "starrocks", + KYUUBI_SESSION_USER_KEY -> "kyuubi", ENGINE_JDBC_DRIVER_CLASS.key -> MySQL8ConnectionProvider.driverClass) } diff --git a/integration-tests/kyuubi-jdbc-it/pom.xml b/integration-tests/kyuubi-jdbc-it/pom.xml index f62d2d9af39..1279f3e8dd3 100644 --- a/integration-tests/kyuubi-jdbc-it/pom.xml +++ b/integration-tests/kyuubi-jdbc-it/pom.xml @@ -109,6 +109,37 @@ http test + + + + org.apache.hadoop + hadoop-client-minicluster + test + + + + org.bouncycastle + bcprov-jdk18on + test + + + + org.bouncycastle + bcpkix-jdk18on + test + + + + jakarta.activation + jakarta.activation-api + test + + + + jakarta.xml.bind + jakarta.xml.bind-api + test + diff --git a/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/OperationWithServerYarnModeSuite.scala b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/OperationWithServerYarnModeSuite.scala new file mode 100644 index 00000000000..5561297ba68 --- /dev/null +++ b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/OperationWithServerYarnModeSuite.scala @@ -0,0 +1,27 @@ +/* + * 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.it.jdbc.mysql + +import org.apache.kyuubi.engine.jdbc.mysql.MySQLOperationSuite + +class OperationWithServerYarnModeSuite extends MySQLOperationSuite + with WithKyuubiServerAndMySQLContainer { + + override protected def jdbcUrl: String = getJdbcUrl + +} diff --git a/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/WithKyuubiServerAndMySQLContainerYarnMode.scala b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/WithKyuubiServerAndMySQLContainerYarnMode.scala new file mode 100644 index 00000000000..0567136e13f --- /dev/null +++ b/integration-tests/kyuubi-jdbc-it/src/test/scala/org/apache/kyuubi/it/jdbc/mysql/WithKyuubiServerAndMySQLContainerYarnMode.scala @@ -0,0 +1,65 @@ +/* + * 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.it.jdbc.mysql + +import java.nio.file.{Files, Path, Paths} + +import org.apache.kyuubi.WithKyuubiServerAndHadoopMiniCluster +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiConf.{ENGINE_IDLE_TIMEOUT, ENGINE_JDBC_EXTRA_CLASSPATH, ENGINE_TYPE, KYUUBI_ENGINE_ENV_PREFIX, KYUUBI_HOME} +import org.apache.kyuubi.engine.deploy.DeployMode +import org.apache.kyuubi.engine.jdbc.mysql.MySQLOperationSuite + +class WithKyuubiServerAndMySQLContainerYarnMode extends MySQLOperationSuite + with WithKyuubiServerAndHadoopMiniCluster { + + private val mysqlJdbcConnectorPath: String = { + val keyword = "mysql-connector" + + val jarsDir = Paths.get(kyuubiHome) + .resolve("integration-tests") + .resolve("kyuubi-jdbc-it") + .resolve("target") + + Files.list(jarsDir) + .filter { p: Path => p.getFileName.toString contains keyword } + .findFirst + .orElseThrow { () => new IllegalStateException(s"Can not find $keyword in $jarsDir.") } + .toAbsolutePath + .toString + } + + override protected val conf: KyuubiConf = { + KyuubiConf() + .set(s"$KYUUBI_ENGINE_ENV_PREFIX.$KYUUBI_HOME", kyuubiHome) + .set(ENGINE_JDBC_EXTRA_CLASSPATH, mysqlJdbcConnectorPath) + .set(ENGINE_TYPE, "JDBC") + .set(KyuubiConf.ENGINE_JDBC_DEPLOY_MODE, DeployMode.YARN.toString) + .setIfMissing(ENGINE_IDLE_TIMEOUT, 30000L) + } + + override def beforeAll(): Unit = { + val configs = withKyuubiConf + configs.foreach(config => conf.set(config._1, config._2)) + super.beforeAll() + conf + .set(KyuubiConf.ENGINE_DEPLOY_YARN_MODE_MEMORY, Math.min(getYarnMaximumAllocationMb, 1024)) + .set(KyuubiConf.ENGINE_DEPLOY_YARN_MODE_CORES, 1) + } + + override protected def jdbcUrl: String = getJdbcUrl +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 170e6950c23..a0dc77f7fcf 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -3131,6 +3131,19 @@ object KyuubiConf { .version("1.10.0") .fallbackConf(OPERATION_INCREMENTAL_COLLECT) + val ENGINE_JDBC_DEPLOY_MODE: ConfigEntry[String] = + buildConf("kyuubi.engine.jdbc.deploy.mode") + .doc("Configures the jdbc engine deploy mode, The value can be 'local', 'yarn'. " + + "In local mode, the engine operates on the same node as the KyuubiServer. " + + "In YARN mode, the engine runs within the Application Master (AM) container of YARN. ") + .version("1.10.0") + .stringConf + .transformToUpperCase + .checkValue( + mode => Set("LOCAL", "YARN").contains(mode), + "Invalid value for 'kyuubi.engine.jdbc.deploy.mode'. Valid values are 'local', 'yarn'.") + .createWithDefault(DeployMode.LOCAL.toString) + val ENGINE_OPERATION_CONVERT_CATALOG_DATABASE_ENABLED: ConfigEntry[Boolean] = buildConf("kyuubi.engine.operation.convert.catalog.database.enabled") .doc("When set to true, The engine converts the JDBC methods of set/get Catalog " + diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitter.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitter.scala index e2913539b46..e0fbe166748 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitter.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitter.scala @@ -313,15 +313,7 @@ abstract class EngineYarnModeSubmitter extends Logging { env) } } - // respect the following priority loading configuration, and distinct files - // hive configuration -> hadoop configuration -> yarn configuration - val hiveConf = kyuubiConf.getOption(KYUUBI_ENGINE_DEPLOY_YARN_MODE_HIVE_CONF_KEY) - listDistinctFiles(hiveConf.get).foreach(putEntry) - val hadoopConf = kyuubiConf.getOption(KYUUBI_ENGINE_DEPLOY_YARN_MODE_HADOOP_CONF_KEY) - listDistinctFiles(hadoopConf.get).foreach(putEntry) - val yarnConf = kyuubiConf.getOption(KYUUBI_ENGINE_DEPLOY_YARN_MODE_YARN_CONF_KEY) - listDistinctFiles(yarnConf.get).foreach(putEntry) - + listConfFiles().foreach(putEntry) val properties = confToProperties(kyuubiConf) amKeytabFileName.foreach(kt => properties.put(ENGINE_KEYTAB.key, kt)) writePropertiesToArchive(properties, KYUUBI_CONF_FILE, confStream) @@ -352,6 +344,14 @@ abstract class EngineYarnModeSubmitter extends Logging { }.toSeq } + def listConfFiles(): Seq[File] = { + // respect the following priority loading configuration, and distinct files + // hadoop configuration -> yarn configuration + val hadoopConf = kyuubiConf.getOption(KYUUBI_ENGINE_DEPLOY_YARN_MODE_HADOOP_CONF_KEY) + val yarnConf = kyuubiConf.getOption(KYUUBI_ENGINE_DEPLOY_YARN_MODE_YARN_CONF_KEY) + listDistinctFiles(hadoopConf.get) ++ listDistinctFiles(yarnConf.get) + } + private def distribute( srcPath: Path, resType: LocalResourceType, @@ -486,6 +486,21 @@ abstract class EngineYarnModeSubmitter extends Logging { writer.flush() out.closeEntry() } + + def parseClasspath(classpath: String, jars: ListBuffer[File]): Unit = { + classpath.split(":").filter(_.nonEmpty).foreach { cp => + if (cp.endsWith("/*")) { + val dir = cp.substring(0, cp.length - 2) + new File(dir) match { + case f if f.isDirectory => + f.listFiles().filter(_.getName.endsWith(".jar")).foreach(jars += _) + case _ => + } + } else { + jars += new File(cp) + } + } + } } object EngineYarnModeSubmitter { diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitterSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitterSuite.scala index f12d3f6fd7f..b9f419ea796 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitterSuite.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/engine/deploy/yarn/EngineYarnModeSubmitterSuite.scala @@ -18,12 +18,14 @@ package org.apache.kyuubi.engine.deploy.yarn import java.io.File +import scala.collection.mutable.ListBuffer + import org.apache.hadoop.fs.Path import org.apache.hadoop.yarn.api.ApplicationConstants.Environment import org.scalatest.matchers.must.Matchers import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper -import org.apache.kyuubi.{KyuubiFunSuite, Utils} +import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiFunSuite, SCALA_COMPILE_VERSION, Utils} import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.engine.deploy.yarn.EngineYarnModeSubmitter.KYUUBI_ENGINE_DEPLOY_YARN_MODE_JARS_KEY import org.apache.kyuubi.util.JavaUtils @@ -66,6 +68,17 @@ class EngineYarnModeSubmitterSuite extends KyuubiFunSuite with Matchers { assert(targetFiles.length == files.length) } + test("hadoop class path") { + val jars = new ListBuffer[File] + val classpath = + s"$kyuubiHome/target/scala-$SCALA_COMPILE_VERSION/jars/*:" + + s"$kyuubiHome/target/kyuubi-common-$SCALA_COMPILE_VERSION-$KYUUBI_VERSION.jar" + MockEngineYarnModeSubmitter.parseClasspath(classpath, jars) + assert(jars.nonEmpty) + assert(jars.exists( + _.getName == s"kyuubi-common-$SCALA_COMPILE_VERSION-$KYUUBI_VERSION.jar")) + } + } object MockEngineYarnModeSubmitter extends EngineYarnModeSubmitter { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala index 0bb13b049f2..bb7f7ecbcf4 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala @@ -208,7 +208,14 @@ private[kyuubi] class EngineRef( extraEngineLog, defaultEngineName) case JDBC => - new JdbcProcessBuilder(appUser, doAsEnabled, conf, engineRefId, extraEngineLog) + conf.setIfMissing(JdbcProcessBuilder.JDBC_ENGINE_NAME, defaultEngineName) + JdbcProcessBuilder( + appUser, + doAsEnabled, + conf, + engineRefId, + extraEngineLog, + defaultEngineName) case CHAT => new ChatProcessBuilder(appUser, doAsEnabled, conf, engineRefId, extraEngineLog) } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala index 1afdcc3cf7c..0e914987c36 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala @@ -188,9 +188,11 @@ object KyuubiApplicationManager { case ("FLINK", Some("YARN")) => // running flink on other platforms is not yet supported setupFlinkYarnTag(applicationTag, conf) - // other engine types are running locally yet case ("HIVE", Some("YARN")) => setupEngineYarnModeTag(applicationTag, conf) + case ("JDBC", Some("YARN")) => + setupEngineYarnModeTag(applicationTag, conf) + // other engine types are running locally yet case _ => } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala index de93fd44b55..e31376f365a 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala @@ -23,12 +23,15 @@ import java.nio.file.Paths import scala.collection.mutable import com.google.common.annotations.VisibleForTesting +import org.apache.hadoop.security.UserGroupInformation -import org.apache.kyuubi.{Logging, SCALA_COMPILE_VERSION, Utils} +import org.apache.kyuubi.{KyuubiException, Logging, SCALA_COMPILE_VERSION, Utils} import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.config.KyuubiConf.{ENGINE_JDBC_CONNECTION_PASSWORD, ENGINE_JDBC_CONNECTION_URL, ENGINE_JDBC_EXTRA_CLASSPATH, ENGINE_JDBC_JAVA_OPTIONS, ENGINE_JDBC_MEMORY} +import org.apache.kyuubi.config.KyuubiConf.{ENGINE_DEPLOY_YARN_MODE_APP_NAME, ENGINE_JDBC_CONNECTION_PASSWORD, ENGINE_JDBC_CONNECTION_URL, ENGINE_JDBC_DEPLOY_MODE, ENGINE_JDBC_EXTRA_CLASSPATH, ENGINE_JDBC_JAVA_OPTIONS, ENGINE_JDBC_MEMORY, ENGINE_KEYTAB, ENGINE_PRINCIPAL} import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY import org.apache.kyuubi.engine.ProcBuilder +import org.apache.kyuubi.engine.deploy.DeployMode +import org.apache.kyuubi.engine.deploy.DeployMode.{LOCAL, YARN} import org.apache.kyuubi.operation.log.OperationLog import org.apache.kyuubi.util.command.CommandLineUtils._ @@ -113,3 +116,55 @@ class JdbcProcessBuilder( } } } + +object JdbcProcessBuilder extends Logging { + + final val JDBC_ENGINE_NAME = "jdbc.engine.name" + + def apply( + proxyUser: String, + doAsEnabled: Boolean, + conf: KyuubiConf, + engineRefId: String, + extraEngineLog: Option[OperationLog], + defaultEngineName: String): JdbcProcessBuilder = { + checkKeytab(proxyUser, conf) + DeployMode.withName(conf.get(ENGINE_JDBC_DEPLOY_MODE)) match { + case LOCAL => + new JdbcProcessBuilder(proxyUser, doAsEnabled, conf, engineRefId, extraEngineLog) + case YARN => + warn(s"JDBC on YARN model is experimental.") + conf.setIfMissing(ENGINE_DEPLOY_YARN_MODE_APP_NAME, Some(defaultEngineName)) + new JdbcYarnModeProcessBuilder(proxyUser, doAsEnabled, conf, engineRefId, extraEngineLog) + case other => throw new KyuubiException(s"Unsupported deploy mode: $other") + } + } + + private def checkKeytab(proxyUser: String, conf: KyuubiConf): Unit = { + val principal = conf.get(ENGINE_PRINCIPAL) + val keytab = conf.get(ENGINE_KEYTAB) + if (!UserGroupInformation.isSecurityEnabled) { + if (principal.isDefined || keytab.isDefined) { + warn("Principal and keytab takes no effect when hadoop security is not enabled.") + } + return + } + + require( + principal.isDefined == keytab.isDefined, + s"Both principal and keytab must be defined, or neither.") + if (principal.isDefined && keytab.isDefined) { + val ugi = UserGroupInformation + .loginUserFromKeytabAndReturnUGI(principal.get, keytab.get) + require( + ugi.getShortUserName == proxyUser, + s"Proxy user: $proxyUser is not same with " + + s"engine principal: ${ugi.getShortUserName}.") + } + + val deployMode = DeployMode.withName(conf.get(ENGINE_JDBC_DEPLOY_MODE)) + if (principal.isEmpty && keytab.isEmpty && deployMode == YARN) { + warn("JDBC on YARN can not work properly without principal and keytab.") + } + } +} diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcYarnModeProcessBuilder.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcYarnModeProcessBuilder.scala new file mode 100644 index 00000000000..fbc4416d73f --- /dev/null +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcYarnModeProcessBuilder.scala @@ -0,0 +1,135 @@ +/* + * 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.engine.jdbc + +import java.io.File +import java.nio.file.Paths +import java.util + +import scala.collection.JavaConverters._ +import scala.collection.mutable.ArrayBuffer + +import org.apache.kyuubi.{Logging, SCALA_COMPILE_VERSION, Utils} +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiConf.{ENGINE_JDBC_EXTRA_CLASSPATH, ENGINE_JDBC_MEMORY} +import org.apache.kyuubi.config.KyuubiReservedKeys.{KYUUBI_ENGINE_ID, KYUUBI_SESSION_USER_KEY} +import org.apache.kyuubi.engine.{ApplicationManagerInfo, KyuubiApplicationManager} +import org.apache.kyuubi.engine.deploy.yarn.EngineYarnModeSubmitter._ +import org.apache.kyuubi.operation.log.OperationLog +import org.apache.kyuubi.util.command.CommandLineUtils.{confKeyValue, confKeyValues} + +/** + * A process builder for JDBC on Yarn. + * + * It will new a process on kyuubi server side to submit jdbc engine to yarn. + */ +class JdbcYarnModeProcessBuilder( + override val proxyUser: String, + override val doAsEnabled: Boolean, + override val conf: KyuubiConf, + override val engineRefId: String, + override val extraEngineLog: Option[OperationLog] = None) + extends JdbcProcessBuilder(proxyUser, doAsEnabled, conf, engineRefId, extraEngineLog) + with Logging { + + override protected def mainClass: String = + "org.apache.kyuubi.engine.jdbc.deploy.JdbcYarnModeSubmitter" + + override def isClusterMode(): Boolean = true + + override def clusterManager(): Option[String] = Some("yarn") + + override def appMgrInfo(): ApplicationManagerInfo = ApplicationManagerInfo(clusterManager()) + + override protected val commands: Iterable[String] = { + KyuubiApplicationManager.tagApplication(engineRefId, shortName, clusterManager(), conf) + val buffer = new ArrayBuffer[String]() + buffer += executable + + val memory = conf.get(ENGINE_JDBC_MEMORY) + buffer += s"-Xmx$memory" + buffer += "-cp" + + val classpathEntries = new util.LinkedHashSet[String] + classpathEntries.addAll(hadoopConfFiles()) + classpathEntries.addAll(yarnConfFiles()) + classpathEntries.addAll(jarFiles(true)) + + buffer += classpathEntries.asScala.mkString(File.pathSeparator) + buffer += mainClass + + buffer ++= confKeyValue(KYUUBI_SESSION_USER_KEY, proxyUser) + buffer ++= confKeyValue(KYUUBI_ENGINE_ID, engineRefId) + + buffer ++= confKeyValue( + KYUUBI_ENGINE_DEPLOY_YARN_MODE_JARS_KEY, + jarFiles(false).asScala.mkString(KYUUBI_ENGINE_DEPLOY_YARN_MODE_ARCHIVE_SEPARATOR)) + buffer ++= confKeyValue( + KYUUBI_ENGINE_DEPLOY_YARN_MODE_HADOOP_CONF_KEY, + hadoopConfFiles().asScala.mkString(KYUUBI_ENGINE_DEPLOY_YARN_MODE_ARCHIVE_SEPARATOR)) + buffer ++= confKeyValue( + KYUUBI_ENGINE_DEPLOY_YARN_MODE_YARN_CONF_KEY, + yarnConfFiles().asScala.mkString(KYUUBI_ENGINE_DEPLOY_YARN_MODE_ARCHIVE_SEPARATOR)) + + buffer ++= confKeyValues(conf.getAll) + + buffer + } + + private def jarFiles(isClasspath: Boolean): util.LinkedHashSet[String] = { + val jarEntries = new util.LinkedHashSet[String] + mainResource.foreach(jarEntries.add) + val javaOptions = conf.get(ENGINE_JDBC_EXTRA_CLASSPATH) + if (isClasspath) { + javaOptions.foreach(jarEntries.add) + } + mainResource.foreach { path => + val parent = Paths.get(path).getParent + if (Utils.isTesting) { + // add dev classpath + val jdbcDeps = parent + .resolve(s"scala-$SCALA_COMPILE_VERSION") + .resolve("jars") + jarEntries.add(s"$jdbcDeps${appendClasspathSuffix(isClasspath)}") + } else { + // add prod classpath + jarEntries.add(s"$parent${appendClasspathSuffix(isClasspath)}") + } + } + jarEntries + } + + private def hadoopConfFiles(): util.LinkedHashSet[String] = { + val confEntries = new util.LinkedHashSet[String] + env.get("HADOOP_CONF_DIR").foreach(confEntries.add) + confEntries + } + + private def yarnConfFiles(): util.LinkedHashSet[String] = { + val confEntries = new util.LinkedHashSet[String] + env.get("YARN_CONF_DIR").foreach(confEntries.add) + confEntries + } + + private def appendClasspathSuffix(isClasspath: Boolean): String = { + if (isClasspath) { + s"${File.separator}*" + } else { + "" + } + } +} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/jdbc/JdbcYarnModeProcessBuilderSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/jdbc/JdbcYarnModeProcessBuilderSuite.scala new file mode 100644 index 00000000000..6c415e88e38 --- /dev/null +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/jdbc/JdbcYarnModeProcessBuilderSuite.scala @@ -0,0 +1,64 @@ +/* + * 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.engine.jdbc + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiConf.{ENGINE_JDBC_CONNECTION_PASSWORD, ENGINE_JDBC_CONNECTION_URL} +import org.apache.kyuubi.engine.deploy.yarn.EngineYarnModeSubmitter.{KYUUBI_ENGINE_DEPLOY_YARN_MODE_HADOOP_CONF_KEY, KYUUBI_ENGINE_DEPLOY_YARN_MODE_YARN_CONF_KEY} +import org.apache.kyuubi.engine.hive.HiveProcessBuilder.HIVE_HADOOP_CLASSPATH_KEY + +class JdbcYarnModeProcessBuilderSuite extends KyuubiFunSuite { + + test("jdbc yarn mode process builder") { + val conf = KyuubiConf().set("kyuubi.on", "off") + .set(ENGINE_JDBC_CONNECTION_URL.key, "") + .set(ENGINE_JDBC_CONNECTION_PASSWORD.key, "123456") + val builder = new JdbcYarnModeProcessBuilder("kyuubi", true, conf, "") + val commands = builder.toString.split('\n') + assert(commands.head.contains("bin/java"), "wrong exec") + assert(builder.toString.contains("--conf kyuubi.session.user=kyuubi")) + assert(commands.exists(ss => ss.contains("kyuubi-jdbc-engine")), "wrong classpath") + assert(builder.toString.contains("--conf kyuubi.on=off")) + } + + test("hadoop conf dir") { + val conf = KyuubiConf().set("kyuubi.on", "off") + .set(ENGINE_JDBC_CONNECTION_URL.key, "") + .set(ENGINE_JDBC_CONNECTION_PASSWORD.key, "123456") + val builder = new JdbcYarnModeProcessBuilder("kyuubi", true, conf, "") { + override def env: Map[String, String] = + super.env + ("HADOOP_CONF_DIR" -> "/etc/hadoop/conf") + + (HIVE_HADOOP_CLASSPATH_KEY -> "/hadoop") + } + assert(builder.toString.contains( + s"--conf $KYUUBI_ENGINE_DEPLOY_YARN_MODE_HADOOP_CONF_KEY=/etc/hadoop/conf")) + } + + test("yarn conf dir") { + val conf = KyuubiConf().set("kyuubi.on", "off") + .set(ENGINE_JDBC_CONNECTION_URL.key, "") + .set(ENGINE_JDBC_CONNECTION_PASSWORD.key, "123456") + val builder = new JdbcYarnModeProcessBuilder("kyuubi", true, conf, "") { + override def env: Map[String, String] = + super.env + ("YARN_CONF_DIR" -> "/etc/hadoop/yarn/conf") + + (HIVE_HADOOP_CLASSPATH_KEY -> "/hadoop") + } + assert(builder.toString.contains( + s"--conf $KYUUBI_ENGINE_DEPLOY_YARN_MODE_YARN_CONF_KEY=/etc/hadoop/yarn/conf")) + } +}