[KYUUBI #753] Use scopt to parse arguments
<!-- Thanks for sending a pull request! Here are some tips for you: 1. If this is your first time, please read our contributor guidelines: https://kyuubi.readthedocs.io/en/latest/community/contributions.html 2. If the PR is related to an issue in https://github.com/NetEase/kyuubi/issues, add '[KYUUBI #XXXX]' in your PR title, e.g., '[KYUUBI #XXXX] Your PR title ...'. 3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP][KYUUBI #XXXX] Your PR title ...'. --> ### _Why are the changes needed?_ <!-- Please clarify why the changes are needed. For instance, 1. If you add a feature, you can talk about the use case of it. 2. If you fix a bug, you can clarify why it is a bug. --> Use [`scopt`](https://github.com/scopt/scopt) replace custom parser to parse arguments. ### _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.readthedocs.io/en/latest/tools/testing.html#running-tests) locally before make a pull request Closes #754 from hddong/kyuubi-753. Closes #753 3d2645d8 [hongdongdong] fix check style fd181452 [hongdongdong] fix output ef7c44e4 [hongdongdong] use scopt help 682b3a2f [hongdongdong] [KYUUBI#753] Use scopt to parse arguments Authored-by: hongdongdong <hongdongdong@cmss.chinamobile.com> Signed-off-by: Kent Yao <yao@apache.org>
This commit is contained in:
parent
5e53748bb5
commit
16b93e4720
@ -258,3 +258,4 @@ MIT License
|
||||
org.slf4j:slf4j-api
|
||||
org.slf4j:slf4j-log4j12
|
||||
org.slf4j:jcl-over-slf4j
|
||||
com.github.scopt:scopt_*
|
||||
|
||||
@ -49,6 +49,7 @@ metrics-jmx/4.1.1//metrics-jmx-4.1.1.jar
|
||||
metrics-json/4.1.1//metrics-json-4.1.1.jar
|
||||
metrics-jvm/4.1.1//metrics-jvm-4.1.1.jar
|
||||
scala-library/2.12.14//scala-library-2.12.14.jar
|
||||
scopt_2.12/4.0.1//scopt_2.12-4.0.1.jar
|
||||
simpleclient/0.10.0//simpleclient-0.10.0.jar
|
||||
simpleclient_common/0.10.0//simpleclient_common-0.10.0.jar
|
||||
simpleclient_dropwizard/0.10.0//simpleclient_dropwizard-0.10.0.jar
|
||||
|
||||
@ -65,6 +65,11 @@
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.scopt</groupId>
|
||||
<artifactId>scopt_${scala.binary.version}</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
|
||||
@ -1,150 +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.ctl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
abstract class ServiceControlCliOptionParser {
|
||||
protected final String CREATE = "create";
|
||||
protected final String GET = "get";
|
||||
protected final String DELETE = "delete";
|
||||
protected final String LIST = "list";
|
||||
protected final String SERVER = "server";
|
||||
protected final String ENGINE = "engine";
|
||||
|
||||
protected final String ZK_QUORUM = "--zk-quorum";
|
||||
protected final String NAMESPACE = "--namespace";
|
||||
protected final String USER = "--user";
|
||||
protected final String HOST = "--host";
|
||||
protected final String PORT = "--port";
|
||||
protected final String VERSION = "--version";
|
||||
|
||||
// Options that do not take arguments.
|
||||
protected final String HELP = "--help";
|
||||
protected final String VERBOSE = "--verbose";
|
||||
|
||||
final String[][] opts = {
|
||||
{ ZK_QUORUM },
|
||||
{ NAMESPACE },
|
||||
{ USER },
|
||||
{ HOST },
|
||||
{ PORT },
|
||||
{ VERSION },
|
||||
};
|
||||
|
||||
/**
|
||||
* List of switches (command line options that do not take parameters) recognized by
|
||||
* kyuubi-ctl.
|
||||
*/
|
||||
final String[][] switches = {
|
||||
{ HELP },
|
||||
{ VERBOSE },
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse action type and service type.
|
||||
*
|
||||
* @return offset of remaining arguments.
|
||||
*/
|
||||
protected abstract int parseActionAndService(List<String> args);
|
||||
|
||||
/**
|
||||
* Parse a list of kyuubi-ctl command line options.
|
||||
* <p>
|
||||
* See KyuubiCtlArguments.scala for a more formal description of available options.
|
||||
*
|
||||
* @throws IllegalArgumentException If an error is found during parsing.
|
||||
*/
|
||||
protected final void parse(List<String> args) {
|
||||
Pattern eqSeparatedOpt = Pattern.compile("(--[^=]+)=(.+)");
|
||||
|
||||
int idx = parseActionAndService(args);
|
||||
for (; idx < args.size(); idx++) {
|
||||
String arg = args.get(idx);
|
||||
String value = null;
|
||||
|
||||
Matcher m = eqSeparatedOpt.matcher(arg);
|
||||
if (m.matches()) {
|
||||
arg = m.group(1);
|
||||
value = m.group(2);
|
||||
}
|
||||
|
||||
// Look for options with a value.
|
||||
String name = findCliOption(arg, opts);
|
||||
if (name != null) {
|
||||
if (value == null) {
|
||||
if (idx == args.size() - 1) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Missing argument for option '%s'.", arg));
|
||||
}
|
||||
idx++;
|
||||
value = args.get(idx);
|
||||
}
|
||||
if (!handle(name, value)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for a switch.
|
||||
name = findCliOption(arg, switches);
|
||||
if (name != null) {
|
||||
if (!handle(name, null)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
handleUnknown(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when an option with an argument is parsed.
|
||||
*
|
||||
* @param opt The long name of the cli option (might differ from actual command line).
|
||||
* @param value The value. This will be <i>null</i> if the option does not take a value.
|
||||
* @return Whether to continue parsing the argument list.
|
||||
*/
|
||||
protected abstract boolean handle(String opt, String value);
|
||||
|
||||
/**
|
||||
* Callback for when an unrecognized option is parsed.
|
||||
*
|
||||
* @param opt Unrecognized option from the command line.
|
||||
* @return Whether to continue parsing the argument list.
|
||||
*/
|
||||
protected abstract boolean handleUnknown(String opt);
|
||||
|
||||
private String findCliOption(String name, String[][] available) {
|
||||
for (String[] candidates : available) {
|
||||
for (String candidate : candidates) {
|
||||
if (candidate.equals(name)) {
|
||||
return candidates[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String findSwitches(String name) {
|
||||
return findCliOption(name, switches);
|
||||
}
|
||||
}
|
||||
@ -75,7 +75,7 @@ private[kyuubi] object Tabulator {
|
||||
}
|
||||
|
||||
def format(title: String, header: Seq[String], rows: Seq[Seq[String]],
|
||||
verbose: Boolean): String = {
|
||||
verbose: Boolean): String = {
|
||||
val data = if (verbose) Seq(header).union(rows) else rows
|
||||
val sb = new StringBuilder
|
||||
val numCols = header.size
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import scopt.DefaultOEffectSetup
|
||||
|
||||
import org.apache.kyuubi.Logging
|
||||
|
||||
class KyuubiOEffectSetup extends DefaultOEffectSetup with Logging {
|
||||
override def displayToOut(msg: String): Unit = info(msg)
|
||||
override def displayToErr(msg: String): Unit = error(msg)
|
||||
override def reportError(msg: String): Unit = info(msg)
|
||||
override def reportWarning(msg: String): Unit = warn(msg)
|
||||
}
|
||||
@ -29,7 +29,7 @@ import org.apache.kyuubi.ha.client.{ServiceDiscovery, ServiceNodeInfo}
|
||||
|
||||
private[ctl] object ServiceControlAction extends Enumeration {
|
||||
type ServiceControlAction = Value
|
||||
val CREATE, GET, DELETE, LIST, HELP = Value
|
||||
val CREATE, GET, DELETE, LIST = Value
|
||||
}
|
||||
|
||||
private[ctl] object ServiceControlObject extends Enumeration {
|
||||
@ -39,7 +39,6 @@ private[ctl] object ServiceControlObject extends Enumeration {
|
||||
|
||||
/**
|
||||
* Main gateway of launching a Kyuubi Ctl action.
|
||||
* See usage in [[ServiceControlCliArguments.printUsageAndExit]].
|
||||
*/
|
||||
private[kyuubi] class ServiceControlCli extends Logging {
|
||||
import ServiceControlCli._
|
||||
@ -53,16 +52,21 @@ private[kyuubi] class ServiceControlCli extends Logging {
|
||||
initializeLoggerIfNecessary(true)
|
||||
|
||||
val ctlArgs = parseArguments(args)
|
||||
verbose = ctlArgs.verbose
|
||||
|
||||
// when parse failed, exit
|
||||
if (ctlArgs.cliArgs == null) {
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
verbose = ctlArgs.cliArgs.verbose
|
||||
if (verbose) {
|
||||
super.info(ctlArgs.toString)
|
||||
}
|
||||
ctlArgs.action match {
|
||||
ctlArgs.cliArgs.action match {
|
||||
case ServiceControlAction.CREATE => create(ctlArgs)
|
||||
case ServiceControlAction.LIST => list(ctlArgs, filterHostPort = false)
|
||||
case ServiceControlAction.GET => list(ctlArgs, filterHostPort = true)
|
||||
case ServiceControlAction.DELETE => delete(ctlArgs)
|
||||
case ServiceControlAction.HELP => printUsage(ctlArgs)
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +80,7 @@ private[kyuubi] class ServiceControlCli extends Logging {
|
||||
private def create(args: ServiceControlCliArguments): Unit = {
|
||||
val kyuubiConf = args.conf
|
||||
|
||||
kyuubiConf.setIfMissing(HA_ZK_QUORUM, args.zkQuorum)
|
||||
kyuubiConf.setIfMissing(HA_ZK_QUORUM, args.cliArgs.zkQuorum)
|
||||
withZkClient(kyuubiConf) { zkClient =>
|
||||
val fromNamespace = ZKPaths.makePath(null, kyuubiConf.get(HA_ZK_NAMESPACE))
|
||||
val toNamespace = getZkNamespace(args)
|
||||
@ -90,17 +94,17 @@ private[kyuubi] class ServiceControlCli extends Logging {
|
||||
info(s"Exposing server instance:${sn.instance} with version:${sn.version}" +
|
||||
s" from $fromNamespace to $toNamespace")
|
||||
val newNode = createZkServiceNode(
|
||||
kyuubiConf, zc, args.namespace, sn.instance, sn.version, true)
|
||||
kyuubiConf, zc, args.cliArgs.namespace, sn.instance, sn.version, true)
|
||||
exposedServiceNodes += sn.copy(
|
||||
namespace = toNamespace,
|
||||
nodeName = newNode.getActualPath.split("/").last)
|
||||
}
|
||||
}
|
||||
|
||||
if (kyuubiConf.get(HA_ZK_QUORUM) == args.zkQuorum) {
|
||||
if (kyuubiConf.get(HA_ZK_QUORUM) == args.cliArgs.zkQuorum) {
|
||||
doCreate(zkClient)
|
||||
} else {
|
||||
kyuubiConf.set(HA_ZK_QUORUM, args.zkQuorum)
|
||||
kyuubiConf.set(HA_ZK_QUORUM, args.cliArgs.zkQuorum)
|
||||
withZkClient(kyuubiConf)(doCreate)
|
||||
}
|
||||
}
|
||||
@ -116,7 +120,9 @@ private[kyuubi] class ServiceControlCli extends Logging {
|
||||
private def list(args: ServiceControlCliArguments, filterHostPort: Boolean): Unit = {
|
||||
withZkClient(args.conf) { zkClient =>
|
||||
val znodeRoot = getZkNamespace(args)
|
||||
val hostPortOpt = if (filterHostPort) Some((args.host, args.port.toInt)) else None
|
||||
val hostPortOpt = if (filterHostPort) {
|
||||
Some((args.cliArgs.host, args.cliArgs.port.toInt))
|
||||
} else None
|
||||
val nodes = getServiceNodes(zkClient, znodeRoot, hostPortOpt)
|
||||
|
||||
val title = "Zookeeper service nodes"
|
||||
@ -143,7 +149,7 @@ private[kyuubi] class ServiceControlCli extends Logging {
|
||||
private def delete(args: ServiceControlCliArguments): Unit = {
|
||||
withZkClient(args.conf) { zkClient =>
|
||||
val znodeRoot = getZkNamespace(args)
|
||||
val hostPortOpt = Some((args.host, args.port.toInt))
|
||||
val hostPortOpt = Some((args.cliArgs.host, args.cliArgs.port.toInt))
|
||||
val nodesToDelete = getServiceNodes(zkClient, znodeRoot, hostPortOpt)
|
||||
|
||||
val deletedNodes = ListBuffer[ServiceNodeInfo]()
|
||||
@ -163,10 +169,6 @@ private[kyuubi] class ServiceControlCli extends Logging {
|
||||
info(renderServiceNodesInfo(title, deletedNodes, verbose))
|
||||
}
|
||||
}
|
||||
|
||||
private def printUsage(args: ServiceControlCliArguments): Unit = {
|
||||
args.printUsageAndExit(0)
|
||||
}
|
||||
}
|
||||
|
||||
object ServiceControlCli extends CommandLineUtils with Logging {
|
||||
@ -181,6 +183,16 @@ object ServiceControlCli extends CommandLineUtils with Logging {
|
||||
override def warn(msg: => Any): Unit = self.warn(msg)
|
||||
|
||||
override def error(msg: => Any): Unit = self.error(msg)
|
||||
|
||||
override private[kyuubi] lazy val effectSetup = new KyuubiOEffectSetup {
|
||||
override def displayToOut(msg: String): Unit = self.info(msg)
|
||||
|
||||
override def displayToErr(msg: String): Unit = self.info(msg)
|
||||
|
||||
override def reportError(msg: String): Unit = self.info(msg)
|
||||
|
||||
override def reportWarning(msg: String): Unit = self.warn(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,11 +217,11 @@ object ServiceControlCli extends CommandLineUtils with Logging {
|
||||
}
|
||||
|
||||
private[ctl] def getZkNamespace(args: ServiceControlCliArguments): String = {
|
||||
args.service match {
|
||||
args.cliArgs.service match {
|
||||
case ServiceControlObject.SERVER =>
|
||||
ZKPaths.makePath(null, args.namespace)
|
||||
ZKPaths.makePath(null, args.cliArgs.namespace)
|
||||
case ServiceControlObject.ENGINE =>
|
||||
ZKPaths.makePath(s"${args.namespace}_${ShareLevel.USER}", args.user)
|
||||
ZKPaths.makePath(s"${args.cliArgs.namespace}_${ShareLevel.USER}", args.cliArgs.user)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,90 +18,182 @@
|
||||
package org.apache.kyuubi.ctl
|
||||
|
||||
import java.net.InetAddress
|
||||
import java.util.{List => JList}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scopt.OParser
|
||||
|
||||
import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiException, Logging}
|
||||
import org.apache.kyuubi.config.KyuubiConf
|
||||
import org.apache.kyuubi.ctl.ServiceControlAction._
|
||||
import org.apache.kyuubi.ctl.ServiceControlObject._
|
||||
import org.apache.kyuubi.ha.HighAvailabilityConf._
|
||||
|
||||
class ServiceControlCliArguments(args: Seq[String], env: Map[String, String] = sys.env)
|
||||
extends ServiceControlCliArgumentsParser with Logging {
|
||||
var action: ServiceControlAction = null
|
||||
var service: ServiceControlObject = null
|
||||
var zkQuorum: String = null
|
||||
var namespace: String = null
|
||||
var user: String = null
|
||||
var host: String = null
|
||||
var port: String = null
|
||||
var version: String = null
|
||||
var verbose: Boolean = false
|
||||
|
||||
var cliArgs: CliArguments = null
|
||||
|
||||
val conf = KyuubiConf().loadFileDefaults()
|
||||
|
||||
// Set parameters from command line arguments
|
||||
parse(args.asJava)
|
||||
parse(args)
|
||||
|
||||
// Use default property value if not set
|
||||
useDefaultPropertyValueIfMissing()
|
||||
lazy val cliParser = parser()
|
||||
|
||||
validateArguments()
|
||||
override def parser(): OParser[Unit, CliArguments] = {
|
||||
val builder = OParser.builder[CliArguments]
|
||||
import builder._
|
||||
|
||||
private def useDefaultPropertyValueIfMissing(): Unit = {
|
||||
if (zkQuorum == null) {
|
||||
// Options after action and service
|
||||
val ops = OParser.sequence(
|
||||
opt[String]("zk-quorum").abbr("zk")
|
||||
.action((v, c) => c.copy(zkQuorum = v))
|
||||
.text("The connection string for the zookeeper ensemble," +
|
||||
" using zk quorum manually."),
|
||||
opt[String]('n', "namespace")
|
||||
.action((v, c) => c.copy(namespace = v))
|
||||
.text("The namespace, using kyuubi-defaults/conf if absent."),
|
||||
opt[String]('s', "host")
|
||||
.action((v, c) => c.copy(host = v))
|
||||
.text("Hostname or IP address of a service."),
|
||||
opt[String]('p', "port")
|
||||
.action((v, c) => c.copy(port = v))
|
||||
.text("Listening port of a service."),
|
||||
opt[String]('v', "version")
|
||||
.action((v, c) => c.copy(version = v))
|
||||
.text("Using the compiled KYUUBI_VERSION default," +
|
||||
" change it if the active service is running in another."),
|
||||
opt[Unit]('b', "verbose")
|
||||
.action((_, c) => c.copy(verbose = true))
|
||||
.text("Print additional debug output."))
|
||||
|
||||
// for engine service only
|
||||
val userOps = opt[String]('u', "user")
|
||||
.action((v, c) => c.copy(user = v))
|
||||
.text("The user name this engine belong to.")
|
||||
|
||||
val serverCmd =
|
||||
cmd("server").action((_, c) => c.copy(service = ServiceControlObject.SERVER))
|
||||
val engineCmd =
|
||||
cmd("engine").action((_, c) => c.copy(service = ServiceControlObject.ENGINE))
|
||||
|
||||
val CtlParser = {
|
||||
OParser.sequence(
|
||||
programName("kyuubi-ctl"),
|
||||
head("kyuubi", KYUUBI_VERSION),
|
||||
ops,
|
||||
note(""),
|
||||
cmd("create")
|
||||
.action((_, c) => c.copy(action = ServiceControlAction.CREATE))
|
||||
.children(
|
||||
serverCmd.text("\tExpose Kyuubi server instance to another domain.")),
|
||||
note(""),
|
||||
cmd("get")
|
||||
.action((_, c) => c.copy(action = ServiceControlAction.GET))
|
||||
.text("\tGet the service/engine node info, host and port needed.")
|
||||
.children(
|
||||
serverCmd.text("\tGet Kyuubi server info of domain"),
|
||||
engineCmd
|
||||
.children(userOps)
|
||||
.text("\tGet Kyuubi engine info belong to a user.")),
|
||||
note(""),
|
||||
cmd("delete")
|
||||
.action((_, c) => c.copy(action = ServiceControlAction.DELETE))
|
||||
.text("\tDelete the specified service/engine node, host and port needed.")
|
||||
.children(
|
||||
serverCmd.text("\tDelete the specified service node for a domain"),
|
||||
engineCmd
|
||||
.children(userOps)
|
||||
.text("\tDelete the specified engine node for user.")),
|
||||
note(""),
|
||||
cmd("list")
|
||||
.action((_, c) => c.copy(action = ServiceControlAction.LIST))
|
||||
.text("\tList all the service/engine nodes for a particular domain.")
|
||||
.children(
|
||||
serverCmd.text("\tList all the service nodes for a particular domain"),
|
||||
engineCmd
|
||||
.children(userOps)
|
||||
.text("\tList all the engine nodes for a user")),
|
||||
checkConfig(f => {
|
||||
if (f.action == null) failure("Must specify action command: [create|get|delete|list].")
|
||||
else success
|
||||
}),
|
||||
note(""),
|
||||
help('h', "help").text("Show help message and exit.")
|
||||
)
|
||||
}
|
||||
CtlParser
|
||||
}
|
||||
|
||||
private[kyuubi] lazy val effectSetup = new KyuubiOEffectSetup
|
||||
|
||||
override def parse(args: Seq[String]): Unit = {
|
||||
OParser.runParser(cliParser, args, CliArguments()) match {
|
||||
case (result, effects) =>
|
||||
OParser.runEffects(effects, effectSetup)
|
||||
result match {
|
||||
case Some(arguments) =>
|
||||
// use default property value if not set
|
||||
cliArgs = useDefaultPropertyValueIfMissing(arguments).copy()
|
||||
|
||||
// validate arguments
|
||||
validateArguments()
|
||||
case _ =>
|
||||
// arguments are bad, exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def useDefaultPropertyValueIfMissing(value: CliArguments): CliArguments = {
|
||||
var arguments: CliArguments = value.copy()
|
||||
if (value.zkQuorum == null) {
|
||||
conf.getOption(HA_ZK_QUORUM.key).foreach { v =>
|
||||
if (verbose) {
|
||||
if (arguments.verbose) {
|
||||
super.info(s"Zookeeper quorum is not specified, use value from default conf:$v")
|
||||
}
|
||||
zkQuorum = v
|
||||
arguments = arguments.copy(zkQuorum = v)
|
||||
}
|
||||
}
|
||||
|
||||
// for create action, it only expose Kyuubi service instance to another domain,
|
||||
// so we do not use namespace from default conf
|
||||
if (action != ServiceControlAction.CREATE && namespace == null) {
|
||||
namespace = conf.get(HA_ZK_NAMESPACE)
|
||||
if (verbose) {
|
||||
super.info(s"Zookeeper namespace is not specified, use value from default conf:$namespace")
|
||||
if (arguments.action != ServiceControlAction.CREATE && arguments.namespace == null) {
|
||||
arguments = arguments.copy(namespace = conf.get(HA_ZK_NAMESPACE))
|
||||
if (arguments.verbose) {
|
||||
super.info(s"Zookeeper namespace is not specified, use value from default conf:" +
|
||||
s"${arguments.namespace}")
|
||||
}
|
||||
}
|
||||
|
||||
if (version == null) {
|
||||
if (verbose) {
|
||||
if (arguments.version == null) {
|
||||
if (arguments.verbose) {
|
||||
super.info(s"version is not specified, use built-in KYUUBI_VERSION:$KYUUBI_VERSION")
|
||||
}
|
||||
version = KYUUBI_VERSION
|
||||
arguments = arguments.copy(version = KYUUBI_VERSION)
|
||||
}
|
||||
arguments
|
||||
}
|
||||
|
||||
/** Ensure that required fields exists. Call this only once all defaults are loaded. */
|
||||
private def validateArguments(): Unit = {
|
||||
service = Option(service).getOrElse(ServiceControlObject.SERVER)
|
||||
action match {
|
||||
cliArgs.action match {
|
||||
case ServiceControlAction.CREATE => validateCreateActionArguments()
|
||||
case ServiceControlAction.GET => validateGetDeleteActionArguments()
|
||||
case ServiceControlAction.DELETE => validateGetDeleteActionArguments()
|
||||
case ServiceControlAction.LIST => validateListActionArguments()
|
||||
case ServiceControlAction.HELP =>
|
||||
case _ => printUsageAndExit(-1)
|
||||
case _ => // do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private def validateCreateActionArguments(): Unit = {
|
||||
if (service != ServiceControlObject.SERVER) {
|
||||
if (cliArgs.service != ServiceControlObject.SERVER) {
|
||||
fail("Only support expose Kyuubi server instance to another domain")
|
||||
}
|
||||
validateZkArguments()
|
||||
|
||||
val defaultNamespace = conf.getOption(HA_ZK_NAMESPACE.key)
|
||||
if (defaultNamespace.isEmpty || defaultNamespace.get.equals(namespace)) {
|
||||
if (defaultNamespace.isEmpty || defaultNamespace.get.equals(cliArgs.namespace)) {
|
||||
fail(
|
||||
s"""
|
||||
|Only support expose Kyuubi server instance to another domain, but the default
|
||||
|namespace is [$defaultNamespace] and specified namespace is [$namespace]
|
||||
|Only support expose Kyuubi server instance to another domain, but the default
|
||||
|namespace is [$defaultNamespace] and specified namespace is [${cliArgs.namespace}]
|
||||
""".stripMargin)
|
||||
}
|
||||
}
|
||||
@ -115,20 +207,24 @@ class ServiceControlCliArguments(args: Seq[String], env: Map[String, String] = s
|
||||
|
||||
private def validateListActionArguments(): Unit = {
|
||||
validateZkArguments()
|
||||
cliArgs.service match {
|
||||
case ServiceControlObject.ENGINE => validateUser()
|
||||
case _ =>
|
||||
}
|
||||
mergeArgsIntoKyuubiConf()
|
||||
}
|
||||
|
||||
private def mergeArgsIntoKyuubiConf(): Unit = {
|
||||
conf.set(HA_ZK_QUORUM.key, zkQuorum)
|
||||
conf.set(HA_ZK_NAMESPACE.key, namespace)
|
||||
conf.set(HA_ZK_QUORUM.key, cliArgs.zkQuorum)
|
||||
conf.set(HA_ZK_NAMESPACE.key, cliArgs.namespace)
|
||||
}
|
||||
|
||||
private def validateZkArguments(): Unit = {
|
||||
if (zkQuorum == null) {
|
||||
if (cliArgs.zkQuorum == null) {
|
||||
fail("Zookeeper quorum is not specified and no default value to load")
|
||||
}
|
||||
if (namespace == null) {
|
||||
if (action == ServiceControlAction.CREATE) {
|
||||
if (cliArgs.namespace == null) {
|
||||
if (cliArgs.action == ServiceControlAction.CREATE) {
|
||||
fail("Zookeeper namespace is not specified")
|
||||
} else {
|
||||
fail("Zookeeper namespace is not specified and no default value to load")
|
||||
@ -137,200 +233,62 @@ class ServiceControlCliArguments(args: Seq[String], env: Map[String, String] = s
|
||||
}
|
||||
|
||||
private def validateHostAndPort(): Unit = {
|
||||
if (host == null) {
|
||||
if (cliArgs.host == null) {
|
||||
fail("Must specify host for service")
|
||||
}
|
||||
if (port == null) {
|
||||
if (cliArgs.port == null) {
|
||||
fail("Must specify port for service")
|
||||
}
|
||||
|
||||
try {
|
||||
host = InetAddress.getByName(host).getCanonicalHostName
|
||||
cliArgs = cliArgs.copy(host = InetAddress.getByName(cliArgs.host).getCanonicalHostName)
|
||||
} catch {
|
||||
case _: Exception =>
|
||||
fail(s"Unknown host: $host")
|
||||
fail(s"Unknown host: ${cliArgs.host}")
|
||||
}
|
||||
|
||||
try {
|
||||
if (port.toInt <= 0 ) {
|
||||
if (cliArgs.port.toInt <= 0 ) {
|
||||
fail(s"Specified port should be a positive number")
|
||||
}
|
||||
} catch {
|
||||
case _: NumberFormatException =>
|
||||
fail(s"Specified port is not a valid integer number: $port")
|
||||
fail(s"Specified port is not a valid integer number: ${cliArgs.port}")
|
||||
}
|
||||
}
|
||||
|
||||
private def validateUser(): Unit = {
|
||||
if (service == ServiceControlObject.ENGINE && user == null) {
|
||||
fail("Must specify user name for engine")
|
||||
if (cliArgs.service == ServiceControlObject.ENGINE && cliArgs.user == null) {
|
||||
fail("Must specify user name for engine, please use -u or --user.")
|
||||
}
|
||||
}
|
||||
|
||||
private[ctl] def printUsageAndExit(exitCode: Int, unknownParam: Any = null): Unit = {
|
||||
if (unknownParam != null) {
|
||||
info("Unknown/unsupported param " + unknownParam)
|
||||
}
|
||||
val command =
|
||||
s"""
|
||||
|Kyuubi Ver $KYUUBI_VERSION.
|
||||
|Usage: kyuubi-ctl <create|get|delete|list> <server|engine> --zk-quorum ...
|
||||
|--namespace ... --user ... --host ... --port ... --version""".stripMargin
|
||||
info(command)
|
||||
|
||||
info(
|
||||
s"""
|
||||
|Command:
|
||||
| - create expose a service to a namespace on the zookeeper cluster of
|
||||
| zk quorum manually
|
||||
| - get get the service node info
|
||||
| - delete delete the specified serviceNode
|
||||
| - list list all the service nodes for a particular domain
|
||||
|
|
||||
|Service:
|
||||
| - server default
|
||||
| - engine
|
||||
|
|
||||
|Arguments:
|
||||
| --zk-quorum The connection string for the zookeeper ensemble, using
|
||||
| kyuubi-defaults/conf if absent
|
||||
| --namespace the namespace, using kyuubi-defaults/conf if absent
|
||||
| --host hostname or IP address of a service
|
||||
| --port listening port of a service
|
||||
| --version using the compiled KYUUBI_VERSION default, change it if the
|
||||
| active service is running in another
|
||||
| --user for engine service only, the user name this engine belong to
|
||||
| --help Show this help message and exit.
|
||||
| --verbose Print additional debug output.
|
||||
""".stripMargin
|
||||
)
|
||||
|
||||
throw new ServiceControlCliException(exitCode)
|
||||
}
|
||||
|
||||
override protected def parseActionAndService(args: JList[String]): Int = {
|
||||
if (args.isEmpty) {
|
||||
printUsageAndExit(-1)
|
||||
}
|
||||
|
||||
var actionParsed = false
|
||||
var serviceParsed = false
|
||||
var offset = 0
|
||||
|
||||
while(offset < args.size() && needContinueHandle() && !(actionParsed && serviceParsed)) {
|
||||
val arg = args.get(offset)
|
||||
if (!actionParsed) {
|
||||
arg match {
|
||||
case CREATE =>
|
||||
action = ServiceControlAction.CREATE
|
||||
actionParsed = true
|
||||
case GET =>
|
||||
action = ServiceControlAction.GET
|
||||
actionParsed = true
|
||||
case DELETE =>
|
||||
action = ServiceControlAction.DELETE
|
||||
actionParsed = true
|
||||
case LIST =>
|
||||
action = ServiceControlAction.LIST
|
||||
actionParsed = true
|
||||
case _ => findSwitches(arg) match {
|
||||
case HELP =>
|
||||
action = ServiceControlAction.HELP
|
||||
actionParsed = true
|
||||
case VERBOSE =>
|
||||
verbose = true
|
||||
case _ =>
|
||||
printUsageAndExit(-1, arg)
|
||||
}
|
||||
}
|
||||
offset += 1
|
||||
} else if (needContinueHandle() && !serviceParsed) {
|
||||
arg match {
|
||||
case SERVER =>
|
||||
service = ServiceControlObject.SERVER
|
||||
serviceParsed = true
|
||||
offset += 1
|
||||
case ENGINE =>
|
||||
service = ServiceControlObject.ENGINE
|
||||
serviceParsed = true
|
||||
offset += 1
|
||||
case _ => findSwitches(arg) match {
|
||||
case HELP =>
|
||||
action = ServiceControlAction.HELP
|
||||
offset += 1
|
||||
case VERBOSE =>
|
||||
verbose = true
|
||||
offset += 1
|
||||
case _ =>
|
||||
service = ServiceControlObject.SERVER
|
||||
serviceParsed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
offset
|
||||
}
|
||||
|
||||
override def toString: String = {
|
||||
s"""Parsed arguments:
|
||||
| action $action
|
||||
| service $service
|
||||
| zkQuorum $zkQuorum
|
||||
| namespace $namespace
|
||||
| user $user
|
||||
| host $host
|
||||
| port $port
|
||||
| version $version
|
||||
| verbose $verbose
|
||||
""".stripMargin
|
||||
}
|
||||
|
||||
private def needContinueHandle(): Boolean = {
|
||||
action != ServiceControlAction.HELP
|
||||
}
|
||||
|
||||
/** Fill in values by parsing user options. */
|
||||
override protected def handle(opt: String, value: String): Boolean = {
|
||||
if (!needContinueHandle()) {
|
||||
return false
|
||||
}
|
||||
opt match {
|
||||
case ZK_QUORUM =>
|
||||
zkQuorum = value
|
||||
|
||||
case NAMESPACE =>
|
||||
namespace = value
|
||||
|
||||
case USER =>
|
||||
user = value
|
||||
|
||||
case HOST =>
|
||||
host = value
|
||||
|
||||
case PORT =>
|
||||
port = value
|
||||
|
||||
case VERSION =>
|
||||
version = value
|
||||
|
||||
case HELP =>
|
||||
action = ServiceControlAction.HELP
|
||||
|
||||
case VERBOSE =>
|
||||
verbose = true
|
||||
|
||||
case _ =>
|
||||
fail(s"Unexpected argument '$opt'.")
|
||||
}
|
||||
needContinueHandle()
|
||||
}
|
||||
|
||||
override protected def handleUnknown(opt: String): Boolean = {
|
||||
if (!needContinueHandle()) {
|
||||
false
|
||||
} else {
|
||||
printUsageAndExit(-1, opt)
|
||||
false
|
||||
cliArgs.service match {
|
||||
case ServiceControlObject.SERVER =>
|
||||
s"""Parsed arguments:
|
||||
| action ${cliArgs.action}
|
||||
| service ${cliArgs.service}
|
||||
| zkQuorum ${cliArgs.zkQuorum}
|
||||
| namespace ${cliArgs.namespace}
|
||||
| host ${cliArgs.host}
|
||||
| port ${cliArgs.port}
|
||||
| version ${cliArgs.version}
|
||||
| verbose ${cliArgs.verbose}
|
||||
""".stripMargin
|
||||
case ServiceControlObject.ENGINE =>
|
||||
s"""Parsed arguments:
|
||||
| action ${cliArgs.action}
|
||||
| service ${cliArgs.service}
|
||||
| zkQuorum ${cliArgs.zkQuorum}
|
||||
| namespace ${cliArgs.namespace}
|
||||
| user ${cliArgs.user}
|
||||
| host ${cliArgs.host}
|
||||
| port ${cliArgs.port}
|
||||
| version ${cliArgs.version}
|
||||
| verbose ${cliArgs.verbose}
|
||||
""".stripMargin
|
||||
case _ => ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,5 +17,36 @@
|
||||
|
||||
package org.apache.kyuubi.ctl
|
||||
|
||||
private[kyuubi] abstract class ServiceControlCliArgumentsParser
|
||||
extends ServiceControlCliOptionParser
|
||||
import scopt.OParser
|
||||
|
||||
import org.apache.kyuubi.ctl.ServiceControlAction.ServiceControlAction
|
||||
import org.apache.kyuubi.ctl.ServiceControlObject.ServiceControlObject
|
||||
|
||||
private[kyuubi] abstract class ServiceControlCliArgumentsParser {
|
||||
|
||||
/**
|
||||
* Description of available options
|
||||
*/
|
||||
case class CliArguments(
|
||||
action: ServiceControlAction = null,
|
||||
service: ServiceControlObject = ServiceControlObject.SERVER,
|
||||
zkQuorum: String = null,
|
||||
namespace: String = null,
|
||||
user: String = null,
|
||||
host: String = null,
|
||||
port: String = null,
|
||||
version: String = null,
|
||||
verbose: Boolean = false)
|
||||
|
||||
/**
|
||||
* Cli arguments parse rules.
|
||||
*/
|
||||
def parser(): OParser[Unit, CliArguments]
|
||||
|
||||
/**
|
||||
* Parse a list of kyuubi-ctl command line options.
|
||||
*
|
||||
* @throws IllegalArgumentException If an error is found during parsing.
|
||||
*/
|
||||
def parse(args: Seq[String]): Unit
|
||||
}
|
||||
|
||||
@ -45,6 +45,29 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
}
|
||||
}
|
||||
|
||||
/** Check whether the script exits and the given search string is printed. */
|
||||
private def testHelpExit(args: Array[String], searchString: String): Unit = {
|
||||
val logAppender = new LogAppender("test premature exit")
|
||||
withLogAppender(logAppender) {
|
||||
val thread = new Thread {
|
||||
override def run(): Unit = try {
|
||||
new ServiceControlCliArguments(args) {
|
||||
override private[kyuubi] lazy val effectSetup = new KyuubiOEffectSetup {
|
||||
// nothing to do, to handle out stream.
|
||||
override def terminate(exitState: Either[String, Unit]): Unit = ()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
error(e)
|
||||
}
|
||||
}
|
||||
thread.start()
|
||||
thread.join()
|
||||
assert(logAppender.loggingEvents.exists(_.getRenderedMessage.contains(searchString)))
|
||||
}
|
||||
}
|
||||
|
||||
test("test basic kyuubi service arguments parser") {
|
||||
Seq("get", "list", "delete").foreach { op =>
|
||||
Seq("server", "engine").foreach { service =>
|
||||
@ -58,14 +81,14 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
"--version", KYUUBI_VERSION
|
||||
)
|
||||
val opArgs = new ServiceControlCliArguments(args)
|
||||
assert(opArgs.action.toString.equalsIgnoreCase(op))
|
||||
assert(opArgs.service.toString.equalsIgnoreCase(service))
|
||||
assert(opArgs.zkQuorum == zkQuorum)
|
||||
assert(opArgs.namespace == namespace)
|
||||
assert(opArgs.user == user)
|
||||
assert(opArgs.host == host)
|
||||
assert(opArgs.port == port)
|
||||
assert(opArgs.version == KYUUBI_VERSION)
|
||||
assert(opArgs.cliArgs.action.toString.equalsIgnoreCase(op))
|
||||
assert(opArgs.cliArgs.service.toString.equalsIgnoreCase(service))
|
||||
assert(opArgs.cliArgs.zkQuorum == zkQuorum)
|
||||
assert(opArgs.cliArgs.namespace == namespace)
|
||||
assert(opArgs.cliArgs.user == user)
|
||||
assert(opArgs.cliArgs.host == host)
|
||||
assert(opArgs.cliArgs.port == port)
|
||||
assert(opArgs.cliArgs.version == KYUUBI_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,56 +100,36 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
op, service,
|
||||
"--zk-quorum", zkQuorum,
|
||||
"--namespace", s"${namespace}_new",
|
||||
"--user", user,
|
||||
"--host", host,
|
||||
"--port", port,
|
||||
"--version", KYUUBI_VERSION
|
||||
)
|
||||
val opArgs = new ServiceControlCliArguments(args)
|
||||
assert(opArgs.action.toString.equalsIgnoreCase(op))
|
||||
assert(opArgs.service.toString.equalsIgnoreCase(service))
|
||||
assert(opArgs.zkQuorum == zkQuorum)
|
||||
assert(opArgs.namespace == newNamespace)
|
||||
assert(opArgs.user == user)
|
||||
assert(opArgs.host == host)
|
||||
assert(opArgs.port == port)
|
||||
assert(opArgs.version == KYUUBI_VERSION)
|
||||
assert(opArgs.cliArgs.action.toString.equalsIgnoreCase(op))
|
||||
assert(opArgs.cliArgs.service.toString.equalsIgnoreCase(service))
|
||||
assert(opArgs.cliArgs.zkQuorum == zkQuorum)
|
||||
assert(opArgs.cliArgs.namespace == newNamespace)
|
||||
assert(opArgs.cliArgs.host == host)
|
||||
assert(opArgs.cliArgs.port == port)
|
||||
assert(opArgs.cliArgs.version == KYUUBI_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
test("treat --help as action") {
|
||||
val args = Seq("--help")
|
||||
val opArgs = new ServiceControlCliArguments(args)
|
||||
assert(opArgs.action == ServiceControlAction.HELP)
|
||||
assert(opArgs.version == KYUUBI_VERSION)
|
||||
|
||||
val args2 = Seq(
|
||||
"create", "server",
|
||||
s"--user=$user",
|
||||
"--host", host,
|
||||
"--verbose",
|
||||
"--help",
|
||||
"--port", port
|
||||
)
|
||||
val opArgs2 = new ServiceControlCliArguments(args2)
|
||||
assert(opArgs2.action == ServiceControlAction.HELP)
|
||||
assert(opArgs2.user == user)
|
||||
assert(opArgs2.host == host)
|
||||
assert(opArgs2.verbose)
|
||||
}
|
||||
|
||||
test("prints usage on empty input") {
|
||||
testPrematureExit(Array.empty[String], "Usage: kyuubi-ctl")
|
||||
testPrematureExit(Array("--verbose"), "Usage: kyuubi-ctl")
|
||||
testPrematureExit(Array.empty[String], "Must specify action command: [create|get|delete|list].")
|
||||
testPrematureExit(Array("--verbose"), "Must specify action command: [create|get|delete|list].")
|
||||
}
|
||||
|
||||
test("prints error with unrecognized options") {
|
||||
testPrematureExit(Array("create", "--unknown"), "Unknown/unsupported param --unknown")
|
||||
testPrematureExit(Array("--unknown"), "Unknown/unsupported param --unknown")
|
||||
testPrematureExit(Array("create", "--unknown"), "Unknown option --unknown")
|
||||
testPrematureExit(Array("--unknown"), "Unknown option --unknown")
|
||||
}
|
||||
|
||||
test("test invalid arguments") {
|
||||
testPrematureExit(Array("create", "--user"), "Missing argument for option '--user'")
|
||||
// for server, user option is not support
|
||||
testPrematureExit(Array("create", "--user"), "Unknown option --user")
|
||||
// for engine, user option need a value
|
||||
testPrematureExit(Array("get", "engine", "--user"), "Missing value after --user")
|
||||
}
|
||||
|
||||
test("test extra unused arguments") {
|
||||
@ -134,7 +137,7 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
"list",
|
||||
"extraArg1", "extraArg2"
|
||||
)
|
||||
testPrematureExit(args, "Unknown/unsupported param extraArg1")
|
||||
testPrematureExit(args, "Unknown argument 'extraArg1'")
|
||||
}
|
||||
|
||||
test("test list action arguments") {
|
||||
@ -149,7 +152,7 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
"--namespace", namespace
|
||||
)
|
||||
val opArgs = new ServiceControlCliArguments(args2)
|
||||
assert(opArgs.action == ServiceControlAction.LIST)
|
||||
assert(opArgs.cliArgs.action == ServiceControlAction.LIST)
|
||||
}
|
||||
|
||||
test("test get/delete action arguments") {
|
||||
@ -191,32 +194,10 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
"--port", port
|
||||
)
|
||||
val opArgs6 = new ServiceControlCliArguments(args5)
|
||||
assert(opArgs6.action.toString.equalsIgnoreCase(op))
|
||||
assert(opArgs6.cliArgs.action.toString.equalsIgnoreCase(op))
|
||||
}
|
||||
}
|
||||
|
||||
test("test with switches at head") {
|
||||
val args = Seq("--verbose", "list", "engine", "--zk-quorum", zkQuorum, "--namespace",
|
||||
namespace)
|
||||
val opArgs = new ServiceControlCliArguments(args)
|
||||
assert(opArgs.verbose)
|
||||
assert(opArgs.action == ServiceControlAction.LIST)
|
||||
assert(opArgs.service == ServiceControlObject.ENGINE)
|
||||
|
||||
val args2 = Seq("list", "--verbose", "engine", "--zk-quorum", zkQuorum, "--namespace",
|
||||
namespace)
|
||||
val opArgs2 = new ServiceControlCliArguments(args2)
|
||||
assert(opArgs2.verbose)
|
||||
assert(opArgs2.action == ServiceControlAction.LIST)
|
||||
assert(opArgs2.service == ServiceControlObject.ENGINE)
|
||||
|
||||
val args3 = Seq("list", "--verbose", "--help", "engine", "--zk-quorum", zkQuorum,
|
||||
"--namespace", namespace)
|
||||
val opArgs3 = new ServiceControlCliArguments(args3)
|
||||
assert(opArgs3.verbose)
|
||||
assert(opArgs3.action == ServiceControlAction.HELP)
|
||||
}
|
||||
|
||||
test("test with unknown host") {
|
||||
val args = Array(
|
||||
"get", "server",
|
||||
@ -263,14 +244,15 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
"--namespace", newNamespace
|
||||
)
|
||||
val opArgs2 = new ServiceControlCliArguments(args2)
|
||||
assert(opArgs2.action.toString.equalsIgnoreCase(op))
|
||||
assert(opArgs2.cliArgs.action.toString.equalsIgnoreCase(op))
|
||||
|
||||
val args4 = Array(
|
||||
op, "engine",
|
||||
"--zk-quorum", zkQuorum,
|
||||
"--namespace", newNamespace
|
||||
)
|
||||
testPrematureExit(args4, "Only support expose Kyuubi server instance to another domain")
|
||||
// engine is not support, expect scopt print Unknown argument.
|
||||
testPrematureExit(args4, "Unknown argument 'engine'")
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +262,92 @@ class ServiceControlCliArgumentsSuite extends KyuubiFunSuite {
|
||||
"--zk-quorum", zkQuorum
|
||||
)
|
||||
val opArgs = new ServiceControlCliArguments(args)
|
||||
assert(opArgs.namespace == namespace)
|
||||
assert(opArgs.version == KYUUBI_VERSION)
|
||||
assert(opArgs.cliArgs.namespace == namespace)
|
||||
assert(opArgs.cliArgs.version == KYUUBI_VERSION)
|
||||
}
|
||||
|
||||
test("test use short options") {
|
||||
Seq("get", "list", "delete").foreach { op =>
|
||||
Seq("server", "engine").foreach { service =>
|
||||
val args = Seq(
|
||||
op, service,
|
||||
"-zk", zkQuorum,
|
||||
"-n", namespace,
|
||||
"-u", user,
|
||||
"-s", host,
|
||||
"-p", port,
|
||||
"-v", KYUUBI_VERSION
|
||||
)
|
||||
val opArgs = new ServiceControlCliArguments(args)
|
||||
assert(opArgs.cliArgs.action.toString.equalsIgnoreCase(op))
|
||||
assert(opArgs.cliArgs.service.toString.equalsIgnoreCase(service))
|
||||
assert(opArgs.cliArgs.zkQuorum == zkQuorum)
|
||||
assert(opArgs.cliArgs.namespace == namespace)
|
||||
assert(opArgs.cliArgs.user == user)
|
||||
assert(opArgs.cliArgs.host == host)
|
||||
assert(opArgs.cliArgs.port == port)
|
||||
assert(opArgs.cliArgs.version == KYUUBI_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
// test verbose
|
||||
val args2 = Array(
|
||||
"list",
|
||||
"-zk", zkQuorum,
|
||||
"-b"
|
||||
)
|
||||
val opArgs3 = new ServiceControlCliArguments(args2)
|
||||
assert(opArgs3.cliArgs.verbose)
|
||||
}
|
||||
|
||||
test("test --help") {
|
||||
// some string is too long for check style
|
||||
val zkHelpString = "The connection string for the zookeeper ensemble, using zk quorum manually."
|
||||
val versionHelpString = "Using the compiled KYUUBI_VERSION default," +
|
||||
" change it if the active service is running in another."
|
||||
val helpString =
|
||||
s"""kyuubi $KYUUBI_VERSION
|
||||
|Usage: kyuubi-ctl [create|get|delete|list] [options]
|
||||
|
|
||||
| -zk, --zk-quorum <value>
|
||||
| $zkHelpString
|
||||
| -n, --namespace <value> The namespace, using kyuubi-defaults/conf if absent.
|
||||
| -s, --host <value> Hostname or IP address of a service.
|
||||
| -p, --port <value> Listening port of a service.
|
||||
| -v, --version <value> $versionHelpString
|
||||
| -b, --verbose Print additional debug output.
|
||||
|
|
||||
|Command: create [server]
|
||||
|
|
||||
|Command: create server
|
||||
|${"\t"}Expose Kyuubi server instance to another domain.
|
||||
|
|
||||
|Command: get [server|engine] [options]
|
||||
|${"\t"}Get the service/engine node info, host and port needed.
|
||||
|Command: get server
|
||||
|${"\t"}Get Kyuubi server info of domain
|
||||
|Command: get engine
|
||||
|${"\t"}Get Kyuubi engine info belong to a user.
|
||||
| -u, --user <value> The user name this engine belong to.
|
||||
|
|
||||
|Command: delete [server|engine] [options]
|
||||
|${"\t"}Delete the specified service/engine node, host and port needed.
|
||||
|Command: delete server
|
||||
|${"\t"}Delete the specified service node for a domain
|
||||
|Command: delete engine
|
||||
|${"\t"}Delete the specified engine node for user.
|
||||
| -u, --user <value> The user name this engine belong to.
|
||||
|
|
||||
|Command: list [server|engine] [options]
|
||||
|${"\t"}List all the service/engine nodes for a particular domain.
|
||||
|Command: list server
|
||||
|${"\t"}List all the service nodes for a particular domain
|
||||
|Command: list engine
|
||||
|${"\t"}List all the engine nodes for a user
|
||||
| -u, --user <value> The user name this engine belong to.
|
||||
|
|
||||
| -h, --help Show help message and exit.""".stripMargin
|
||||
|
||||
testHelpExit(Array("--help"), helpString)
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ class ServiceControlCliSuite extends KyuubiFunSuite with TestPrematureExit {
|
||||
|
||||
/** Get the rendered service node info without title */
|
||||
private def getRenderedNodesInfoWithoutTitle(nodesInfo: Seq[ServiceNodeInfo],
|
||||
verbose: Boolean): String = {
|
||||
verbose: Boolean): String = {
|
||||
val renderedInfo = renderServiceNodesInfo("", nodesInfo, verbose)
|
||||
if (verbose) {
|
||||
renderedInfo.substring(renderedInfo.indexOf("|"))
|
||||
@ -130,11 +130,6 @@ class ServiceControlCliSuite extends KyuubiFunSuite with TestPrematureExit {
|
||||
}
|
||||
}
|
||||
|
||||
test("test help") {
|
||||
val args = Array("--help")
|
||||
testPrematureExit(args, "Usage: kyuubi-ctl")
|
||||
}
|
||||
|
||||
test("test expose to same namespace or not specified namespace") {
|
||||
conf
|
||||
.unset(KyuubiConf.SERVER_KEYTAB)
|
||||
|
||||
7
pom.xml
7
pom.xml
@ -84,6 +84,7 @@
|
||||
<jetty.version>9.4.41.v20210516</jetty.version>
|
||||
<ldapsdk.version>5.1.4</ldapsdk.version>
|
||||
<prometheus.version>0.10.0</prometheus.version>
|
||||
<scopt.version>4.0.1</scopt.version>
|
||||
<spark.version>3.1.2</spark.version>
|
||||
<spark.archive.name>spark-${spark.version}-bin-hadoop${hadoop.binary.version}.tgz</spark.archive.name>
|
||||
<spark.archive.mirror>https://archive.apache.org/dist/spark/spark-${spark.version}</spark.archive.mirror>
|
||||
@ -1168,6 +1169,12 @@
|
||||
<artifactId>activation</artifactId>
|
||||
<version>${javax-activation.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.scopt</groupId>
|
||||
<artifactId>scopt_${scala.binary.version}</artifactId>
|
||||
<version>${scopt.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user