]>
Commit | Line | Data |
---|---|---|
d4f2397d | 1 | use anyhow::{Error}; |
fbbcd858 | 2 | use serde_json::json; |
d4f2397d DM |
3 | use ::serde::{Deserialize, Serialize}; |
4 | ||
5 | use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType}; | |
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::{ | |
11 | DiskManage, FileSystemType, | |
12 | create_file_system, create_single_linux_partition, get_fs_uuid, | |
13 | }; | |
14 | use crate::tools::systemd::{self, types::*}; | |
15 | ||
16 | use crate::server::WorkerTask; | |
17 | ||
18 | use crate::api2::types::*; | |
19 | ||
20 | #[api( | |
21 | properties: { | |
22 | "filesystem": { | |
23 | type: FileSystemType, | |
24 | optional: true, | |
25 | }, | |
26 | }, | |
27 | )] | |
28 | #[derive(Debug, Serialize, Deserialize)] | |
29 | #[serde(rename_all="kebab-case")] | |
30 | /// Datastore mount info. | |
31 | pub struct DatastoreMountInfo { | |
32 | /// The path of the mount unit. | |
33 | pub unitfile: String, | |
34 | /// The mount path. | |
35 | pub path: String, | |
36 | /// The mounted device. | |
37 | pub device: String, | |
38 | /// File system type | |
39 | pub filesystem: Option<String>, | |
40 | /// Mount options | |
41 | pub options: Option<String>, | |
42 | } | |
43 | ||
44 | #[api( | |
45 | protected: true, | |
46 | input: { | |
47 | properties: { | |
48 | node: { | |
49 | schema: NODE_SCHEMA, | |
50 | }, | |
51 | } | |
52 | }, | |
53 | returns: { | |
54 | description: "List of systemd datastore mount units.", | |
55 | type: Array, | |
56 | items: { | |
57 | type: DatastoreMountInfo, | |
58 | }, | |
59 | }, | |
60 | access: { | |
61 | permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_AUDIT, false), | |
62 | }, | |
63 | )] | |
64 | /// List systemd datastore mount units. | |
65 | fn list_datastore_mounts() -> Result<Vec<DatastoreMountInfo>, Error> { | |
66 | ||
67 | lazy_static::lazy_static! { | |
68 | static ref MOUNT_NAME_REGEX: regex::Regex = regex::Regex::new(r"^mnt-datastore-(.+)\.mount$").unwrap(); | |
69 | } | |
70 | ||
71 | let mut list = Vec::new(); | |
72 | ||
73 | let basedir = "/etc/systemd/system"; | |
74 | for item in crate::tools::fs::scan_subdir(libc::AT_FDCWD, basedir, &MOUNT_NAME_REGEX)? { | |
75 | let item = item?; | |
76 | let name = item.file_name().to_string_lossy().to_string(); | |
77 | ||
78 | let unitfile = format!("{}/{}", basedir, name); | |
79 | let config = systemd::config::parse_systemd_mount(&unitfile)?; | |
80 | let data: SystemdMountSection = config.lookup("Mount", "Mount")?; | |
81 | ||
82 | list.push(DatastoreMountInfo { | |
83 | unitfile, | |
84 | device: data.What, | |
85 | path: data.Where, | |
86 | filesystem: data.Type, | |
87 | options: data.Options, | |
88 | }); | |
89 | } | |
90 | ||
91 | Ok(list) | |
92 | } | |
93 | ||
94 | #[api( | |
95 | protected: true, | |
96 | input: { | |
97 | properties: { | |
98 | node: { | |
99 | schema: NODE_SCHEMA, | |
100 | }, | |
101 | name: { | |
102 | schema: DATASTORE_SCHEMA, | |
103 | }, | |
104 | disk: { | |
105 | schema: BLOCKDEVICE_NAME_SCHEMA, | |
106 | }, | |
107 | "add-datastore": { | |
108 | description: "Configure a datastore using the directory.", | |
109 | type: bool, | |
110 | optional: true, | |
111 | }, | |
112 | filesystem: { | |
113 | type: FileSystemType, | |
114 | optional: true, | |
115 | }, | |
116 | } | |
117 | }, | |
118 | returns: { | |
119 | schema: UPID_SCHEMA, | |
120 | }, | |
121 | access: { | |
122 | permission: &Permission::Privilege(&["system", "disks"], PRIV_SYS_MODIFY, false), | |
123 | }, | |
124 | )] | |
125 | /// Create a Filesystem on an unused disk. Will be mounted under '/mnt/datastore/<name>'.". | |
126 | fn create_datastore_disk( | |
127 | name: String, | |
128 | disk: String, | |
129 | add_datastore: Option<bool>, | |
130 | filesystem: Option<FileSystemType>, | |
131 | rpcenv: &mut dyn RpcEnvironment, | |
132 | ) -> Result<String, Error> { | |
133 | ||
134 | let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false }; | |
135 | ||
136 | let username = rpcenv.get_user().unwrap(); | |
137 | ||
138 | let upid_str = WorkerTask::new_thread( | |
139 | "dircreate", Some(name.clone()), &username.clone(), to_stdout, move |worker| | |
140 | { | |
141 | worker.log(format!("create datastore '{}' on disk {}", name, disk)); | |
142 | ||
143 | let add_datastore = add_datastore.unwrap_or(false); | |
144 | let filesystem = filesystem.unwrap_or(FileSystemType::Ext4); | |
145 | ||
146 | let manager = DiskManage::new(); | |
147 | ||
148 | let disk = manager.clone().disk_by_name(&disk)?; | |
149 | ||
150 | let partition = create_single_linux_partition(&disk)?; | |
151 | create_file_system(&partition, filesystem)?; | |
152 | ||
153 | let uuid = get_fs_uuid(&partition)?; | |
154 | let uuid_path = format!("/dev/disk/by-uuid/{}", uuid); | |
155 | ||
fbbcd858 | 156 | let (mount_unit_name, mount_point) = create_datastore_mount_unit(&name, filesystem, &uuid_path)?; |
d4f2397d DM |
157 | |
158 | systemd::reload_daemon()?; | |
159 | systemd::enable_unit(&mount_unit_name)?; | |
160 | systemd::start_unit(&mount_unit_name)?; | |
161 | ||
fbbcd858 DM |
162 | if add_datastore { |
163 | crate::api2::config::datastore::create_datastore(json!({ "name": name, "path": mount_point }))? | |
164 | } | |
165 | ||
d4f2397d DM |
166 | Ok(()) |
167 | })?; | |
168 | ||
169 | Ok(upid_str) | |
170 | } | |
171 | ||
172 | pub const ROUTER: Router = Router::new() | |
173 | .get(&API_METHOD_LIST_DATASTORE_MOUNTS) | |
174 | .post(&API_METHOD_CREATE_DATASTORE_DISK); | |
175 | ||
176 | ||
177 | fn create_datastore_mount_unit( | |
178 | datastore_name: &str, | |
179 | fs_type: FileSystemType, | |
180 | what: &str, | |
fbbcd858 | 181 | ) -> Result<(String, String), Error> { |
d4f2397d DM |
182 | |
183 | let mount_point = format!("/mnt/datastore/{}", datastore_name); | |
184 | let mut mount_unit_name = systemd::escape_unit(&mount_point, true); | |
185 | mount_unit_name.push_str(".mount"); | |
186 | ||
187 | let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name); | |
188 | ||
189 | let unit = SystemdUnitSection { | |
190 | Description: format!("Mount datatstore '{}' under '{}'", datastore_name, mount_point), | |
191 | ..Default::default() | |
192 | }; | |
193 | ||
194 | let install = SystemdInstallSection { | |
195 | WantedBy: Some(vec!["multi-user.target".to_string()]), | |
196 | ..Default::default() | |
197 | }; | |
198 | ||
199 | let mount = SystemdMountSection { | |
200 | What: what.to_string(), | |
fbbcd858 | 201 | Where: mount_point.clone(), |
d4f2397d DM |
202 | Type: Some(fs_type.to_string()), |
203 | Options: Some(String::from("defaults")), | |
204 | ..Default::default() | |
205 | }; | |
206 | ||
207 | let mut config = SectionConfigData::new(); | |
208 | config.set_data("Unit", "Unit", unit)?; | |
209 | config.set_data("Install", "Install", install)?; | |
210 | config.set_data("Mount", "Mount", mount)?; | |
211 | ||
212 | systemd::config::save_systemd_mount(&mount_unit_path, &config)?; | |
213 | ||
fbbcd858 | 214 | Ok((mount_unit_name, mount_point)) |
d4f2397d | 215 | } |