This is an automated email from the ASF dual-hosted git repository.

feiwang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new 2d937f758 [KYUUBI #3679] Admin command line supports delete/list 
engine operation
2d937f758 is described below

commit 2d937f75899cfddc583a2cb6451ef5d8a1a1b22b
Author: Tianlin Liao <[email protected]>
AuthorDate: Mon Oct 24 11:30:26 2022 +0800

    [KYUUBI #3679] Admin command line supports delete/list engine operation
    
    ### _Why are the changes needed?_
    
    Close #3679
    
    ### _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
    
    - [ ] [Run 
test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests)
 locally before make a pull request
    
    Closes #3680 from lightning-L/admin-ctl.
    
    Closes #3679
    
    9c748f073 [Tianlin Liao] [KYUUBI #3679] Admin command line supports 
delete/list engine operation
    
    Authored-by: Tianlin Liao <[email protected]>
    Signed-off-by: Fei Wang <[email protected]>
---
 bin/kyuubi-admin                                   |   2 +-
 bin/kyuubi-ctl                                     |   2 +-
 .../org/apache/kyuubi/ctl/RestClientFactory.scala  |   1 +
 .../kyuubi/ctl/{ => cli}/AdminControlCli.scala     |   3 +-
 .../ctl/{ => cli}/AdminControlCliArguments.scala   |  23 ++++-
 .../apache/kyuubi/ctl/{ => cli}/ControlCli.scala   |   3 +-
 .../kyuubi/ctl/{ => cli}/ControlCliArguments.scala |   6 +-
 .../ctl/{ => cli}/ControlCliArgumentsParser.scala  |   4 +-
 .../apache/kyuubi/ctl/cmd/AdminCtlCommand.scala    |   4 +-
 .../scala/org/apache/kyuubi/ctl/cmd/Command.scala  |   4 +-
 .../kyuubi/ctl/cmd/create/CreateBatchCommand.scala |   3 +-
 .../ctl/cmd/create/CreateServerCommand.scala       |   2 +-
 .../AdminDeleteEngineCommand.scala}                |  24 ++---
 .../kyuubi/ctl/cmd/delete/DeleteBatchCommand.scala |   5 +-
 .../kyuubi/ctl/cmd/delete/DeleteCommand.scala      |   2 +-
 .../ctl/cmd/delete/DeleteEngineCommand.scala       |   2 +-
 .../ctl/cmd/delete/DeleteServerCommand.scala       |   2 +-
 .../kyuubi/ctl/cmd/get/GetBatchCommand.scala       |   2 +-
 .../org/apache/kyuubi/ctl/cmd/get/GetCommand.scala |   2 +-
 .../kyuubi/ctl/cmd/get/GetEngineCommand.scala      |   2 +-
 .../kyuubi/ctl/cmd/get/GetServerCommand.scala      |   2 +-
 .../AdminListEngineCommand.scala}                  |  32 ++++---
 .../kyuubi/ctl/cmd/list/ListBatchCommand.scala     |   2 +-
 .../apache/kyuubi/ctl/cmd/list/ListCommand.scala   |   2 +-
 .../kyuubi/ctl/cmd/list/ListEngineCommand.scala    |   2 +-
 .../kyuubi/ctl/cmd/list/ListServerCommand.scala    |   2 +-
 .../kyuubi/ctl/cmd/log/LogBatchCommand.scala       |   2 +-
 .../ctl/cmd/refresh/RefreshConfigCommand.scala     |   2 +-
 .../kyuubi/ctl/cmd/submit/SubmitBatchCommand.scala |   3 +-
 .../apache/kyuubi/ctl/opt/AdminCommandLine.scala   | 105 +++++++++++++++++++++
 .../apache/kyuubi/ctl/{ => opt}/CliConfig.scala    |  10 +-
 .../apache/kyuubi/ctl/{ => opt}/CommandLine.scala  |  83 ++--------------
 .../apache/kyuubi/ctl/opt/CommonCommandLine.scala  |  59 ++++++++++++
 .../org/apache/kyuubi/ctl/util/CtlUtils.scala      |   2 +-
 .../scala/org/apache/kyuubi/ctl/util/Render.scala  |  11 ++-
 .../org/apache/kyuubi/ctl/util/Validator.scala     |   2 +-
 .../kyuubi/ctl/AdminControlCliArgumentsSuite.scala |  50 +++++++++-
 .../apache/kyuubi/ctl/BatchCliArgumentsSuite.scala |  13 +++
 .../kyuubi/ctl/ControlCliArgumentsSuite.scala      |   6 +-
 .../org/apache/kyuubi/ctl/ControlCliSuite.scala    |   1 +
 .../org/apache/kyuubi/ctl/TestPrematureExit.scala  |   1 +
 .../org/apache/kyuubi/client/AdminRestApi.java     |  29 ++++++
 .../apache/kyuubi/client/api/v1/dto/Engine.java    |  13 ++-
 .../kyuubi/server/api/v1/AdminResource.scala       |   4 +-
 .../kyuubi/server/rest/client/AdminCtlSuite.scala  |  64 ++++++++++++-
 .../server/rest/client/AdminRestApiSuite.scala     |  54 ++++++++++-
 46 files changed, 502 insertions(+), 152 deletions(-)

diff --git a/bin/kyuubi-admin b/bin/kyuubi-admin
index 74b5f39a1..a1f176ec1 100755
--- a/bin/kyuubi-admin
+++ b/bin/kyuubi-admin
@@ -17,7 +17,7 @@
 #
 
 ## Kyuubi Admin Control Client Entrance
-CLASS="org.apache.kyuubi.ctl.AdminControlCli"
+CLASS="org.apache.kyuubi.ctl.cli.AdminControlCli"
 
 export KYUUBI_HOME="$(cd "$(dirname "$0")"/..; pwd)"
 
diff --git a/bin/kyuubi-ctl b/bin/kyuubi-ctl
index d989eeb2d..16809c075 100755
--- a/bin/kyuubi-ctl
+++ b/bin/kyuubi-ctl
@@ -17,7 +17,7 @@
 #
 
 ## Kyuubi Control Client Entrance
-CLASS="org.apache.kyuubi.ctl.ControlCli"
+CLASS="org.apache.kyuubi.ctl.cli.ControlCli"
 
 export KYUUBI_HOME="$(cd "$(dirname "$0")"/..; pwd)"
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/RestClientFactory.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/RestClientFactory.scala
index 5821598fa..bbaa5f668 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/RestClientFactory.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/RestClientFactory.scala
@@ -24,6 +24,7 @@ import org.apache.kyuubi.KyuubiException
 import org.apache.kyuubi.client.KyuubiRestClient
 import org.apache.kyuubi.config.KyuubiConf
 import org.apache.kyuubi.ctl.CtlConf._
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 object RestClientFactory {
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/AdminControlCli.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/AdminControlCli.scala
similarity index 95%
rename from 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/AdminControlCli.scala
rename to 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/AdminControlCli.scala
index d857febb1..728f2b7fe 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/AdminControlCli.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/AdminControlCli.scala
@@ -15,9 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.kyuubi.ctl
+package org.apache.kyuubi.ctl.cli
 
 import org.apache.kyuubi.Logging
+import org.apache.kyuubi.ctl.{ControlCliException, KyuubiOEffectSetup}
 import org.apache.kyuubi.ctl.util.CommandLineUtils
 
 class AdminControlCli extends ControlCli {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/AdminControlCliArguments.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/AdminControlCliArguments.scala
similarity index 62%
rename from 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/AdminControlCliArguments.scala
rename to 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/AdminControlCliArguments.scala
index 9bc33808e..4bc1e1317 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/AdminControlCliArguments.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/AdminControlCliArguments.scala
@@ -15,23 +15,34 @@
  * limitations under the License.
  */
 
-package org.apache.kyuubi.ctl
+package org.apache.kyuubi.ctl.cli
 
 import scopt.OParser
 
 import org.apache.kyuubi.KyuubiException
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.cmd.delete.AdminDeleteEngineCommand
+import org.apache.kyuubi.ctl.cmd.list.AdminListEngineCommand
 import org.apache.kyuubi.ctl.cmd.refresh.RefreshConfigCommand
+import org.apache.kyuubi.ctl.opt.{AdminCommandLine, CliConfig, ControlAction, 
ControlObject}
 
 class AdminControlCliArguments(args: Seq[String], env: Map[String, String] = 
sys.env)
   extends ControlCliArguments(args, env) {
   override def parser(): OParser[Unit, CliConfig] = {
     val builder = OParser.builder[CliConfig]
-    CommandLine.getAdminCtlOptionParser(builder)
+    AdminCommandLine.getAdminCtlOptionParser(builder)
   }
 
   override protected def getCommand(cliConfig: CliConfig): Command[_] = {
     cliConfig.action match {
+      case ControlAction.LIST => cliConfig.resource match {
+          case ControlObject.ENGINE => new AdminListEngineCommand(cliConfig)
+          case _ => throw new KyuubiException(s"Invalid resource: 
${cliConfig.resource}")
+        }
+      case ControlAction.DELETE => cliConfig.resource match {
+          case ControlObject.ENGINE => new AdminDeleteEngineCommand(cliConfig)
+          case _ => throw new KyuubiException(s"Invalid resource: 
${cliConfig.resource}")
+        }
       case ControlAction.REFRESH => cliConfig.resource match {
           case ControlObject.CONFIG => new RefreshConfigCommand(cliConfig)
           case _ => throw new KyuubiException(s"Invalid resource: 
${cliConfig.resource}")
@@ -42,6 +53,14 @@ class AdminControlCliArguments(args: Seq[String], env: 
Map[String, String] = sys
 
   override def toString: String = {
     cliConfig.resource match {
+      case ControlObject.ENGINE =>
+        s"""Parsed arguments:
+           |  action                  ${cliConfig.action}
+           |  resource                ${cliConfig.resource}
+           |  type                    ${cliConfig.engineOpts.engineType}
+           |  sharelevel              ${cliConfig.engineOpts.engineShareLevel}
+           |  sharesubdomain          ${cliConfig.engineOpts.engineSubdomain}
+        """.stripMargin
       case ControlObject.CONFIG =>
         s"""Parsed arguments:
            |  action                  ${cliConfig.action}
diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCli.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCli.scala
similarity index 96%
rename from kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCli.scala
rename to kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCli.scala
index 9bc537b13..8214263c6 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCli.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCli.scala
@@ -15,9 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.kyuubi.ctl
+package org.apache.kyuubi.ctl.cli
 
 import org.apache.kyuubi.Logging
+import org.apache.kyuubi.ctl.{ControlCliException, KyuubiOEffectSetup}
 import org.apache.kyuubi.ctl.util.CommandLineUtils
 
 /**
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCliArguments.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCliArguments.scala
similarity index 96%
rename from 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCliArguments.scala
rename to 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCliArguments.scala
index 0d64ef3eb..5c55689f1 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCliArguments.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCliArguments.scala
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.kyuubi.ctl
+package org.apache.kyuubi.ctl.cli
 
 import scopt.OParser
 
 import org.apache.kyuubi.{KyuubiException, Logging}
+import org.apache.kyuubi.ctl.{opt, KyuubiOEffectSetup}
 import org.apache.kyuubi.ctl.cmd._
 import org.apache.kyuubi.ctl.cmd.create.{CreateBatchCommand, 
CreateServerCommand}
 import org.apache.kyuubi.ctl.cmd.delete.{DeleteBatchCommand, 
DeleteEngineCommand, DeleteServerCommand}
@@ -27,6 +28,7 @@ import org.apache.kyuubi.ctl.cmd.get.{GetBatchCommand, 
GetEngineCommand, GetServ
 import org.apache.kyuubi.ctl.cmd.list.{ListBatchCommand, ListEngineCommand, 
ListServerCommand}
 import org.apache.kyuubi.ctl.cmd.log.LogBatchCommand
 import org.apache.kyuubi.ctl.cmd.submit.SubmitBatchCommand
+import org.apache.kyuubi.ctl.opt.{CliConfig, CommandLine, ControlAction, 
ControlObject}
 
 class ControlCliArguments(args: Seq[String], env: Map[String, String] = 
sys.env)
   extends ControlCliArgumentsParser with Logging {
@@ -48,7 +50,7 @@ class ControlCliArguments(args: Seq[String], env: Map[String, 
String] = sys.env)
   private[kyuubi] lazy val effectSetup = new KyuubiOEffectSetup
 
   override def parse(args: Seq[String]): Unit = {
-    OParser.runParser(cliParser, args, CliConfig()) match {
+    OParser.runParser(cliParser, args, opt.CliConfig()) match {
       case (result, effects) =>
         OParser.runEffects(effects, effectSetup)
         result match {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCliArgumentsParser.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCliArgumentsParser.scala
similarity index 93%
rename from 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCliArgumentsParser.scala
rename to 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCliArgumentsParser.scala
index 4a6707f39..a49e17aea 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/ControlCliArgumentsParser.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cli/ControlCliArgumentsParser.scala
@@ -15,10 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.kyuubi.ctl
+package org.apache.kyuubi.ctl.cli
 
 import scopt.OParser
 
+import org.apache.kyuubi.ctl.opt.CliConfig
+
 abstract private[kyuubi] class ControlCliArgumentsParser {
 
   /**
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/AdminCtlCommand.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/AdminCtlCommand.scala
index 7e5eb5cd3..f527b7de1 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/AdminCtlCommand.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/AdminCtlCommand.scala
@@ -17,8 +17,8 @@
 
 package org.apache.kyuubi.ctl.cmd
 
-import org.apache.kyuubi.ctl.AdminControlCli
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.cli.AdminControlCli
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 abstract class AdminCtlCommand[T](cliConfig: CliConfig) extends 
Command[T](cliConfig) {
   override def info(msg: => Any): Unit = AdminControlCli.printMessage(msg)
diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/Command.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/Command.scala
index 6dd88e5e7..1cf44e8cd 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/Command.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/Command.scala
@@ -18,8 +18,8 @@ package org.apache.kyuubi.ctl.cmd
 
 import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiException, Logging}
 import org.apache.kyuubi.config.KyuubiConf
-import org.apache.kyuubi.ctl.CliConfig
-import org.apache.kyuubi.ctl.ControlCli
+import org.apache.kyuubi.ctl.cli.ControlCli
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ha.HighAvailabilityConf._
 
 abstract class Command[T](cliConfig: CliConfig) extends Logging {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateBatchCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateBatchCommand.scala
index 3c5c6c55c..18618e37e 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateBatchCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateBatchCommand.scala
@@ -22,9 +22,10 @@ import scala.collection.JavaConverters._
 
 import org.apache.kyuubi.client.BatchRestApi
 import org.apache.kyuubi.client.api.v1.dto.{Batch, BatchRequest}
-import org.apache.kyuubi.ctl.{CliConfig, ControlCliException}
+import org.apache.kyuubi.ctl.ControlCliException
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.{CtlUtils, Render, Validator}
 
 class CreateBatchCommand(cliConfig: CliConfig) extends 
Command[Batch](cliConfig) {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateServerCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateServerCommand.scala
index 3a942e0ab..66f75fc5f 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateServerCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/create/CreateServerCommand.scala
@@ -18,8 +18,8 @@ package org.apache.kyuubi.ctl.cmd.create
 
 import scala.collection.mutable.ListBuffer
 
-import org.apache.kyuubi.ctl.{CliConfig, ControlObject}
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.{CliConfig, ControlObject}
 import org.apache.kyuubi.ctl.util.{CtlUtils, Render, Validator}
 import org.apache.kyuubi.ha.HighAvailabilityConf._
 import org.apache.kyuubi.ha.client.{DiscoveryClient, DiscoveryPaths, 
ServiceNodeInfo}
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/AdminDeleteEngineCommand.scala
similarity index 68%
copy from 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
copy to 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/AdminDeleteEngineCommand.scala
index 18830f7b8..989b38edc 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/AdminDeleteEngineCommand.scala
@@ -15,31 +15,31 @@
  * limitations under the License.
  */
 
-package org.apache.kyuubi.ctl.cmd.refresh
+package org.apache.kyuubi.ctl.cmd.delete
 
-import org.apache.kyuubi.KyuubiException
 import org.apache.kyuubi.client.AdminRestApi
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
 import org.apache.kyuubi.ctl.cmd.AdminCtlCommand
-import org.apache.kyuubi.ctl.util.{Tabulator, Validator}
+import org.apache.kyuubi.ctl.opt.CliConfig
+import org.apache.kyuubi.ctl.util.Tabulator
 
-class RefreshConfigCommand(cliConfig: CliConfig) extends 
AdminCtlCommand[String](cliConfig) {
-  def validate(): Unit = {
-    Validator.validateAdminConfigType(cliConfig)
-  }
+class AdminDeleteEngineCommand(cliConfig: CliConfig) extends 
AdminCtlCommand[String](cliConfig) {
+
+  override def validate(): Unit = {}
 
   def doRun(): String = {
     withKyuubiRestClient(normalizedCliConfig, null, conf) { kyuubiRestClient =>
       val adminRestApi = new AdminRestApi(kyuubiRestClient)
-      normalizedCliConfig.adminConfigOpts.configType match {
-        case "hadoopConf" => adminRestApi.refreshHadoopConf()
-        case configType => throw new KyuubiException(s"Invalid config 
type:$configType")
-      }
+      adminRestApi.deleteEngine(
+        normalizedCliConfig.engineOpts.engineType,
+        normalizedCliConfig.engineOpts.engineShareLevel,
+        normalizedCliConfig.engineOpts.engineSubdomain,
+        normalizedCliConfig.commonOpts.hs2ProxyUser)
     }
   }
 
   def render(resp: String): Unit = {
     info(Tabulator.format("", Array("Response"), Array(Array(resp))))
   }
+
 }
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteBatchCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteBatchCommand.scala
index 548faa008..3988620ad 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteBatchCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteBatchCommand.scala
@@ -19,9 +19,10 @@ package org.apache.kyuubi.ctl.cmd.delete
 import org.apache.kyuubi.client.BatchRestApi
 import org.apache.kyuubi.client.api.v1.dto.Batch
 import org.apache.kyuubi.client.util.{BatchUtils, JsonUtils}
-import org.apache.kyuubi.ctl.{CliConfig, ControlCliException}
+import org.apache.kyuubi.ctl.ControlCliException
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 class DeleteBatchCommand(cliConfig: CliConfig) extends 
Command[Batch](cliConfig) {
   def validate(): Unit = {
@@ -35,7 +36,7 @@ class DeleteBatchCommand(cliConfig: CliConfig) extends 
Command[Batch](cliConfig)
       val batchRestApi: BatchRestApi = new BatchRestApi(kyuubiRestClient)
       val batchId = normalizedCliConfig.batchOpts.batchId
 
-      val result = batchRestApi.deleteBatch(batchId, 
normalizedCliConfig.batchOpts.hs2ProxyUser)
+      val result = batchRestApi.deleteBatch(batchId, 
normalizedCliConfig.commonOpts.hs2ProxyUser)
 
       info(JsonUtils.toJson(result))
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteCommand.scala
index d42e23507..69479259a 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteCommand.scala
@@ -18,8 +18,8 @@ package org.apache.kyuubi.ctl.cmd.delete
 
 import scala.collection.mutable.ListBuffer
 
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.{CtlUtils, Render, Validator}
 import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient
 import org.apache.kyuubi.ha.client.ServiceNodeInfo
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteEngineCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteEngineCommand.scala
index d0b0067a1..7be607467 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteEngineCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteEngineCommand.scala
@@ -16,7 +16,7 @@
  */
 package org.apache.kyuubi.ctl.cmd.delete
 
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 class DeleteEngineCommand(cliConfig: CliConfig) extends 
DeleteCommand(cliConfig) {
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteServerCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteServerCommand.scala
index fea1014a2..6debba4d5 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteServerCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/delete/DeleteServerCommand.scala
@@ -16,6 +16,6 @@
  */
 package org.apache.kyuubi.ctl.cmd.delete
 
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 class DeleteServerCommand(cliConfig: CliConfig) extends 
DeleteCommand(cliConfig) {}
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetBatchCommand.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetBatchCommand.scala
index 4822cb3f0..48921b99f 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetBatchCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetBatchCommand.scala
@@ -18,9 +18,9 @@ package org.apache.kyuubi.ctl.cmd.get
 
 import org.apache.kyuubi.client.BatchRestApi
 import org.apache.kyuubi.client.api.v1.dto.Batch
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.Render
 
 class GetBatchCommand(cliConfig: CliConfig) extends Command[Batch](cliConfig) {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetCommand.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetCommand.scala
index 8f668b332..d78f0b995 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetCommand.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetCommand.scala
@@ -16,8 +16,8 @@
  */
 package org.apache.kyuubi.ctl.cmd.get
 
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.{CtlUtils, Render, Validator}
 import org.apache.kyuubi.ha.client.ServiceNodeInfo
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetEngineCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetEngineCommand.scala
index 17557ceb6..4d9101625 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetEngineCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetEngineCommand.scala
@@ -16,7 +16,7 @@
  */
 package org.apache.kyuubi.ctl.cmd.get
 
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 class GetEngineCommand(cliConfig: CliConfig) extends GetCommand(cliConfig) {
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetServerCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetServerCommand.scala
index fd0f52bd9..71b868453 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetServerCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/get/GetServerCommand.scala
@@ -16,6 +16,6 @@
  */
 package org.apache.kyuubi.ctl.cmd.get
 
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 class GetServerCommand(cliConfig: CliConfig) extends GetCommand(cliConfig) {}
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/AdminListEngineCommand.scala
similarity index 60%
copy from 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
copy to 
kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/AdminListEngineCommand.scala
index 18830f7b8..bc0b16e67 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/AdminListEngineCommand.scala
@@ -15,31 +15,33 @@
  * limitations under the License.
  */
 
-package org.apache.kyuubi.ctl.cmd.refresh
+package org.apache.kyuubi.ctl.cmd.list
+
+import scala.collection.JavaConverters._
 
-import org.apache.kyuubi.KyuubiException
 import org.apache.kyuubi.client.AdminRestApi
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.client.api.v1.dto.Engine
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
 import org.apache.kyuubi.ctl.cmd.AdminCtlCommand
-import org.apache.kyuubi.ctl.util.{Tabulator, Validator}
+import org.apache.kyuubi.ctl.opt.CliConfig
+import org.apache.kyuubi.ctl.util.Render
 
-class RefreshConfigCommand(cliConfig: CliConfig) extends 
AdminCtlCommand[String](cliConfig) {
-  def validate(): Unit = {
-    Validator.validateAdminConfigType(cliConfig)
-  }
+class AdminListEngineCommand(cliConfig: CliConfig) extends 
AdminCtlCommand[Seq[Engine]](cliConfig) {
+
+  override def validate(): Unit = {}
 
-  def doRun(): String = {
+  def doRun(): Seq[Engine] = {
     withKyuubiRestClient(normalizedCliConfig, null, conf) { kyuubiRestClient =>
       val adminRestApi = new AdminRestApi(kyuubiRestClient)
-      normalizedCliConfig.adminConfigOpts.configType match {
-        case "hadoopConf" => adminRestApi.refreshHadoopConf()
-        case configType => throw new KyuubiException(s"Invalid config 
type:$configType")
-      }
+      adminRestApi.listEngines(
+        normalizedCliConfig.engineOpts.engineType,
+        normalizedCliConfig.engineOpts.engineShareLevel,
+        normalizedCliConfig.engineOpts.engineSubdomain,
+        normalizedCliConfig.commonOpts.hs2ProxyUser).asScala
     }
   }
 
-  def render(resp: String): Unit = {
-    info(Tabulator.format("", Array("Response"), Array(Array(resp))))
+  def render(resp: Seq[Engine]): Unit = {
+    info(Render.renderEngineNodesInfo(resp))
   }
 }
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListBatchCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListBatchCommand.scala
index f6877cf92..4ce1b49b2 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListBatchCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListBatchCommand.scala
@@ -18,9 +18,9 @@ package org.apache.kyuubi.ctl.cmd.list
 
 import org.apache.kyuubi.client.BatchRestApi
 import org.apache.kyuubi.client.api.v1.dto.GetBatchesResponse
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.Render
 
 class ListBatchCommand(cliConfig: CliConfig) extends 
Command[GetBatchesResponse](cliConfig) {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListCommand.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListCommand.scala
index 4ad62c92b..0cfeb8e4e 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListCommand.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListCommand.scala
@@ -16,8 +16,8 @@
  */
 package org.apache.kyuubi.ctl.cmd.list
 
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.{CtlUtils, Render, Validator}
 import org.apache.kyuubi.ha.client.ServiceNodeInfo
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListEngineCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListEngineCommand.scala
index 1cad9ac23..6a78a9e97 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListEngineCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListEngineCommand.scala
@@ -16,7 +16,7 @@
  */
 package org.apache.kyuubi.ctl.cmd.list
 
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 class ListEngineCommand(cliConfig: CliConfig) extends ListCommand(cliConfig) {
 
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListServerCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListServerCommand.scala
index cea56e70c..8c3219ece 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListServerCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/list/ListServerCommand.scala
@@ -16,6 +16,6 @@
  */
 package org.apache.kyuubi.ctl.cmd.list
 
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 class ListServerCommand(cliConfig: CliConfig) extends ListCommand(cliConfig) {}
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/log/LogBatchCommand.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/log/LogBatchCommand.scala
index 42d492896..24197d8d6 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/log/LogBatchCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/log/LogBatchCommand.scala
@@ -23,10 +23,10 @@ import scala.collection.JavaConverters._
 import org.apache.kyuubi.client.BatchRestApi
 import org.apache.kyuubi.client.api.v1.dto.{Batch, OperationLog}
 import org.apache.kyuubi.client.util.BatchUtils
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.CtlConf._
 import org.apache.kyuubi.ctl.RestClientFactory.{withKyuubiInstanceRestClient, 
withKyuubiRestClient}
 import org.apache.kyuubi.ctl.cmd.Command
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.Render
 
 class LogBatchCommand(
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
index 18830f7b8..80d673327 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala
@@ -19,9 +19,9 @@ package org.apache.kyuubi.ctl.cmd.refresh
 
 import org.apache.kyuubi.KyuubiException
 import org.apache.kyuubi.client.AdminRestApi
-import org.apache.kyuubi.ctl.CliConfig
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
 import org.apache.kyuubi.ctl.cmd.AdminCtlCommand
+import org.apache.kyuubi.ctl.opt.CliConfig
 import org.apache.kyuubi.ctl.util.{Tabulator, Validator}
 
 class RefreshConfigCommand(cliConfig: CliConfig) extends 
AdminCtlCommand[String](cliConfig) {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/submit/SubmitBatchCommand.scala
 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/submit/SubmitBatchCommand.scala
index 8a846ce17..863da98d4 100644
--- 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/submit/SubmitBatchCommand.scala
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/submit/SubmitBatchCommand.scala
@@ -18,10 +18,11 @@ package org.apache.kyuubi.ctl.cmd.submit
 
 import org.apache.kyuubi.client.api.v1.dto.Batch
 import org.apache.kyuubi.client.util.{BatchUtils, JsonUtils}
-import org.apache.kyuubi.ctl.{BatchOpts, CliConfig, ControlCliException, 
LogOpts}
+import org.apache.kyuubi.ctl.ControlCliException
 import org.apache.kyuubi.ctl.cmd.Command
 import org.apache.kyuubi.ctl.cmd.create.CreateBatchCommand
 import org.apache.kyuubi.ctl.cmd.log.LogBatchCommand
+import org.apache.kyuubi.ctl.opt.{BatchOpts, CliConfig, LogOpts}
 import org.apache.kyuubi.ctl.util.{CtlUtils, Render, Validator}
 
 class SubmitBatchCommand(cliConfig: CliConfig) extends 
Command[Batch](cliConfig) {
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala
new file mode 100644
index 000000000..524f2954e
--- /dev/null
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala
@@ -0,0 +1,105 @@
+/*
+ * 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.ctl.opt
+
+import scopt.{OParser, OParserBuilder}
+
+import org.apache.kyuubi.KYUUBI_VERSION
+
+object AdminCommandLine extends CommonCommandLine {
+
+  def getAdminCtlOptionParser(builder: OParserBuilder[CliConfig]): 
OParser[Unit, CliConfig] = {
+    import builder._
+    OParser.sequence(
+      programName("kyuubi-admin"),
+      head("kyuubi", KYUUBI_VERSION),
+      common(builder),
+      list(builder),
+      delete(builder),
+      refresh(builder),
+      checkConfig(f => {
+        if (f.action == null) {
+          failure("Must specify action command: [list|delete|refresh].")
+        } else {
+          success
+        }
+      }),
+      note(""),
+      help('h', "help").text("Show help message and exit."))
+  }
+
+  private def delete(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
+    import builder._
+    OParser.sequence(
+      note(""),
+      cmd("delete")
+        .text("\tDelete resources.")
+        .action((_, c) => c.copy(action = ControlAction.DELETE))
+        .children(
+          engineCmd(builder).text("\tDelete the specified engine node for 
user.")))
+
+  }
+
+  private def list(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] 
= {
+    import builder._
+    OParser.sequence(
+      note(""),
+      cmd("list")
+        .text("\tList information about resources.")
+        .action((_, c) => c.copy(action = ControlAction.LIST))
+        .children(
+          engineCmd(builder).text("\tList all the engine nodes for a user")))
+
+  }
+
+  private def refresh(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
+    import builder._
+    OParser.sequence(
+      note(""),
+      cmd("refresh")
+        .text("\tRefresh the resource.")
+        .action((_, c) => c.copy(action = ControlAction.REFRESH))
+        .children(
+          refreshConfigCmd(builder).text("\tRefresh the config with specified 
type.")))
+  }
+
+  private def engineCmd(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
+    import builder._
+    cmd("engine").action((_, c) => c.copy(resource = ControlObject.ENGINE))
+      .children(
+        opt[String]("engine-type").abbr("et")
+          .action((v, c) => c.copy(engineOpts = c.engineOpts.copy(engineType = 
v)))
+          .text("The engine type this engine belong to."),
+        opt[String]("engine-subdomain").abbr("es")
+          .action((v, c) => c.copy(engineOpts = 
c.engineOpts.copy(engineSubdomain = v)))
+          .text("The engine subdomain this engine belong to."),
+        opt[String]("engine-share-level").abbr("esl")
+          .action((v, c) => c.copy(engineOpts = 
c.engineOpts.copy(engineShareLevel = v)))
+          .text("The engine share level this engine belong to."))
+  }
+
+  private def refreshConfigCmd(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
+    import builder._
+    cmd("config").action((_, c) => c.copy(resource = ControlObject.CONFIG))
+      .children(
+        arg[String]("<configType>")
+          .optional()
+          .action((v, c) => c.copy(adminConfigOpts = 
c.adminConfigOpts.copy(configType = v)))
+          .text("The valid config type can be one of the following: 
hadoopConf."))
+  }
+}
diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CliConfig.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CliConfig.scala
similarity index 91%
rename from kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CliConfig.scala
rename to kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CliConfig.scala
index f2f4aab51..12bd51d6d 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CliConfig.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CliConfig.scala
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.kyuubi.ctl
+package org.apache.kyuubi.ctl.opt
 
-import org.apache.kyuubi.ctl.ControlAction.ControlAction
-import org.apache.kyuubi.ctl.ControlObject.ControlObject
+import org.apache.kyuubi.ctl.opt.ControlAction.ControlAction
+import org.apache.kyuubi.ctl.opt.ControlObject.ControlObject
 
 private[ctl] object ControlAction extends Enumeration {
   type ControlAction = Value
@@ -47,7 +47,8 @@ case class CommonOpts(
     authSchema: String = null,
     username: String = null,
     password: String = null,
-    spnegoHost: String = null)
+    spnegoHost: String = null,
+    hs2ProxyUser: String = null)
 
 case class ZookeeperOpts(
     zkQuorum: String = null,
@@ -69,7 +70,6 @@ case class BatchOpts(
     endTime: Long = 0,
     from: Int = -1,
     size: Int = 100,
-    hs2ProxyUser: String = null,
     waitCompletion: Boolean = true)
 
 case class EngineOpts(
diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CommandLine.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CommandLine.scala
similarity index 78%
rename from kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CommandLine.scala
rename to kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CommandLine.scala
index 56c381848..aeab8bcdf 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CommandLine.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CommandLine.scala
@@ -14,14 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.kyuubi.ctl
+
+package org.apache.kyuubi.ctl.opt
 
 import scopt.{OParser, OParserBuilder}
 
-import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiException}
-import org.apache.kyuubi.ctl.util.DateTimeUtils._
+import org.apache.kyuubi.KYUUBI_VERSION
+import org.apache.kyuubi.ctl.util.DateTimeUtils.dateStringToMillis
 
-object CommandLine {
+object CommandLine extends CommonCommandLine {
 
   def getCtlOptionParser(builder: OParserBuilder[CliConfig]): OParser[Unit, 
CliConfig] = {
     import builder._
@@ -47,55 +48,6 @@ object CommandLine {
       help('h', "help").text("Show help message and exit."))
   }
 
-  def getAdminCtlOptionParser(builder: OParserBuilder[CliConfig]): 
OParser[Unit, CliConfig] = {
-    import builder._
-    OParser.sequence(
-      programName("kyuubi-admin"),
-      head("kyuubi", KYUUBI_VERSION),
-      common(builder),
-      refresh(builder),
-      checkConfig(f => {
-        if (f.action == null) {
-          failure("Must specify action command: [refresh].")
-        } else {
-          success
-        }
-      }),
-      note(""),
-      help('h', "help").text("Show help message and exit."))
-  }
-
-  private def common(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
-    import builder._
-    OParser.sequence(
-      opt[Unit]('b', "verbose")
-        .action((_, c) => c.copy(commonOpts = c.commonOpts.copy(verbose = 
true)))
-        .text("Print additional debug output."),
-      opt[String]("hostUrl")
-        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(hostUrl = v)))
-        .text("Host url for rest api."),
-      opt[String]("authSchema")
-        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(authSchema = 
v)))
-        .text("Auth schema for rest api, valid values are basic, spnego."),
-      opt[String]("username")
-        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(username = v)))
-        .text("Username for basic authentication."),
-      opt[String]("password")
-        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(password = v)))
-        .text("Password for basic authentication."),
-      opt[String]("spnegoHost")
-        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(spnegoHost = 
v)))
-        .text("Spnego host for spnego authentication."),
-      opt[String]("conf")
-        .action((v, c) => {
-          v.split("=", 2).toSeq match {
-            case Seq(k, v) => c.copy(conf = c.conf ++ Map(k -> v))
-            case _ => throw new KyuubiException(s"Kyuubi config without '=': 
$v")
-          }
-        })
-        .text("Kyuubi config property pair, formatted key=value."))
-  }
-
   private def zooKeeper(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
     import builder._
     OParser.sequence(
@@ -203,17 +155,6 @@ object CommandLine {
           submitBatchCmd(builder).text("\topen batch session and wait for 
completion.")))
   }
 
-  private def refresh(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
-    import builder._
-    OParser.sequence(
-      note(""),
-      cmd("refresh")
-        .text("\tRefresh the resource.")
-        .action((_, c) => c.copy(action = ControlAction.REFRESH))
-        .children(
-          refreshConfigCmd(builder).text("\tRefresh the config with specified 
type.")))
-  }
-
   private def serverCmd(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
     import builder._
     cmd("server").action((_, c) => c.copy(resource = ControlObject.SERVER))
@@ -259,10 +200,7 @@ object CommandLine {
         arg[String]("<batchId>")
           .optional()
           .action((v, c) => c.copy(batchOpts = c.batchOpts.copy(batchId = v)))
-          .text("Batch id."),
-        opt[String]("hs2ProxyUser")
-          .action((v, c) => c.copy(createOpts = c.createOpts.copy(filename = 
v)))
-          .text("The value of hive.server2.proxy.user config."))
+          .text("Batch id."))
   }
 
   private def listBatchCmd(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
@@ -353,13 +291,4 @@ object CommandLine {
             "when the batch is no longer in PENDING state."))
   }
 
-  private def refreshConfigCmd(builder: OParserBuilder[CliConfig]): OParser[_, 
CliConfig] = {
-    import builder._
-    cmd("config").action((_, c) => c.copy(resource = ControlObject.CONFIG))
-      .children(
-        arg[String]("<configType>")
-          .optional()
-          .action((v, c) => c.copy(adminConfigOpts = 
c.adminConfigOpts.copy(configType = v)))
-          .text("The valid config type can be one of the following: 
hadoopConf."))
-  }
 }
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CommonCommandLine.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CommonCommandLine.scala
new file mode 100644
index 000000000..da855bcd0
--- /dev/null
+++ 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/CommonCommandLine.scala
@@ -0,0 +1,59 @@
+/*
+ * 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.ctl.opt
+
+import scopt.{OParser, OParserBuilder}
+
+import org.apache.kyuubi.KyuubiException
+
+trait CommonCommandLine {
+
+  def common(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
+    import builder._
+    OParser.sequence(
+      opt[Unit]('b', "verbose")
+        .action((_, c) => c.copy(commonOpts = c.commonOpts.copy(verbose = 
true)))
+        .text("Print additional debug output."),
+      opt[String]("hostUrl")
+        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(hostUrl = v)))
+        .text("Host url for rest api."),
+      opt[String]("authSchema")
+        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(authSchema = 
v)))
+        .text("Auth schema for rest api, valid values are basic, spnego."),
+      opt[String]("username")
+        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(username = v)))
+        .text("Username for basic authentication."),
+      opt[String]("password")
+        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(password = v)))
+        .text("Password for basic authentication."),
+      opt[String]("spnegoHost")
+        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(spnegoHost = 
v)))
+        .text("Spnego host for spnego authentication."),
+      opt[String]("hs2ProxyUser")
+        .action((v, c) => c.copy(commonOpts = c.commonOpts.copy(hs2ProxyUser = 
v)))
+        .text("The value of hive.server2.proxy.user config."),
+      opt[String]("conf")
+        .action((v, c) => {
+          v.split("=", 2).toSeq match {
+            case Seq(k, v) => c.copy(conf = c.conf ++ Map(k -> v))
+            case _ => throw new KyuubiException(s"Kyuubi config without '=': 
$v")
+          }
+        })
+        .text("Kyuubi config property pair, formatted key=value."))
+  }
+}
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/CtlUtils.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/CtlUtils.scala
index 305447993..194f691e5 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/CtlUtils.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/CtlUtils.scala
@@ -25,7 +25,7 @@ import org.yaml.snakeyaml.Yaml
 import org.apache.kyuubi.KyuubiException
 import org.apache.kyuubi.config.KyuubiConf
 import org.apache.kyuubi.config.KyuubiConf.{ENGINE_SHARE_LEVEL, 
ENGINE_SHARE_LEVEL_SUBDOMAIN, ENGINE_TYPE}
-import org.apache.kyuubi.ctl.{CliConfig, ControlObject}
+import org.apache.kyuubi.ctl.opt.{CliConfig, ControlObject}
 import org.apache.kyuubi.ha.client.{DiscoveryClient, DiscoveryPaths, 
ServiceNodeInfo}
 import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient
 
diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Render.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Render.scala
index 190c2b239..b3977b21f 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Render.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Render.scala
@@ -19,7 +19,7 @@ package org.apache.kyuubi.ctl.util
 import scala.collection.JavaConverters._
 import scala.collection.mutable.ListBuffer
 
-import org.apache.kyuubi.client.api.v1.dto.{Batch, GetBatchesResponse}
+import org.apache.kyuubi.client.api.v1.dto.{Batch, Engine, GetBatchesResponse}
 import org.apache.kyuubi.ctl.util.DateTimeUtils._
 import org.apache.kyuubi.ha.client.ServiceNodeInfo
 
@@ -33,6 +33,15 @@ private[ctl] object Render {
     Tabulator.format(title, header, rows)
   }
 
+  def renderEngineNodesInfo(engineNodesInfo: Seq[Engine]): String = {
+    val title = s"Engine Node List (total ${engineNodesInfo.size})"
+    val header = Array("Namespace", "Instance", "Version")
+    val rows = engineNodesInfo.map { engine =>
+      Array(engine.getNamespace, engine.getInstance, engine.getVersion)
+    }.toArray
+    Tabulator.format(title, header, rows)
+  }
+
   def renderBatchListInfo(batchListInfo: GetBatchesResponse): String = {
     val title = s"Batch List (from ${batchListInfo.getFrom} total 
${batchListInfo.getTotal})"
     val rows = 
batchListInfo.getBatches.asScala.sortBy(_.getCreateTime).map(buildBatchRow).toArray
diff --git 
a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Validator.scala 
b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Validator.scala
index 05df08c1e..896f1f1c2 100644
--- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Validator.scala
+++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/util/Validator.scala
@@ -22,7 +22,7 @@ import java.nio.file.{Files, Paths}
 import org.apache.commons.lang3.StringUtils
 
 import org.apache.kyuubi.KyuubiException
-import org.apache.kyuubi.ctl.CliConfig
+import org.apache.kyuubi.ctl.opt.CliConfig
 
 private[ctl] object Validator {
 
diff --git 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
index 48f2d6854..03b606d34 100644
--- 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
+++ 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala
@@ -18,6 +18,8 @@
 package org.apache.kyuubi.ctl
 
 import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiFunSuite}
+import org.apache.kyuubi.ctl.cli.AdminControlCliArguments
+import org.apache.kyuubi.ctl.opt.{ControlAction, ControlObject}
 
 class AdminControlCliArgumentsSuite extends KyuubiFunSuite with 
TestPrematureExit {
 
@@ -71,11 +73,34 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite 
with TestPrematureExi
     testPrematureExitForAdminControlCli(args, "Invalid config type:otherConf")
   }
 
+  test("test list engine") {
+    Seq("list", "delete").foreach { op =>
+      val args = Array(
+        op,
+        "engine",
+        "-et",
+        "spark-sql",
+        "-esl",
+        "user",
+        "--engine-subdomain",
+        "default",
+        "--hs2ProxyUser",
+        "b_kyuubi")
+      val opArgs = new AdminControlCliArguments(args)
+      assert(opArgs.cliConfig.action.toString === op.toUpperCase)
+      assert(opArgs.cliConfig.resource.toString === "ENGINE")
+      assert(opArgs.cliConfig.engineOpts.engineType === "spark-sql")
+      assert(opArgs.cliConfig.engineOpts.engineShareLevel === "user")
+      assert(opArgs.cliConfig.engineOpts.engineSubdomain === "default")
+      assert(opArgs.cliConfig.commonOpts.hs2ProxyUser === "b_kyuubi")
+    }
+  }
+
   test("test --help") {
     // scalastyle:off
     val helpString =
       s"""kyuubi $KYUUBI_VERSION
-         |Usage: kyuubi-admin [refresh] [options]
+         |Usage: kyuubi-admin [list|delete|refresh] [options]
          |
          |  -b, --verbose            Print additional debug output.
          |  --hostUrl <value>        Host url for rest api.
@@ -83,8 +108,31 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite 
with TestPrematureExi
          |  --username <value>       Username for basic authentication.
          |  --password <value>       Password for basic authentication.
          |  --spnegoHost <value>     Spnego host for spnego authentication.
+         |  --hs2ProxyUser <value>   The value of hive.server2.proxy.user 
config.
          |  --conf <value>           Kyuubi config property pair, formatted 
key=value.
          |
+         |Command: list [engine]
+         |     List information about resources.
+         |Command: list engine [options]
+         |     List all the engine nodes for a user
+         |  -et, --engine-type <value>
+         |                           The engine type this engine belong to.
+         |  -es, --engine-subdomain <value>
+         |                           The engine subdomain this engine belong 
to.
+         |  -esl, --engine-share-level <value>
+         |                           The engine share level this engine belong 
to.
+         |
+         |Command: delete [engine]
+         |     Delete resources.
+         |Command: delete engine [options]
+         |     Delete the specified engine node for user.
+         |  -et, --engine-type <value>
+         |                           The engine type this engine belong to.
+         |  -es, --engine-subdomain <value>
+         |                           The engine subdomain this engine belong 
to.
+         |  -esl, --engine-share-level <value>
+         |                           The engine share level this engine belong 
to.
+         |
          |Command: refresh [config] <args>...
          |     Refresh the resource.
          |Command: refresh config [<configType>]
diff --git 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/BatchCliArgumentsSuite.scala 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/BatchCliArgumentsSuite.scala
index 47abee364..7563d985a 100644
--- 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/BatchCliArgumentsSuite.scala
+++ 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/BatchCliArgumentsSuite.scala
@@ -17,6 +17,7 @@
 package org.apache.kyuubi.ctl
 
 import org.apache.kyuubi.KyuubiFunSuite
+import org.apache.kyuubi.ctl.cli.ControlCliArguments
 import org.apache.kyuubi.ctl.util.DateTimeUtils._
 
 class BatchCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExit {
@@ -118,6 +119,18 @@ class BatchCliArgumentsSuite extends KyuubiFunSuite with 
TestPrematureExit {
     }
   }
 
+  test("delete batch with hs2ProxyUser") {
+    val args = Array(
+      "delete",
+      "batch",
+      "f7fd702c-e54e-11ec-8fea-0242ac120002",
+      "--hs2ProxyUser",
+      "b_user")
+    val opArgs = new ControlCliArguments(args)
+    assert(opArgs.cliConfig.batchOpts.batchId == 
"f7fd702c-e54e-11ec-8fea-0242ac120002")
+    assert(opArgs.cliConfig.commonOpts.hs2ProxyUser == "b_user")
+  }
+
   test("test list batch option") {
     val args = Array(
       "list",
diff --git 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliArgumentsSuite.scala
 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliArgumentsSuite.scala
index 037f26aa1..215de65f5 100644
--- 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliArgumentsSuite.scala
+++ 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliArgumentsSuite.scala
@@ -19,6 +19,8 @@ package org.apache.kyuubi.ctl
 
 import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiFunSuite}
 import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient
+import org.apache.kyuubi.ctl.cli.ControlCliArguments
+import org.apache.kyuubi.ctl.opt.ControlAction
 import org.apache.kyuubi.ha.HighAvailabilityConf.HA_NAMESPACE
 
 class ControlCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExit {
@@ -363,6 +365,7 @@ class ControlCliArgumentsSuite extends KyuubiFunSuite with 
TestPrematureExit {
          |  --username <value>       Username for basic authentication.
          |  --password <value>       Password for basic authentication.
          |  --spnegoHost <value>     Spnego host for spnego authentication.
+         |  --hs2ProxyUser <value>   The value of hive.server2.proxy.user 
config.
          |  --conf <value>           Kyuubi config property pair, formatted 
key=value.
          |  -zk, --zk-quorum <value>
          |                           $zkHelpString
@@ -398,10 +401,9 @@ class ControlCliArgumentsSuite extends KyuubiFunSuite with 
TestPrematureExit {
          |
          |Command: delete [batch|server|engine] <args>...
          |${"\t"}Delete resources.
-         |Command: delete batch [options] [<batchId>]
+         |Command: delete batch [<batchId>]
          |${"\t"}Close batch session.
          |  <batchId>                Batch id.
-         |  --hs2ProxyUser <value>   The value of hive.server2.proxy.user 
config.
          |Command: delete server
          |${"\t"}Delete the specified service node for a domain
          |Command: delete engine [options]
diff --git 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliSuite.scala 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliSuite.scala
index 32187daf3..d27f3ec2a 100644
--- a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliSuite.scala
+++ b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/ControlCliSuite.scala
@@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger
 
 import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiFunSuite}
 import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.ctl.cli.{ControlCli, ControlCliArguments}
 import org.apache.kyuubi.ctl.util.{CtlUtils, Render}
 import org.apache.kyuubi.ha.HighAvailabilityConf.{HA_ADDRESSES, HA_NAMESPACE}
 import org.apache.kyuubi.ha.client.{DiscoveryClientProvider, ServiceNodeInfo}
diff --git 
a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/TestPrematureExit.scala 
b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/TestPrematureExit.scala
index 43e1454f3..0e4cc1302 100644
--- a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/TestPrematureExit.scala
+++ b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/TestPrematureExit.scala
@@ -22,6 +22,7 @@ import java.io.{OutputStream, PrintStream}
 import scala.collection.mutable.ArrayBuffer
 
 import org.apache.kyuubi.KyuubiFunSuite
+import org.apache.kyuubi.ctl.cli.{AdminControlCli, AdminControlCliArguments, 
ControlCli, ControlCliArguments}
 import org.apache.kyuubi.ctl.util.CommandLineUtils
 
 trait TestPrematureExit {
diff --git 
a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java 
b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java
index 46853bb51..7d7c341ab 100644
--- 
a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java
+++ 
b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java
@@ -17,6 +17,12 @@
 
 package org.apache.kyuubi.client;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.kyuubi.client.api.v1.dto.Engine;
+
 public class AdminRestApi {
   private KyuubiRestClient client;
 
@@ -33,6 +39,29 @@ public class AdminRestApi {
     return this.getClient().post(path, null, client.getAuthHeader());
   }
 
+  public String deleteEngine(
+      String engineType, String shareLevel, String subdomain, String 
hs2ProxyUser) {
+    Map<String, Object> params = new HashMap<>();
+    params.put("type", engineType);
+    params.put("sharelevel", shareLevel);
+    params.put("subdomain", subdomain);
+    params.put("hive.server2.proxy.user", hs2ProxyUser);
+    return this.getClient().delete(API_BASE_PATH + "/engine", params, 
client.getAuthHeader());
+  }
+
+  public List<Engine> listEngines(
+      String engineType, String shareLevel, String subdomain, String 
hs2ProxyUser) {
+    Map<String, Object> params = new HashMap<>();
+    params.put("type", engineType);
+    params.put("sharelevel", shareLevel);
+    params.put("subdomain", subdomain);
+    params.put("hive.server2.proxy.user", hs2ProxyUser);
+    Engine[] result =
+        this.getClient()
+            .get(API_BASE_PATH + "/engine", params, Engine[].class, 
client.getAuthHeader());
+    return Arrays.asList(result);
+  }
+
   private IRestClient getClient() {
     return this.client.getHttpClient();
   }
diff --git 
a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java
 
b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java
index 0f4d70adc..5566414fa 100644
--- 
a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java
+++ 
b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java
@@ -29,6 +29,7 @@ public class Engine {
   private String sharelevel;
   private String subdomain;
   private String instance;
+  private String namespace;
 
   public Engine() {}
 
@@ -38,13 +39,15 @@ public class Engine {
       String engineType,
       String sharelevel,
       String subdomain,
-      String instance) {
+      String instance,
+      String namespace) {
     this.version = version;
     this.user = user;
     this.engineType = engineType;
     this.sharelevel = sharelevel;
     this.subdomain = subdomain;
     this.instance = instance;
+    this.namespace = namespace;
   }
 
   public String getVersion() {
@@ -95,6 +98,14 @@ public class Engine {
     this.instance = instance;
   }
 
+  public String getNamespace() {
+    return namespace;
+  }
+
+  public void setNamespace(String namespace) {
+    this.namespace = namespace;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
diff --git 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
index 674b475a2..ad7ea2114 100644
--- 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
+++ 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
@@ -135,7 +135,8 @@ private[v1] class AdminResource extends ApiRequestContext 
with Logging {
         engine.getEngineType,
         engine.getSharelevel,
         node.namespace.split("/").last,
-        node.instance))
+        node.instance,
+        node.namespace))
   }
 
   private def getEngine(
@@ -161,6 +162,7 @@ private[v1] class AdminResource extends ApiRequestContext 
with Logging {
       normalizedEngineType,
       engineShareLevel,
       engineSubdomain,
+      null,
       null)
   }
 
diff --git 
a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminCtlSuite.scala
 
b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminCtlSuite.scala
index b52c6f67e..10c636924 100644
--- 
a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminCtlSuite.scala
+++ 
b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminCtlSuite.scala
@@ -17,15 +17,21 @@
 
 package org.apache.kyuubi.server.rest.client
 
-import org.apache.kyuubi.RestClientTestHelper
+import java.util.UUID
+
+import org.apache.kyuubi.{KYUUBI_VERSION, RestClientTestHelper}
+import org.apache.kyuubi.config.KyuubiConf
 import org.apache.kyuubi.ctl.{CtlConf, TestPrematureExit}
+import org.apache.kyuubi.engine.EngineRef
+import org.apache.kyuubi.ha.HighAvailabilityConf
+import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient
+import org.apache.kyuubi.ha.client.DiscoveryPaths
 
 class AdminCtlSuite extends RestClientTestHelper with TestPrematureExit {
   override def beforeAll(): Unit = {
     super.beforeAll()
     System.setProperty(CtlConf.CTL_REST_CLIENT_BASE_URL.key, baseUri.toString)
     System.setProperty(CtlConf.CTL_REST_CLIENT_SPNEGO_HOST.key, "localhost")
-    System.setProperty(CtlConf.CTL_REST_CLIENT_AUTH_SCHEMA.key, "spnego")
   }
 
   override def afterAll(): Unit = {
@@ -36,9 +42,61 @@ class AdminCtlSuite extends RestClientTestHelper with 
TestPrematureExit {
   }
 
   test("refresh config - hadoop conf") {
-    val args = Array("refresh", "config", "hadoopConf")
+    val args = Array("refresh", "config", "hadoopConf", "--authSchema", 
"spnego")
     testPrematureExitForAdminControlCli(
       args,
       s"Refresh the hadoop conf for ${fe.connectionUrl} successfully.")
   }
+
+  test("engine list/delete operation") {
+    val id = UUID.randomUUID().toString
+    conf.set(HighAvailabilityConf.HA_NAMESPACE, "kyuubi_test")
+    conf.set(KyuubiConf.ENGINE_IDLE_TIMEOUT, 180000L)
+    conf.set(KyuubiConf.AUTHENTICATION_METHOD, Seq("LDAP", "CUSTOM"))
+    val user = ldapUser
+    val engine = new EngineRef(conf.clone, user, id, null)
+
+    val engineSpace = DiscoveryPaths.makePath(
+      s"kyuubi_test_${KYUUBI_VERSION}_USER_SPARK_SQL",
+      user,
+      Array("default"))
+
+    withDiscoveryClient(conf) { client =>
+      engine.getOrCreate(client)
+
+    }
+
+    var args = Array(
+      "list",
+      "engine",
+      "--username",
+      ldapUser,
+      "--password",
+      ldapUserPasswd)
+    testPrematureExitForAdminControlCli(
+      args,
+      "Engine Node List (total 1)")
+
+    args = Array(
+      "delete",
+      "engine",
+      "--username",
+      ldapUser,
+      "--password",
+      ldapUserPasswd)
+    testPrematureExitForAdminControlCli(
+      args,
+      s"Engine ${engineSpace} is deleted successfully.")
+
+    args = Array(
+      "list",
+      "engine",
+      "--username",
+      ldapUser,
+      "--password",
+      ldapUserPasswd)
+    testPrematureExitForAdminControlCli(
+      args,
+      "Engine Node List (total 0)")
+  }
 }
diff --git 
a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminRestApiSuite.scala
 
b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminRestApiSuite.scala
index 9ba0b89a4..b6ec539d3 100644
--- 
a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminRestApiSuite.scala
+++ 
b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/AdminRestApiSuite.scala
@@ -17,8 +17,17 @@
 
 package org.apache.kyuubi.server.rest.client
 
-import org.apache.kyuubi.RestClientTestHelper
+import java.util.UUID
+
+import scala.collection.JavaConverters.asScalaBufferConverter
+
+import org.apache.kyuubi.{KYUUBI_VERSION, RestClientTestHelper}
 import org.apache.kyuubi.client.{AdminRestApi, KyuubiRestClient}
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.engine.EngineRef
+import org.apache.kyuubi.ha.HighAvailabilityConf
+import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient
+import org.apache.kyuubi.ha.client.DiscoveryPaths
 
 class AdminRestApiSuite extends RestClientTestHelper {
   test("refresh kyuubi server hadoop conf") {
@@ -31,4 +40,47 @@ class AdminRestApiSuite extends RestClientTestHelper {
     val result = adminRestApi.refreshHadoopConf()
     assert(result === s"Refresh the hadoop conf for ${fe.connectionUrl} 
successfully.")
   }
+
+  test("engine list/delete operation") {
+    val id = UUID.randomUUID().toString
+    conf.set(HighAvailabilityConf.HA_NAMESPACE, "kyuubi_test")
+    conf.set(KyuubiConf.ENGINE_IDLE_TIMEOUT, 180000L)
+    conf.set(KyuubiConf.AUTHENTICATION_METHOD, Seq("LDAP", "CUSTOM"))
+    val user = ldapUser
+    val engine = new EngineRef(conf.clone, user, id, null)
+
+    val engineSpace = DiscoveryPaths.makePath(
+      s"kyuubi_test_${KYUUBI_VERSION}_USER_SPARK_SQL",
+      user,
+      Array("default"))
+
+    withDiscoveryClient(conf) { client =>
+      engine.getOrCreate(client)
+
+    }
+
+    val basicKyuubiRestClient: KyuubiRestClient =
+      KyuubiRestClient.builder(baseUri.toString)
+        .authHeaderMethod(KyuubiRestClient.AuthHeaderMethod.BASIC)
+        .username(ldapUser)
+        .password(ldapUserPasswd)
+        .socketTimeout(30000)
+        .build()
+
+    val adminRestApi = new AdminRestApi(basicKyuubiRestClient)
+    var engines = adminRestApi.listEngines("spark_sql", "user", "default", 
"").asScala
+    assert(engines.size == 1)
+    assert(engines(0).getUser == user)
+    assert(engines(0).getVersion == KYUUBI_VERSION)
+    assert(engines(0).getEngineType == "SPARK_SQL")
+    assert(engines(0).getSharelevel == "USER")
+    assert(engines(0).getSubdomain == "default")
+    assert(engines(0).getNamespace == engineSpace)
+
+    val result = adminRestApi.deleteEngine("spark_sql", "user", "default", "")
+    assert(result == s"Engine ${engineSpace} is deleted successfully.")
+
+    engines = adminRestApi.listEngines("spark_sql", "user", "default", 
"").asScala
+    assert(engines.size == 0)
+  }
 }

Reply via email to