1 package PVE
::LXC
::Config
;
6 use PVE
::AbstractConfig
;
7 use PVE
::Cluster
qw(cfs_register_file);
9 use PVE
::JSONSchema
qw(get_standard_option);
11 use PVE
::ReplicationTools
;
13 use base
qw(PVE::AbstractConfig);
15 my $nodename = PVE
::INotify
::nodename
();
16 my $lock_handles = {};
17 my $lockdir = "/run/lock/lxc";
19 mkdir "/etc/pve/nodes/$nodename/lxc";
20 my $MAX_MOUNT_POINTS = 10;
21 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
23 # BEGIN implemented abstract methods from PVE::AbstractConfig
29 sub __config_max_unused_disks
{
32 return $MAX_UNUSED_DISKS;
35 sub config_file_lock
{
36 my ($class, $vmid) = @_;
38 return "$lockdir/pve-config-${vmid}.lock";
42 my ($class, $vmid, $node) = @_;
44 $node = $nodename if !$node;
45 return "nodes/$node/lxc/$vmid.conf";
48 sub mountpoint_backup_enabled
{
49 my ($class, $mp_key, $mountpoint) = @_;
51 return 1 if $mp_key eq 'rootfs';
53 return 0 if $mountpoint->{type
} ne 'volume';
55 return 1 if $mountpoint->{backup
};
61 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
64 $class->foreach_mountpoint($conf, sub {
65 my ($ms, $mountpoint) = @_;
67 return if $err; # skip further test
68 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
71 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
72 $mountpoint->{volume
},
79 sub __snapshot_save_vmstate
{
80 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
81 die "implement me - snapshot_save_vmstate\n";
84 sub __snapshot_check_running
{
85 my ($class, $vmid) = @_;
86 return PVE
::LXC
::check_running
($vmid);
89 sub __snapshot_check_freeze_needed
{
90 my ($class, $vmid, $config, $save_vmstate) = @_;
92 my $ret = $class->__snapshot_check_running($vmid);
96 sub __snapshot_freeze
{
97 my ($class, $vmid, $unfreeze) = @_;
100 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
103 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
104 PVE
::LXC
::sync_container_namespace
($vmid);
108 sub __snapshot_create_vol_snapshot
{
109 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
111 my $storecfg = PVE
::Storage
::config
();
113 return if $snapname eq 'vzdump' &&
114 !$class->mountpoint_backup_enabled($ms, $mountpoint);
116 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
119 sub __snapshot_delete_remove_drive
{
120 my ($class, $snap, $remove_drive) = @_;
122 if ($remove_drive eq 'vmstate') {
123 die "implement me - saving vmstate\n";
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};
129 $class->add_unused_volume($snap, $mountpoint->{volume
})
130 if ($mountpoint->{type
} eq 'volume');
134 sub __snapshot_delete_vmstate_file
{
135 my ($class, $snap, $force) = @_;
137 die "implement me - saving vmstate\n";
140 sub __snapshot_delete_vol_snapshot
{
141 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
143 return if $snapname eq 'vzdump' &&
144 !$class->mountpoint_backup_enabled($ms, $mountpoint);
146 my $storecfg = PVE
::Storage
::config
();
147 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
148 push @$unused, $mountpoint->{volume
};
151 sub __snapshot_rollback_vol_possible
{
152 my ($class, $mountpoint, $snapname) = @_;
154 my $storecfg = PVE
::Storage
::config
();
155 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
158 sub __snapshot_rollback_vol_rollback
{
159 my ($class, $mountpoint, $snapname) = @_;
161 my $storecfg = PVE
::Storage
::config
();
162 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
165 sub __snapshot_rollback_vm_stop
{
166 my ($class, $vmid) = @_;
168 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
169 if $class->__snapshot_check_running($vmid);
172 sub __snapshot_rollback_vm_start
{
173 my ($class, $vmid, $vmstate, $forcemachine);
175 die "implement me - save vmstate\n";
178 sub __snapshot_rollback_get_unused
{
179 my ($class, $conf, $snap) = @_;
183 $class->__snapshot_foreach_volume($conf, sub {
184 my ($vs, $volume) = @_;
186 return if $volume->{type
} ne 'volume';
189 my $volid = $volume->{volume
};
191 $class->__snapshot_foreach_volume($snap, sub {
192 my ($ms, $mountpoint) = @_;
195 return if ($mountpoint->{type
} ne 'volume');
198 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
201 push @$unused, $volid if !$found;
207 sub __snapshot_foreach_volume
{
208 my ($class, $conf, $func) = @_;
210 $class->foreach_mountpoint($conf, $func);
213 # END implemented abstract methods from PVE::AbstractConfig
215 # BEGIN JSON config code
217 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
223 format
=> 'pve-lxc-mp-string',
224 format_description
=> 'volume',
225 description
=> 'Volume, device or directory to mount into the container.',
229 format
=> 'disk-size',
230 format_description
=> 'DiskSize',
231 description
=> 'Volume size (read only value).',
236 description
=> 'Explicitly enable or disable ACL support.',
241 description
=> 'Read-only mount point',
246 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
251 description
=> 'Will include this volume to a storage replica job.',
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!",
264 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
265 type
=> 'string', format
=> $rootfs_desc,
266 description
=> "Use volume as container root.",
270 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
271 description
=> "The name of the snapshot.",
272 type
=> 'string', format
=> 'pve-configid',
280 description
=> "Lock/unlock the VM.",
281 enum
=> [qw(migrate backup snapshot rollback)],
286 description
=> "Specifies whether a VM will be started during system bootup.",
289 startup
=> get_standard_option
('pve-startup-order'),
293 description
=> "Enable/disable Template.",
299 enum
=> ['amd64', 'i386'],
300 description
=> "OS architecture type.",
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.",
312 description
=> "Attach a console device (/dev/console) to the container.",
318 description
=> "Specify the number of tty available to the container",
326 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
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.",
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.",
349 description
=> "Amount of RAM for the VM in MB.",
356 description
=> "Amount of SWAP for the VM in MB.",
362 description
=> "Set a host name for the container.",
363 type
=> 'string', format
=> 'dns-name',
369 description
=> "Container description. Only used on the configuration web interface.",
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.",
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.",
381 rootfs
=> get_standard_option
('pve-ct-rootfs'),
384 type
=> 'string', format
=> 'pve-configid',
386 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
390 description
=> "Timestamp for snapshots.",
396 description
=> "Storage replica for local storage.",
400 replica_rate_limit
=> {
402 description
=> "Storage replica rate limit in KBytes/s.",
408 description
=> "Storage replica target node.",
411 replica_interval
=> {
413 description
=> "Storage replica sync interval.",
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).",
423 enum
=> ['shell', 'console', 'tty'],
429 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
435 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
440 my $valid_lxc_conf_keys = {
444 'lxc.haltsignal' => 1,
445 'lxc.rebootsignal' => 1,
446 'lxc.stopsignal' => 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,
461 'lxc.console.logfile' => 1,
464 'lxc.devttydir' => 1,
465 'lxc.hook.autodev' => 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',
479 'lxc.aa_profile' => 1,
480 'lxc.aa_allow_incomplete' => 1,
481 'lxc.se_context' => 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,
494 'lxc.start.auto' => 1,
495 'lxc.start.delay' => 1,
496 'lxc.start.order' => 1,
498 'lxc.environment' => 1,
501 our $netconf_desc = {
505 description
=> "Network interface type.",
510 format_description
=> 'string',
511 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
512 pattern
=> '[-_.\w\d]+',
516 format_description
=> 'bridge',
517 description
=> 'Bridge to attach the network device to.',
518 pattern
=> '[-_.\w\d]+',
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,
530 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
531 minimum
=> 64, # minimum ethernet frame is 64 bytes
536 format
=> 'pve-ipv4-config',
537 format_description
=> 'IPv4Format/CIDR',
538 description
=> 'IPv4 address in CIDR format.',
544 format_description
=> 'GatewayIPv4',
545 description
=> 'Default gateway for IPv4 traffic.',
550 format
=> 'pve-ipv6-config',
551 format_description
=> 'IPv6Format/CIDR',
552 description
=> 'IPv6 address in CIDR format.',
558 format_description
=> 'GatewayIPv6',
559 description
=> 'Default gateway for IPv6 traffic.',
564 description
=> "Controls whether this interface's firewall rules should be used.",
571 description
=> "VLAN tag for this interface.",
576 pattern
=> qr/\d+(?:;\d+)*/,
577 format_description
=> 'vlanid[;vlanid...]',
578 description
=> "VLAN ids to pass through the interface",
583 format_description
=> 'mbps',
584 description
=> "Apply rate limiting to the interface",
588 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
590 my $MAX_LXC_NETWORKS = 10;
591 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
592 $confdesc->{"net$i"} = {
594 type
=> 'string', format
=> $netconf_desc,
595 description
=> "Specifies network interfaces for the container.",
599 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
600 sub verify_lxc_mp_string
{
601 my ($mp, $noerr) = @_;
605 # /. or /.. at the end
606 # ../ at the beginning
608 if($mp =~ m
@/\.\
.?
/@ ||
611 return undef if $noerr;
612 die "$mp contains illegal character sequences\n";
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).',
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."
636 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
640 type
=> 'string', format
=> 'pve-volume-id',
641 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
644 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
645 $confdesc->{"mp$i"} = {
647 type
=> 'string', format
=> $mp_desc,
648 description
=> "Use volume as container mount point.",
653 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
654 $confdesc->{"unused$i"} = $unuseddesc;
657 sub parse_pct_config
{
658 my ($filename, $raw) = @_;
660 return undef if !defined($raw);
663 digest
=> Digest
::SHA
::sha1_hex
($raw),
667 $filename =~ m
|/lxc/(\d
+).conf
$|
668 || die "got strange filename '$filename'";
676 my @lines = split(/\n/, $raw);
677 foreach my $line (@lines) {
678 next if $line =~ m/^\s*$/;
680 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
682 $conf->{description
} = $descr if $descr;
684 $conf = $res->{snapshots
}->{$section} = {};
688 if ($line =~ m/^\#(.*)\s*$/) {
689 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
693 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
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";
702 warn "vm $vmid - unable to parse config: $line\n";
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*$/) {
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;
715 warn "vm $vmid - unable to parse config: $line\n";
719 $conf->{description
} = $descr if $descr;
721 delete $res->{snapstate
}; # just to be sure
726 sub write_pct_config
{
727 my ($filename, $conf) = @_;
729 delete $conf->{snapstate
}; # just to be sure
731 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
732 my $used_volids = {};
733 foreach my $vid (@$volidlist) {
734 $used_volids->{$vid} = 1;
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};
745 my $generate_raw_config = sub {
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";
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"
763 $raw .= "$key: $value\n";
766 if (my $lxcconf = $conf->{lxc
}) {
767 foreach my $entry (@$lxcconf) {
768 my ($k, $v) = @$entry;
776 my $raw = &$generate_raw_config($conf);
778 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
779 $raw .= "\n[$snapname]\n";
780 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
786 sub update_pct_config
{
787 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
796 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
797 $rootdir = "/proc/$pid/root";
800 my $hotplug_error = sub {
809 if (defined($delete)) {
810 foreach my $opt (@$delete) {
811 if (!exists($conf->{$opt})) {
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};
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
});
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";
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});
870 die "implement me (delete: $opt)"
872 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
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)) {
882 my $old_memory = ($conf->{memory
} || 512);
883 my $old_swap = ($conf->{swap
} || 0);
885 $wanted_memory //= $old_memory;
886 $wanted_swap //= $old_swap;
888 my $total = $wanted_memory + $wanted_swap;
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));
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));
907 $conf->{memory
} = $wanted_memory;
908 $conf->{swap
} = $wanted_swap;
910 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
913 my $used_volids = {};
914 my $check_content_type = sub {
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
};
923 foreach my $opt (keys %$param) {
924 my $value = $param->{$opt};
925 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
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') {
948 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
950 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
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+)$/) {
960 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
962 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
964 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
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;
977 $conf->{$opt} = $value;
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
});
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;
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
});
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);
1016 PVE
::ReplicationTools
::job_disable
($vmid);
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});
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});
1027 PVE
::ReplicationTools
::update_conf
($vmid, $opt, $param->{$opt})
1028 if defined($conf->{$opt});
1029 $conf->{$opt} = $param->{$opt};
1031 die "implement me: $opt";
1033 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running || $update;
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);
1048 my $storage_cfg = PVE
::Storage
::config
();
1049 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
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";
1059 my ($class, $key, $value) = @_;
1061 die "unknown setting '$key'\n" if !$confdesc->{$key};
1063 my $type = $confdesc->{$key}->{type
};
1065 if (!defined($value)) {
1066 die "got undefined value\n";
1069 if ($value =~ m/[\n\r]/) {
1070 die "property contains a line feed\n";
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);
1090 die "internal error"
1095 # add JSON properties for create and set function
1096 sub json_config_properties
{
1097 my ($class, $prop) = @_;
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};
1108 sub __parse_ct_mountpoint_full
{
1109 my ($class, $desc, $data, $noerr) = @_;
1114 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1116 return undef if $noerr;
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";
1126 $res->{size
} = $size;
1129 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1134 sub parse_ct_rootfs
{
1135 my ($class, $data, $noerr) = @_;
1137 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1139 $res->{mp
} = '/' if defined($res);
1144 sub parse_ct_mountpoint
{
1145 my ($class, $data, $noerr) = @_;
1147 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
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);
1157 sub print_lxc_network
{
1158 my ($class, $net) = @_;
1159 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1162 sub parse_lxc_network
{
1163 my ($class, $data) = @_;
1167 return $res if !$data;
1169 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
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
});
1181 my ($class, $name) = @_;
1183 return defined($confdesc->{$name});
1185 # END JSON config code
1187 sub classify_mountpoint
{
1188 my ($class, $vol) = @_;
1189 if ($vol =~ m!^/!) {
1190 return 'device' if $vol =~ m!^/dev/!;
1196 sub is_volume_in_use
{
1197 my ($class, $config, $volid, $include_snapshots) = @_;
1200 $class->foreach_mountpoint($config, sub {
1201 my ($ms, $mountpoint) = @_;
1203 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
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);
1216 sub has_dev_console
{
1217 my ($class, $conf) = @_;
1219 return !(defined($conf->{console
}) && !$conf->{console
});
1223 my ($class, $conf, $keyname) = @_;
1225 if (my $lxcconf = $conf->{lxc
}) {
1226 foreach my $entry (@$lxcconf) {
1227 my ($key, undef) = @$entry;
1228 return 1 if $key eq $keyname;
1236 my ($class, $conf) = @_;
1238 return $conf->{tty
} // $confdesc->{tty
}->{default};
1242 my ($class, $conf) = @_;
1244 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1247 sub mountpoint_names
{
1248 my ($class, $reverse) = @_;
1250 my @names = ('rootfs');
1252 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1253 push @names, "mp$i";
1256 return $reverse ?
reverse @names : @names;
1259 sub foreach_mountpoint_full
{
1260 my ($class, $conf, $reverse, $func, @param) = @_;
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);
1268 &$func($key, $mountpoint, @param);
1272 sub foreach_mountpoint
{
1273 my ($class, $conf, $func, @param) = @_;
1275 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1278 sub foreach_mountpoint_reverse
{
1279 my ($class, $conf, $func, @param) = @_;
1281 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1284 sub get_vm_volumes
{
1285 my ($class, $conf, $excludes) = @_;
1289 $class->foreach_mountpoint($conf, sub {
1290 my ($ms, $mountpoint) = @_;
1292 return if $excludes && $ms eq $excludes;
1294 my $volid = $mountpoint->{volume
};
1295 return if !$volid || $mountpoint->{type
} ne 'volume';
1297 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1300 push @$vollist, $volid;