]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/disks/directory.rs
mount zpools created via API under /mnt/datastore
[proxmox-backup.git] / src / api2 / node / disks / directory.rs
CommitLineData
3f851d13 1use anyhow::{bail, Error};
fbbcd858 2use serde_json::json;
d4f2397d
DM
3use ::serde::{Deserialize, Serialize};
4
98161fdd 5use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType};
d4f2397d
DM
6use proxmox::api::section_config::SectionConfigData;
7use proxmox::api::router::Router;
8
9use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
10use crate::tools::disks::{
3f851d13
DM
11 DiskManage, FileSystemType, DiskUsageType,
12 create_file_system, create_single_linux_partition, get_fs_uuid, get_disk_usage_info,
d4f2397d
DM
13};
14use crate::tools::systemd::{self, types::*};
15
16use crate::server::WorkerTask;
17
18use crate::api2::types::*;
be614c62 19use crate::config::datastore::DataStoreConfig;
d4f2397d
DM
20
21#[api(
22 properties: {
23 "filesystem": {
24 type: FileSystemType,
25 optional: true,
26 },
27 },
28)]
29#[derive(Debug, Serialize, Deserialize)]
30#[serde(rename_all="kebab-case")]
31/// Datastore mount info.
32pub struct DatastoreMountInfo {
33 /// The path of the mount unit.
34 pub unitfile: String,
35 /// The mount path.
36 pub path: String,
37 /// The mounted device.
38 pub device: String,
39 /// File system type
40 pub filesystem: Option<String>,
41 /// Mount options
42 pub options: Option<String>,
43}
44
45#[api(
46 protected: true,
47 input: {
48 properties: {
49 node: {
50 schema: NODE_SCHEMA,
51 },
52 }
53 },
54 returns: {
55 description: "List of systemd datastore mount units.",
56 type: Array,
57 items: {
58 type: DatastoreMountInfo,
59 },
60 },
61 access: {
62 permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_AUDIT, false),
63 },
64)]
65/// List systemd datastore mount units.
1aef491e 66pub fn list_datastore_mounts() -> Result<Vec<DatastoreMountInfo>, Error> {
d4f2397d
DM
67
68 lazy_static::lazy_static! {
69 static ref MOUNT_NAME_REGEX: regex::Regex = regex::Regex::new(r"^mnt-datastore-(.+)\.mount$").unwrap();
70 }
71
72 let mut list = Vec::new();
73
74 let basedir = "/etc/systemd/system";
75 for item in crate::tools::fs::scan_subdir(libc::AT_FDCWD, basedir, &MOUNT_NAME_REGEX)? {
76 let item = item?;
77 let name = item.file_name().to_string_lossy().to_string();
78
79 let unitfile = format!("{}/{}", basedir, name);
80 let config = systemd::config::parse_systemd_mount(&unitfile)?;
81 let data: SystemdMountSection = config.lookup("Mount", "Mount")?;
82
83 list.push(DatastoreMountInfo {
84 unitfile,
85 device: data.What,
86 path: data.Where,
87 filesystem: data.Type,
88 options: data.Options,
89 });
90 }
91
92 Ok(list)
93}
94
95#[api(
96 protected: true,
97 input: {
98 properties: {
99 node: {
100 schema: NODE_SCHEMA,
101 },
102 name: {
103 schema: DATASTORE_SCHEMA,
104 },
105 disk: {
106 schema: BLOCKDEVICE_NAME_SCHEMA,
107 },
108 "add-datastore": {
109 description: "Configure a datastore using the directory.",
110 type: bool,
111 optional: true,
112 },
113 filesystem: {
114 type: FileSystemType,
115 optional: true,
116 },
117 }
118 },
119 returns: {
120 schema: UPID_SCHEMA,
121 },
122 access: {
123 permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_MODIFY, false),
124 },
125)]
126/// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.".
1aef491e 127pub fn create_datastore_disk(
d4f2397d
DM
128 name: String,
129 disk: String,
130 add_datastore: Option<bool>,
131 filesystem: Option<FileSystemType>,
132 rpcenv: &mut dyn RpcEnvironment,
133) -> Result<String, Error> {
134
135 let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
136
e6dc35ac 137 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
d4f2397d 138
3f851d13
DM
139 let info = get_disk_usage_info(&disk, true)?;
140
141 if info.used != DiskUsageType::Unused {
142 bail!("disk '{}' is already in use.", disk);
143 }
144
d4f2397d 145 let upid_str = WorkerTask::new_thread(
e6dc35ac 146 "dircreate", Some(name.clone()), auth_id, to_stdout, move |worker|
d4f2397d
DM
147 {
148 worker.log(format!("create datastore '{}' on disk {}", name, disk));
149
150 let add_datastore = add_datastore.unwrap_or(false);
151 let filesystem = filesystem.unwrap_or(FileSystemType::Ext4);
152
153 let manager = DiskManage::new();
154
155 let disk = manager.clone().disk_by_name(&disk)?;
156
157 let partition = create_single_linux_partition(&disk)?;
158 create_file_system(&partition, filesystem)?;
159
160 let uuid = get_fs_uuid(&partition)?;
161 let uuid_path = format!("/dev/disk/by-uuid/{}", uuid);
162
fbbcd858 163 let (mount_unit_name, mount_point) = create_datastore_mount_unit(&name, filesystem, &uuid_path)?;
d4f2397d
DM
164
165 systemd::reload_daemon()?;
166 systemd::enable_unit(&mount_unit_name)?;
167 systemd::start_unit(&mount_unit_name)?;
168
fbbcd858
DM
169 if add_datastore {
170 crate::api2::config::datastore::create_datastore(json!({ "name": name, "path": mount_point }))?
171 }
172
d4f2397d
DM
173 Ok(())
174 })?;
175
176 Ok(upid_str)
177}
178
be614c62
HL
179#[api(
180 protected: true,
181 input: {
182 properties: {
183 node: {
184 schema: NODE_SCHEMA,
185 },
186 name: {
187 schema: DATASTORE_SCHEMA,
188 },
189 }
190 },
191 access: {
192 permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_MODIFY, false),
193 },
194)]
195/// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
98161fdd
DM
196pub fn delete_datastore_disk(name: String) -> Result<(), Error> {
197
be614c62
HL
198 let path = format!("/mnt/datastore/{}", name);
199 // path of datastore cannot be changed
200 let (config, _) = crate::config::datastore::config()?;
201 let datastores: Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
202 let conflicting_datastore: Option<DataStoreConfig> = datastores.into_iter()
203 .filter(|ds| ds.path == path)
204 .next();
205
98161fdd
DM
206 if let Some(conflicting_datastore) = conflicting_datastore {
207 bail!("Can't remove '{}' since it's required by datastore '{}'",
208 conflicting_datastore.path, conflicting_datastore.name);
209 }
210
211 // disable systemd mount-unit
212 let mut mount_unit_name = systemd::escape_unit(&path, true);
213 mount_unit_name.push_str(".mount");
214 systemd::disable_unit(&mount_unit_name)?;
215
216 // delete .mount-file
217 let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
218 let full_path = std::path::Path::new(&mount_unit_path);
219 log::info!("removing systemd mount unit {:?}", full_path);
220 std::fs::remove_file(&full_path)?;
221
222 // try to unmount, if that fails tell the user to reboot or unmount manually
223 let mut command = std::process::Command::new("umount");
224 command.arg(&path);
225 match crate::tools::run_command(command, None) {
226 Err(_) => bail!(
227 "Could not umount '{}' since it is busy. It will stay mounted \
228 until the next reboot or until unmounted manually!",
229 path
230 ),
231 Ok(_) => Ok(())
be614c62
HL
232 }
233}
234
235const ITEM_ROUTER: Router = Router::new()
236 .delete(&API_METHOD_DELETE_DATASTORE_DISK);
237
d4f2397d
DM
238pub const ROUTER: Router = Router::new()
239 .get(&API_METHOD_LIST_DATASTORE_MOUNTS)
be614c62
HL
240 .post(&API_METHOD_CREATE_DATASTORE_DISK)
241 .match_all("name", &ITEM_ROUTER);
d4f2397d
DM
242
243
244fn create_datastore_mount_unit(
245 datastore_name: &str,
246 fs_type: FileSystemType,
247 what: &str,
fbbcd858 248) -> Result<(String, String), Error> {
d4f2397d
DM
249
250 let mount_point = format!("/mnt/datastore/{}", datastore_name);
251 let mut mount_unit_name = systemd::escape_unit(&mount_point, true);
252 mount_unit_name.push_str(".mount");
253
254 let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
255
256 let unit = SystemdUnitSection {
257 Description: format!("Mount datatstore '{}' under '{}'", datastore_name, mount_point),
258 ..Default::default()
259 };
260
261 let install = SystemdInstallSection {
262 WantedBy: Some(vec!["multi-user.target".to_string()]),
263 ..Default::default()
264 };
265
266 let mount = SystemdMountSection {
267 What: what.to_string(),
fbbcd858 268 Where: mount_point.clone(),
d4f2397d
DM
269 Type: Some(fs_type.to_string()),
270 Options: Some(String::from("defaults")),
271 ..Default::default()
272 };
273
274 let mut config = SectionConfigData::new();
275 config.set_data("Unit", "Unit", unit)?;
276 config.set_data("Install", "Install", install)?;
277 config.set_data("Mount", "Mount", mount)?;
278
279 systemd::config::save_systemd_mount(&mount_unit_path, &config)?;
280
fbbcd858 281 Ok((mount_unit_name, mount_point))
d4f2397d 282}