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