]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/types.rs
use the existing async method for read_raw_chunk
[proxmox-backup.git] / src / api2 / types.rs
CommitLineData
f7d4e4b5 1use anyhow::{bail};
fc189b19 2use ::serde::{Deserialize, Serialize};
4ebf0eab 3
9ea4bce4
WB
4use proxmox::api::{api, schema::*};
5use proxmox::const_regex;
6use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
255f378a
DM
7
8// File names: may not contain slashes, may not start with "."
9pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
10 if name.starts_with('.') {
11 bail!("file names may not start with '.'");
12 }
13 if name.contains('/') {
14 bail!("file names may not contain slashes");
15 }
16 Ok(())
17});
18
b25f313d
DM
19macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
20macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) }
255f378a 21
163dc16c
DM
22// we only allow a limited set of characters
23// colon is not allowed, because we store usernames in
24// colon separated lists)!
25// slash is not allowed because it is used as pve API delimiter
26// also see "man useradd"
ae62c4fe 27macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
9765092e 28macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
163dc16c 29
c3218659
TL
30macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
31
f486e9e5 32#[macro_export]
163dc16c
DM
33macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
34
76cf5208
DM
35macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
36macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
37
255f378a 38const_regex!{
76cf5208
DM
39 pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$");
40 pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$");
41 pub IP_REGEX = concat!(r"^", IPRE!(), r"$");
42 pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$");
43 pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$");
44 pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$");
45
255f378a
DM
46 pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
47 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
d0adf270 48
da4a15a3
DM
49 pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters
50
d0adf270
DM
51 /// Regex for safe identifiers.
52 ///
53 /// This
54 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
55 /// contains further information why it is reasonable to restict
56 /// names this way. This is not only useful for filenames, but for
57 /// any identifier command line tools work with.
163dc16c 58 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
454c13ed
DM
59
60 pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$";
b25f313d
DM
61
62 pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
63
ae62c4fe 64 pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$");
b25f313d 65
ae62c4fe 66 pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$");
163dc16c 67
c3218659 68 pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
dcb8db66 69
090decbe
TL
70 pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE!() ,"):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
71
9765092e
DM
72 pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
73
dcb8db66 74 pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
ed3e60ae 75
9765092e 76 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
9069debc
DM
77
78 pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$";
255f378a 79}
4ebf0eab 80
255f378a
DM
81pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
82 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
4ebf0eab 83
76cf5208
DM
84pub const IP_V4_FORMAT: ApiStringFormat =
85 ApiStringFormat::Pattern(&IP_V4_REGEX);
86
87pub const IP_V6_FORMAT: ApiStringFormat =
88 ApiStringFormat::Pattern(&IP_V6_REGEX);
89
255f378a 90pub const IP_FORMAT: ApiStringFormat =
76cf5208 91 ApiStringFormat::Pattern(&IP_REGEX);
bbf9e7e9 92
255f378a
DM
93pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat =
94 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
95
dcb8db66
DM
96pub const CERT_FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
97 ApiStringFormat::Pattern(&CERT_FINGERPRINT_SHA256_REGEX);
98
d0adf270
DM
99pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
100 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
101
454c13ed
DM
102pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
103 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
104
b25f313d
DM
105pub const HOSTNAME_FORMAT: ApiStringFormat =
106 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
107
108pub const DNS_NAME_FORMAT: ApiStringFormat =
109 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
110
111pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
112 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
113
163dc16c
DM
114pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
115 ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
116
9765092e
DM
117pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
118 ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
119
7e7b781a
DM
120pub const PASSWORD_FORMAT: ApiStringFormat =
121 ApiStringFormat::Pattern(&PASSWORD_REGEX);
122
ed3e60ae
DM
123pub const ACL_PATH_FORMAT: ApiStringFormat =
124 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
125
68da20bf
DM
126pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
127 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
454c13ed 128
76cf5208
DM
129pub const CIDR_V4_FORMAT: ApiStringFormat =
130 ApiStringFormat::Pattern(&CIDR_V4_REGEX);
131
132pub const CIDR_V6_FORMAT: ApiStringFormat =
133 ApiStringFormat::Pattern(&CIDR_V6_REGEX);
134
135pub const CIDR_FORMAT: ApiStringFormat =
136 ApiStringFormat::Pattern(&CIDR_REGEX);
137
9069debc
DM
138pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
139 ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
76cf5208 140
685e1334
DM
141pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
142 .format(&PASSWORD_FORMAT)
143 .min_length(1)
b88f9c5b 144 .max_length(1024)
685e1334
DM
145 .schema();
146
147pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
148 .format(&PASSWORD_FORMAT)
149 .min_length(5)
150 .max_length(64)
151 .schema();
dcb8db66
DM
152
153pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new(
154 "X509 certificate fingerprint (sha256)."
155)
156 .format(&CERT_FINGERPRINT_SHA256_FORMAT)
157 .schema();
158
002a191a 159pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(r#"\
255f378a
DM
160Prevent changes if current configuration file has different SHA256 digest.
161This can be used to prevent concurrent modifications.
162"#
163)
164 .format(&PVE_CONFIG_DIGEST_FORMAT)
165 .schema();
166
167
168pub const CHUNK_DIGEST_FORMAT: ApiStringFormat =
169 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
170
171pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
172 .format(&CHUNK_DIGEST_FORMAT)
173 .schema();
174
175pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')")
176 .format(&ApiStringFormat::VerifyFn(|node| {
177 if node == "localhost" || node == proxmox::tools::nodename() {
178 Ok(())
179 } else {
180 bail!("no such node '{}'", node);
181 }
182 }))
183 .schema();
184
185pub const SEARCH_DOMAIN_SCHEMA: Schema =
186 StringSchema::new("Search domain for host-name lookup.").schema();
187
188pub const FIRST_DNS_SERVER_SCHEMA: Schema =
189 StringSchema::new("First name server IP address.")
190 .format(&IP_FORMAT)
191 .schema();
192
193pub const SECOND_DNS_SERVER_SCHEMA: Schema =
194 StringSchema::new("Second name server IP address.")
195 .format(&IP_FORMAT)
196 .schema();
197
198pub const THIRD_DNS_SERVER_SCHEMA: Schema =
199 StringSchema::new("Third name server IP address.")
200 .format(&IP_FORMAT)
201 .schema();
202
76cf5208
DM
203pub const IP_V4_SCHEMA: Schema =
204 StringSchema::new("IPv4 address.")
205 .format(&IP_V4_FORMAT)
206 .max_length(15)
207 .schema();
208
209pub const IP_V6_SCHEMA: Schema =
210 StringSchema::new("IPv6 address.")
211 .format(&IP_V6_FORMAT)
212 .max_length(39)
213 .schema();
214
215pub const IP_SCHEMA: Schema =
216 StringSchema::new("IP (IPv4 or IPv6) address.")
217 .format(&IP_FORMAT)
218 .max_length(39)
219 .schema();
220
221pub const CIDR_V4_SCHEMA: Schema =
222 StringSchema::new("IPv4 address with netmask (CIDR notation).")
223 .format(&CIDR_V4_FORMAT)
224 .max_length(18)
225 .schema();
226
227pub const CIDR_V6_SCHEMA: Schema =
228 StringSchema::new("IPv6 address with netmask (CIDR notation).")
229 .format(&CIDR_V6_FORMAT)
230 .max_length(43)
231 .schema();
232
233pub const CIDR_SCHEMA: Schema =
234 StringSchema::new("IP address (IPv4 or IPv6) with netmask (CIDR notation).")
235 .format(&CIDR_FORMAT)
236 .max_length(43)
237 .schema();
238
4b40148c
DM
239pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new(
240 "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.")
241 .format(&SINGLE_LINE_COMMENT_FORMAT)
242 .min_length(2)
243 .max_length(64)
244 .schema();
245
ca257c80
DM
246pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
247 "Access control path.")
248 .format(&ACL_PATH_FORMAT)
249 .min_length(1)
250 .max_length(128)
251 .schema();
252
253pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
254 "Allow to propagate (inherit) permissions.")
255 .default(true)
256 .schema();
257
258pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
259 "Type of 'ugid' property.")
ca257c80 260 .format(&ApiStringFormat::Enum(&[
bc0d0388
DM
261 EnumEntry::new("user", "User"),
262 EnumEntry::new("group", "Group")]))
ca257c80
DM
263 .schema();
264
255f378a
DM
265pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema =
266 StringSchema::new("Backup archive name.")
1ae5677d 267 .format(&PROXMOX_SAFE_ID_FORMAT)
255f378a
DM
268 .schema();
269
270pub const BACKUP_TYPE_SCHEMA: Schema =
271 StringSchema::new("Backup type.")
bc0d0388
DM
272 .format(&ApiStringFormat::Enum(&[
273 EnumEntry::new("vm", "Virtual Machine Backup"),
274 EnumEntry::new("ct", "Container Backup"),
275 EnumEntry::new("host", "Host Backup")]))
255f378a
DM
276 .schema();
277
278pub const BACKUP_ID_SCHEMA: Schema =
279 StringSchema::new("Backup ID.")
1ae5677d 280 .format(&PROXMOX_SAFE_ID_FORMAT)
255f378a
DM
281 .schema();
282
283pub const BACKUP_TIME_SCHEMA: Schema =
284 IntegerSchema::new("Backup time (Unix epoch.)")
285 .minimum(1_547_797_308)
286 .schema();
5830c205
DM
287
288pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
289 .max_length(256)
290 .schema();
66c49c21
DM
291
292pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
d0adf270 293 .format(&PROXMOX_SAFE_ID_FORMAT)
688fbe07 294 .min_length(3)
66c49c21
DM
295 .max_length(32)
296 .schema();
fc189b19 297
2888b27f
DC
298pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
299 "Run sync job at specified schedule.")
300 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
301 .schema();
302
42fdbe51
DM
303pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
304 "Run garbage collection job at specified schedule.")
305 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
306 .schema();
307
67f7ffd0
DM
308pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
309 "Run prune job at specified schedule.")
310 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
311 .schema();
312
167971ed
DM
313pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.")
314 .format(&PROXMOX_SAFE_ID_FORMAT)
315 .min_length(3)
316 .max_length(32)
317 .schema();
318
b4900286
DM
319pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
320 .format(&PROXMOX_SAFE_ID_FORMAT)
321 .min_length(3)
322 .max_length(32)
323 .schema();
324
325pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
326 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
327 .default(true)
328 .schema();
329
454c13ed
DM
330pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).")
331 .format(&SINGLE_LINE_COMMENT_FORMAT)
332 .schema();
fc189b19 333
b25f313d
DM
334pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
335 .format(&HOSTNAME_FORMAT)
336 .schema();
337
338pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
339 .format(&DNS_NAME_OR_IP_FORMAT)
340 .schema();
341
163dc16c
DM
342pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = StringSchema::new("Authentication domain ID")
343 .format(&PROXMOX_SAFE_ID_FORMAT)
344 .min_length(3)
345 .max_length(32)
346 .schema();
347
348pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID")
349 .format(&PROXMOX_USER_ID_FORMAT)
350 .min_length(3)
351 .max_length(64)
352 .schema();
353
9765092e
DM
354pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
355 .format(&PROXMOX_GROUP_ID_FORMAT)
356 .min_length(3)
357 .max_length(64)
358 .schema();
359
9069debc
DM
360pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
361 .format(&BLOCKDEVICE_NAME_FORMAT)
362 .min_length(3)
363 .max_length(64)
364 .schema();
fc189b19
DM
365
366// Complex type definitions
367
b31c8019
DM
368#[api(
369 properties: {
370 "backup-type": {
371 schema: BACKUP_TYPE_SCHEMA,
372 },
373 "backup-id": {
374 schema: BACKUP_ID_SCHEMA,
375 },
376 "last-backup": {
377 schema: BACKUP_TIME_SCHEMA,
378 },
379 "backup-count": {
380 type: Integer,
381 },
382 files: {
383 items: {
384 schema: BACKUP_ARCHIVE_NAME_SCHEMA
385 },
386 },
387 },
388)]
389#[derive(Serialize, Deserialize)]
390#[serde(rename_all="kebab-case")]
391/// Basic information about a backup group.
392pub struct GroupListItem {
393 pub backup_type: String, // enum
394 pub backup_id: String,
395 pub last_backup: i64,
396 /// Number of contained snapshots
397 pub backup_count: u64,
398 /// List of contained archive files.
399 pub files: Vec<String>,
04b0ca8b
DC
400 /// The owner of group
401 #[serde(skip_serializing_if="Option::is_none")]
402 pub owner: Option<String>,
b31c8019
DM
403}
404
fc189b19 405#[api(
fc189b19
DM
406 properties: {
407 "backup-type": {
408 schema: BACKUP_TYPE_SCHEMA,
409 },
410 "backup-id": {
411 schema: BACKUP_ID_SCHEMA,
412 },
413 "backup-time": {
414 schema: BACKUP_TIME_SCHEMA,
415 },
71da3d6a
DM
416 files: {
417 items: {
418 schema: BACKUP_ARCHIVE_NAME_SCHEMA
419 },
420 },
fc189b19
DM
421 },
422)]
423#[derive(Serialize, Deserialize)]
424#[serde(rename_all="kebab-case")]
71da3d6a 425/// Basic information about backup snapshot.
fc189b19
DM
426pub struct SnapshotListItem {
427 pub backup_type: String, // enum
428 pub backup_id: String,
429 pub backup_time: i64,
71da3d6a 430 /// List of contained archive files.
fc189b19 431 pub files: Vec<String>,
71da3d6a 432 /// Overall snapshot size (sum of all archive sizes).
fc189b19
DM
433 #[serde(skip_serializing_if="Option::is_none")]
434 pub size: Option<u64>,
04b0ca8b
DC
435 /// The owner of the snapshots group
436 #[serde(skip_serializing_if="Option::is_none")]
437 pub owner: Option<String>,
fc189b19 438}
ff620a3d 439
db1e061d
DM
440#[api(
441 properties: {
442 "backup-type": {
443 schema: BACKUP_TYPE_SCHEMA,
444 },
445 "backup-id": {
446 schema: BACKUP_ID_SCHEMA,
447 },
448 "backup-time": {
449 schema: BACKUP_TIME_SCHEMA,
450 },
451 },
452)]
453#[derive(Serialize, Deserialize)]
454#[serde(rename_all="kebab-case")]
455/// Prune result.
456pub struct PruneListItem {
457 pub backup_type: String, // enum
458 pub backup_id: String,
459 pub backup_time: i64,
460 /// Keep snapshot
461 pub keep: bool,
462}
463
49ff1092
DM
464pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new(
465 "Number of daily backups to keep.")
466 .minimum(1)
467 .schema();
468
469pub const PRUNE_SCHEMA_KEEP_HOURLY: Schema = IntegerSchema::new(
470 "Number of hourly backups to keep.")
471 .minimum(1)
472 .schema();
473
474pub const PRUNE_SCHEMA_KEEP_LAST: Schema = IntegerSchema::new(
475 "Number of backups to keep.")
476 .minimum(1)
477 .schema();
478
479pub const PRUNE_SCHEMA_KEEP_MONTHLY: Schema = IntegerSchema::new(
480 "Number of monthly backups to keep.")
481 .minimum(1)
482 .schema();
483
484pub const PRUNE_SCHEMA_KEEP_WEEKLY: Schema = IntegerSchema::new(
485 "Number of weekly backups to keep.")
486 .minimum(1)
487 .schema();
488
489pub const PRUNE_SCHEMA_KEEP_YEARLY: Schema = IntegerSchema::new(
490 "Number of yearly backups to keep.")
491 .minimum(1)
492 .schema();
493
09b1f7b2
DM
494#[api(
495 properties: {
496 "filename": {
497 schema: BACKUP_ARCHIVE_NAME_SCHEMA,
498 },
499 },
500)]
501#[derive(Serialize, Deserialize)]
502#[serde(rename_all="kebab-case")]
503/// Basic information about archive files inside a backup snapshot.
504pub struct BackupContent {
505 pub filename: String,
506 /// Archive size (from backup manifest).
507 #[serde(skip_serializing_if="Option::is_none")]
508 pub size: Option<u64>,
509}
510
a92830dc
DM
511#[api(
512 properties: {
513 "upid": {
514 optional: true,
515 schema: UPID_SCHEMA,
516 },
517 },
518)]
519#[derive(Clone, Serialize, Deserialize)]
520#[serde(rename_all="kebab-case")]
521/// Garbage collection status.
522pub struct GarbageCollectionStatus {
523 pub upid: Option<String>,
524 /// Number of processed index files.
525 pub index_file_count: usize,
526 /// Sum of bytes referred by index files.
527 pub index_data_bytes: u64,
528 /// Bytes used on disk.
529 pub disk_bytes: u64,
530 /// Chunks used on disk.
531 pub disk_chunks: usize,
532 /// Sum of removed bytes.
533 pub removed_bytes: u64,
534 /// Number of removed chunks.
535 pub removed_chunks: usize,
cf459b19
DM
536 /// Sum of pending bytes (pending removal - kept for safety).
537 pub pending_bytes: u64,
538 /// Number of pending chunks (pending removal - kept for safety).
539 pub pending_chunks: usize,
a92830dc
DM
540}
541
542impl Default for GarbageCollectionStatus {
543 fn default() -> Self {
544 GarbageCollectionStatus {
545 upid: None,
546 index_file_count: 0,
547 index_data_bytes: 0,
548 disk_bytes: 0,
549 disk_chunks: 0,
550 removed_bytes: 0,
551 removed_chunks: 0,
cf459b19
DM
552 pending_bytes: 0,
553 pending_chunks: 0,
a92830dc
DM
554 }
555 }
556}
557
558
1dc117bb
DM
559#[api()]
560#[derive(Serialize, Deserialize)]
561/// Storage space usage information.
562pub struct StorageStatus {
563 /// Total space (bytes).
564 pub total: u64,
565 /// Used space (bytes).
566 pub used: u64,
567 /// Available space (bytes).
568 pub avail: u64,
569}
ff620a3d 570
99384f79
DM
571#[api(
572 properties: {
573 "upid": { schema: UPID_SCHEMA },
574 },
575)]
576#[derive(Serialize, Deserialize)]
577/// Task properties.
578pub struct TaskListItem {
579 pub upid: String,
580 /// The node name where the task is running on.
581 pub node: String,
582 /// The Unix PID
583 pub pid: i64,
584 /// The task start time (Epoch)
585 pub pstart: u64,
586 /// The task start time (Epoch)
587 pub starttime: i64,
588 /// Worker type (arbitrary ASCII string)
589 pub worker_type: String,
590 /// Worker ID (arbitrary ASCII string)
591 pub worker_id: Option<String>,
592 /// The user who started the task
593 pub user: String,
594 /// The task end time (Epoch)
595 #[serde(skip_serializing_if="Option::is_none")]
596 pub endtime: Option<i64>,
597 /// Task end status
598 #[serde(skip_serializing_if="Option::is_none")]
599 pub status: Option<String>,
600}
601
df528ee6
DC
602impl From<crate::server::TaskListInfo> for TaskListItem {
603 fn from(info: crate::server::TaskListInfo) -> Self {
604 let (endtime, status) = info
605 .state
606 .map_or_else(|| (None, None), |(a,b)| (Some(a), Some(b)));
607
608 TaskListItem {
609 upid: info.upid_str,
610 node: "localhost".to_string(),
611 pid: info.upid.pid as i64,
612 pstart: info.upid.pstart,
613 starttime: info.upid.starttime,
614 worker_type: info.upid.worker_type,
615 worker_id: info.upid.worker_id,
616 user: info.upid.username,
617 endtime,
618 status,
619 }
620 }
621}
622
ed751dc2
DM
623#[api()]
624#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
625#[serde(rename_all = "lowercase")]
626/// Node Power command type.
627pub enum NodePowerCommand {
628 /// Restart the server
629 Reboot,
630 /// Shutdown the server
631 Shutdown,
632}
633
c357260d
DM
634#[api()]
635#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
636#[serde(rename_all = "lowercase")]
637/// Interface configuration method
638pub enum NetworkConfigMethod {
639 /// Configuration is done manually using other tools
640 Manual,
641 /// Define interfaces with statically allocated addresses.
642 Static,
643 /// Obtain an address via DHCP
644 DHCP,
645 /// Define the loopback interface.
646 Loopback,
647}
648
bab5d18c
DM
649#[api()]
650#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
651#[serde(rename_all = "kebab-case")]
652#[allow(non_camel_case_types)]
653#[repr(u8)]
654/// Linux Bond Mode
655pub enum LinuxBondMode {
656 /// Round-robin policy
657 balance_rr = 0,
658 /// Active-backup policy
659 active_backup = 1,
660 /// XOR policy
661 balance_xor = 2,
662 /// Broadcast policy
663 broadcast = 3,
664 /// IEEE 802.3ad Dynamic link aggregation
665 //#[serde(rename = "802.3ad")]
666 ieee802_3ad = 4,
667 /// Adaptive transmit load balancing
668 balance_tlb = 5,
669 /// Adaptive load balancing
670 balance_alb = 6,
671}
672
02269f3d
DM
673#[api()]
674#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
675#[serde(rename_all = "lowercase")]
676/// Network interface type
677pub enum NetworkInterfaceType {
678 /// Loopback
679 Loopback,
680 /// Physical Ethernet device
7b22acd0 681 Eth,
02269f3d
DM
682 /// Linux Bridge
683 Bridge,
684 /// Linux Bond
685 Bond,
686 /// Linux VLAN (eth.10)
687 Vlan,
688 /// Interface Alias (eth:1)
689 Alias,
690 /// Unknown interface type
691 Unknown,
692}
693
68da20bf
DM
694pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
695 .format(&NETWORK_INTERFACE_FORMAT)
696 .min_length(1)
697 .max_length(libc::IFNAMSIZ-1)
698 .schema();
699
3aedb738 700pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema = ArraySchema::new(
1d9a68c2
DM
701 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
702 .schema();
703
3aedb738
DM
704pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = StringSchema::new(
705 "A list of network devices, comma separated.")
706 .format(&ApiStringFormat::PropertyString(&NETWORK_INTERFACE_ARRAY_SCHEMA))
707 .schema();
708
c357260d
DM
709#[api(
710 properties: {
711 name: {
68da20bf 712 schema: NETWORK_INTERFACE_NAME_SCHEMA,
c357260d 713 },
7b22acd0 714 "type": {
02269f3d
DM
715 type: NetworkInterfaceType,
716 },
7b22acd0 717 method: {
c357260d
DM
718 type: NetworkConfigMethod,
719 optional: true,
720 },
7b22acd0 721 method6: {
c357260d
DM
722 type: NetworkConfigMethod,
723 optional: true,
724 },
7b22acd0
DM
725 cidr: {
726 schema: CIDR_V4_SCHEMA,
727 optional: true,
728 },
729 cidr6: {
730 schema: CIDR_V6_SCHEMA,
731 optional: true,
732 },
733 gateway: {
734 schema: IP_V4_SCHEMA,
735 optional: true,
736 },
737 gateway6: {
738 schema: IP_V6_SCHEMA,
739 optional: true,
740 },
741 options: {
c357260d
DM
742 description: "Option list (inet)",
743 type: Array,
744 items: {
68da20bf 745 description: "Optional attribute line.",
c357260d
DM
746 type: String,
747 },
748 },
7b22acd0 749 options6: {
c357260d
DM
750 description: "Option list (inet6)",
751 type: Array,
752 items: {
68da20bf 753 description: "Optional attribute line.",
c357260d
DM
754 type: String,
755 },
756 },
7b22acd0 757 comments: {
8a6b86b8
DM
758 description: "Comments (inet, may span multiple lines)",
759 type: String,
760 optional: true,
5f60a58f 761 },
7b22acd0 762 comments6: {
8a6b86b8
DM
763 description: "Comments (inet6, may span multiple lines)",
764 type: String,
765 optional: true,
5f60a58f 766 },
1d9a68c2 767 bridge_ports: {
3aedb738 768 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
1d9a68c2
DM
769 optional: true,
770 },
bab5d18c 771 slaves: {
3aedb738 772 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
42fbe91a
DM
773 optional: true,
774 },
bab5d18c
DM
775 bond_mode: {
776 type: LinuxBondMode,
777 optional: true,
778 }
c357260d
DM
779 }
780)]
781#[derive(Debug, Serialize, Deserialize)]
782/// Network Interface configuration
783pub struct Interface {
784 /// Autostart interface
7b22acd0
DM
785 #[serde(rename = "autostart")]
786 pub autostart: bool,
c357260d
DM
787 /// Interface is active (UP)
788 pub active: bool,
789 /// Interface name
790 pub name: String,
02269f3d 791 /// Interface type
7b22acd0 792 #[serde(rename = "type")]
02269f3d 793 pub interface_type: NetworkInterfaceType,
c357260d 794 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 795 pub method: Option<NetworkConfigMethod>,
c357260d 796 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 797 pub method6: Option<NetworkConfigMethod>,
c357260d 798 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44 799 /// IPv4 address with netmask
7b22acd0 800 pub cidr: Option<String>,
c357260d
DM
801 #[serde(skip_serializing_if="Option::is_none")]
802 /// IPv4 gateway
7b22acd0 803 pub gateway: Option<String>,
c357260d 804 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44 805 /// IPv6 address with netmask
7b22acd0 806 pub cidr6: Option<String>,
c357260d
DM
807 #[serde(skip_serializing_if="Option::is_none")]
808 /// IPv6 gateway
7b22acd0 809 pub gateway6: Option<String>,
3fce3bc3 810
c357260d 811 #[serde(skip_serializing_if="Vec::is_empty")]
7b22acd0 812 pub options: Vec<String>,
c357260d 813 #[serde(skip_serializing_if="Vec::is_empty")]
7b22acd0 814 pub options6: Vec<String>,
2c18efd9 815
8a6b86b8 816 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 817 pub comments: Option<String>,
8a6b86b8 818 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 819 pub comments6: Option<String>,
5f60a58f 820
2c18efd9
DM
821 #[serde(skip_serializing_if="Option::is_none")]
822 /// Maximum Transmission Unit
823 pub mtu: Option<u64>,
1d9a68c2
DM
824
825 #[serde(skip_serializing_if="Option::is_none")]
826 pub bridge_ports: Option<Vec<String>>,
7b22acd0
DM
827 /// Enable bridge vlan support.
828 #[serde(skip_serializing_if="Option::is_none")]
829 pub bridge_vlan_aware: Option<bool>,
42fbe91a
DM
830
831 #[serde(skip_serializing_if="Option::is_none")]
bab5d18c
DM
832 pub slaves: Option<Vec<String>>,
833 #[serde(skip_serializing_if="Option::is_none")]
834 pub bond_mode: Option<LinuxBondMode>,
c357260d
DM
835}
836
ff620a3d
DM
837// Regression tests
838
dcb8db66 839#[test]
ff329f97 840fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
dcb8db66
DM
841
842 let schema = CERT_FINGERPRINT_SHA256_SCHEMA;
843
844 let invalid_fingerprints = [
845 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
846 "88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
847 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8:ff",
848 "XX:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
849 "86:88:Y4:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
850 "86:88:0:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
851 ];
852
853 for fingerprint in invalid_fingerprints.iter() {
854 if let Ok(_) = parse_simple_value(fingerprint, &schema) {
add5861e 855 bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
dcb8db66
DM
856 }
857 }
858
859 let valid_fingerprints = [
860 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
861 "86:88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
862 ];
863
864 for fingerprint in valid_fingerprints.iter() {
865 let v = match parse_simple_value(fingerprint, &schema) {
866 Ok(v) => v,
867 Err(err) => {
868 bail!("unable to parse fingerprint '{}' - {}", fingerprint, err);
869 }
870 };
871
872 if v != serde_json::json!(fingerprint) {
873 bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
874 }
875 }
876
877 Ok(())
878}
879
ff620a3d 880#[test]
ff329f97 881fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
ff620a3d
DM
882
883 let schema = PROXMOX_USER_ID_SCHEMA;
884
885 let invalid_user_ids = [
886 "x", // too short
887 "xx", // too short
888 "xxx", // no realm
889 "xxx@", // no realm
890 "xx x@test", // contains space
891 "xx\nx@test", // contains control character
892 "x:xx@test", // contains collon
893 "xx/x@test", // contains slash
894 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
895 ];
896
897 for name in invalid_user_ids.iter() {
898 if let Ok(_) = parse_simple_value(name, &schema) {
add5861e 899 bail!("test userid '{}' failed - got Ok() while exception an error.", name);
ff620a3d
DM
900 }
901 }
902
903 let valid_user_ids = [
904 "xxx@y",
905 "name@y",
906 "xxx@test-it.com",
907 "xxx@_T_E_S_T-it.com",
908 "x_x-x.x@test-it.com",
909 ];
910
911 for name in valid_user_ids.iter() {
912 let v = match parse_simple_value(name, &schema) {
913 Ok(v) => v,
914 Err(err) => {
915 bail!("unable to parse userid '{}' - {}", name, err);
916 }
917 };
918
919 if v != serde_json::json!(name) {
920 bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
921 }
922 }
923
924 Ok(())
925}
a2f862ee
DM
926
927#[api()]
928#[derive(Copy, Clone, Serialize, Deserialize)]
929#[serde(rename_all = "UPPERCASE")]
930pub enum RRDMode {
931 /// Maximum
932 Max,
933 /// Average
934 Average,
935}
936
937
938#[api()]
939#[repr(u64)]
940#[derive(Copy, Clone, Serialize, Deserialize)]
941#[serde(rename_all = "lowercase")]
942pub enum RRDTimeFrameResolution {
943 /// 1 min => last 70 minutes
944 Hour = 60,
945 /// 30 min => last 35 hours
946 Day = 60*30,
947 /// 3 hours => about 8 days
948 Week = 60*180,
949 /// 12 hours => last 35 days
950 Month = 60*720,
951 /// 1 week => last 490 days
952 Year = 60*10080,
953}