]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/types/mod.rs
move drive config to pbs_config workspace
[proxmox-backup.git] / src / api2 / types / mod.rs
1 //! API Type Definitions
2
3 use anyhow::bail;
4 use serde::{Deserialize, Serialize};
5
6 use proxmox::api::{api, schema::*};
7 use proxmox::const_regex;
8
9 use crate::config::acl::Role;
10
11 mod acme;
12 pub use acme::*;
13
14 pub use pbs_api_types::*;
15
16 // File names: may not contain slashes, may not start with "."
17 pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
18 if name.starts_with('.') {
19 bail!("file names may not start with '.'");
20 }
21 if name.contains('/') {
22 bail!("file names may not contain slashes");
23 }
24 Ok(())
25 });
26
27 const_regex!{
28 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
29
30 /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
31 pub VERIFICATION_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):");
32 /// Regex for sync jobs 'REMOTE:REMOTE_DATASTORE:LOCAL_DATASTORE:ACTUAL_JOB_ID'
33 pub SYNC_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):");
34
35 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
36
37 pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
38
39 pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$";
40
41 pub DATASTORE_MAP_REGEX = concat!(r"(:?", PROXMOX_SAFE_ID_REGEX_STR!(), r"=)?", PROXMOX_SAFE_ID_REGEX_STR!());
42
43 }
44
45 pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
46 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
47
48 pub const HOSTNAME_FORMAT: ApiStringFormat =
49 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
50
51 pub const DNS_ALIAS_FORMAT: ApiStringFormat =
52 ApiStringFormat::Pattern(&DNS_ALIAS_REGEX);
53
54 pub const ACL_PATH_FORMAT: ApiStringFormat =
55 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
56
57 pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
58 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
59
60 pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat =
61 ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX);
62
63 pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
64 ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
65
66 pub const DATASTORE_MAP_FORMAT: ApiStringFormat =
67 ApiStringFormat::Pattern(&DATASTORE_MAP_REGEX);
68
69 pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
70 .format(&PASSWORD_FORMAT)
71 .min_length(1)
72 .max_length(1024)
73 .schema();
74
75 pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
76 .format(&PASSWORD_FORMAT)
77 .min_length(5)
78 .max_length(64)
79 .schema();
80
81 pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
82 .format(&CHUNK_DIGEST_FORMAT)
83 .schema();
84
85 pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')")
86 .format(&ApiStringFormat::VerifyFn(|node| {
87 if node == "localhost" || node == proxmox::tools::nodename() {
88 Ok(())
89 } else {
90 bail!("no such node '{}'", node);
91 }
92 }))
93 .schema();
94
95 pub const SEARCH_DOMAIN_SCHEMA: Schema =
96 StringSchema::new("Search domain for host-name lookup.").schema();
97
98 pub const FIRST_DNS_SERVER_SCHEMA: Schema =
99 StringSchema::new("First name server IP address.")
100 .format(&IP_FORMAT)
101 .schema();
102
103 pub const SECOND_DNS_SERVER_SCHEMA: Schema =
104 StringSchema::new("Second name server IP address.")
105 .format(&IP_FORMAT)
106 .schema();
107
108 pub const THIRD_DNS_SERVER_SCHEMA: Schema =
109 StringSchema::new("Third name server IP address.")
110 .format(&IP_FORMAT)
111 .schema();
112
113 pub const IP_V4_SCHEMA: Schema =
114 StringSchema::new("IPv4 address.")
115 .format(&IP_V4_FORMAT)
116 .max_length(15)
117 .schema();
118
119 pub const IP_V6_SCHEMA: Schema =
120 StringSchema::new("IPv6 address.")
121 .format(&IP_V6_FORMAT)
122 .max_length(39)
123 .schema();
124
125 pub const IP_SCHEMA: Schema =
126 StringSchema::new("IP (IPv4 or IPv6) address.")
127 .format(&IP_FORMAT)
128 .max_length(39)
129 .schema();
130
131 pub const CIDR_V4_SCHEMA: Schema =
132 StringSchema::new("IPv4 address with netmask (CIDR notation).")
133 .format(&CIDR_V4_FORMAT)
134 .max_length(18)
135 .schema();
136
137 pub const CIDR_V6_SCHEMA: Schema =
138 StringSchema::new("IPv6 address with netmask (CIDR notation).")
139 .format(&CIDR_V6_FORMAT)
140 .max_length(43)
141 .schema();
142
143 pub const CIDR_SCHEMA: Schema =
144 StringSchema::new("IP address (IPv4 or IPv6) with netmask (CIDR notation).")
145 .format(&CIDR_FORMAT)
146 .max_length(43)
147 .schema();
148
149 pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new(
150 "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.")
151 .format(&SINGLE_LINE_COMMENT_FORMAT)
152 .min_length(2)
153 .max_length(64)
154 .schema();
155
156 pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
157 "Access control path.")
158 .format(&ACL_PATH_FORMAT)
159 .min_length(1)
160 .max_length(128)
161 .schema();
162
163 pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
164 "Allow to propagate (inherit) permissions.")
165 .default(true)
166 .schema();
167
168 pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
169 "Type of 'ugid' property.")
170 .format(&ApiStringFormat::Enum(&[
171 EnumEntry::new("user", "User"),
172 EnumEntry::new("group", "Group")]))
173 .schema();
174
175 #[api(
176 properties: {
177 propagate: {
178 schema: ACL_PROPAGATE_SCHEMA,
179 },
180 path: {
181 schema: ACL_PATH_SCHEMA,
182 },
183 ugid_type: {
184 schema: ACL_UGID_TYPE_SCHEMA,
185 },
186 ugid: {
187 type: String,
188 description: "User or Group ID.",
189 },
190 roleid: {
191 type: Role,
192 }
193 }
194 )]
195 #[derive(Serialize, Deserialize)]
196 /// ACL list entry.
197 pub struct AclListItem {
198 pub path: String,
199 pub ugid: String,
200 pub ugid_type: String,
201 pub propagate: bool,
202 pub roleid: String,
203 }
204
205 pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
206 .max_length(256)
207 .schema();
208
209 pub const DATASTORE_MAP_SCHEMA: Schema = StringSchema::new("Datastore mapping.")
210 .format(&DATASTORE_MAP_FORMAT)
211 .min_length(3)
212 .max_length(65)
213 .type_text("(<source>=)?<target>")
214 .schema();
215
216 pub const DATASTORE_MAP_ARRAY_SCHEMA: Schema = ArraySchema::new(
217 "Datastore mapping list.", &DATASTORE_MAP_SCHEMA)
218 .schema();
219
220 pub const DATASTORE_MAP_LIST_SCHEMA: Schema = StringSchema::new(
221 "A list of Datastore mappings (or single datastore), comma separated. \
222 For example 'a=b,e' maps the source datastore 'a' to target 'b and \
223 all other sources to the default 'e'. If no default is given, only the \
224 specified sources are mapped.")
225 .format(&ApiStringFormat::PropertyString(&DATASTORE_MAP_ARRAY_SCHEMA))
226 .schema();
227
228 pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
229 "Run sync job at specified schedule.")
230 .format(&ApiStringFormat::VerifyFn(pbs_systemd::time::verify_calendar_event))
231 .type_text("<calendar-event>")
232 .schema();
233
234 pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
235 "Run garbage collection job at specified schedule.")
236 .format(&ApiStringFormat::VerifyFn(pbs_systemd::time::verify_calendar_event))
237 .type_text("<calendar-event>")
238 .schema();
239
240 pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
241 "Run prune job at specified schedule.")
242 .format(&ApiStringFormat::VerifyFn(pbs_systemd::time::verify_calendar_event))
243 .type_text("<calendar-event>")
244 .schema();
245
246 pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new(
247 "Run verify job at specified schedule.")
248 .format(&ApiStringFormat::VerifyFn(pbs_systemd::time::verify_calendar_event))
249 .type_text("<calendar-event>")
250 .schema();
251
252 pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
253 .format(&PROXMOX_SAFE_ID_FORMAT)
254 .min_length(3)
255 .max_length(32)
256 .schema();
257
258 pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
259 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
260 .default(true)
261 .schema();
262
263 pub const IGNORE_VERIFIED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
264 "Do not verify backups that are already verified if their verification is not outdated.")
265 .default(true)
266 .schema();
267
268 pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema = IntegerSchema::new(
269 "Days after that a verification becomes outdated")
270 .minimum(1)
271 .schema();
272
273 pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
274 .format(&HOSTNAME_FORMAT)
275 .schema();
276
277 pub const SUBSCRIPTION_KEY_SCHEMA: Schema = StringSchema::new("Proxmox Backup Server subscription key.")
278 .format(&SUBSCRIPTION_KEY_FORMAT)
279 .min_length(15)
280 .max_length(16)
281 .schema();
282
283 pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
284 .format(&BLOCKDEVICE_NAME_FORMAT)
285 .min_length(3)
286 .max_length(64)
287 .schema();
288
289 // Complex type definitions
290
291 #[api(
292 properties: {
293 "gc-status": {
294 type: GarbageCollectionStatus,
295 optional: true,
296 },
297 counts: {
298 type: Counts,
299 optional: true,
300 },
301 },
302 )]
303 #[derive(Serialize, Deserialize)]
304 #[serde(rename_all="kebab-case")]
305 /// Overall Datastore status and useful information.
306 pub struct DataStoreStatus {
307 /// Total space (bytes).
308 pub total: u64,
309 /// Used space (bytes).
310 pub used: u64,
311 /// Available space (bytes).
312 pub avail: u64,
313 /// Status of last GC
314 #[serde(skip_serializing_if="Option::is_none")]
315 pub gc_status: Option<GarbageCollectionStatus>,
316 /// Group/Snapshot counts
317 #[serde(skip_serializing_if="Option::is_none")]
318 pub counts: Option<Counts>,
319 }
320
321 #[api()]
322 #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
323 #[serde(rename_all = "lowercase")]
324 pub enum TaskStateType {
325 /// Ok
326 OK,
327 /// Warning
328 Warning,
329 /// Error
330 Error,
331 /// Unknown
332 Unknown,
333 }
334
335 #[api()]
336 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
337 #[serde(rename_all = "lowercase")]
338 /// Node Power command type.
339 pub enum NodePowerCommand {
340 /// Restart the server
341 Reboot,
342 /// Shutdown the server
343 Shutdown,
344 }
345
346 #[api()]
347 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
348 #[serde(rename_all = "lowercase")]
349 /// Interface configuration method
350 pub enum NetworkConfigMethod {
351 /// Configuration is done manually using other tools
352 Manual,
353 /// Define interfaces with statically allocated addresses.
354 Static,
355 /// Obtain an address via DHCP
356 DHCP,
357 /// Define the loopback interface.
358 Loopback,
359 }
360
361 #[api()]
362 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
363 #[serde(rename_all = "kebab-case")]
364 #[allow(non_camel_case_types)]
365 #[repr(u8)]
366 /// Linux Bond Mode
367 pub enum LinuxBondMode {
368 /// Round-robin policy
369 balance_rr = 0,
370 /// Active-backup policy
371 active_backup = 1,
372 /// XOR policy
373 balance_xor = 2,
374 /// Broadcast policy
375 broadcast = 3,
376 /// IEEE 802.3ad Dynamic link aggregation
377 #[serde(rename = "802.3ad")]
378 ieee802_3ad = 4,
379 /// Adaptive transmit load balancing
380 balance_tlb = 5,
381 /// Adaptive load balancing
382 balance_alb = 6,
383 }
384
385 #[api()]
386 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
387 #[serde(rename_all = "kebab-case")]
388 #[allow(non_camel_case_types)]
389 #[repr(u8)]
390 /// Bond Transmit Hash Policy for LACP (802.3ad)
391 pub enum BondXmitHashPolicy {
392 /// Layer 2
393 layer2 = 0,
394 /// Layer 2+3
395 #[serde(rename = "layer2+3")]
396 layer2_3 = 1,
397 /// Layer 3+4
398 #[serde(rename = "layer3+4")]
399 layer3_4 = 2,
400 }
401
402 #[api()]
403 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
404 #[serde(rename_all = "lowercase")]
405 /// Network interface type
406 pub enum NetworkInterfaceType {
407 /// Loopback
408 Loopback,
409 /// Physical Ethernet device
410 Eth,
411 /// Linux Bridge
412 Bridge,
413 /// Linux Bond
414 Bond,
415 /// Linux VLAN (eth.10)
416 Vlan,
417 /// Interface Alias (eth:1)
418 Alias,
419 /// Unknown interface type
420 Unknown,
421 }
422
423 pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
424 .format(&NETWORK_INTERFACE_FORMAT)
425 .min_length(1)
426 .max_length(libc::IFNAMSIZ-1)
427 .schema();
428
429 pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema = ArraySchema::new(
430 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
431 .schema();
432
433 pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = StringSchema::new(
434 "A list of network devices, comma separated.")
435 .format(&ApiStringFormat::PropertyString(&NETWORK_INTERFACE_ARRAY_SCHEMA))
436 .schema();
437
438 #[api(
439 properties: {
440 name: {
441 schema: NETWORK_INTERFACE_NAME_SCHEMA,
442 },
443 "type": {
444 type: NetworkInterfaceType,
445 },
446 method: {
447 type: NetworkConfigMethod,
448 optional: true,
449 },
450 method6: {
451 type: NetworkConfigMethod,
452 optional: true,
453 },
454 cidr: {
455 schema: CIDR_V4_SCHEMA,
456 optional: true,
457 },
458 cidr6: {
459 schema: CIDR_V6_SCHEMA,
460 optional: true,
461 },
462 gateway: {
463 schema: IP_V4_SCHEMA,
464 optional: true,
465 },
466 gateway6: {
467 schema: IP_V6_SCHEMA,
468 optional: true,
469 },
470 options: {
471 description: "Option list (inet)",
472 type: Array,
473 items: {
474 description: "Optional attribute line.",
475 type: String,
476 },
477 },
478 options6: {
479 description: "Option list (inet6)",
480 type: Array,
481 items: {
482 description: "Optional attribute line.",
483 type: String,
484 },
485 },
486 comments: {
487 description: "Comments (inet, may span multiple lines)",
488 type: String,
489 optional: true,
490 },
491 comments6: {
492 description: "Comments (inet6, may span multiple lines)",
493 type: String,
494 optional: true,
495 },
496 bridge_ports: {
497 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
498 optional: true,
499 },
500 slaves: {
501 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
502 optional: true,
503 },
504 bond_mode: {
505 type: LinuxBondMode,
506 optional: true,
507 },
508 "bond-primary": {
509 schema: NETWORK_INTERFACE_NAME_SCHEMA,
510 optional: true,
511 },
512 bond_xmit_hash_policy: {
513 type: BondXmitHashPolicy,
514 optional: true,
515 },
516 }
517 )]
518 #[derive(Debug, Serialize, Deserialize)]
519 /// Network Interface configuration
520 pub struct Interface {
521 /// Autostart interface
522 #[serde(rename = "autostart")]
523 pub autostart: bool,
524 /// Interface is active (UP)
525 pub active: bool,
526 /// Interface name
527 pub name: String,
528 /// Interface type
529 #[serde(rename = "type")]
530 pub interface_type: NetworkInterfaceType,
531 #[serde(skip_serializing_if="Option::is_none")]
532 pub method: Option<NetworkConfigMethod>,
533 #[serde(skip_serializing_if="Option::is_none")]
534 pub method6: Option<NetworkConfigMethod>,
535 #[serde(skip_serializing_if="Option::is_none")]
536 /// IPv4 address with netmask
537 pub cidr: Option<String>,
538 #[serde(skip_serializing_if="Option::is_none")]
539 /// IPv4 gateway
540 pub gateway: Option<String>,
541 #[serde(skip_serializing_if="Option::is_none")]
542 /// IPv6 address with netmask
543 pub cidr6: Option<String>,
544 #[serde(skip_serializing_if="Option::is_none")]
545 /// IPv6 gateway
546 pub gateway6: Option<String>,
547
548 #[serde(skip_serializing_if="Vec::is_empty")]
549 pub options: Vec<String>,
550 #[serde(skip_serializing_if="Vec::is_empty")]
551 pub options6: Vec<String>,
552
553 #[serde(skip_serializing_if="Option::is_none")]
554 pub comments: Option<String>,
555 #[serde(skip_serializing_if="Option::is_none")]
556 pub comments6: Option<String>,
557
558 #[serde(skip_serializing_if="Option::is_none")]
559 /// Maximum Transmission Unit
560 pub mtu: Option<u64>,
561
562 #[serde(skip_serializing_if="Option::is_none")]
563 pub bridge_ports: Option<Vec<String>>,
564 /// Enable bridge vlan support.
565 #[serde(skip_serializing_if="Option::is_none")]
566 pub bridge_vlan_aware: Option<bool>,
567
568 #[serde(skip_serializing_if="Option::is_none")]
569 pub slaves: Option<Vec<String>>,
570 #[serde(skip_serializing_if="Option::is_none")]
571 pub bond_mode: Option<LinuxBondMode>,
572 #[serde(skip_serializing_if="Option::is_none")]
573 #[serde(rename = "bond-primary")]
574 pub bond_primary: Option<String>,
575 pub bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
576 }
577
578 // Regression tests
579
580 #[test]
581 fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
582
583 let schema = CERT_FINGERPRINT_SHA256_SCHEMA;
584
585 let invalid_fingerprints = [
586 "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",
587 "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",
588 "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",
589 "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",
590 "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",
591 "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",
592 ];
593
594 for fingerprint in invalid_fingerprints.iter() {
595 if parse_simple_value(fingerprint, &schema).is_ok() {
596 bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
597 }
598 }
599
600 let valid_fingerprints = [
601 "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",
602 "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",
603 ];
604
605 for fingerprint in valid_fingerprints.iter() {
606 let v = match parse_simple_value(fingerprint, &schema) {
607 Ok(v) => v,
608 Err(err) => {
609 bail!("unable to parse fingerprint '{}' - {}", fingerprint, err);
610 }
611 };
612
613 if v != serde_json::json!(fingerprint) {
614 bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
615 }
616 }
617
618 Ok(())
619 }
620
621 #[test]
622 fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
623 let invalid_user_ids = [
624 "x", // too short
625 "xx", // too short
626 "xxx", // no realm
627 "xxx@", // no realm
628 "xx x@test", // contains space
629 "xx\nx@test", // contains control character
630 "x:xx@test", // contains collon
631 "xx/x@test", // contains slash
632 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
633 ];
634
635 for name in invalid_user_ids.iter() {
636 if parse_simple_value(name, &Userid::API_SCHEMA).is_ok() {
637 bail!("test userid '{}' failed - got Ok() while exception an error.", name);
638 }
639 }
640
641 let valid_user_ids = [
642 "xxx@y",
643 "name@y",
644 "xxx@test-it.com",
645 "xxx@_T_E_S_T-it.com",
646 "x_x-x.x@test-it.com",
647 ];
648
649 for name in valid_user_ids.iter() {
650 let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
651 Ok(v) => v,
652 Err(err) => {
653 bail!("unable to parse userid '{}' - {}", name, err);
654 }
655 };
656
657 if v != serde_json::json!(name) {
658 bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
659 }
660 }
661
662 Ok(())
663 }
664
665 #[api()]
666 #[derive(Copy, Clone, Serialize, Deserialize)]
667 #[serde(rename_all = "UPPERCASE")]
668 pub enum RRDMode {
669 /// Maximum
670 Max,
671 /// Average
672 Average,
673 }
674
675
676 #[api()]
677 #[repr(u64)]
678 #[derive(Copy, Clone, Serialize, Deserialize)]
679 #[serde(rename_all = "lowercase")]
680 pub enum RRDTimeFrameResolution {
681 /// 1 min => last 70 minutes
682 Hour = 60,
683 /// 30 min => last 35 hours
684 Day = 60*30,
685 /// 3 hours => about 8 days
686 Week = 60*180,
687 /// 12 hours => last 35 days
688 Month = 60*720,
689 /// 1 week => last 490 days
690 Year = 60*10080,
691 }
692
693 #[api()]
694 #[derive(Debug, Clone, Serialize, Deserialize)]
695 #[serde(rename_all = "PascalCase")]
696 /// Describes a package for which an update is available.
697 pub struct APTUpdateInfo {
698 /// Package name
699 pub package: String,
700 /// Package title
701 pub title: String,
702 /// Package architecture
703 pub arch: String,
704 /// Human readable package description
705 pub description: String,
706 /// New version to be updated to
707 pub version: String,
708 /// Old version currently installed
709 pub old_version: String,
710 /// Package origin
711 pub origin: String,
712 /// Package priority in human-readable form
713 pub priority: String,
714 /// Package section
715 pub section: String,
716 /// URL under which the package's changelog can be retrieved
717 pub change_log_url: String,
718 /// Custom extra field for additional package information
719 #[serde(skip_serializing_if="Option::is_none")]
720 pub extra_info: Option<String>,
721 }
722
723 #[api()]
724 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
725 #[serde(rename_all = "lowercase")]
726 /// When do we send notifications
727 pub enum Notify {
728 /// Never send notification
729 Never,
730 /// Send notifications for failed and successful jobs
731 Always,
732 /// Send notifications for failed jobs only
733 Error,
734 }
735
736 #[api(
737 properties: {
738 gc: {
739 type: Notify,
740 optional: true,
741 },
742 verify: {
743 type: Notify,
744 optional: true,
745 },
746 sync: {
747 type: Notify,
748 optional: true,
749 },
750 },
751 )]
752 #[derive(Debug, Serialize, Deserialize)]
753 /// Datastore notify settings
754 pub struct DatastoreNotify {
755 /// Garbage collection settings
756 pub gc: Option<Notify>,
757 /// Verify job setting
758 pub verify: Option<Notify>,
759 /// Sync job setting
760 pub sync: Option<Notify>,
761 }
762
763 pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
764 "Datastore notification setting")
765 .format(&ApiStringFormat::PropertyString(&DatastoreNotify::API_SCHEMA))
766 .schema();
767
768
769 #[api(
770 properties: {
771 "next-run": {
772 description: "Estimated time of the next run (UNIX epoch).",
773 optional: true,
774 type: Integer,
775 },
776 "last-run-state": {
777 description: "Result of the last run.",
778 optional: true,
779 type: String,
780 },
781 "last-run-upid": {
782 description: "Task UPID of the last run.",
783 optional: true,
784 type: String,
785 },
786 "last-run-endtime": {
787 description: "Endtime of the last run.",
788 optional: true,
789 type: Integer,
790 },
791 }
792 )]
793 #[derive(Serialize,Deserialize,Default)]
794 #[serde(rename_all="kebab-case")]
795 /// Job Scheduling Status
796 pub struct JobScheduleStatus {
797 #[serde(skip_serializing_if="Option::is_none")]
798 pub next_run: Option<i64>,
799 #[serde(skip_serializing_if="Option::is_none")]
800 pub last_run_state: Option<String>,
801 #[serde(skip_serializing_if="Option::is_none")]
802 pub last_run_upid: Option<String>,
803 #[serde(skip_serializing_if="Option::is_none")]
804 pub last_run_endtime: Option<i64>,
805 }
806
807 #[api]
808 #[derive(Serialize, Deserialize, Default)]
809 #[serde(rename_all = "kebab-case")]
810 /// Node memory usage counters
811 pub struct NodeMemoryCounters {
812 /// Total memory
813 pub total: u64,
814 /// Used memory
815 pub used: u64,
816 /// Free memory
817 pub free: u64,
818 }
819
820 #[api]
821 #[derive(Serialize, Deserialize, Default)]
822 #[serde(rename_all = "kebab-case")]
823 /// Node swap usage counters
824 pub struct NodeSwapCounters {
825 /// Total swap
826 pub total: u64,
827 /// Used swap
828 pub used: u64,
829 /// Free swap
830 pub free: u64,
831 }
832
833 #[api]
834 #[derive(Serialize,Deserialize,Default)]
835 #[serde(rename_all = "kebab-case")]
836 /// Contains general node information such as the fingerprint`
837 pub struct NodeInformation {
838 /// The SSL Fingerprint
839 pub fingerprint: String,
840 }
841
842 #[api]
843 #[derive(Serialize, Deserialize, Default)]
844 #[serde(rename_all = "kebab-case")]
845 /// Information about the CPU
846 pub struct NodeCpuInformation {
847 /// The CPU model
848 pub model: String,
849 /// The number of CPU sockets
850 pub sockets: usize,
851 /// The number of CPU cores (incl. threads)
852 pub cpus: usize,
853 }
854
855 #[api(
856 properties: {
857 memory: {
858 type: NodeMemoryCounters,
859 },
860 root: {
861 type: StorageStatus,
862 },
863 swap: {
864 type: NodeSwapCounters,
865 },
866 loadavg: {
867 type: Array,
868 items: {
869 type: Number,
870 description: "the load",
871 }
872 },
873 cpuinfo: {
874 type: NodeCpuInformation,
875 },
876 info: {
877 type: NodeInformation,
878 }
879 },
880 )]
881 #[derive(Serialize, Deserialize, Default)]
882 #[serde(rename_all = "kebab-case")]
883 /// The Node status
884 pub struct NodeStatus {
885 pub memory: NodeMemoryCounters,
886 pub root: StorageStatus,
887 pub swap: NodeSwapCounters,
888 /// The current uptime of the server.
889 pub uptime: u64,
890 /// Load for 1, 5 and 15 minutes.
891 pub loadavg: [f64; 3],
892 /// The current kernel version.
893 pub kversion: String,
894 /// Total CPU usage since last query.
895 pub cpu: f64,
896 /// Total IO wait since last query.
897 pub wait: f64,
898 pub cpuinfo: NodeCpuInformation,
899 pub info: NodeInformation,
900 }
901
902 pub const HTTP_PROXY_SCHEMA: Schema = StringSchema::new(
903 "HTTP proxy configuration [http://]<host>[:port]")
904 .format(&ApiStringFormat::VerifyFn(|s| {
905 proxmox_http::ProxyConfig::parse_proxy_url(s)?;
906 Ok(())
907 }))
908 .min_length(1)
909 .max_length(128)
910 .type_text("[http://]<host>[:port]")
911 .schema();