[KYUUBI #4889] Admin command line supports list server command
### _Why are the changes needed?_ Support to list server with kyuubi-admin/kyuubi rest client. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - UT for AdminRestAPI - UT for AminCtlArgument - UT for AdminCtl - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4889 from turboFei/list_server. Closes #4889 bfd13fbde [fwang12] nit 0a0131552 [fwang12] list server Authored-by: fwang12 <fwang12@ebay.com> Signed-off-by: fwang12 <fwang12@ebay.com>
This commit is contained in:
parent
ec7e7479fa
commit
52d3bf25ed
@ -98,6 +98,15 @@ Usage: ``bin/kyuubi-admin list engine [options]``
|
||||
* - --hs2ProxyUser
|
||||
- The proxy user to impersonate. When specified, it will list engines for the hs2ProxyUser.
|
||||
|
||||
.. _list_server:
|
||||
|
||||
List Servers
|
||||
-------------------------------------
|
||||
|
||||
Prints a table of the key information about the servers.
|
||||
|
||||
Usage: ``bin/kyuubi-admin list server``
|
||||
|
||||
.. _delete_engine:
|
||||
|
||||
Delete an Engine
|
||||
|
||||
@ -22,7 +22,7 @@ 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.list.{AdminListEngineCommand, AdminListServerCommand}
|
||||
import org.apache.kyuubi.ctl.cmd.refresh.RefreshConfigCommand
|
||||
import org.apache.kyuubi.ctl.opt.{AdminCommandLine, CliConfig, ControlAction, ControlObject}
|
||||
|
||||
@ -37,6 +37,7 @@ class AdminControlCliArguments(args: Seq[String], env: Map[String, String] = sys
|
||||
cliConfig.action match {
|
||||
case ControlAction.LIST => cliConfig.resource match {
|
||||
case ControlObject.ENGINE => new AdminListEngineCommand(cliConfig)
|
||||
case ControlObject.SERVER => new AdminListServerCommand(cliConfig)
|
||||
case _ => throw new KyuubiException(s"Invalid resource: ${cliConfig.resource}")
|
||||
}
|
||||
case ControlAction.DELETE => cliConfig.resource match {
|
||||
@ -61,6 +62,11 @@ class AdminControlCliArguments(args: Seq[String], env: Map[String, String] = sys
|
||||
| sharelevel ${cliConfig.engineOpts.engineShareLevel}
|
||||
| sharesubdomain ${cliConfig.engineOpts.engineSubdomain}
|
||||
""".stripMargin
|
||||
case ControlObject.SERVER =>
|
||||
s"""Parsed arguments:
|
||||
| action ${cliConfig.action}
|
||||
| resource ${cliConfig.resource}
|
||||
""".stripMargin
|
||||
case ControlObject.CONFIG =>
|
||||
s"""Parsed arguments:
|
||||
| action ${cliConfig.action}
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.cmd.list
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
import org.apache.kyuubi.client.AdminRestApi
|
||||
import org.apache.kyuubi.client.api.v1.dto.ServerData
|
||||
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.Render
|
||||
|
||||
class AdminListServerCommand(cliConfig: CliConfig)
|
||||
extends AdminCtlCommand[Seq[ServerData]](cliConfig) {
|
||||
|
||||
override def validate(): Unit = {}
|
||||
|
||||
override def doRun(): Seq[ServerData] = {
|
||||
withKyuubiRestClient(normalizedCliConfig, null, conf) { kyuubiRestClient =>
|
||||
val adminRestApi = new AdminRestApi(kyuubiRestClient)
|
||||
adminRestApi.listServers().asScala
|
||||
}
|
||||
}
|
||||
|
||||
override def render(resp: Seq[ServerData]): Unit = {
|
||||
info(Render.renderServerNodesInfo(resp))
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,8 @@ object AdminCommandLine extends CommonCommandLine {
|
||||
.text("\tList information about resources.")
|
||||
.action((_, c) => c.copy(action = ControlAction.LIST))
|
||||
.children(
|
||||
engineCmd(builder).text("\tList all the engine nodes for a user")))
|
||||
engineCmd(builder).text("\tList all the engine nodes for a user"),
|
||||
serverCmd(builder).text("\tList all the server nodes")))
|
||||
|
||||
}
|
||||
|
||||
@ -94,6 +95,11 @@ object AdminCommandLine extends CommonCommandLine {
|
||||
.text("The engine share level this engine belong to."))
|
||||
}
|
||||
|
||||
private def serverCmd(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
|
||||
import builder._
|
||||
cmd("server").action((_, c) => c.copy(resource = ControlObject.SERVER))
|
||||
}
|
||||
|
||||
private def refreshConfigCmd(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
|
||||
import builder._
|
||||
cmd("config").action((_, c) => c.copy(resource = ControlObject.CONFIG))
|
||||
|
||||
@ -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, Engine, GetBatchesResponse, SessionData}
|
||||
import org.apache.kyuubi.client.api.v1.dto.{Batch, Engine, GetBatchesResponse, ServerData, SessionData}
|
||||
import org.apache.kyuubi.ctl.util.DateTimeUtils._
|
||||
import org.apache.kyuubi.ha.client.ServiceNodeInfo
|
||||
|
||||
@ -45,6 +45,19 @@ private[ctl] object Render {
|
||||
Tabulator.format(title, header, rows)
|
||||
}
|
||||
|
||||
def renderServerNodesInfo(serverNodesInfo: Seq[ServerData]): String = {
|
||||
val title = s"Server Node List (total ${serverNodesInfo.size})"
|
||||
val header = Array("Namespace", "Instance", "Attributes", "Status")
|
||||
val rows = serverNodesInfo.map { server =>
|
||||
Array(
|
||||
server.getNamespace,
|
||||
server.getInstance,
|
||||
server.getAttributes.asScala.map { case (k, v) => s"$k=$v" }.mkString("\n"),
|
||||
server.getStatus)
|
||||
}.toArray
|
||||
Tabulator.format(title, header, rows)
|
||||
}
|
||||
|
||||
def renderSessionDataListInfo(sessions: Seq[SessionData]): String = {
|
||||
val title = s"Live Session List (total ${sessions.size})"
|
||||
val header = Array(
|
||||
|
||||
@ -115,6 +115,13 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExi
|
||||
}
|
||||
}
|
||||
|
||||
test("test list server") {
|
||||
val args = Array("list", "server")
|
||||
val opArgs = new AdminControlCliArguments(args)
|
||||
assert(opArgs.cliConfig.action.toString === "LIST")
|
||||
assert(opArgs.cliConfig.resource.toString === "SERVER")
|
||||
}
|
||||
|
||||
test("test --help") {
|
||||
// scalastyle:off
|
||||
val helpString =
|
||||
@ -130,7 +137,7 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExi
|
||||
| --hs2ProxyUser <value> The value of hive.server2.proxy.user config.
|
||||
| --conf <value> Kyuubi config property pair, formatted key=value.
|
||||
|
|
||||
|Command: list [engine]
|
||||
|Command: list [engine|server]
|
||||
| List information about resources.
|
||||
|Command: list engine [options]
|
||||
| List all the engine nodes for a user
|
||||
@ -140,6 +147,8 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExi
|
||||
| The engine subdomain this engine belong to.
|
||||
| -esl, --engine-share-level <value>
|
||||
| The engine share level this engine belong to.
|
||||
|Command: list server
|
||||
| List all the server nodes
|
||||
|
|
||||
|Command: delete [engine]
|
||||
| Delete resources.
|
||||
|
||||
@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.kyuubi.client.api.v1.dto.Engine;
|
||||
import org.apache.kyuubi.client.api.v1.dto.OperationData;
|
||||
import org.apache.kyuubi.client.api.v1.dto.ServerData;
|
||||
import org.apache.kyuubi.client.api.v1.dto.SessionData;
|
||||
|
||||
public class AdminRestApi {
|
||||
@ -99,6 +100,13 @@ public class AdminRestApi {
|
||||
return this.getClient().delete(url, null, client.getAuthHeader());
|
||||
}
|
||||
|
||||
public List<ServerData> listServers() {
|
||||
ServerData[] result =
|
||||
this.getClient()
|
||||
.get(API_BASE_PATH + "/server", null, ServerData[].class, client.getAuthHeader());
|
||||
return Arrays.asList(result);
|
||||
}
|
||||
|
||||
private IRestClient getClient() {
|
||||
return this.client.getHttpClient();
|
||||
}
|
||||
|
||||
@ -29,6 +29,8 @@ public class ServerData {
|
||||
private Map<String, String> attributes;
|
||||
private String status;
|
||||
|
||||
public ServerData() {}
|
||||
|
||||
public ServerData(
|
||||
String nodeName,
|
||||
String namespace,
|
||||
|
||||
@ -19,13 +19,16 @@ package org.apache.kyuubi.server.rest.client
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.mockito.Mockito.lenient
|
||||
import org.scalatestplus.mockito.MockitoSugar.mock
|
||||
|
||||
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.{DiscoveryPaths, ServiceDiscovery}
|
||||
import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient
|
||||
import org.apache.kyuubi.ha.client.DiscoveryPaths
|
||||
import org.apache.kyuubi.plugin.PluginLoader
|
||||
|
||||
class AdminCtlSuite extends RestClientTestHelper with TestPrematureExit {
|
||||
@ -102,4 +105,17 @@ class AdminCtlSuite extends RestClientTestHelper with TestPrematureExit {
|
||||
args,
|
||||
"Engine Node List (total 0)")
|
||||
}
|
||||
|
||||
test("list server") {
|
||||
// Mock Kyuubi Server
|
||||
val serverDiscovery = mock[ServiceDiscovery]
|
||||
lenient.when(serverDiscovery.fe).thenReturn(fe)
|
||||
val namespace = conf.get(HighAvailabilityConf.HA_NAMESPACE)
|
||||
withDiscoveryClient(conf) { client =>
|
||||
client.registerService(conf, namespace, serverDiscovery)
|
||||
|
||||
val args = Array("list", "server", "--authSchema", "spnego")
|
||||
testPrematureExitForAdminControlCli(args, "Server Node List (total 1)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,14 +22,16 @@ import java.util.UUID
|
||||
import scala.collection.JavaConverters.asScalaBufferConverter
|
||||
|
||||
import org.apache.hive.service.rpc.thrift.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V2
|
||||
import org.mockito.Mockito.lenient
|
||||
import org.scalatestplus.mockito.MockitoSugar.mock
|
||||
|
||||
import org.apache.kyuubi.{KYUUBI_VERSION, RestClientTestHelper}
|
||||
import org.apache.kyuubi.client.{AdminRestApi, KyuubiRestClient}
|
||||
import org.apache.kyuubi.config.{KyuubiConf, KyuubiReservedKeys}
|
||||
import org.apache.kyuubi.engine.EngineRef
|
||||
import org.apache.kyuubi.ha.HighAvailabilityConf
|
||||
import org.apache.kyuubi.ha.client.{DiscoveryPaths, ServiceDiscovery}
|
||||
import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient
|
||||
import org.apache.kyuubi.ha.client.DiscoveryPaths
|
||||
import org.apache.kyuubi.plugin.PluginLoader
|
||||
|
||||
class AdminRestApiSuite extends RestClientTestHelper {
|
||||
@ -148,4 +150,25 @@ class AdminRestApiSuite extends RestClientTestHelper {
|
||||
assert(!operations.map(op => op.getIdentifier).contains(operation.identifier.toString))
|
||||
|
||||
}
|
||||
|
||||
test("list server") {
|
||||
val spnegoKyuubiRestClient: KyuubiRestClient =
|
||||
KyuubiRestClient.builder(baseUri.toString)
|
||||
.authHeaderMethod(KyuubiRestClient.AuthHeaderMethod.SPNEGO)
|
||||
.spnegoHost("localhost")
|
||||
.build()
|
||||
val adminRestApi = new AdminRestApi(spnegoKyuubiRestClient)
|
||||
|
||||
// Mock Kyuubi Server
|
||||
val serverDiscovery = mock[ServiceDiscovery]
|
||||
lenient.when(serverDiscovery.fe).thenReturn(fe)
|
||||
val namespace = conf.get(HighAvailabilityConf.HA_NAMESPACE)
|
||||
withDiscoveryClient(conf) { client =>
|
||||
client.registerService(conf, namespace, serverDiscovery)
|
||||
|
||||
val servers = adminRestApi.listServers().asScala
|
||||
assert(servers.nonEmpty)
|
||||
assert(servers.map(s => s.getInstance()).contains(server.frontendServices.last.connectionUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user