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