Skip to content

Commit a462230

Browse files
lightning-Lulysses-you
authored andcommitted
[KYUUBI #2765][SUB-TASK][KPIP-4] Refactor current kyuubi-ctl
### _Why are the changes needed?_ To close #2765 ### _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 #2766 from lightning-L/kyuubi-2628. Closes #2765 749eebd [Tianlin Liao] rename ServiceControlXXX to ControlXXX 842265f [Tianlin Liao] [KYUUBI #2765] refactor current kyuubi-ctl Authored-by: Tianlin Liao <tiliao@ebay.com> Signed-off-by: ulysses-you <ulyssesyou@apache.org>
1 parent 80d45e4 commit a462230

16 files changed

+902
-640
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.kyuubi.ctl
18+
19+
import org.apache.kyuubi.ctl.ServiceControlAction.ControlAction
20+
import org.apache.kyuubi.ctl.ServiceControlObject.ControlObject
21+
22+
private[ctl] object ServiceControlAction extends Enumeration {
23+
type ControlAction = Value
24+
val CREATE, GET, DELETE, LIST = Value
25+
}
26+
27+
private[ctl] object ServiceControlObject extends Enumeration {
28+
type ControlObject = Value
29+
val SERVER, ENGINE = Value
30+
}
31+
32+
case class CliConfig(
33+
action: ControlAction = null,
34+
service: ControlObject = ServiceControlObject.SERVER,
35+
commonOpts: CommonOpts = CommonOpts(),
36+
engineOpts: EngineOpts = EngineOpts())
37+
38+
case class CommonOpts(
39+
zkQuorum: String = null,
40+
namespace: String = null,
41+
host: String = null,
42+
port: String = null,
43+
version: String = null,
44+
verbose: Boolean = false)
45+
46+
case class EngineOpts(
47+
user: String = null,
48+
engineType: String = null,
49+
engineSubdomain: String = null,
50+
engineShareLevel: String = null)
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.kyuubi.ctl
18+
19+
import scopt.{OParser, OParserBuilder}
20+
21+
import org.apache.kyuubi.KYUUBI_VERSION
22+
23+
object CommandLine {
24+
25+
def getOptionParser(builder: OParserBuilder[CliConfig]): OParser[Unit, CliConfig] = {
26+
import builder._
27+
OParser.sequence(
28+
programName("kyuubi-ctl"),
29+
head("kyuubi", KYUUBI_VERSION),
30+
common(builder),
31+
create(builder),
32+
get(builder),
33+
delete(builder),
34+
list(builder),
35+
checkConfig(f => {
36+
if (f.action == null) failure("Must specify action command: [create|get|delete|list].")
37+
else success
38+
}),
39+
note(""),
40+
help('h', "help").text("Show help message and exit."))
41+
}
42+
43+
private def common(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
44+
import builder._
45+
OParser.sequence(
46+
opt[String]("zk-quorum").abbr("zk")
47+
.action((v, c) => c.copy(commonOpts = c.commonOpts.copy(zkQuorum = v)))
48+
.text("The connection string for the zookeeper ensemble," +
49+
" using zk quorum manually."),
50+
opt[String]('n', "namespace")
51+
.action((v, c) => c.copy(commonOpts = c.commonOpts.copy(namespace = v)))
52+
.text("The namespace, using kyuubi-defaults/conf if absent."),
53+
opt[String]('s', "host")
54+
.action((v, c) => c.copy(commonOpts = c.commonOpts.copy(host = v)))
55+
.text("Hostname or IP address of a service."),
56+
opt[String]('p', "port")
57+
.action((v, c) => c.copy(commonOpts = c.commonOpts.copy(port = v)))
58+
.text("Listening port of a service."),
59+
opt[String]('v', "version")
60+
.action((v, c) => c.copy(commonOpts = c.commonOpts.copy(version = v)))
61+
.text("Using the compiled KYUUBI_VERSION default," +
62+
" change it if the active service is running in another."),
63+
opt[Unit]('b', "verbose")
64+
.action((_, c) => c.copy(commonOpts = c.commonOpts.copy(verbose = true)))
65+
.text("Print additional debug output."))
66+
}
67+
68+
private def create(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
69+
import builder._
70+
OParser.sequence(
71+
note(""),
72+
cmd("create")
73+
.action((_, c) => c.copy(action = ServiceControlAction.CREATE))
74+
.children(
75+
serverCmd(builder).text("\tExpose Kyuubi server instance to another domain.")))
76+
}
77+
78+
private def get(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
79+
import builder._
80+
OParser.sequence(
81+
note(""),
82+
cmd("get")
83+
.text("\tGet the service/engine node info, host and port needed.")
84+
.action((_, c) => c.copy(action = ServiceControlAction.GET))
85+
.children(
86+
serverCmd(builder).text("\tGet Kyuubi server info of domain"),
87+
engineCmd(builder).text("\tGet Kyuubi engine info belong to a user.")))
88+
89+
}
90+
91+
private def delete(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
92+
import builder._
93+
OParser.sequence(
94+
note(""),
95+
cmd("delete")
96+
.text("\tDelete the specified service/engine node, host and port needed.")
97+
.action((_, c) => c.copy(action = ServiceControlAction.DELETE))
98+
.children(
99+
serverCmd(builder).text("\tDelete the specified service node for a domain"),
100+
engineCmd(builder).text("\tDelete the specified engine node for user.")))
101+
102+
}
103+
104+
private def list(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
105+
import builder._
106+
OParser.sequence(
107+
note(""),
108+
cmd("list")
109+
.text("\tList all the service/engine nodes for a particular domain.")
110+
.action((_, c) => c.copy(action = ServiceControlAction.LIST))
111+
.children(
112+
serverCmd(builder).text("\tList all the service nodes for a particular domain"),
113+
engineCmd(builder).text("\tList all the engine nodes for a user")))
114+
115+
}
116+
117+
private def serverCmd(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
118+
import builder._
119+
cmd("server").action((_, c) => c.copy(service = ServiceControlObject.SERVER))
120+
}
121+
122+
private def engineCmd(builder: OParserBuilder[CliConfig]): OParser[_, CliConfig] = {
123+
import builder._
124+
cmd("engine").action((_, c) => c.copy(service = ServiceControlObject.ENGINE))
125+
.children(
126+
opt[String]('u', "user")
127+
.action((v, c) => c.copy(engineOpts = c.engineOpts.copy(user = v)))
128+
.text("The user name this engine belong to."),
129+
opt[String]("engine-type").abbr("et")
130+
.action((v, c) => c.copy(engineOpts = c.engineOpts.copy(engineType = v)))
131+
.text("The engine type this engine belong to."),
132+
opt[String]("engine-subdomain").abbr("es")
133+
.action((v, c) => c.copy(engineOpts = c.engineOpts.copy(engineSubdomain = v)))
134+
.text("The engine subdomain this engine belong to."),
135+
opt[String]("engine-share-level").abbr("esl")
136+
.action((v, c) => c.copy(engineOpts = c.engineOpts.copy(engineShareLevel = v)))
137+
.text("The engine share level this engine belong to."))
138+
}
139+
140+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.kyuubi.ctl
19+
20+
import org.apache.kyuubi.Logging
21+
22+
/**
23+
* Main gateway of launching a Kyuubi Ctl action.
24+
*/
25+
private[kyuubi] class ControlCli extends Logging {
26+
27+
def doAction(args: Array[String]): Unit = {
28+
// Initialize logging if it hasn't been done yet.
29+
// Set log level ERROR
30+
initializeLoggerIfNecessary(true)
31+
32+
val ctlArgs = parseArguments(args)
33+
34+
// when parse failed, exit
35+
if (ctlArgs.cliArgs == null) {
36+
sys.exit(1)
37+
}
38+
39+
val verbose = ctlArgs.cliArgs.commonOpts.verbose
40+
if (verbose) {
41+
super.info(ctlArgs.toString)
42+
}
43+
44+
ctlArgs.command.run()
45+
}
46+
47+
protected def parseArguments(args: Array[String]): ControlCliArguments = {
48+
new ControlCliArguments(args)
49+
}
50+
51+
}
52+
53+
object ControlCli extends CommandLineUtils with Logging {
54+
override def main(args: Array[String]): Unit = {
55+
val ctl = new ControlCli() {
56+
self =>
57+
override protected def parseArguments(args: Array[String]): ControlCliArguments = {
58+
new ControlCliArguments(args) {
59+
override def info(msg: => Any): Unit = self.info(msg)
60+
61+
override def warn(msg: => Any): Unit = self.warn(msg)
62+
63+
override def error(msg: => Any): Unit = self.error(msg)
64+
65+
override private[kyuubi] lazy val effectSetup = new KyuubiOEffectSetup {
66+
override def displayToOut(msg: String): Unit = self.info(msg)
67+
68+
override def displayToErr(msg: String): Unit = self.info(msg)
69+
70+
override def reportError(msg: String): Unit = self.info(msg)
71+
72+
override def reportWarning(msg: String): Unit = self.warn(msg)
73+
}
74+
}
75+
}
76+
77+
override def info(msg: => Any): Unit = printMessage(msg)
78+
79+
override def warn(msg: => Any): Unit = printMessage(s"Warning: $msg")
80+
81+
override def error(msg: => Any): Unit = printMessage(s"Error: $msg")
82+
83+
override def doAction(args: Array[String]): Unit = {
84+
try {
85+
super.doAction(args)
86+
exitFn(0)
87+
} catch {
88+
case e: ControlCliException =>
89+
exitFn(e.exitCode)
90+
}
91+
}
92+
}
93+
94+
ctl.doAction(args)
95+
}
96+
97+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.kyuubi.ctl
19+
20+
import scopt.OParser
21+
22+
import org.apache.kyuubi.Logging
23+
import org.apache.kyuubi.ctl.cmd._
24+
25+
class ControlCliArguments(args: Seq[String], env: Map[String, String] = sys.env)
26+
extends ControlCliArgumentsParser with Logging {
27+
28+
var cliArgs: CliConfig = null
29+
30+
var command: Command = null
31+
32+
// Set parameters from command line arguments
33+
parse(args)
34+
35+
lazy val cliParser = parser()
36+
37+
override def parser(): OParser[Unit, CliConfig] = {
38+
val builder = OParser.builder[CliConfig]
39+
CommandLine.getOptionParser(builder)
40+
}
41+
42+
private[kyuubi] lazy val effectSetup = new KyuubiOEffectSetup
43+
44+
override def parse(args: Seq[String]): Unit = {
45+
OParser.runParser(cliParser, args, CliConfig()) match {
46+
case (result, effects) =>
47+
OParser.runEffects(effects, effectSetup)
48+
result match {
49+
case Some(arguments) =>
50+
command = getCommand(arguments)
51+
command.preProcess()
52+
cliArgs = command.cliArgs
53+
case _ =>
54+
// arguments are bad, exit
55+
}
56+
}
57+
}
58+
59+
private def getCommand(cliArgs: CliConfig): Command = {
60+
cliArgs.action match {
61+
case ServiceControlAction.CREATE => new CreateCommand(cliArgs)
62+
case ServiceControlAction.GET => new GetCommand(cliArgs)
63+
case ServiceControlAction.DELETE => new DeleteCommand(cliArgs)
64+
case ServiceControlAction.LIST => new ListCommand(cliArgs)
65+
case _ => null
66+
}
67+
}
68+
69+
override def toString: String = {
70+
cliArgs.service match {
71+
case ServiceControlObject.SERVER =>
72+
s"""Parsed arguments:
73+
| action ${cliArgs.action}
74+
| service ${cliArgs.service}
75+
| zkQuorum ${cliArgs.commonOpts.zkQuorum}
76+
| namespace ${cliArgs.commonOpts.namespace}
77+
| host ${cliArgs.commonOpts.host}
78+
| port ${cliArgs.commonOpts.port}
79+
| version ${cliArgs.commonOpts.version}
80+
| verbose ${cliArgs.commonOpts.verbose}
81+
""".stripMargin
82+
case ServiceControlObject.ENGINE =>
83+
s"""Parsed arguments:
84+
| action ${cliArgs.action}
85+
| service ${cliArgs.service}
86+
| zkQuorum ${cliArgs.commonOpts.zkQuorum}
87+
| namespace ${cliArgs.commonOpts.namespace}
88+
| user ${cliArgs.engineOpts.user}
89+
| host ${cliArgs.commonOpts.host}
90+
| port ${cliArgs.commonOpts.port}
91+
| version ${cliArgs.commonOpts.version}
92+
| verbose ${cliArgs.commonOpts.verbose}
93+
""".stripMargin
94+
case _ => ""
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)