src/api2/node/disks.rs: implement initgpt API

This commit is contained in:
Dietmar Maurer 2020-06-07 10:30:34 +02:00
parent 9069debcd8
commit 707974fdb3
4 changed files with 171 additions and 7 deletions

View File

@ -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)

View File

@ -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();

View File

@ -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()

View File

@ -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();