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