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