]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/disks/directory.rs
src/api2/node/disks/directory.rs: implement add-datastore feature
[proxmox-backup.git] / src / api2 / node / disks / directory.rs
1 use anyhow::{Error};
2 use serde_json::json;
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
156 let (mount_unit_name, mount_point) = create_datastore_mount_unit(&name, filesystem, &uuid_path)?;
157
158 systemd::reload_daemon()?;
159 systemd::enable_unit(&mount_unit_name)?;
160 systemd::start_unit(&mount_unit_name)?;
161
162 if add_datastore {
163 crate::api2::config::datastore::create_datastore(json!({ "name": name, "path": mount_point }))?
164 }
165
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,
181 ) -> Result<(String, String), Error> {
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(),
201 Where: mount_point.clone(),
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
214 Ok((mount_unit_name, mount_point))
215 }