]> git.proxmox.com Git - pve-cluster.git/blame - data/PVE/DataCenterConfig.pm
dc config: mark HA CRS scheduler sub-property optional
[pve-cluster.git] / data / PVE / DataCenterConfig.pm
CommitLineData
ab966729
FG
1package PVE::DataCenterConfig;
2
3use strict;
4use warnings;
5
51866274 6use PVE::JSONSchema qw(parse_property_string);
ab966729
FG
7use PVE::Tools;
8use PVE::Cluster;
9
b466e489
FE
10my $crs_format = {
11 ha => {
12 type => 'string',
13 enum => ['basic', 'static'],
c008170e 14 optional => 1,
b466e489 15 default => 'basic',
c008170e 16 description => "Use this resource scheduler mode for HA.",
b466e489
FE
17 verbose_description => "Configures how the HA manager should select nodes to start or ".
18 "recover services. With 'basic', only the number of services is used, with 'static', ".
19 "static CPU and memory configuration of services is considered.",
20 },
21};
22
ab966729
FG
23my $migration_format = {
24 type => {
25 default_key => 1,
26 type => 'string',
27 enum => ['secure', 'insecure'],
28 description => "Migration traffic is encrypted using an SSH tunnel by " .
29 "default. On secure, completely private networks this can be " .
30 "disabled to increase performance.",
31 default => 'secure',
32 },
33 network => {
34 optional => 1,
35 type => 'string', format => 'CIDR',
36 format_description => 'CIDR',
37 description => "CIDR of the (sub) network that is used for migration."
38 },
39};
40
63e5cd09
TL
41my $notification_format = {
42 'package-updates' => {
43 type => 'string',
44 enum => ['auto', 'always', 'never'],
45 description => "Control when the daily update job should send out notification mails.",
46 verbose_description => "Control how often the daily update job should send out notification mails:\n"
47 ."* 'auto' daily for systems with a valid subscription, as those are assumed to be "
48 ." production-ready and thus should know about pending updates.\n"
49 ."* 'always' every update, if there are new pending updates.\n"
50 ."* 'never' never send a notification for new pending updates.\n",
51 default => 'auto',
52 },
53};
54
ab966729
FG
55my $ha_format = {
56 shutdown_policy => {
57 type => 'string',
28c22b8d
TL
58 enum => ['freeze', 'failover', 'conditional', 'migrate'],
59 description => "The policy for HA services on node shutdown. 'freeze' disables ".
60 "auto-recovery, 'failover' ensures recovery, 'conditional' recovers on ".
61 "poweroff and freezes on reboot. 'migrate' will migrate running services ".
62 "to other nodes, if possible. With 'freeze' or 'failover', HA Services will ".
63 "always get stopped first on shutdown.",
64 verbose_description => "Describes the policy for handling HA services on poweroff ".
65 "or reboot of a node. Freeze will always freeze services which are still located ".
66 "on the node on shutdown, those services won't be recovered by the HA manager. ".
67 "Failover will not mark the services as frozen and thus the services will get ".
68 "recovered to other nodes, if the shutdown node does not come up again quickly ".
69 "(< 1min). 'conditional' chooses automatically depending on the type of shutdown, ".
70 "i.e., on a reboot the service will be frozen but on a poweroff the service will ".
1818509b 71 "stay as is, and thus get recovered after about 2 minutes. ".
dc43b8a4
TL
72 "Migrate will try to move all running services to another node when a reboot or ".
73 "shutdown was triggered. The poweroff process will only continue once no running services ".
1818509b
TL
74 "are located on the node anymore. If the node comes up again, the service will ".
75 "be moved back to the previously powered-off node, at least if no other migration, ".
76 "reloaction or recovery took place.",
ab966729
FG
77 default => 'conditional',
78 }
79};
80
75e5d02e
TL
81my $next_id_format = {
82 lower => {
83 type => 'integer',
84 description => "Lower, inclusive boundary for free next-id API range.",
85 min => 100,
86 max => 1000 * 1000 * 1000 - 1,
87 default => 100,
88 optional => 1,
89 },
90 upper => {
91 type => 'integer',
8ca3c4bb 92 description => "Upper, exclusive boundary for free next-id API range.",
75e5d02e 93 min => 100,
8ca3c4bb 94 max => 1000 * 1000 * 1000,
75e5d02e
TL
95 default => 1000 * 1000, # lower than the maximum on purpose
96 optional => 1,
97 },
98};
99
bcfa5ac1 100my $u2f_format = {
ab966729
FG
101 appid => {
102 type => 'string',
103 description => "U2F AppId URL override. Defaults to the origin.",
104 format_description => 'APPID',
105 optional => 1,
106 },
107 origin => {
108 type => 'string',
109 description => "U2F Origin override. Mostly useful for single nodes with a single URL.",
110 format_description => 'URL',
111 optional => 1,
112 },
113};
114
8545a705
WB
115my $webauthn_format = {
116 rp => {
117 type => 'string',
118 description =>
119 'Relying party name. Any text identifier.'
120 .' Changing this *may* break existing credentials.',
121 format_description => 'RELYING_PARTY',
122 optional => 1,
123 },
124 origin => {
125 type => 'string',
126 description =>
127 'Site origin. Must be a `https://` URL (or `http://localhost`).'
128 .' Should contain the address users type in their browsers to access'
129 .' the web interface.'
130 .' Changing this *may* break existing credentials.',
131 format_description => 'URL',
132 optional => 1,
133 },
134 id => {
135 type => 'string',
136 description =>
3b0214b6 137 'Relying party ID. Must be the domain name without protocol, port or location.'
8545a705
WB
138 .' Changing this *will* break existing credentials.',
139 format_description => 'DOMAINNAME',
140 optional => 1,
141 },
3b0214b6
WB
142 'allow-subdomains' => {
143 type => 'boolean',
144 description => 'Whether to allow the origin to be a subdomain, rather than the exact URL.',
145 optional => 1,
146 default => 1,
147 },
8545a705 148};
ab966729
FG
149
150PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
151sub pve_verify_mac_prefix {
152 my ($mac_prefix, $noerr) = @_;
153
154 if ($mac_prefix !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){0,2}:?$/i) {
155 return undef if $noerr;
156 die "value is not a valid unicast MAC address prefix\n";
157 }
158 return $mac_prefix;
159}
160
af234c4b
DC
161my $COLOR_RE = '[0-9a-fA-F]{6}';
162my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
163
164my $tag_style_format = {
165 'shape' => {
166 optional => 1,
167 type => 'string',
168 enum => ['full', 'circle', 'dense', 'none'],
169 default => 'circle',
170 description => "Tag shape for the web ui tree. 'full' draws the full tag. "
171 ."'circle' draws only a circle with the background color. "
172 ."'dense' only draws a small rectancle (useful when many tags are assigned to each guest)."
173 ."'none' disables showing the tags.",
174 },
175 'color-map' => {
176 optional => 1,
177 type => 'string',
178 pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
179 typetext => '<tag>:<hex-color>[:<hex-color-for-text>][;<tag>=...]',
180 description => "Manual color mapping for tags (semicolon separated).",
181 },
8456e52e
DC
182 ordering => {
183 optional => 1,
184 type => 'string',
185 enum => ['config', 'alphabetical'],
186 default => 'alphabetical',
2453d13f
TL
187 description => 'Controls the sorting of the tags in the web-interface and the API update.',
188 },
189 'case-sensitive' => {
190 type => 'boolean',
191 description => 'Controls if filtering for unique tags on update should check case-sensitive.',
192 optional => 1,
193 default => 0,
194 },
af234c4b
DC
195};
196
c17e397b
DC
197my $user_tag_privs_format = {
198 'user-allow' => {
199 optional => 1,
200 type => 'string',
201 enum => ['none', 'list', 'existing', 'free'],
202 default => 'free',
203 description => "Controls tag usage for users without `Sys.Modify` on `/` by either "
204 ."allowing `none`, a `list`, already `existing` or anything (`free`).",
205 verbose_description => "Controls which tags can be set or deleted on resources a user "
206 ."controls (such as guests). Users with the `Sys.Modify` privilege on `/` are always "
207 ." unrestricted. "
63e5cd09
TL
208 ."* 'none' no tags are usable. "
209 ."* 'list' tags from 'user-allow-list' are usable. "
210 ."* 'existing' like list, but already existing tags of resources are also usable."
211 ."* 'free' no tag restrictions.",
c17e397b
DC
212 },
213 'user-allow-list' => {
214 optional => 1,
215 type => 'string',
216 pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*",
217 typetext => "<tag>[;<tag>...]",
218 description => "List of tags users are allowed to set and delete (semicolon separated) "
219 ."for 'user-allow' values 'list' and 'existing'.",
220 },
221};
222
ab966729
FG
223my $datacenter_schema = {
224 type => "object",
225 additionalProperties => 0,
226 properties => {
b466e489
FE
227 crs => {
228 optional => 1,
229 type => 'string', format => $crs_format,
230 description => "Cluster resource scheduling settings.",
231 },
ab966729
FG
232 keyboard => {
233 optional => 1,
234 type => 'string',
235 description => "Default keybord layout for vnc server.",
236 enum => PVE::Tools::kvmkeymaplist(),
237 },
238 language => {
239 optional => 1,
240 type => 'string',
241 description => "Default GUI language.",
242 enum => [
243 'ca',
244 'da',
245 'de',
246 'en',
247 'es',
248 'eu',
249 'fa',
250 'fr',
251 'he',
252 'it',
253 'ja',
254 'nb',
255 'nn',
256 'pl',
257 'pt_BR',
258 'ru',
259 'sl',
260 'sv',
261 'tr',
262 'zh_CN',
263 'zh_TW',
264 ],
265 },
266 http_proxy => {
267 optional => 1,
268 type => 'string',
269 description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
270 pattern => "http://.*",
271 },
0cc6e737 272 # FIXME: remove with 8.0 (add check to pve7to8!), merged into "migration" since 4.3
ab966729
FG
273 migration_unsecure => {
274 optional => 1,
275 type => 'boolean',
276 description => "Migration is secure using SSH tunnel by default. " .
277 "For secure private networks you can disable it to speed up " .
278 "migration. Deprecated, use the 'migration' property instead!",
279 },
75e5d02e
TL
280 'next-id' => {
281 optional => 1,
282 type => 'string',
283 format => $next_id_format,
284 description => "Control the range for the free VMID auto-selection pool.",
285 },
ab966729
FG
286 migration => {
287 optional => 1,
288 type => 'string', format => $migration_format,
289 description => "For cluster wide migration settings.",
290 },
291 console => {
292 optional => 1,
293 type => 'string',
0cc6e737
TL
294 description => "Select the default Console viewer. You can either use the builtin java"
295 ." applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
296 # FIXME: remove 'applet' with 8.0 (add pve7to8 check!)
ab966729
FG
297 enum => ['applet', 'vv', 'html5', 'xtermjs'],
298 },
299 email_from => {
300 optional => 1,
301 type => 'string',
302 format => 'email-opt',
303 description => "Specify email address to send notification from (default is root@\$hostname)",
304 },
305 max_workers => {
306 optional => 1,
307 type => 'integer',
308 minimum => 1,
309 description => "Defines how many workers (per node) are maximal started ".
310 " on actions like 'stopall VMs' or task from the ha-manager.",
311 },
312 fencing => {
313 optional => 1,
314 type => 'string',
315 default => 'watchdog',
316 enum => [ 'watchdog', 'hardware', 'both' ],
317 description => "Set the fencing mode of the HA cluster. Hardware mode " .
318 "needs a valid configuration of fence devices in /etc/pve/ha/fence.cfg." .
319 " With both all two modes are used." .
320 "\n\nWARNING: 'hardware' and 'both' are EXPERIMENTAL & WIP",
321 },
322 ha => {
323 optional => 1,
324 type => 'string', format => $ha_format,
325 description => "Cluster wide HA settings.",
326 },
327 mac_prefix => {
328 optional => 1,
329 type => 'string',
330 format => 'mac-prefix',
331 description => 'Prefix for autogenerated MAC addresses.',
332 },
63e5cd09
TL
333 notify => {
334 optional => 1,
335 type => 'string', format => $notification_format,
336 description => "Cluster-wide notification settings.",
337 },
ab966729
FG
338 bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
339 u2f => {
340 optional => 1,
341 type => 'string',
342 format => $u2f_format,
343 description => 'u2f',
344 },
8545a705
WB
345 webauthn => {
346 optional => 1,
347 type => 'string',
348 format => $webauthn_format,
349 description => 'webauthn configuration',
350 },
2ae1c0bb
DJ
351 description => {
352 type => 'string',
353 description => "Datacenter description. Shown in the web-interface datacenter notes panel."
354 ." This is saved as comment inside the configuration file.",
355 maxLength => 64 * 1024,
356 optional => 1,
357 },
af234c4b
DC
358 'tag-style' => {
359 optional => 1,
360 type => 'string',
361 description => "Tag style options.",
362 format => $tag_style_format,
363 },
c17e397b
DC
364 'user-tag-access' => {
365 optional => 1,
366 type => 'string',
367 description => "Privilege options for user-settable tags",
368 format => $user_tag_privs_format,
369 },
370 'registered-tags' => {
371 optional => 1,
372 type => 'string',
373 description => "A list of tags that require a `Sys.Modify` on '/' to set and delete. "
374 ."Tags set here that are also in 'user-tag-access' also require `Sys.Modify`.",
375 pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}",
376 typetext => "<tag>[;<tag>...]",
377 },
ab966729
FG
378 },
379};
380
381# make schema accessible from outside (for documentation)
382sub get_datacenter_schema { return $datacenter_schema };
383
384sub parse_datacenter_config {
385 my ($filename, $raw) = @_;
386
b5e2b244
TL
387 $raw = '' if !defined($raw);
388
2ae1c0bb
DJ
389 # description may be comment or key-value pair (or both)
390 my $comment = '';
459f6084 391 for my $line (split(/\n/, $raw)) {
09b99550 392 if ($line =~ /^\#(.*)$/) {
2ae1c0bb
DJ
393 $comment .= PVE::Tools::decode_text($1) . "\n";
394 }
395 }
396
397 # parse_config ignores lines with # => use $raw
b5e2b244 398 my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw);
ab966729 399
2ae1c0bb
DJ
400 $res->{description} = $comment;
401
b466e489
FE
402 if (my $crs = $res->{crs}) {
403 $res->{crs} = parse_property_string($crs_format, $crs);
404 }
405
ab966729 406 if (my $migration = $res->{migration}) {
51866274 407 $res->{migration} = parse_property_string($migration_format, $migration);
ab966729
FG
408 }
409
75e5d02e
TL
410 if (my $next_id = $res->{'next-id'}) {
411 $res->{'next-id'} = parse_property_string($next_id_format, $next_id);
412 }
413
ab966729 414 if (my $ha = $res->{ha}) {
51866274 415 $res->{ha} = parse_property_string($ha_format, $ha);
ab966729 416 }
63e5cd09
TL
417 if (my $notify = $res->{notify}) {
418 $res->{notify} = parse_property_string($notification_format, $notify);
419 }
ab966729 420
bcfa5ac1 421 if (my $u2f = $res->{u2f}) {
51866274 422 $res->{u2f} = parse_property_string($u2f_format, $u2f);
bcfa5ac1
FG
423 }
424
8545a705 425 if (my $webauthn = $res->{webauthn}) {
51866274 426 $res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
8545a705
WB
427 }
428
af234c4b
DC
429 if (my $tag_style = $res->{'tag-style'}) {
430 $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style);
431 }
432
c17e397b
DC
433 if (my $user_tag_privs = $res->{'user-tag-access'}) {
434 $res->{'user-tag-access'} =
435 parse_property_string($user_tag_privs_format, $user_tag_privs);
436
437 if (my $user_tags = $res->{'user-tag-access'}->{'user-allow-list'}) {
438 $res->{'user-tag-access'}->{'user-allow-list'} = [split(';', $user_tags)];
439 }
440 }
441
442 if (my $admin_tags = $res->{'registered-tags'}) {
443 $res->{'registered-tags'} = [split(';', $admin_tags)];
444 }
445
ab966729
FG
446 # for backwards compatibility only, new migration property has precedence
447 if (defined($res->{migration_unsecure})) {
448 if (defined($res->{migration}->{type})) {
449 warn "deprecated setting 'migration_unsecure' and new 'migration: type' " .
450 "set at same time! Ignore 'migration_unsecure'\n";
451 } else {
452 $res->{migration}->{type} = ($res->{migration_unsecure}) ? 'insecure' : 'secure';
453 }
454 }
455
456 # for backwards compatibility only, applet maps to html5
457 if (defined($res->{console}) && $res->{console} eq 'applet') {
458 $res->{console} = 'html5';
459 }
460
461 return $res;
462}
463
464sub write_datacenter_config {
465 my ($filename, $cfg) = @_;
466
467 # map deprecated setting to new one
468 if (defined($cfg->{migration_unsecure}) && !defined($cfg->{migration})) {
469 my $migration_unsecure = delete $cfg->{migration_unsecure};
470 $cfg->{migration}->{type} = ($migration_unsecure) ? 'insecure' : 'secure';
471 }
472
473 # map deprecated applet setting to html5
474 if (defined($cfg->{console}) && $cfg->{console} eq 'applet') {
475 $cfg->{console} = 'html5';
476 }
477
b466e489
FE
478 if (ref(my $crs = $cfg->{crs})) {
479 $cfg->{crs} = PVE::JSONSchema::print_property_string($crs, $crs_format);
480 }
481
5b576e26 482 if (ref(my $migration = $cfg->{migration})) {
ab966729
FG
483 $cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
484 }
485
75e5d02e
TL
486 if (defined(my $next_id = $cfg->{'next-id'})) {
487 $next_id = parse_property_string($next_id_format, $next_id) if !ref($next_id);
488
489 my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
490 my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
491
fe2bc758 492 die "lower ($lower) <= upper ($upper) boundary rule broken\n" if $lower > $upper;
75e5d02e
TL
493
494 $cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
495 }
496
5b576e26 497 if (ref(my $ha = $cfg->{ha})) {
ab966729
FG
498 $cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
499 }
63e5cd09
TL
500 if (ref(my $notify = $cfg->{notify})) {
501 $cfg->{notify} = PVE::JSONSchema::print_property_string($notify, $notification_format);
502 }
ab966729 503
5b576e26 504 if (ref(my $u2f = $cfg->{u2f})) {
bcfa5ac1
FG
505 $cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
506 }
507
5b576e26 508 if (ref(my $webauthn = $cfg->{webauthn})) {
8545a705
WB
509 $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
510 }
511
af234c4b
DC
512 if (ref(my $tag_style = $cfg->{'tag-style'})) {
513 $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
514 }
515
c17e397b
DC
516 if (ref(my $user_tag_privs = $cfg->{'user-tag-access'})) {
517 if (my $user_tags = $user_tag_privs->{'user-allow-list'}) {
518 $user_tag_privs->{'user-allow-list'} = join(';', sort $user_tags->@*);
519 }
520 $cfg->{'user-tag-access'} =
521 PVE::JSONSchema::print_property_string($user_tag_privs, $user_tag_privs_format);
522 }
523
524 if (ref(my $admin_tags = $cfg->{'registered-tags'})) {
525 $cfg->{'registered-tags'} = join(';', sort $admin_tags->@*);
526 }
527
2ae1c0bb
DJ
528 my $comment = '';
529 # add description as comment to top of file
530 my $description = $cfg->{description} || '';
531 foreach my $line (split(/\n/, $description)) {
532 $comment .= '#' . PVE::Tools::encode_text($line) . "\n";
533 }
534 delete $cfg->{description}; # add only as comment, no additional key-value pair
535 my $dump = PVE::JSONSchema::dump_config($datacenter_schema, $filename, $cfg);
536
537 return $comment . "\n" . $dump;
ab966729
FG
538}
539
02ce710f
TL
540PVE::Cluster::cfs_register_file(
541 'datacenter.cfg',
542 \&parse_datacenter_config,
543 \&write_datacenter_config,
544);
ab966729
FG
545
5461;