]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
cleanup replication config update
[pve-container.git] / src / PVE / LXC / Config.pm
1 package PVE::LXC::Config;
2
3 use strict;
4 use warnings;
5
6 use PVE::AbstractConfig;
7 use PVE::Cluster qw(cfs_register_file);
8 use PVE::INotify;
9 use PVE::JSONSchema qw(get_standard_option);
10 use PVE::Tools;
11 use PVE::ReplicationTools;
12
13 use base qw(PVE::AbstractConfig);
14
15 my $nodename = PVE::INotify::nodename();
16 my $lock_handles = {};
17 my $lockdir = "/run/lock/lxc";
18 mkdir $lockdir;
19 mkdir "/etc/pve/nodes/$nodename/lxc";
20 my $MAX_MOUNT_POINTS = 10;
21 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
22
23 # BEGIN implemented abstract methods from PVE::AbstractConfig
24
25 sub guest_type {
26 return "CT";
27 }
28
29 sub __config_max_unused_disks {
30 my ($class) = @_;
31
32 return $MAX_UNUSED_DISKS;
33 }
34
35 sub config_file_lock {
36 my ($class, $vmid) = @_;
37
38 return "$lockdir/pve-config-${vmid}.lock";
39 }
40
41 sub cfs_config_path {
42 my ($class, $vmid, $node) = @_;
43
44 $node = $nodename if !$node;
45 return "nodes/$node/lxc/$vmid.conf";
46 }
47
48 sub mountpoint_backup_enabled {
49 my ($class, $mp_key, $mountpoint) = @_;
50
51 return 1 if $mp_key eq 'rootfs';
52
53 return 0 if $mountpoint->{type} ne 'volume';
54
55 return 1 if $mountpoint->{backup};
56
57 return 0;
58 }
59
60 sub has_feature {
61 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
62 my $err;
63
64 $class->foreach_mountpoint($conf, sub {
65 my ($ms, $mountpoint) = @_;
66
67 return if $err; # skip further test
68 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
69
70 $err = 1
71 if !PVE::Storage::volume_has_feature($storecfg, $feature,
72 $mountpoint->{volume},
73 $snapname, $running);
74 });
75
76 return $err ? 0 : 1;
77 }
78
79 sub __snapshot_save_vmstate {
80 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
81 die "implement me - snapshot_save_vmstate\n";
82 }
83
84 sub __snapshot_check_running {
85 my ($class, $vmid) = @_;
86 return PVE::LXC::check_running($vmid);
87 }
88
89 sub __snapshot_check_freeze_needed {
90 my ($class, $vmid, $config, $save_vmstate) = @_;
91
92 my $ret = $class->__snapshot_check_running($vmid);
93 return ($ret, $ret);
94 }
95
96 sub __snapshot_freeze {
97 my ($class, $vmid, $unfreeze) = @_;
98
99 if ($unfreeze) {
100 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
101 warn $@ if $@;
102 } else {
103 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
104 PVE::LXC::sync_container_namespace($vmid);
105 }
106 }
107
108 sub __snapshot_create_vol_snapshot {
109 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
110
111 my $storecfg = PVE::Storage::config();
112
113 return if $snapname eq 'vzdump' &&
114 !$class->mountpoint_backup_enabled($ms, $mountpoint);
115
116 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
117 }
118
119 sub __snapshot_delete_remove_drive {
120 my ($class, $snap, $remove_drive) = @_;
121
122 if ($remove_drive eq 'vmstate') {
123 die "implement me - saving vmstate\n";
124 } else {
125 my $value = $snap->{$remove_drive};
126 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
127 delete $snap->{$remove_drive};
128
129 $class->add_unused_volume($snap, $mountpoint->{volume})
130 if ($mountpoint->{type} eq 'volume');
131 }
132 }
133
134 sub __snapshot_delete_vmstate_file {
135 my ($class, $snap, $force) = @_;
136
137 die "implement me - saving vmstate\n";
138 }
139
140 sub __snapshot_delete_vol_snapshot {
141 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
142
143 return if $snapname eq 'vzdump' &&
144 !$class->mountpoint_backup_enabled($ms, $mountpoint);
145
146 my $storecfg = PVE::Storage::config();
147 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
148 push @$unused, $mountpoint->{volume};
149 }
150
151 sub __snapshot_rollback_vol_possible {
152 my ($class, $mountpoint, $snapname) = @_;
153
154 my $storecfg = PVE::Storage::config();
155 PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
156 }
157
158 sub __snapshot_rollback_vol_rollback {
159 my ($class, $mountpoint, $snapname) = @_;
160
161 my $storecfg = PVE::Storage::config();
162 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
163 }
164
165 sub __snapshot_rollback_vm_stop {
166 my ($class, $vmid) = @_;
167
168 PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
169 if $class->__snapshot_check_running($vmid);
170 }
171
172 sub __snapshot_rollback_vm_start {
173 my ($class, $vmid, $vmstate, $forcemachine);
174
175 die "implement me - save vmstate\n";
176 }
177
178 sub __snapshot_rollback_get_unused {
179 my ($class, $conf, $snap) = @_;
180
181 my $unused = [];
182
183 $class->__snapshot_foreach_volume($conf, sub {
184 my ($vs, $volume) = @_;
185
186 return if $volume->{type} ne 'volume';
187
188 my $found = 0;
189 my $volid = $volume->{volume};
190
191 $class->__snapshot_foreach_volume($snap, sub {
192 my ($ms, $mountpoint) = @_;
193
194 return if $found;
195 return if ($mountpoint->{type} ne 'volume');
196
197 $found = 1
198 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
199 });
200
201 push @$unused, $volid if !$found;
202 });
203
204 return $unused;
205 }
206
207 sub __snapshot_foreach_volume {
208 my ($class, $conf, $func) = @_;
209
210 $class->foreach_mountpoint($conf, $func);
211 }
212
213 # END implemented abstract methods from PVE::AbstractConfig
214
215 # BEGIN JSON config code
216
217 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
218
219 my $rootfs_desc = {
220 volume => {
221 type => 'string',
222 default_key => 1,
223 format => 'pve-lxc-mp-string',
224 format_description => 'volume',
225 description => 'Volume, device or directory to mount into the container.',
226 },
227 size => {
228 type => 'string',
229 format => 'disk-size',
230 format_description => 'DiskSize',
231 description => 'Volume size (read only value).',
232 optional => 1,
233 },
234 acl => {
235 type => 'boolean',
236 description => 'Explicitly enable or disable ACL support.',
237 optional => 1,
238 },
239 ro => {
240 type => 'boolean',
241 description => 'Read-only mount point',
242 optional => 1,
243 },
244 quota => {
245 type => 'boolean',
246 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
247 optional => 1,
248 },
249 replica => {
250 type => 'boolean',
251 description => 'Will include this volume to a storage replica job.',
252 optional => 1,
253 default => 1,
254 },
255 shared => {
256 type => 'boolean',
257 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
258 verbose_description => "Mark this non-volume mount point as available on all nodes.\n\nWARNING: This option does not share the mount point automatically, it assumes it is shared already!",
259 optional => 1,
260 default => 0,
261 },
262 };
263
264 PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
265 type => 'string', format => $rootfs_desc,
266 description => "Use volume as container root.",
267 optional => 1,
268 });
269
270 PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
271 description => "The name of the snapshot.",
272 type => 'string', format => 'pve-configid',
273 maxLength => 40,
274 });
275
276 my $confdesc = {
277 lock => {
278 optional => 1,
279 type => 'string',
280 description => "Lock/unlock the VM.",
281 enum => [qw(migrate backup snapshot rollback)],
282 },
283 onboot => {
284 optional => 1,
285 type => 'boolean',
286 description => "Specifies whether a VM will be started during system bootup.",
287 default => 0,
288 },
289 startup => get_standard_option('pve-startup-order'),
290 template => {
291 optional => 1,
292 type => 'boolean',
293 description => "Enable/disable Template.",
294 default => 0,
295 },
296 arch => {
297 optional => 1,
298 type => 'string',
299 enum => ['amd64', 'i386'],
300 description => "OS architecture type.",
301 default => 'amd64',
302 },
303 ostype => {
304 optional => 1,
305 type => 'string',
306 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
307 description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
308 },
309 console => {
310 optional => 1,
311 type => 'boolean',
312 description => "Attach a console device (/dev/console) to the container.",
313 default => 1,
314 },
315 tty => {
316 optional => 1,
317 type => 'integer',
318 description => "Specify the number of tty available to the container",
319 minimum => 0,
320 maximum => 6,
321 default => 2,
322 },
323 cores => {
324 optional => 1,
325 type => 'integer',
326 description => "The number of cores assigned to the container. A container can use all available cores by default.",
327 minimum => 1,
328 maximum => 128,
329 },
330 cpulimit => {
331 optional => 1,
332 type => 'number',
333 description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
334 minimum => 0,
335 maximum => 128,
336 default => 0,
337 },
338 cpuunits => {
339 optional => 1,
340 type => 'integer',
341 description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
342 minimum => 0,
343 maximum => 500000,
344 default => 1024,
345 },
346 memory => {
347 optional => 1,
348 type => 'integer',
349 description => "Amount of RAM for the VM in MB.",
350 minimum => 16,
351 default => 512,
352 },
353 swap => {
354 optional => 1,
355 type => 'integer',
356 description => "Amount of SWAP for the VM in MB.",
357 minimum => 0,
358 default => 512,
359 },
360 hostname => {
361 optional => 1,
362 description => "Set a host name for the container.",
363 type => 'string', format => 'dns-name',
364 maxLength => 255,
365 },
366 description => {
367 optional => 1,
368 type => 'string',
369 description => "Container description. Only used on the configuration web interface.",
370 },
371 searchdomain => {
372 optional => 1,
373 type => 'string', format => 'dns-name-list',
374 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
375 },
376 nameserver => {
377 optional => 1,
378 type => 'string', format => 'address-list',
379 description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
380 },
381 rootfs => get_standard_option('pve-ct-rootfs'),
382 parent => {
383 optional => 1,
384 type => 'string', format => 'pve-configid',
385 maxLength => 40,
386 description => "Parent snapshot name. This is used internally, and should not be modified.",
387 },
388 snaptime => {
389 optional => 1,
390 description => "Timestamp for snapshots.",
391 type => 'integer',
392 minimum => 0,
393 },
394 replica => {
395 optional => 1,
396 description => "Storage replica for local storage.",
397 type => 'boolean',
398 default => 0,
399 },
400 replica_rate_limit => {
401 optional => 1,
402 description => "Storage replica rate limit in KBytes/s.",
403 type => 'integer',
404 minimum => 1,
405 },
406 replica_target => {
407 optional => 1,
408 description => "Storage replica target node.",
409 type => 'string',
410 },
411 replica_interval => {
412 optional => 1,
413 description => "Storage replica sync interval.",
414 type => 'integer',
415 minimum => 1,
416 maximum => 1440,
417 default => 15,
418 },
419 cmode => {
420 optional => 1,
421 description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
422 type => 'string',
423 enum => ['shell', 'console', 'tty'],
424 default => 'tty',
425 },
426 protection => {
427 optional => 1,
428 type => 'boolean',
429 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
430 default => 0,
431 },
432 unprivileged => {
433 optional => 1,
434 type => 'boolean',
435 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
436 default => 0,
437 },
438 };
439
440 my $valid_lxc_conf_keys = {
441 'lxc.include' => 1,
442 'lxc.arch' => 1,
443 'lxc.utsname' => 1,
444 'lxc.haltsignal' => 1,
445 'lxc.rebootsignal' => 1,
446 'lxc.stopsignal' => 1,
447 'lxc.init_cmd' => 1,
448 'lxc.network.type' => 1,
449 'lxc.network.flags' => 1,
450 'lxc.network.link' => 1,
451 'lxc.network.mtu' => 1,
452 'lxc.network.name' => 1,
453 'lxc.network.hwaddr' => 1,
454 'lxc.network.ipv4' => 1,
455 'lxc.network.ipv4.gateway' => 1,
456 'lxc.network.ipv6' => 1,
457 'lxc.network.ipv6.gateway' => 1,
458 'lxc.network.script.up' => 1,
459 'lxc.network.script.down' => 1,
460 'lxc.pts' => 1,
461 'lxc.console.logfile' => 1,
462 'lxc.console' => 1,
463 'lxc.tty' => 1,
464 'lxc.devttydir' => 1,
465 'lxc.hook.autodev' => 1,
466 'lxc.autodev' => 1,
467 'lxc.kmsg' => 1,
468 'lxc.mount' => 1,
469 'lxc.mount.entry' => 1,
470 'lxc.mount.auto' => 1,
471 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
472 'lxc.rootfs.mount' => 1,
473 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
474 ', please use mount point options in the "rootfs" key',
475 # lxc.cgroup.*
476 # lxc.limit.*
477 'lxc.cap.drop' => 1,
478 'lxc.cap.keep' => 1,
479 'lxc.aa_profile' => 1,
480 'lxc.aa_allow_incomplete' => 1,
481 'lxc.se_context' => 1,
482 'lxc.seccomp' => 1,
483 'lxc.id_map' => 1,
484 'lxc.hook.pre-start' => 1,
485 'lxc.hook.pre-mount' => 1,
486 'lxc.hook.mount' => 1,
487 'lxc.hook.start' => 1,
488 'lxc.hook.stop' => 1,
489 'lxc.hook.post-stop' => 1,
490 'lxc.hook.clone' => 1,
491 'lxc.hook.destroy' => 1,
492 'lxc.loglevel' => 1,
493 'lxc.logfile' => 1,
494 'lxc.start.auto' => 1,
495 'lxc.start.delay' => 1,
496 'lxc.start.order' => 1,
497 'lxc.group' => 1,
498 'lxc.environment' => 1,
499 };
500
501 our $netconf_desc = {
502 type => {
503 type => 'string',
504 optional => 1,
505 description => "Network interface type.",
506 enum => [qw(veth)],
507 },
508 name => {
509 type => 'string',
510 format_description => 'string',
511 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
512 pattern => '[-_.\w\d]+',
513 },
514 bridge => {
515 type => 'string',
516 format_description => 'bridge',
517 description => 'Bridge to attach the network device to.',
518 pattern => '[-_.\w\d]+',
519 optional => 1,
520 },
521 hwaddr => {
522 type => 'string',
523 format_description => "XX:XX:XX:XX:XX:XX",
524 description => 'The interface MAC address. This is dynamically allocated by default, but you can set that statically if needed, for example to always have the same link-local IPv6 address. (lxc.network.hwaddr)',
525 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
526 optional => 1,
527 },
528 mtu => {
529 type => 'integer',
530 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
531 minimum => 64, # minimum ethernet frame is 64 bytes
532 optional => 1,
533 },
534 ip => {
535 type => 'string',
536 format => 'pve-ipv4-config',
537 format_description => 'IPv4Format/CIDR',
538 description => 'IPv4 address in CIDR format.',
539 optional => 1,
540 },
541 gw => {
542 type => 'string',
543 format => 'ipv4',
544 format_description => 'GatewayIPv4',
545 description => 'Default gateway for IPv4 traffic.',
546 optional => 1,
547 },
548 ip6 => {
549 type => 'string',
550 format => 'pve-ipv6-config',
551 format_description => 'IPv6Format/CIDR',
552 description => 'IPv6 address in CIDR format.',
553 optional => 1,
554 },
555 gw6 => {
556 type => 'string',
557 format => 'ipv6',
558 format_description => 'GatewayIPv6',
559 description => 'Default gateway for IPv6 traffic.',
560 optional => 1,
561 },
562 firewall => {
563 type => 'boolean',
564 description => "Controls whether this interface's firewall rules should be used.",
565 optional => 1,
566 },
567 tag => {
568 type => 'integer',
569 minimum => 1,
570 maximum => 4094,
571 description => "VLAN tag for this interface.",
572 optional => 1,
573 },
574 trunks => {
575 type => 'string',
576 pattern => qr/\d+(?:;\d+)*/,
577 format_description => 'vlanid[;vlanid...]',
578 description => "VLAN ids to pass through the interface",
579 optional => 1,
580 },
581 rate => {
582 type => 'number',
583 format_description => 'mbps',
584 description => "Apply rate limiting to the interface",
585 optional => 1,
586 },
587 };
588 PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
589
590 my $MAX_LXC_NETWORKS = 10;
591 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
592 $confdesc->{"net$i"} = {
593 optional => 1,
594 type => 'string', format => $netconf_desc,
595 description => "Specifies network interfaces for the container.",
596 };
597 }
598
599 PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
600 sub verify_lxc_mp_string {
601 my ($mp, $noerr) = @_;
602
603 # do not allow:
604 # /./ or /../
605 # /. or /.. at the end
606 # ../ at the beginning
607
608 if($mp =~ m@/\.\.?/@ ||
609 $mp =~ m@/\.\.?$@ ||
610 $mp =~ m@^\.\./@) {
611 return undef if $noerr;
612 die "$mp contains illegal character sequences\n";
613 }
614 return $mp;
615 }
616
617 my $mp_desc = {
618 %$rootfs_desc,
619 backup => {
620 type => 'boolean',
621 description => 'Whether to include the mount point in backups.',
622 verbose_description => 'Whether to include the mount point in backups '.
623 '(only used for volume mount points).',
624 optional => 1,
625 },
626 mp => {
627 type => 'string',
628 format => 'pve-lxc-mp-string',
629 format_description => 'Path',
630 description => 'Path to the mount point as seen from inside the container '.
631 '(must not contain symlinks).',
632 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
633 "NOTE: Must not contain any symlinks for security reasons."
634 },
635 };
636 PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
637
638 my $unuseddesc = {
639 optional => 1,
640 type => 'string', format => 'pve-volume-id',
641 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
642 };
643
644 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
645 $confdesc->{"mp$i"} = {
646 optional => 1,
647 type => 'string', format => $mp_desc,
648 description => "Use volume as container mount point.",
649 optional => 1,
650 };
651 }
652
653 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
654 $confdesc->{"unused$i"} = $unuseddesc;
655 }
656
657 sub parse_pct_config {
658 my ($filename, $raw) = @_;
659
660 return undef if !defined($raw);
661
662 my $res = {
663 digest => Digest::SHA::sha1_hex($raw),
664 snapshots => {},
665 };
666
667 $filename =~ m|/lxc/(\d+).conf$|
668 || die "got strange filename '$filename'";
669
670 my $vmid = $1;
671
672 my $conf = $res;
673 my $descr = '';
674 my $section = '';
675
676 my @lines = split(/\n/, $raw);
677 foreach my $line (@lines) {
678 next if $line =~ m/^\s*$/;
679
680 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
681 $section = $1;
682 $conf->{description} = $descr if $descr;
683 $descr = '';
684 $conf = $res->{snapshots}->{$section} = {};
685 next;
686 }
687
688 if ($line =~ m/^\#(.*)\s*$/) {
689 $descr .= PVE::Tools::decode_text($1) . "\n";
690 next;
691 }
692
693 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
694 my $key = $1;
695 my $value = $3;
696 my $validity = $valid_lxc_conf_keys->{$key} || 0;
697 if ($validity eq 1 || $key =~ m/^lxc\.(?:cgroup|limit)\./) {
698 push @{$conf->{lxc}}, [$key, $value];
699 } elsif (my $errmsg = $validity) {
700 warn "vm $vmid - $key: $errmsg\n";
701 } else {
702 warn "vm $vmid - unable to parse config: $line\n";
703 }
704 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
705 $descr .= PVE::Tools::decode_text($2);
706 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
707 $conf->{snapstate} = $1;
708 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
709 my $key = $1;
710 my $value = $2;
711 eval { $value = PVE::LXC::Config->check_type($key, $value); };
712 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
713 $conf->{$key} = $value;
714 } else {
715 warn "vm $vmid - unable to parse config: $line\n";
716 }
717 }
718
719 $conf->{description} = $descr if $descr;
720
721 delete $res->{snapstate}; # just to be sure
722
723 return $res;
724 }
725
726 sub write_pct_config {
727 my ($filename, $conf) = @_;
728
729 delete $conf->{snapstate}; # just to be sure
730
731 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
732 my $used_volids = {};
733 foreach my $vid (@$volidlist) {
734 $used_volids->{$vid} = 1;
735 }
736
737 # remove 'unusedX' settings if the volume is still used
738 foreach my $key (keys %$conf) {
739 my $value = $conf->{$key};
740 if ($key =~ m/^unused/ && $used_volids->{$value}) {
741 delete $conf->{$key};
742 }
743 }
744
745 my $generate_raw_config = sub {
746 my ($conf) = @_;
747
748 my $raw = '';
749
750 # add description as comment to top of file
751 my $descr = $conf->{description} || '';
752 foreach my $cl (split(/\n/, $descr)) {
753 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
754 }
755
756 foreach my $key (sort keys %$conf) {
757 next if $key eq 'digest' || $key eq 'description' ||
758 $key eq 'pending' || $key eq 'snapshots' ||
759 $key eq 'snapname' || $key eq 'lxc';
760 my $value = $conf->{$key};
761 die "detected invalid newline inside property '$key'\n"
762 if $value =~ m/\n/;
763 $raw .= "$key: $value\n";
764 }
765
766 if (my $lxcconf = $conf->{lxc}) {
767 foreach my $entry (@$lxcconf) {
768 my ($k, $v) = @$entry;
769 $raw .= "$k: $v\n";
770 }
771 }
772
773 return $raw;
774 };
775
776 my $raw = &$generate_raw_config($conf);
777
778 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
779 $raw .= "\n[$snapname]\n";
780 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
781 }
782
783 return $raw;
784 }
785
786 sub update_pct_config {
787 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
788
789 my @nohotplug;
790
791 my $new_disks = 0;
792 my @deleted_volumes;
793
794 my $rootdir;
795 if ($running) {
796 my $pid = PVE::LXC::find_lxc_pid($vmid);
797 $rootdir = "/proc/$pid/root";
798 }
799
800 my $hotplug_error = sub {
801 if ($running) {
802 push @nohotplug, @_;
803 return 1;
804 } else {
805 return 0;
806 }
807 };
808
809 if (defined($delete)) {
810 foreach my $opt (@$delete) {
811 if (!exists($conf->{$opt})) {
812 # silently ignore
813 next;
814 }
815
816 if ($opt eq 'memory' || $opt eq 'rootfs') {
817 die "unable to delete required option '$opt'\n";
818 } elsif ($opt eq 'hostname') {
819 delete $conf->{$opt};
820 } elsif ($opt eq 'swap') {
821 delete $conf->{$opt};
822 PVE::LXC::write_cgroup_value("memory", $vmid,
823 "memory.memsw.limit_in_bytes", -1);
824 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
825 delete $conf->{$opt};
826 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
827 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
828 next if $hotplug_error->($opt);
829 delete $conf->{$opt};
830 } elsif ($opt eq 'cores') {
831 delete $conf->{$opt}; # rest is handled by pvestatd
832 } elsif ($opt eq 'cpulimit') {
833 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
834 delete $conf->{$opt};
835 } elsif ($opt eq 'cpuunits') {
836 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
837 delete $conf->{$opt};
838 } elsif ($opt =~ m/^net(\d)$/) {
839 delete $conf->{$opt};
840 next if !$running;
841 my $netid = $1;
842 PVE::Network::veth_delete("veth${vmid}i$netid");
843 } elsif ($opt eq 'protection') {
844 delete $conf->{$opt};
845 } elsif ($opt =~ m/^unused(\d+)$/) {
846 next if $hotplug_error->($opt);
847 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
848 push @deleted_volumes, $conf->{$opt};
849 delete $conf->{$opt};
850 } elsif ($opt =~ m/^mp(\d+)$/) {
851 next if $hotplug_error->($opt);
852 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
853 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
854 delete $conf->{$opt};
855 if ($mp->{type} eq 'volume') {
856 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
857 }
858 } elsif ($opt eq 'unprivileged') {
859 die "unable to delete read-only option: '$opt'\n";
860 } elsif ($opt eq "replica" || $opt eq "replica_target") {
861 delete $conf->{$opt};
862 delete $conf->{replica} if $opt eq "replica_target";
863
864 # job_remove required updated lxc conf
865 PVE::ReplicationTools::job_remove($vmid);
866 } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") {
867 delete $conf->{$opt};
868 PVE::ReplicationTools::update_conf($vmid, $opt, $param->{$opt});
869 } else {
870 die "implement me (delete: $opt)"
871 }
872 PVE::LXC::Config->write_config($vmid, $conf) if $running;
873 }
874 }
875
876 # There's no separate swap size to configure, there's memory and "total"
877 # memory (iow. memory+swap). This means we have to change them together.
878 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
879 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
880 if (defined($wanted_memory) || defined($wanted_swap)) {
881
882 my $old_memory = ($conf->{memory} || 512);
883 my $old_swap = ($conf->{swap} || 0);
884
885 $wanted_memory //= $old_memory;
886 $wanted_swap //= $old_swap;
887
888 my $total = $wanted_memory + $wanted_swap;
889 if ($running) {
890 my $old_total = $old_memory + $old_swap;
891 if ($total > $old_total) {
892 PVE::LXC::write_cgroup_value("memory", $vmid,
893 "memory.memsw.limit_in_bytes",
894 int($total*1024*1024));
895 PVE::LXC::write_cgroup_value("memory", $vmid,
896 "memory.limit_in_bytes",
897 int($wanted_memory*1024*1024));
898 } else {
899 PVE::LXC::write_cgroup_value("memory", $vmid,
900 "memory.limit_in_bytes",
901 int($wanted_memory*1024*1024));
902 PVE::LXC::write_cgroup_value("memory", $vmid,
903 "memory.memsw.limit_in_bytes",
904 int($total*1024*1024));
905 }
906 }
907 $conf->{memory} = $wanted_memory;
908 $conf->{swap} = $wanted_swap;
909
910 PVE::LXC::Config->write_config($vmid, $conf) if $running;
911 }
912
913 my $used_volids = {};
914 my $check_content_type = sub {
915 my ($mp) = @_;
916 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
917 my $scfg = PVE::Storage::config();
918 my $storage_config = PVE::Storage::storage_config($scfg, $sid);
919 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
920 if !$storage_config->{content}->{rootdir};
921 };
922
923 foreach my $opt (keys %$param) {
924 my $value = $param->{$opt};
925 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
926 my $update;
927 if ($opt eq 'hostname' || $opt eq 'arch') {
928 $conf->{$opt} = $value;
929 } elsif ($opt eq 'onboot') {
930 $conf->{$opt} = $value ? 1 : 0;
931 } elsif ($opt eq 'startup') {
932 $conf->{$opt} = $value;
933 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
934 next if $hotplug_error->($opt);
935 $conf->{$opt} = $value;
936 } elsif ($opt eq 'nameserver') {
937 next if $hotplug_error->($opt);
938 my $list = PVE::LXC::verify_nameserver_list($value);
939 $conf->{$opt} = $list;
940 } elsif ($opt eq 'searchdomain') {
941 next if $hotplug_error->($opt);
942 my $list = PVE::LXC::verify_searchdomain_list($value);
943 $conf->{$opt} = $list;
944 } elsif ($opt eq 'cores') {
945 $conf->{$opt} = $value;# rest is handled by pvestatd
946 } elsif ($opt eq 'cpulimit') {
947 if ($value == 0) {
948 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
949 } else {
950 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
951 }
952 $conf->{$opt} = $value;
953 } elsif ($opt eq 'cpuunits') {
954 $conf->{$opt} = $value;
955 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
956 } elsif ($opt eq 'description') {
957 $conf->{$opt} = PVE::Tools::encode_text($value);
958 } elsif ($opt =~ m/^net(\d+)$/) {
959 my $netid = $1;
960 my $net = PVE::LXC::Config->parse_lxc_network($value);
961 if (!$running) {
962 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
963 } else {
964 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
965 }
966 } elsif ($opt eq 'protection') {
967 $conf->{$opt} = $value ? 1 : 0;
968 } elsif ($opt =~ m/^mp(\d+)$/) {
969 next if $hotplug_error->($opt);
970 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
971 my $old = $conf->{$opt};
972 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
973 if ($mp->{type} eq 'volume') {
974 &$check_content_type($mp);
975 $used_volids->{$mp->{volume}} = 1;
976 }
977 $conf->{$opt} = $value;
978 if (defined($old)) {
979 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
980 if ($mp->{type} eq 'volume') {
981 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
982 }
983 }
984 $new_disks = 1;
985 } elsif ($opt eq 'rootfs') {
986 next if $hotplug_error->($opt);
987 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
988 my $old = $conf->{$opt};
989 $conf->{$opt} = $value;
990 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
991 if ($mp->{type} eq 'volume') {
992 &$check_content_type($mp);
993 $used_volids->{$mp->{volume}} = 1;
994 }
995 if (defined($old)) {
996 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
997 if ($mp->{type} eq 'volume') {
998 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
999 }
1000 }
1001 $new_disks = 1;
1002 } elsif ($opt eq 'unprivileged') {
1003 die "unable to modify read-only option: '$opt'\n";
1004 } elsif ($opt eq 'ostype') {
1005 next if $hotplug_error->($opt);
1006 $conf->{$opt} = $value;
1007 } elsif ($opt eq "replica") {
1008 die "Not all volumes are syncable, please check your config\n"
1009 if !PVE::ReplicationTools::check_guest_volumes_syncable($conf, 'lxc');
1010 $conf->{$opt} = $param->{$opt};
1011 die "replica_target is required\n" if !$conf->{replica_target}
1012 && !$param->{replica_target};
1013 if ($param->{replica}) {
1014 PVE::ReplicationTools::job_enable($vmid);
1015 } else {
1016 PVE::ReplicationTools::job_disable($vmid);
1017 }
1018 $update = 1;
1019 } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") {
1020 $conf->{$opt} = $param->{$opt};
1021 PVE::ReplicationTools::update_conf($vmid, $opt, $param->{$opt});
1022 $update = 1;
1023 } elsif ($opt eq "replica_target") {
1024 die "Node: $param->{$opt} does not exists in Cluster.\n"
1025 if !PVE::Cluster::check_node_exists($param->{$opt});
1026 $update = 1;
1027 PVE::ReplicationTools::update_conf($vmid, $opt, $param->{$opt})
1028 if defined($conf->{$opt});
1029 $conf->{$opt} = $param->{$opt};
1030 } else {
1031 die "implement me: $opt";
1032 }
1033 PVE::LXC::Config->write_config($vmid, $conf) if $running || $update;
1034 }
1035
1036 # Apply deletions and creations of new volumes
1037 if (@deleted_volumes) {
1038 my $storage_cfg = PVE::Storage::config();
1039 foreach my $volume (@deleted_volumes) {
1040 next if $used_volids->{$volume}; # could have been re-added, too
1041 # also check for references in snapshots
1042 next if $class->is_volume_in_use($conf, $volume, 1);
1043 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
1044 }
1045 }
1046
1047 if ($new_disks) {
1048 my $storage_cfg = PVE::Storage::config();
1049 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
1050 }
1051
1052 # This should be the last thing we do here
1053 if ($running && scalar(@nohotplug)) {
1054 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1055 }
1056 }
1057
1058 sub check_type {
1059 my ($class, $key, $value) = @_;
1060
1061 die "unknown setting '$key'\n" if !$confdesc->{$key};
1062
1063 my $type = $confdesc->{$key}->{type};
1064
1065 if (!defined($value)) {
1066 die "got undefined value\n";
1067 }
1068
1069 if ($value =~ m/[\n\r]/) {
1070 die "property contains a line feed\n";
1071 }
1072
1073 if ($type eq 'boolean') {
1074 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1075 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1076 die "type check ('boolean') failed - got '$value'\n";
1077 } elsif ($type eq 'integer') {
1078 return int($1) if $value =~ m/^(\d+)$/;
1079 die "type check ('integer') failed - got '$value'\n";
1080 } elsif ($type eq 'number') {
1081 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1082 die "type check ('number') failed - got '$value'\n";
1083 } elsif ($type eq 'string') {
1084 if (my $fmt = $confdesc->{$key}->{format}) {
1085 PVE::JSONSchema::check_format($fmt, $value);
1086 return $value;
1087 }
1088 return $value;
1089 } else {
1090 die "internal error"
1091 }
1092 }
1093
1094
1095 # add JSON properties for create and set function
1096 sub json_config_properties {
1097 my ($class, $prop) = @_;
1098
1099 foreach my $opt (keys %$confdesc) {
1100 next if $opt eq 'parent' || $opt eq 'snaptime';
1101 next if $prop->{$opt};
1102 $prop->{$opt} = $confdesc->{$opt};
1103 }
1104
1105 return $prop;
1106 }
1107
1108 sub __parse_ct_mountpoint_full {
1109 my ($class, $desc, $data, $noerr) = @_;
1110
1111 $data //= '';
1112
1113 my $res;
1114 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1115 if ($@) {
1116 return undef if $noerr;
1117 die $@;
1118 }
1119
1120 if (defined(my $size = $res->{size})) {
1121 $size = PVE::JSONSchema::parse_size($size);
1122 if (!defined($size)) {
1123 return undef if $noerr;
1124 die "invalid size: $size\n";
1125 }
1126 $res->{size} = $size;
1127 }
1128
1129 $res->{type} = $class->classify_mountpoint($res->{volume});
1130
1131 return $res;
1132 };
1133
1134 sub parse_ct_rootfs {
1135 my ($class, $data, $noerr) = @_;
1136
1137 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1138
1139 $res->{mp} = '/' if defined($res);
1140
1141 return $res;
1142 }
1143
1144 sub parse_ct_mountpoint {
1145 my ($class, $data, $noerr) = @_;
1146
1147 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1148 }
1149
1150 sub print_ct_mountpoint {
1151 my ($class, $info, $nomp) = @_;
1152 my $skip = [ 'type' ];
1153 push @$skip, 'mp' if $nomp;
1154 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1155 }
1156
1157 sub print_lxc_network {
1158 my ($class, $net) = @_;
1159 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1160 }
1161
1162 sub parse_lxc_network {
1163 my ($class, $data) = @_;
1164
1165 my $res = {};
1166
1167 return $res if !$data;
1168
1169 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1170
1171 $res->{type} = 'veth';
1172 if (!$res->{hwaddr}) {
1173 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1174 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1175 }
1176
1177 return $res;
1178 }
1179
1180 sub option_exists {
1181 my ($class, $name) = @_;
1182
1183 return defined($confdesc->{$name});
1184 }
1185 # END JSON config code
1186
1187 sub classify_mountpoint {
1188 my ($class, $vol) = @_;
1189 if ($vol =~ m!^/!) {
1190 return 'device' if $vol =~ m!^/dev/!;
1191 return 'bind';
1192 }
1193 return 'volume';
1194 }
1195
1196 sub is_volume_in_use {
1197 my ($class, $config, $volid, $include_snapshots) = @_;
1198 my $used = 0;
1199
1200 $class->foreach_mountpoint($config, sub {
1201 my ($ms, $mountpoint) = @_;
1202 return if $used;
1203 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1204 });
1205
1206 my $snapshots = $config->{snapshots};
1207 if ($include_snapshots && $snapshots) {
1208 foreach my $snap (keys %$snapshots) {
1209 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1210 }
1211 }
1212
1213 return $used;
1214 }
1215
1216 sub has_dev_console {
1217 my ($class, $conf) = @_;
1218
1219 return !(defined($conf->{console}) && !$conf->{console});
1220 }
1221
1222 sub has_lxc_entry {
1223 my ($class, $conf, $keyname) = @_;
1224
1225 if (my $lxcconf = $conf->{lxc}) {
1226 foreach my $entry (@$lxcconf) {
1227 my ($key, undef) = @$entry;
1228 return 1 if $key eq $keyname;
1229 }
1230 }
1231
1232 return 0;
1233 }
1234
1235 sub get_tty_count {
1236 my ($class, $conf) = @_;
1237
1238 return $conf->{tty} // $confdesc->{tty}->{default};
1239 }
1240
1241 sub get_cmode {
1242 my ($class, $conf) = @_;
1243
1244 return $conf->{cmode} // $confdesc->{cmode}->{default};
1245 }
1246
1247 sub mountpoint_names {
1248 my ($class, $reverse) = @_;
1249
1250 my @names = ('rootfs');
1251
1252 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1253 push @names, "mp$i";
1254 }
1255
1256 return $reverse ? reverse @names : @names;
1257 }
1258
1259 sub foreach_mountpoint_full {
1260 my ($class, $conf, $reverse, $func, @param) = @_;
1261
1262 foreach my $key ($class->mountpoint_names($reverse)) {
1263 my $value = $conf->{$key};
1264 next if !defined($value);
1265 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1266 next if !defined($mountpoint);
1267
1268 &$func($key, $mountpoint, @param);
1269 }
1270 }
1271
1272 sub foreach_mountpoint {
1273 my ($class, $conf, $func, @param) = @_;
1274
1275 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1276 }
1277
1278 sub foreach_mountpoint_reverse {
1279 my ($class, $conf, $func, @param) = @_;
1280
1281 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1282 }
1283
1284 sub get_vm_volumes {
1285 my ($class, $conf, $excludes) = @_;
1286
1287 my $vollist = [];
1288
1289 $class->foreach_mountpoint($conf, sub {
1290 my ($ms, $mountpoint) = @_;
1291
1292 return if $excludes && $ms eq $excludes;
1293
1294 my $volid = $mountpoint->{volume};
1295 return if !$volid || $mountpoint->{type} ne 'volume';
1296
1297 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1298 return if !$sid;
1299
1300 push @$vollist, $volid;
1301 });
1302
1303 return $vollist;
1304 }
1305
1306 return 1;