1 use anyhow
::{bail, Error}
;
2 use serde_json
::{json, Value}
;
4 use proxmox_router
::{Router, RpcEnvironment, RpcEnvironmentType, Permission}
;
5 use proxmox_schema
::{api, parse_property_string}
;
6 use proxmox_sys
::task_log
;
9 ZpoolListItem
, ZfsRaidLevel
, ZfsCompressionType
, DataStoreConfig
,
10 NODE_SCHEMA
, ZPOOL_NAME_SCHEMA
, DATASTORE_SCHEMA
, DISK_ARRAY_SCHEMA
,
11 DISK_LIST_SCHEMA
, ZFS_ASHIFT_SCHEMA
, UPID_SCHEMA
,
12 PRIV_SYS_AUDIT
, PRIV_SYS_MODIFY
,
15 use crate::tools
::disks
::{
16 zpool_list
, zpool_status
, parse_zpool_status_config_tree
, vdev_list_to_tree
,
20 use proxmox_rest_server
::WorkerTask
;
33 description
: "List of zpools.",
40 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_AUDIT
, false),
44 pub fn list_zpools() -> Result
<Vec
<ZpoolListItem
>, Error
> {
46 let data
= zpool_list(None
, false)?
;
48 let mut list
= Vec
::new();
51 if let Some(usage
) = item
.usage
{
52 list
.push(ZpoolListItem
{
75 schema
: ZPOOL_NAME_SCHEMA
,
80 description
: "zpool vdev tree with status",
86 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_AUDIT
, false),
89 /// Get zpool status details.
92 ) -> Result
<Value
, Error
> {
94 let key_value_list
= zpool_status(&name
)?
;
96 let config
= match key_value_list
.iter().find(|(k
, _
)| k
== "config") {
98 None
=> bail
!("got zpool status without config key"),
101 let vdev_list
= parse_zpool_status_config_tree(config
)?
;
102 let mut tree
= vdev_list_to_tree(&vdev_list
)?
;
104 for (k
, v
) in key_value_list
{
110 tree
["name"] = tree
.as_object_mut().unwrap()
112 .unwrap_or_else(|| name
.into());
126 schema
: DATASTORE_SCHEMA
,
129 schema
: DISK_LIST_SCHEMA
,
135 schema
: ZFS_ASHIFT_SCHEMA
,
139 type: ZfsCompressionType
,
143 description
: "Configure a datastore using the zpool.",
153 permission
: &Permission
::Privilege(&["system", "disks"], PRIV_SYS_MODIFY
, false),
156 /// Create a new ZFS pool. Will be mounted under '/mnt/datastore/<name>'.
160 raidlevel
: ZfsRaidLevel
,
161 compression
: Option
<String
>,
162 ashift
: Option
<usize>,
163 add_datastore
: Option
<bool
>,
164 rpcenv
: &mut dyn RpcEnvironment
,
165 ) -> Result
<String
, Error
> {
167 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
169 let auth_id
= rpcenv
.get_auth_id().unwrap();
171 let add_datastore
= add_datastore
.unwrap_or(false);
173 let ashift
= ashift
.unwrap_or(12);
175 let devices_text
= devices
.clone();
176 let devices
= parse_property_string(&devices
, &DISK_ARRAY_SCHEMA
)?
;
177 let devices
: Vec
<String
> = devices
.as_array().unwrap().iter()
178 .map(|v
| v
.as_str().unwrap().to_string()).collect();
180 let disk_map
= crate::tools
::disks
::get_disks(None
, true)?
;
181 for disk
in devices
.iter() {
182 match disk_map
.get(disk
) {
184 if info
.used
!= DiskUsageType
::Unused
{
185 bail
!("disk '{}' is already in use.", disk
);
189 bail
!("no such disk '{}'", disk
);
194 let min_disks
= match raidlevel
{
195 ZfsRaidLevel
::Single
=> 1,
196 ZfsRaidLevel
::Mirror
=> 2,
197 ZfsRaidLevel
::Raid10
=> 4,
198 ZfsRaidLevel
::RaidZ
=> 3,
199 ZfsRaidLevel
::RaidZ2
=> 4,
200 ZfsRaidLevel
::RaidZ3
=> 5,
204 if raidlevel
== ZfsRaidLevel
::Raid10
&& devices
.len() % 2 != 0 {
205 bail
!("Raid10 needs an even number of disks.");
208 if raidlevel
== ZfsRaidLevel
::Single
&& devices
.len() > 1 {
209 bail
!("Please give only one disk for single disk mode.");
212 if devices
.len() < min_disks
{
213 bail
!("{:?} needs at least {} disks.", raidlevel
, min_disks
);
216 let mount_point
= format
!("/mnt/datastore/{}", &name
);
218 // check if the default path does exist already and bail if it does
219 // otherwise 'zpool create' aborts after partitioning, but before creating the pool
220 let default_path
= std
::path
::PathBuf
::from(&mount_point
);
222 match std
::fs
::metadata(&default_path
) {
223 Err(_
) => {}
, // path does not exist
225 bail
!("path {:?} already exists", default_path
);
229 let upid_str
= WorkerTask
::new_thread(
230 "zfscreate", Some(name
.clone()), auth_id
, to_stdout
, move |worker
|
232 task_log
!(worker
, "create {:?} zpool '{}' on devices '{}'", raidlevel
, name
, devices_text
);
235 let mut command
= std
::process
::Command
::new("zpool");
236 command
.args(&["create", "-o", &format
!("ashift={}", ashift
), "-m", &mount_point
, &name
]);
239 ZfsRaidLevel
::Single
=> {
240 command
.arg(&devices
[0]);
242 ZfsRaidLevel
::Mirror
=> {
243 command
.arg("mirror");
244 command
.args(devices
);
246 ZfsRaidLevel
::Raid10
=> {
247 devices
.chunks(2).for_each(|pair
| {
248 command
.arg("mirror");
252 ZfsRaidLevel
::RaidZ
=> {
253 command
.arg("raidz");
254 command
.args(devices
);
256 ZfsRaidLevel
::RaidZ2
=> {
257 command
.arg("raidz2");
258 command
.args(devices
);
260 ZfsRaidLevel
::RaidZ3
=> {
261 command
.arg("raidz3");
262 command
.args(devices
);
266 task_log
!(worker
, "# {:?}", command
);
268 let output
= pbs_tools
::run_command(command
, None
)?
;
269 task_log
!(worker
, "{}", output
);
271 if std
::path
::Path
::new("/lib/systemd/system/zfs-import@.service").exists() {
272 let import_unit
= format
!("zfs-import@{}.service", proxmox
::tools
::systemd
::escape_unit(&name
, false));
273 crate::tools
::systemd
::enable_unit(&import_unit
)?
;
276 if let Some(compression
) = compression
{
277 let mut command
= std
::process
::Command
::new("zfs");
278 command
.args(&["set", &format
!("compression={}", compression
), &name
]);
279 task_log
!(worker
, "# {:?}", command
);
280 let output
= pbs_tools
::run_command(command
, None
)?
;
281 task_log
!(worker
, "{}", output
);
285 let lock
= pbs_config
::datastore
::lock_config()?
;
286 let datastore
: DataStoreConfig
=
287 serde_json
::from_value(json
!({ "name": name, "path": mount_point }
))?
;
289 let (config
, _digest
) = pbs_config
::datastore
::config()?
;
291 if config
.sections
.get(&datastore
.name
).is_some() {
292 bail
!("datastore '{}' already exists.", datastore
.name
);
295 crate::api2
::config
::datastore
::do_create_datastore(lock
, config
, datastore
, Some(&worker
))?
;
304 pub const POOL_ROUTER
: Router
= Router
::new()
305 .get(&API_METHOD_ZPOOL_DETAILS
);
307 pub const ROUTER
: Router
= Router
::new()
308 .get(&API_METHOD_LIST_ZPOOLS
)
309 .post(&API_METHOD_CREATE_ZPOOL
)
310 .match_all("name", &POOL_ROUTER
);