Skip to content

Commit baf9725

Browse files
authored
feat: add Lua admin client support and metrics subcommand to metactl (#18461)
* feat: add Lua admin client support and metrics subcommand to metactl - Add MetricsArgs and metrics subcommand to metactl CLI - Implement LuaAdminClient with admin API methods (metrics, status, transfer_leader, etc.) - Add new_admin_client function to Lua environment - Add comprehensive test suite for Lua admin client functionality - Update utils.py to improve error handling in run_command * M tests/metactl/subcommands/cmd_metrics.py
1 parent 082edcd commit baf9725

File tree

7 files changed

+467
-5
lines changed

7 files changed

+467
-5
lines changed

โ€Žsrc/meta/binaries/metactl/main.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use databend_common_meta_control::args::GlobalArgs;
3434
use databend_common_meta_control::args::ImportArgs;
3535
use databend_common_meta_control::args::ListFeatures;
3636
use databend_common_meta_control::args::LuaArgs;
37+
use databend_common_meta_control::args::MetricsArgs;
3738
use databend_common_meta_control::args::SetFeature;
3839
use databend_common_meta_control::args::StatusArgs;
3940
use databend_common_meta_control::args::TransferLeaderArgs;
@@ -255,6 +256,26 @@ impl App {
255256
Ok(())
256257
}
257258

259+
async fn get_metrics(&self, args: &MetricsArgs) -> anyhow::Result<()> {
260+
let lua_script = format!(
261+
r#"
262+
local admin_client = metactl.new_admin_client("{}")
263+
local metrics, err = admin_client:metrics()
264+
if err then
265+
return nil, err
266+
end
267+
print(metrics)
268+
return metrics, nil
269+
"#,
270+
args.admin_api_address
271+
);
272+
273+
match lua_support::run_lua_script_with_result(&lua_script).await? {
274+
Ok(_result) => Ok(()),
275+
Err(error_msg) => Err(anyhow::anyhow!("Failed to get metrics: {}", error_msg)),
276+
}
277+
}
278+
258279
fn new_grpc_client(&self, addresses: Vec<String>) -> Result<Arc<ClientHandle>, CreationError> {
259280
lua_support::new_grpc_client(addresses)
260281
}
@@ -274,6 +295,7 @@ enum CtlCommand {
274295
Upsert(UpsertArgs),
275296
Get(GetArgs),
276297
Lua(LuaArgs),
298+
Metrics(MetricsArgs),
277299
}
278300

279301
/// Usage:
@@ -348,6 +370,9 @@ async fn main() -> anyhow::Result<()> {
348370
CtlCommand::Lua(args) => {
349371
app.run_lua(args).await?;
350372
}
373+
CtlCommand::Metrics(args) => {
374+
app.get_metrics(args).await?;
375+
}
351376
},
352377
// for backward compatibility
353378
None => {

โ€Žsrc/meta/control/src/admin.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use databend_meta::api::http::v1::features::FeatureResponse;
1616
use reqwest::Client;
1717
use serde::Deserialize;
18+
use serde::Serialize;
1819

1920
pub struct MetaAdminClient {
2021
client: Client,
@@ -135,14 +136,31 @@ impl MetaAdminClient {
135136
Err(anyhow::anyhow!("status code: {}, msg: {}", status, msg))
136137
}
137138
}
139+
140+
pub async fn get_metrics(&self) -> anyhow::Result<String> {
141+
let resp = self
142+
.client
143+
.get(format!("{}/v1/metrics", self.endpoint))
144+
.send()
145+
.await?;
146+
let status = resp.status();
147+
if status.is_success() {
148+
let result = resp.text().await?;
149+
Ok(result)
150+
} else {
151+
let data = resp.bytes().await?;
152+
let msg = String::from_utf8_lossy(&data);
153+
Err(anyhow::anyhow!("status code: {}, msg: {}", status, msg))
154+
}
155+
}
138156
}
139157

140-
#[derive(Deserialize, Debug)]
158+
#[derive(Serialize, Deserialize, Debug)]
141159
pub struct AdminStatusResponse {
142160
pub name: String,
143161
}
144162

145-
#[derive(Deserialize, Debug)]
163+
#[derive(Serialize, Deserialize, Debug)]
146164
pub struct AdminTransferLeaderResponse {
147165
pub from: u64,
148166
pub to: u64,

โ€Žsrc/meta/control/src/args.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,9 @@ pub struct LuaArgs {
270270
#[clap(long)]
271271
pub file: Option<String>,
272272
}
273+
274+
#[derive(Debug, Clone, Deserialize, Args)]
275+
pub struct MetricsArgs {
276+
#[clap(long, default_value = "127.0.0.1:28002")]
277+
pub admin_api_address: String,
278+
}

โ€Žsrc/meta/control/src/lua_support.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ use mlua::UserDataMethods;
2929
use mlua::Value;
3030
use tokio::time;
3131

32+
use crate::admin::MetaAdminClient;
33+
3234
const LUA_UTIL: &str = include_str!("../lua_util.lua");
3335

3436
pub struct LuaGrpcClient {
@@ -69,6 +71,77 @@ impl UserData for LuaGrpcClient {
6971
}
7072
}
7173

74+
pub struct LuaAdminClient {
75+
client: MetaAdminClient,
76+
}
77+
78+
impl LuaAdminClient {
79+
pub fn new(client: MetaAdminClient) -> Self {
80+
Self { client }
81+
}
82+
}
83+
84+
impl UserData for LuaAdminClient {
85+
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
86+
methods.add_async_method("metrics", |_lua, this, ()| async move {
87+
match this.client.get_metrics().await {
88+
Ok(metrics) => Ok((Some(metrics), None::<String>)),
89+
Err(e) => Ok((None::<String>, Some(format!("Admin API error: {}", e)))),
90+
}
91+
});
92+
93+
methods.add_async_method("status", |lua, this, ()| async move {
94+
match this.client.status().await {
95+
Ok(result) => match lua.to_value(&result) {
96+
Ok(lua_value) => Ok((Some(lua_value), None::<String>)),
97+
Err(e) => Ok((None::<Value>, Some(format!("Lua conversion error: {}", e)))),
98+
},
99+
Err(e) => Ok((None::<Value>, Some(format!("Admin API error: {}", e)))),
100+
}
101+
});
102+
103+
methods.add_async_method("transfer_leader", |lua, this, to: Option<u64>| async move {
104+
match this.client.transfer_leader(to).await {
105+
Ok(result) => match lua.to_value(&result) {
106+
Ok(lua_value) => Ok((Some(lua_value), None::<String>)),
107+
Err(e) => Ok((None::<Value>, Some(format!("Lua conversion error: {}", e)))),
108+
},
109+
Err(e) => Ok((None::<Value>, Some(format!("Admin API error: {}", e)))),
110+
}
111+
});
112+
113+
methods.add_async_method("trigger_snapshot", |_lua, this, ()| async move {
114+
match this.client.trigger_snapshot().await {
115+
Ok(_) => Ok((Some(true), None::<String>)),
116+
Err(e) => Ok((None::<bool>, Some(format!("Admin API error: {}", e)))),
117+
}
118+
});
119+
120+
methods.add_async_method("list_features", |lua, this, ()| async move {
121+
match this.client.list_features().await {
122+
Ok(result) => match lua.to_value(&result) {
123+
Ok(lua_value) => Ok((Some(lua_value), None::<String>)),
124+
Err(e) => Ok((None::<Value>, Some(format!("Lua conversion error: {}", e)))),
125+
},
126+
Err(e) => Ok((None::<Value>, Some(format!("Admin API error: {}", e)))),
127+
}
128+
});
129+
130+
methods.add_async_method(
131+
"set_feature",
132+
|lua, this, (feature, enable): (String, bool)| async move {
133+
match this.client.set_feature(&feature, enable).await {
134+
Ok(result) => match lua.to_value(&result) {
135+
Ok(lua_value) => Ok((Some(lua_value), None::<String>)),
136+
Err(e) => Ok((None::<Value>, Some(format!("Lua conversion error: {}", e)))),
137+
},
138+
Err(e) => Ok((None::<Value>, Some(format!("Admin API error: {}", e)))),
139+
}
140+
},
141+
);
142+
}
143+
}
144+
72145
pub struct LuaTask {
73146
handle: Rc<RefCell<Option<tokio::task::JoinHandle<mlua::Value>>>>,
74147
}
@@ -121,6 +194,18 @@ pub fn setup_lua_environment(lua: &Lua) -> anyhow::Result<()> {
121194
.set("new_grpc_client", new_grpc_client)
122195
.map_err(|e| anyhow::anyhow!("Failed to register new_grpc_client: {}", e))?;
123196

197+
// Register new_admin_client function
198+
let new_admin_client = lua
199+
.create_function(|_lua, address: String| {
200+
let client = MetaAdminClient::new(&address);
201+
Ok(LuaAdminClient::new(client))
202+
})
203+
.map_err(|e| anyhow::anyhow!("Failed to create new_admin_client function: {}", e))?;
204+
205+
metactl_table
206+
.set("new_admin_client", new_admin_client)
207+
.map_err(|e| anyhow::anyhow!("Failed to register new_admin_client: {}", e))?;
208+
124209
// Export NULL constant to metactl namespace
125210
metactl_table
126211
.set("NULL", Value::NULL)
@@ -190,3 +275,69 @@ pub fn new_grpc_client(addresses: Vec<String>) -> Result<Arc<ClientHandle>, Crea
190275
None,
191276
)
192277
}
278+
279+
pub fn new_admin_client(addr: &str) -> MetaAdminClient {
280+
MetaAdminClient::new(addr)
281+
}
282+
283+
pub async fn run_lua_script(script: &str) -> anyhow::Result<()> {
284+
let lua = Lua::new();
285+
286+
setup_lua_environment(&lua)?;
287+
288+
#[allow(clippy::disallowed_types)]
289+
let local = tokio::task::LocalSet::new();
290+
let res = local.run_until(lua.load(script).exec_async()).await;
291+
292+
if let Err(e) = res {
293+
return Err(anyhow::anyhow!("Lua execution error: {}", e));
294+
}
295+
Ok(())
296+
}
297+
298+
pub async fn run_lua_script_with_result(
299+
script: &str,
300+
) -> anyhow::Result<Result<Option<String>, String>> {
301+
let lua = Lua::new();
302+
303+
setup_lua_environment(&lua)?;
304+
305+
#[allow(clippy::disallowed_types)]
306+
let local = tokio::task::LocalSet::new();
307+
let res = local
308+
.run_until(lua.load(script).eval_async::<mlua::MultiValue>())
309+
.await;
310+
311+
match res {
312+
Ok(values) => {
313+
let mut iter = values.iter();
314+
let first = iter.next();
315+
let second = iter.next();
316+
317+
match (first, second) {
318+
// (result, nil) - success with result
319+
(Some(Value::String(s)), Some(Value::Nil)) => match s.to_str() {
320+
Ok(str_val) => Ok(Ok(Some(str_val.to_string()))),
321+
Err(_) => Ok(Err("String conversion error".to_string())),
322+
},
323+
// (nil, nil) - success with no result
324+
(Some(Value::Nil), Some(Value::Nil)) => Ok(Ok(None)),
325+
// (nil, error) - error case
326+
(Some(Value::Nil), Some(Value::String(err))) => match err.to_str() {
327+
Ok(err_str) => Ok(Err(err_str.to_string())),
328+
Err(_) => Ok(Err("Error string conversion failed".to_string())),
329+
},
330+
// Single return value - treat as result
331+
(Some(Value::Nil), None) => Ok(Ok(None)),
332+
(Some(Value::String(s)), None) => match s.to_str() {
333+
Ok(str_val) => Ok(Ok(Some(str_val.to_string()))),
334+
Err(_) => Ok(Err("String conversion error".to_string())),
335+
},
336+
// Other combinations - treat first value as error if second is missing
337+
(Some(other), None) => Ok(Err(format!("{:?}", other))),
338+
_ => Ok(Ok(None)),
339+
}
340+
}
341+
Err(e) => Err(anyhow::anyhow!("Lua execution error: {}", e)),
342+
}
343+
}

0 commit comments

Comments
ย (0)