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