]>
Commit | Line | Data |
---|---|---|
3f851d13 | 1 | use anyhow::{bail, Error}; |
fbbcd858 | 2 | use serde_json::json; |
d4f2397d DM |
3 | use ::serde::{Deserialize, Serialize}; |
4 | ||
98161fdd | 5 | use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType}; |
d4f2397d DM |
6 | use proxmox::api::section_config::SectionConfigData; |
7 | use proxmox::api::router::Router; | |
8 | ||
8cc3760e | 9 | use 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 | 14 | use 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 | }; |
18 | use crate::tools::systemd::{self, types::*}; | |
19 | ||
20 | use 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. | |
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. | |
1aef491e | 67 | pub 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 | 128 | pub 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 |
219 | pub 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 | ||
257 | const ITEM_ROUTER: Router = Router::new() | |
258 | .delete(&API_METHOD_DELETE_DATASTORE_DISK); | |
259 | ||
d4f2397d DM |
260 | pub 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 | ||
266 | fn 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 | } |