]>
Commit | Line | Data |
---|---|---|
dc7a5b34 | 1 | use ::serde::{Deserialize, Serialize}; |
3f851d13 | 2 | use anyhow::{bail, Error}; |
fbbcd858 | 3 | use serde_json::json; |
d4f2397d | 4 | |
dc7a5b34 | 5 | use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType}; |
6ef1b649 WB |
6 | use proxmox_schema::api; |
7 | use proxmox_section_config::SectionConfigData; | |
d5790a9f | 8 | use proxmox_sys::task_log; |
d4f2397d | 9 | |
8cc3760e | 10 | use 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 | 15 | use crate::tools::disks::{ |
be260410 HL |
16 | create_file_system, create_single_linux_partition, get_fs_uuid, DiskManage, DiskUsageQuery, |
17 | DiskUsageType, FileSystemType, | |
d4f2397d DM |
18 | }; |
19 | use crate::tools::systemd::{self, types::*}; | |
20 | ||
b9700a9f | 21 | use proxmox_rest_server::WorkerTask; |
d4f2397d | 22 | |
2de1b06a DC |
23 | const 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. |
36 | pub 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 | 72 | pub 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 | 139 | pub 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 | 239 | pub 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 | 279 | const ITEM_ROUTER: Router = Router::new().delete(&API_METHOD_DELETE_DATASTORE_DISK); |
be614c62 | 280 | |
d4f2397d DM |
281 | pub 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 |
286 | fn 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 | } |