]> git.proxmox.com Git - pve-cluster.git/blob - data/PVE/DataCenterConfig.pm
fix #439: datacenter config: add infrastructure for next-id range configuration
[pve-cluster.git] / data / PVE / DataCenterConfig.pm
1 package PVE::DataCenterConfig;
2
3 use strict;
4 use warnings;
5
6 use PVE::JSONSchema qw(parse_property_string);
7 use PVE::Tools;
8 use PVE::Cluster;
9
10 my $migration_format = {
11 type => {
12 default_key => 1,
13 type => 'string',
14 enum => ['secure', 'insecure'],
15 description => "Migration traffic is encrypted using an SSH tunnel by " .
16 "default. On secure, completely private networks this can be " .
17 "disabled to increase performance.",
18 default => 'secure',
19 },
20 network => {
21 optional => 1,
22 type => 'string', format => 'CIDR',
23 format_description => 'CIDR',
24 description => "CIDR of the (sub) network that is used for migration."
25 },
26 };
27
28 my $ha_format = {
29 shutdown_policy => {
30 type => 'string',
31 enum => ['freeze', 'failover', 'conditional', 'migrate'],
32 description => "The policy for HA services on node shutdown. 'freeze' disables ".
33 "auto-recovery, 'failover' ensures recovery, 'conditional' recovers on ".
34 "poweroff and freezes on reboot. 'migrate' will migrate running services ".
35 "to other nodes, if possible. With 'freeze' or 'failover', HA Services will ".
36 "always get stopped first on shutdown.",
37 verbose_description => "Describes the policy for handling HA services on poweroff ".
38 "or reboot of a node. Freeze will always freeze services which are still located ".
39 "on the node on shutdown, those services won't be recovered by the HA manager. ".
40 "Failover will not mark the services as frozen and thus the services will get ".
41 "recovered to other nodes, if the shutdown node does not come up again quickly ".
42 "(< 1min). 'conditional' chooses automatically depending on the type of shutdown, ".
43 "i.e., on a reboot the service will be frozen but on a poweroff the service will ".
44 "stay as is, and thus get recovered after about 2 minutes. ".
45 "Migrate will try to move all running services to another node when a reboot or ".
46 "shutdown was triggered. The poweroff process will only continue once no running services ".
47 "are located on the node anymore. If the node comes up again, the service will ".
48 "be moved back to the previously powered-off node, at least if no other migration, ".
49 "reloaction or recovery took place.",
50 default => 'conditional',
51 }
52 };
53
54 my $next_id_format = {
55 lower => {
56 type => 'integer',
57 description => "Lower, inclusive boundary for free next-id API range.",
58 min => 100,
59 max => 1000 * 1000 * 1000 - 1,
60 default => 100,
61 optional => 1,
62 },
63 upper => {
64 type => 'integer',
65 description => "Upper, inclusive boundary for free next-id API range.",
66 min => 100,
67 max => 1000 * 1000 * 1000 - 1,
68 default => 1000 * 1000, # lower than the maximum on purpose
69 optional => 1,
70 },
71 };
72
73 my $u2f_format = {
74 appid => {
75 type => 'string',
76 description => "U2F AppId URL override. Defaults to the origin.",
77 format_description => 'APPID',
78 optional => 1,
79 },
80 origin => {
81 type => 'string',
82 description => "U2F Origin override. Mostly useful for single nodes with a single URL.",
83 format_description => 'URL',
84 optional => 1,
85 },
86 };
87
88 my $webauthn_format = {
89 rp => {
90 type => 'string',
91 description =>
92 'Relying party name. Any text identifier.'
93 .' Changing this *may* break existing credentials.',
94 format_description => 'RELYING_PARTY',
95 optional => 1,
96 },
97 origin => {
98 type => 'string',
99 description =>
100 'Site origin. Must be a `https://` URL (or `http://localhost`).'
101 .' Should contain the address users type in their browsers to access'
102 .' the web interface.'
103 .' Changing this *may* break existing credentials.',
104 format_description => 'URL',
105 optional => 1,
106 },
107 id => {
108 type => 'string',
109 description =>
110 'Relying part ID. Must be the domain name without protocol, port or location.'
111 .' Changing this *will* break existing credentials.',
112 format_description => 'DOMAINNAME',
113 optional => 1,
114 },
115 };
116
117 PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
118 sub pve_verify_mac_prefix {
119 my ($mac_prefix, $noerr) = @_;
120
121 if ($mac_prefix !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){0,2}:?$/i) {
122 return undef if $noerr;
123 die "value is not a valid unicast MAC address prefix\n";
124 }
125 return $mac_prefix;
126 }
127
128 my $datacenter_schema = {
129 type => "object",
130 additionalProperties => 0,
131 properties => {
132 keyboard => {
133 optional => 1,
134 type => 'string',
135 description => "Default keybord layout for vnc server.",
136 enum => PVE::Tools::kvmkeymaplist(),
137 },
138 language => {
139 optional => 1,
140 type => 'string',
141 description => "Default GUI language.",
142 enum => [
143 'ca',
144 'da',
145 'de',
146 'en',
147 'es',
148 'eu',
149 'fa',
150 'fr',
151 'he',
152 'it',
153 'ja',
154 'nb',
155 'nn',
156 'pl',
157 'pt_BR',
158 'ru',
159 'sl',
160 'sv',
161 'tr',
162 'zh_CN',
163 'zh_TW',
164 ],
165 },
166 http_proxy => {
167 optional => 1,
168 type => 'string',
169 description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
170 pattern => "http://.*",
171 },
172 migration_unsecure => {
173 optional => 1,
174 type => 'boolean',
175 description => "Migration is secure using SSH tunnel by default. " .
176 "For secure private networks you can disable it to speed up " .
177 "migration. Deprecated, use the 'migration' property instead!",
178 },
179 'next-id' => {
180 optional => 1,
181 type => 'string',
182 format => $next_id_format,
183 description => "Control the range for the free VMID auto-selection pool.",
184 },
185 migration => {
186 optional => 1,
187 type => 'string', format => $migration_format,
188 description => "For cluster wide migration settings.",
189 },
190 console => {
191 optional => 1,
192 type => 'string',
193 description => "Select the default Console viewer. You can either use the builtin java 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.",
194 enum => ['applet', 'vv', 'html5', 'xtermjs'],
195 },
196 email_from => {
197 optional => 1,
198 type => 'string',
199 format => 'email-opt',
200 description => "Specify email address to send notification from (default is root@\$hostname)",
201 },
202 max_workers => {
203 optional => 1,
204 type => 'integer',
205 minimum => 1,
206 description => "Defines how many workers (per node) are maximal started ".
207 " on actions like 'stopall VMs' or task from the ha-manager.",
208 },
209 fencing => {
210 optional => 1,
211 type => 'string',
212 default => 'watchdog',
213 enum => [ 'watchdog', 'hardware', 'both' ],
214 description => "Set the fencing mode of the HA cluster. Hardware mode " .
215 "needs a valid configuration of fence devices in /etc/pve/ha/fence.cfg." .
216 " With both all two modes are used." .
217 "\n\nWARNING: 'hardware' and 'both' are EXPERIMENTAL & WIP",
218 },
219 ha => {
220 optional => 1,
221 type => 'string', format => $ha_format,
222 description => "Cluster wide HA settings.",
223 },
224 mac_prefix => {
225 optional => 1,
226 type => 'string',
227 format => 'mac-prefix',
228 description => 'Prefix for autogenerated MAC addresses.',
229 },
230 bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
231 u2f => {
232 optional => 1,
233 type => 'string',
234 format => $u2f_format,
235 description => 'u2f',
236 },
237 webauthn => {
238 optional => 1,
239 type => 'string',
240 format => $webauthn_format,
241 description => 'webauthn configuration',
242 },
243 description => {
244 type => 'string',
245 description => "Datacenter description. Shown in the web-interface datacenter notes panel."
246 ." This is saved as comment inside the configuration file.",
247 maxLength => 64 * 1024,
248 optional => 1,
249 },
250 },
251 };
252
253 # make schema accessible from outside (for documentation)
254 sub get_datacenter_schema { return $datacenter_schema };
255
256 sub parse_datacenter_config {
257 my ($filename, $raw) = @_;
258
259 $raw = '' if !defined($raw);
260
261 # description may be comment or key-value pair (or both)
262 my $comment = '';
263 for my $line (split(/\n/, $raw)) {
264 if ($line =~ /^\#(.*)\s*$/) {
265 $comment .= PVE::Tools::decode_text($1) . "\n";
266 }
267 }
268
269 # parse_config ignores lines with # => use $raw
270 my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw);
271
272 $res->{description} = $comment;
273
274 if (my $migration = $res->{migration}) {
275 $res->{migration} = parse_property_string($migration_format, $migration);
276 }
277
278 if (my $next_id = $res->{'next-id'}) {
279 $res->{'next-id'} = parse_property_string($next_id_format, $next_id);
280 }
281
282 if (my $ha = $res->{ha}) {
283 $res->{ha} = parse_property_string($ha_format, $ha);
284 }
285
286 if (my $u2f = $res->{u2f}) {
287 $res->{u2f} = parse_property_string($u2f_format, $u2f);
288 }
289
290 if (my $webauthn = $res->{webauthn}) {
291 $res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
292 }
293
294 # for backwards compatibility only, new migration property has precedence
295 if (defined($res->{migration_unsecure})) {
296 if (defined($res->{migration}->{type})) {
297 warn "deprecated setting 'migration_unsecure' and new 'migration: type' " .
298 "set at same time! Ignore 'migration_unsecure'\n";
299 } else {
300 $res->{migration}->{type} = ($res->{migration_unsecure}) ? 'insecure' : 'secure';
301 }
302 }
303
304 # for backwards compatibility only, applet maps to html5
305 if (defined($res->{console}) && $res->{console} eq 'applet') {
306 $res->{console} = 'html5';
307 }
308
309 return $res;
310 }
311
312 sub write_datacenter_config {
313 my ($filename, $cfg) = @_;
314
315 # map deprecated setting to new one
316 if (defined($cfg->{migration_unsecure}) && !defined($cfg->{migration})) {
317 my $migration_unsecure = delete $cfg->{migration_unsecure};
318 $cfg->{migration}->{type} = ($migration_unsecure) ? 'insecure' : 'secure';
319 }
320
321 # map deprecated applet setting to html5
322 if (defined($cfg->{console}) && $cfg->{console} eq 'applet') {
323 $cfg->{console} = 'html5';
324 }
325
326 if (ref(my $migration = $cfg->{migration})) {
327 $cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
328 }
329
330 if (defined(my $next_id = $cfg->{'next-id'})) {
331 $next_id = parse_property_string($next_id_format, $next_id) if !ref($next_id);
332
333 my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
334 my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
335
336 die "lower ($lower) <= upper ($upper) boundary rule broken" if $lower > $upper;
337
338 $cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
339 }
340
341 if (ref(my $ha = $cfg->{ha})) {
342 $cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
343 }
344
345 if (ref(my $u2f = $cfg->{u2f})) {
346 $cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
347 }
348
349 if (ref(my $webauthn = $cfg->{webauthn})) {
350 $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
351 }
352
353 my $comment = '';
354 # add description as comment to top of file
355 my $description = $cfg->{description} || '';
356 foreach my $line (split(/\n/, $description)) {
357 $comment .= '#' . PVE::Tools::encode_text($line) . "\n";
358 }
359 delete $cfg->{description}; # add only as comment, no additional key-value pair
360 my $dump = PVE::JSONSchema::dump_config($datacenter_schema, $filename, $cfg);
361
362 return $comment . "\n" . $dump;
363 }
364
365 PVE::Cluster::cfs_register_file(
366 'datacenter.cfg',
367 \&parse_datacenter_config,
368 \&write_datacenter_config,
369 );
370
371 1;