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