]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox_backup_manager/disk.rs
replace Userid with Authid
[proxmox-backup.git] / src / bin / proxmox_backup_manager / disk.rs
1 use anyhow::{bail, Error};
2 use serde_json::Value;
3
4 use proxmox::api::{api, cli::*, RpcEnvironment, ApiHandler};
5
6 use proxmox_backup::tools::disks::{
7 FileSystemType,
8 SmartAttribute,
9 complete_disk_name,
10 };
11
12 use proxmox_backup::api2::node::disks::{
13 zfs::DISK_LIST_SCHEMA,
14 zfs::ZFS_ASHIFT_SCHEMA,
15 zfs::ZfsRaidLevel,
16 zfs::ZfsCompressionType,
17 };
18
19 use proxmox_backup::api2::{self, types::* };
20
21 #[api(
22 input: {
23 properties: {
24 "output-format": {
25 schema: OUTPUT_FORMAT,
26 optional: true,
27 },
28 }
29 }
30 )]
31 /// Local disk list.
32 fn list_disks(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
33
34 let output_format = get_output_format(&param);
35
36 param["node"] = "localhost".into();
37
38 let info = &api2::node::disks::API_METHOD_LIST_DISKS;
39 let mut data = match info.handler {
40 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
41 _ => unreachable!(),
42 };
43
44 let render_wearout = |value: &Value, _record: &Value| -> Result<String, Error> {
45 match value.as_f64() {
46 Some(value) => Ok(format!("{:.2} %", if value <= 100.0 { 100.0 - value } else { 0.0 })),
47 None => Ok(String::from("-")),
48 }
49 };
50
51 let options = default_table_format_options()
52 .column(ColumnConfig::new("name"))
53 .column(ColumnConfig::new("used"))
54 .column(ColumnConfig::new("gpt"))
55 .column(ColumnConfig::new("disk-type"))
56 .column(ColumnConfig::new("size"))
57 .column(ColumnConfig::new("model"))
58 .column(ColumnConfig::new("wearout").renderer(render_wearout))
59 .column(ColumnConfig::new("status"))
60 ;
61
62 format_and_print_result_full(&mut data, info.returns, &output_format, &options);
63
64 Ok(Value::Null)
65 }
66
67 #[api(
68 input: {
69 properties: {
70 disk: {
71 schema: BLOCKDEVICE_NAME_SCHEMA,
72 },
73 "output-format": {
74 schema: OUTPUT_FORMAT,
75 optional: true,
76 },
77 }
78 },
79 returns: {
80 description: "SMART attributes.",
81 type: Array,
82 items: {
83 type: SmartAttribute,
84 },
85 }
86 )]
87 /// Show SMART attributes.
88 fn smart_attributes(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
89
90 let output_format = get_output_format(&param);
91
92 param["node"] = "localhost".into();
93
94 let info = &api2::node::disks::API_METHOD_SMART_STATUS;
95 let mut data = match info.handler {
96 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
97 _ => unreachable!(),
98 };
99
100 let mut data = data["attributes"].take();
101
102 let options = default_table_format_options();
103 format_and_print_result_full(&mut data, API_METHOD_SMART_ATTRIBUTES.returns, &output_format, &options);
104
105 Ok(Value::Null)
106 }
107
108 #[api(
109 input: {
110 properties: {
111 disk: {
112 schema: BLOCKDEVICE_NAME_SCHEMA,
113 },
114 uuid: {
115 description: "UUID for the GPT table.",
116 type: String,
117 optional: true,
118 max_length: 36,
119 },
120 },
121 },
122 )]
123 /// Initialize empty Disk with GPT
124 async fn initialize_disk(
125 mut param: Value,
126 rpcenv: &mut dyn RpcEnvironment,
127 ) -> Result<Value, Error> {
128
129 param["node"] = "localhost".into();
130
131 let info = &api2::node::disks::API_METHOD_INITIALIZE_DISK;
132 let result = match info.handler {
133 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
134 _ => unreachable!(),
135 };
136
137 crate::wait_for_local_worker(result.as_str().unwrap()).await?;
138
139 Ok(Value::Null)
140 }
141
142 #[api(
143 input: {
144 properties: {
145 name: {
146 schema: DATASTORE_SCHEMA,
147 },
148 devices: {
149 schema: DISK_LIST_SCHEMA,
150 },
151 raidlevel: {
152 type: ZfsRaidLevel,
153 },
154 ashift: {
155 schema: ZFS_ASHIFT_SCHEMA,
156 optional: true,
157 },
158 compression: {
159 type: ZfsCompressionType,
160 optional: true,
161 },
162 "add-datastore": {
163 description: "Configure a datastore using the zpool.",
164 type: bool,
165 optional: true,
166 },
167 },
168 },
169 )]
170 /// create a zfs pool
171 async fn create_zpool(
172 mut param: Value,
173 rpcenv: &mut dyn RpcEnvironment,
174 ) -> Result<Value, Error> {
175
176 param["node"] = "localhost".into();
177
178 let info = &api2::node::disks::zfs::API_METHOD_CREATE_ZPOOL;
179 let result = match info.handler {
180 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
181 _ => unreachable!(),
182 };
183
184 crate::wait_for_local_worker(result.as_str().unwrap()).await?;
185
186 Ok(Value::Null)
187 }
188
189 #[api(
190 input: {
191 properties: {
192 "output-format": {
193 schema: OUTPUT_FORMAT,
194 optional: true,
195 },
196 }
197 }
198 )]
199 /// Local zfs pools.
200 fn list_zpools(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
201
202 let output_format = get_output_format(&param);
203
204 param["node"] = "localhost".into();
205
206 let info = &api2::node::disks::zfs::API_METHOD_LIST_ZPOOLS;
207 let mut data = match info.handler {
208 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
209 _ => unreachable!(),
210 };
211
212 let render_usage = |value: &Value, record: &Value| -> Result<String, Error> {
213 let value = value.as_u64().unwrap_or(0);
214 let size = match record["size"].as_u64() {
215 Some(size) => size,
216 None => bail!("missing size property"),
217 };
218 if size == 0 {
219 bail!("got zero size");
220 }
221 Ok(format!("{:.2} %", (value as f64)/(size as f64)))
222 };
223
224 let options = default_table_format_options()
225 .column(ColumnConfig::new("name"))
226 .column(ColumnConfig::new("size"))
227 .column(ColumnConfig::new("alloc").right_align(true).renderer(render_usage))
228 .column(ColumnConfig::new("health"));
229
230 format_and_print_result_full(&mut data, info.returns, &output_format, &options);
231
232 Ok(Value::Null)
233 }
234
235 pub fn zpool_commands() -> CommandLineInterface {
236
237 let cmd_def = CliCommandMap::new()
238 .insert("list", CliCommand::new(&API_METHOD_LIST_ZPOOLS))
239 .insert("create",
240 CliCommand::new(&API_METHOD_CREATE_ZPOOL)
241 .arg_param(&["name"])
242 .completion_cb("devices", complete_disk_name) // fixme: complete the list
243 );
244
245 cmd_def.into()
246 }
247
248 #[api(
249 input: {
250 properties: {
251 "output-format": {
252 schema: OUTPUT_FORMAT,
253 optional: true,
254 },
255 }
256 }
257 )]
258 /// List systemd datastore mount units.
259 fn list_datastore_mounts(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
260
261 let output_format = get_output_format(&param);
262
263 param["node"] = "localhost".into();
264
265 let info = &api2::node::disks::directory::API_METHOD_LIST_DATASTORE_MOUNTS;
266 let mut data = match info.handler {
267 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
268 _ => unreachable!(),
269 };
270
271 let options = default_table_format_options()
272 .column(ColumnConfig::new("path"))
273 .column(ColumnConfig::new("device"))
274 .column(ColumnConfig::new("filesystem"))
275 .column(ColumnConfig::new("options"));
276
277 format_and_print_result_full(&mut data, info.returns, &output_format, &options);
278
279 Ok(Value::Null)
280 }
281
282 #[api(
283 input: {
284 properties: {
285 name: {
286 schema: DATASTORE_SCHEMA,
287 },
288 disk: {
289 schema: BLOCKDEVICE_NAME_SCHEMA,
290 },
291 "add-datastore": {
292 description: "Configure a datastore using the directory.",
293 type: bool,
294 optional: true,
295 },
296 filesystem: {
297 type: FileSystemType,
298 optional: true,
299 },
300 },
301 },
302 )]
303 /// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.
304 async fn create_datastore_disk(
305 mut param: Value,
306 rpcenv: &mut dyn RpcEnvironment,
307 ) -> Result<Value, Error> {
308
309 param["node"] = "localhost".into();
310
311 let info = &api2::node::disks::directory::API_METHOD_CREATE_DATASTORE_DISK;
312 let result = match info.handler {
313 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
314 _ => unreachable!(),
315 };
316
317 crate::wait_for_local_worker(result.as_str().unwrap()).await?;
318
319 Ok(Value::Null)
320 }
321
322 pub fn filesystem_commands() -> CommandLineInterface {
323
324 let cmd_def = CliCommandMap::new()
325 .insert("list", CliCommand::new(&API_METHOD_LIST_DATASTORE_MOUNTS))
326 .insert("create",
327 CliCommand::new(&API_METHOD_CREATE_DATASTORE_DISK)
328 .arg_param(&["name"])
329 .completion_cb("disk", complete_disk_name)
330 );
331
332 cmd_def.into()
333 }
334
335 pub fn disk_commands() -> CommandLineInterface {
336
337 let cmd_def = CliCommandMap::new()
338 .insert("list", CliCommand::new(&API_METHOD_LIST_DISKS))
339 .insert("smart-attributes",
340 CliCommand::new(&API_METHOD_SMART_ATTRIBUTES)
341 .arg_param(&["disk"])
342 .completion_cb("disk", complete_disk_name)
343 )
344 .insert("fs", filesystem_commands())
345 .insert("zpool", zpool_commands())
346 .insert("initialize",
347 CliCommand::new(&API_METHOD_INITIALIZE_DISK)
348 .arg_param(&["disk"])
349 .completion_cb("disk", complete_disk_name)
350 );
351
352 cmd_def.into()
353 }