]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/disks/directory.rs
api: nodes: rustfmt
[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
8cc3760e 9use pbs_api_types::{
e7d4be9d
DM
10 Authid, DataStoreConfig, NODE_SCHEMA, BLOCKDEVICE_NAME_SCHEMA,
11 DATASTORE_SCHEMA, UPID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
8cc3760e
DM
12};
13
d4f2397d 14use crate::tools::disks::{
3f851d13
DM
15 DiskManage, FileSystemType, DiskUsageType,
16 create_file_system, create_single_linux_partition, get_fs_uuid, get_disk_usage_info,
d4f2397d
DM
17};
18use crate::tools::systemd::{self, types::*};
19
20use crate::server::WorkerTask;
21
d4f2397d
DM
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.
33pub 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.
1aef491e 67pub fn list_datastore_mounts() -> Result<Vec<DatastoreMountInfo>, Error> {
d4f2397d
DM
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";
770a36e5 76 for item in pbs_tools::fs::scan_subdir(libc::AT_FDCWD, basedir, &MOUNT_NAME_REGEX)? {
d4f2397d
DM
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>'.".
1aef491e 128pub fn create_datastore_disk(
d4f2397d
DM
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
39735609 136 let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
d4f2397d 137
e6dc35ac 138 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
d4f2397d 139
3f851d13
DM
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
c960d2b5
FE
146 let mount_point = format!("/mnt/datastore/{}", &name);
147
148 // check if the default path does exist already and bail if it does
149 let default_path = std::path::PathBuf::from(&mount_point);
150
151 match std::fs::metadata(&default_path) {
152 Err(_) => {}, // path does not exist
153 Ok(_) => {
154 bail!("path {:?} already exists", default_path);
155 }
156 }
157
d4f2397d 158 let upid_str = WorkerTask::new_thread(
e6dc35ac 159 "dircreate", Some(name.clone()), auth_id, to_stdout, move |worker|
d4f2397d
DM
160 {
161 worker.log(format!("create datastore '{}' on disk {}", name, disk));
162
163 let add_datastore = add_datastore.unwrap_or(false);
164 let filesystem = filesystem.unwrap_or(FileSystemType::Ext4);
165
166 let manager = DiskManage::new();
167
44288184 168 let disk = manager.disk_by_name(&disk)?;
d4f2397d
DM
169
170 let partition = create_single_linux_partition(&disk)?;
171 create_file_system(&partition, filesystem)?;
172
173 let uuid = get_fs_uuid(&partition)?;
174 let uuid_path = format!("/dev/disk/by-uuid/{}", uuid);
175
c960d2b5 176 let mount_unit_name = create_datastore_mount_unit(&name, &mount_point, filesystem, &uuid_path)?;
d4f2397d 177
dd2162f6
WB
178 pbs_systemd::reload_daemon()?;
179 pbs_systemd::enable_unit(&mount_unit_name)?;
180 pbs_systemd::start_unit(&mount_unit_name)?;
d4f2397d 181
fbbcd858 182 if add_datastore {
e7d4be9d 183 let lock = pbs_config::datastore::lock_config()?;
4708f4fc
DC
184 let datastore: DataStoreConfig =
185 serde_json::from_value(json!({ "name": name, "path": mount_point }))?;
186
e7d4be9d 187 let (config, _digest) = pbs_config::datastore::config()?;
4708f4fc
DC
188
189 if config.sections.get(&datastore.name).is_some() {
190 bail!("datastore '{}' already exists.", datastore.name);
191 }
192
2de4dc3a 193 crate::api2::config::datastore::do_create_datastore(lock, config, datastore, Some(&worker))?;
fbbcd858
DM
194 }
195
d4f2397d
DM
196 Ok(())
197 })?;
198
199 Ok(upid_str)
200}
201
be614c62
HL
202#[api(
203 protected: true,
204 input: {
205 properties: {
206 node: {
207 schema: NODE_SCHEMA,
208 },
209 name: {
210 schema: DATASTORE_SCHEMA,
211 },
212 }
213 },
214 access: {
215 permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_MODIFY, false),
216 },
217)]
218/// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
98161fdd
DM
219pub fn delete_datastore_disk(name: String) -> Result<(), Error> {
220
be614c62
HL
221 let path = format!("/mnt/datastore/{}", name);
222 // path of datastore cannot be changed
e7d4be9d 223 let (config, _) = pbs_config::datastore::config()?;
be614c62
HL
224 let datastores: Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
225 let conflicting_datastore: Option<DataStoreConfig> = datastores.into_iter()
ea368a06 226 .find(|ds| ds.path == path);
be614c62 227
98161fdd
DM
228 if let Some(conflicting_datastore) = conflicting_datastore {
229 bail!("Can't remove '{}' since it's required by datastore '{}'",
230 conflicting_datastore.path, conflicting_datastore.name);
231 }
232
233 // disable systemd mount-unit
dd2162f6 234 let mut mount_unit_name = pbs_systemd::escape_unit(&path, true);
98161fdd 235 mount_unit_name.push_str(".mount");
dd2162f6 236 pbs_systemd::disable_unit(&mount_unit_name)?;
98161fdd
DM
237
238 // delete .mount-file
239 let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
240 let full_path = std::path::Path::new(&mount_unit_path);
241 log::info!("removing systemd mount unit {:?}", full_path);
242 std::fs::remove_file(&full_path)?;
243
244 // try to unmount, if that fails tell the user to reboot or unmount manually
245 let mut command = std::process::Command::new("umount");
246 command.arg(&path);
4c1b7761 247 match pbs_tools::run_command(command, None) {
98161fdd
DM
248 Err(_) => bail!(
249 "Could not umount '{}' since it is busy. It will stay mounted \
250 until the next reboot or until unmounted manually!",
251 path
252 ),
253 Ok(_) => Ok(())
be614c62
HL
254 }
255}
256
257const ITEM_ROUTER: Router = Router::new()
258 .delete(&API_METHOD_DELETE_DATASTORE_DISK);
259
d4f2397d
DM
260pub const ROUTER: Router = Router::new()
261 .get(&API_METHOD_LIST_DATASTORE_MOUNTS)
be614c62
HL
262 .post(&API_METHOD_CREATE_DATASTORE_DISK)
263 .match_all("name", &ITEM_ROUTER);
d4f2397d
DM
264
265
266fn create_datastore_mount_unit(
267 datastore_name: &str,
c960d2b5 268 mount_point: &str,
d4f2397d
DM
269 fs_type: FileSystemType,
270 what: &str,
c960d2b5 271) -> Result<String, Error> {
d4f2397d 272
dd2162f6 273 let mut mount_unit_name = pbs_systemd::escape_unit(&mount_point, true);
d4f2397d
DM
274 mount_unit_name.push_str(".mount");
275
276 let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
277
278 let unit = SystemdUnitSection {
279 Description: format!("Mount datatstore '{}' under '{}'", datastore_name, mount_point),
280 ..Default::default()
281 };
282
283 let install = SystemdInstallSection {
284 WantedBy: Some(vec!["multi-user.target".to_string()]),
285 ..Default::default()
286 };
287
288 let mount = SystemdMountSection {
289 What: what.to_string(),
c960d2b5 290 Where: mount_point.to_string(),
d4f2397d
DM
291 Type: Some(fs_type.to_string()),
292 Options: Some(String::from("defaults")),
293 ..Default::default()
294 };
295
296 let mut config = SectionConfigData::new();
297 config.set_data("Unit", "Unit", unit)?;
298 config.set_data("Install", "Install", install)?;
299 config.set_data("Mount", "Mount", mount)?;
300
301 systemd::config::save_systemd_mount(&mount_unit_path, &config)?;
302
c960d2b5 303 Ok(mount_unit_name)
d4f2397d 304}