src/api2/node/disks.rs: implement initgpt API
This commit is contained in:
		@ -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<String>,
 | 
			
		||||
    rpcenv: &mut dyn RpcEnvironment,
 | 
			
		||||
) -> Result<Value, Error> {
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
@ -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<HttpClient, Error> {
 | 
			
		||||
 | 
			
		||||
    let uid = nix::unistd::Uid::current();
 | 
			
		||||
 | 
			
		||||
@ -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<Value, Error> {
 | 
			
		||||
 | 
			
		||||
    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()
 | 
			
		||||
 | 
			
		||||
@ -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<DiskUsageInfo, Error> {
 | 
			
		||||
    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<Vec<String>>,
 | 
			
		||||
@ -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<String, String>) -> Vec<String> {
 | 
			
		||||
    let mut list = Vec::new();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user