From 707974fdb33765708fc6b6d794ebdbb7d51b850a Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Sun, 7 Jun 2020 10:30:34 +0200 Subject: [PATCH] src/api2/node/disks.rs: implement initgpt API --- src/api2/node/disks.rs | 76 ++++++++++++++++++++++++-- src/bin/proxmox-backup-manager.rs | 18 ++++++ src/bin/proxmox_backup_manager/disk.rs | 39 +++++++++++++ src/tools/disks.rs | 45 ++++++++++++++- 4 files changed, 171 insertions(+), 7 deletions(-) diff --git a/src/api2/node/disks.rs b/src/api2/node/disks.rs index 6ea97abe..c9f5ae7a 100644 --- a/src/api2/node/disks.rs +++ b/src/api2/node/disks.rs @@ -1,17 +1,19 @@ -use anyhow::{Error}; +use anyhow::{bail, Error}; +use serde_json::{json, Value}; -use proxmox::api::{api, Permission}; +use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType}; use proxmox::api::router::{Router, SubdirMap}; use proxmox::{sortable, identity}; use proxmox::{list_subdirs_api_method}; -use crate::config::acl::{PRIV_SYS_AUDIT}; +use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; use crate::tools::disks::{ DiskUsageInfo, DiskUsageType, DiskManage, SmartData, - get_disks, get_smart_data, + get_disks, get_smart_data, get_disk_usage_info, inititialize_gpt_disk, }; +use crate::server::WorkerTask; -use crate::api2::types::{NODE_SCHEMA, BLOCKDEVICE_NAME_SCHEMA}; +use crate::api2::types::{UPID_SCHEMA, NODE_SCHEMA, BLOCKDEVICE_NAME_SCHEMA}; #[api( protected: true, @@ -101,9 +103,71 @@ pub fn smart_status( get_smart_data(&disk, healthonly) } +#[api( + protected: true, + input: { + properties: { + node: { + schema: NODE_SCHEMA, + }, + disk: { + schema: BLOCKDEVICE_NAME_SCHEMA, + }, + uuid: { + description: "UUID for the GPT table.", + type: String, + optional: true, + max_length: 36, + }, + }, + }, + returns: { + schema: UPID_SCHEMA, + }, + access: { + permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_MODIFY, false), + }, +)] +/// Initialize empty Disk with GPT +pub fn initialize_disk( + disk: String, + uuid: Option, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + + let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false }; + + let username = rpcenv.get_user().unwrap(); + + let info = get_disk_usage_info(&disk, true)?; + + if info.used != DiskUsageType::Unused { + bail!("disk '{}' is already in use.", disk); + } + + let upid_str = WorkerTask::new_thread( + "diskinit", Some(disk.clone()), &username.clone(), to_stdout, move |worker| + { + worker.log(format!("initialize disk {}", disk)); + + let disk_manager = DiskManage::new(); + let disk_info = disk_manager.disk_by_node(&disk)?; + + inititialize_gpt_disk(&disk_info, uuid.as_deref())?; + + Ok(()) + })?; + + Ok(json!(upid_str)) +} + #[sortable] const SUBDIRS: SubdirMap = &sorted!([ -// ("lvm", &lvm::ROUTER), + // ("lvm", &lvm::ROUTER), + ( + "initgpt", &Router::new() + .post(&API_METHOD_INITIALIZE_DISK) + ), ( "list", &Router::new() .get(&API_METHOD_LIST_DISKS) diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index d8cdd65f..bf0a6533 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -32,6 +32,24 @@ async fn view_task_result( Ok(()) } +// Note: local worker already printf logs to stdout, so there is no need +// to fetch/display logs. We just wait for the worker to finish. +pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> { + + let upid: proxmox_backup::server::UPID = upid_str.parse()?; + + let sleep_duration = core::time::Duration::new(0, 100_000_000); + + loop { + if proxmox_backup::server::worker_is_active_local(&upid) { + tokio::time::delay_for(sleep_duration).await; + } else { + break; + } + } + Ok(()) +} + fn connect() -> Result { let uid = nix::unistd::Uid::current(); diff --git a/src/bin/proxmox_backup_manager/disk.rs b/src/bin/proxmox_backup_manager/disk.rs index 86338bd3..7a94c263 100644 --- a/src/bin/proxmox_backup_manager/disk.rs +++ b/src/bin/proxmox_backup_manager/disk.rs @@ -85,6 +85,40 @@ fn smart_attributes(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result Ok(Value::Null) } +#[api( + input: { + properties: { + disk: { + schema: BLOCKDEVICE_NAME_SCHEMA, + }, + uuid: { + description: "UUID for the GPT table.", + type: String, + optional: true, + max_length: 36, + }, + }, + }, +)] +/// Initialize empty Disk with GPT +async fn initialize_disk( + mut param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + + param["node"] = "localhost".into(); + + let info = &api2::node::disks::API_METHOD_INITIALIZE_DISK; + let result = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + crate::wait_for_local_worker(result.as_str().unwrap()).await?; + + Ok(Value::Null) +} + pub fn disk_commands() -> CommandLineInterface { let cmd_def = CliCommandMap::new() @@ -93,6 +127,11 @@ pub fn disk_commands() -> CommandLineInterface { CliCommand::new(&API_METHOD_SMART_ATTRIBUTES) .arg_param(&["disk"]) .completion_cb("disk", complete_disk_name) + ) + .insert("initialize", + CliCommand::new(&API_METHOD_INITIALIZE_DISK) + .arg_param(&["disk"]) + .completion_cb("disk", complete_disk_name) ); cmd_def.into() diff --git a/src/tools/disks.rs b/src/tools/disks.rs index f8c90435..03a6683f 100644 --- a/src/tools/disks.rs +++ b/src/tools/disks.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use bitflags::bitflags; -use anyhow::{format_err, Error}; +use anyhow::{bail, format_err, Error}; use libc::dev_t; use once_cell::sync::OnceCell; @@ -708,6 +708,23 @@ fn scan_partitions( Ok(used) } + +/// Get disk usage information for a single disk +pub fn get_disk_usage_info( + disk: &str, + no_smart: bool, +) -> Result { + let mut filter = Vec::new(); + filter.push(disk.to_string()); + let mut map = get_disks(Some(filter), no_smart)?; + if let Some(info) = map.remove(disk) { + return Ok(info); + } else { + bail!("failed to get disk usage info - internal error"); // should not happen + } +} + +/// Get disk usage information for multiple disks pub fn get_disks( // filter - list of device names (without leading /dev) disks: Option>, @@ -820,6 +837,32 @@ pub fn get_disks( Ok(result) } +/// Initialize disk by writing a GPT partition table +pub fn inititialize_gpt_disk(disk: &Disk, uuid: Option<&str>) -> Result<(), Error> { + + const SGDISK_BIN_PATH: &str = "/usr/sbin/sgdisk"; + + let disk_path = match disk.device_path() { + Some(path) => path, + None => bail!("disk {:?} has no node in /dev", disk.syspath()), + }; + + let uuid = uuid.unwrap_or("R"); // R .. random disk GUID + + let mut command = std::process::Command::new(SGDISK_BIN_PATH); + command.arg(disk_path); + command.args(&["-U", uuid]); + + let output = command.output() + .map_err(|err| format_err!("failed to execute '{}' - {}", SGDISK_BIN_PATH, err))?; + + crate::tools::command_output(output, None) + .map_err(|err| format_err!("sgdisk command failed: {}", err))?; + + Ok(()) +} + +/// Block device name completion helper pub fn complete_disk_name(_arg: &str, _param: &HashMap) -> Vec { let mut list = Vec::new();