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