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.",
394 replicate
=> get_standard_option
('pve-replicate'),
397 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).",
399 enum
=> ['shell', 'console', 'tty'],
405 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
411 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
416 my $valid_lxc_conf_keys = {
420 'lxc.haltsignal' => 1,
421 'lxc.rebootsignal' => 1,
422 'lxc.stopsignal' => 1,
424 'lxc.network.type' => 1,
425 'lxc.network.flags' => 1,
426 'lxc.network.link' => 1,
427 'lxc.network.mtu' => 1,
428 'lxc.network.name' => 1,
429 'lxc.network.hwaddr' => 1,
430 'lxc.network.ipv4' => 1,
431 'lxc.network.ipv4.gateway' => 1,
432 'lxc.network.ipv6' => 1,
433 'lxc.network.ipv6.gateway' => 1,
434 'lxc.network.script.up' => 1,
435 'lxc.network.script.down' => 1,
437 'lxc.console.logfile' => 1,
440 'lxc.devttydir' => 1,
441 'lxc.hook.autodev' => 1,
445 'lxc.mount.entry' => 1,
446 'lxc.mount.auto' => 1,
447 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
448 'lxc.rootfs.mount' => 1,
449 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
450 ', please use mount point options in the "rootfs" key',
455 'lxc.aa_profile' => 1,
456 'lxc.aa_allow_incomplete' => 1,
457 'lxc.se_context' => 1,
460 'lxc.hook.pre-start' => 1,
461 'lxc.hook.pre-mount' => 1,
462 'lxc.hook.mount' => 1,
463 'lxc.hook.start' => 1,
464 'lxc.hook.stop' => 1,
465 'lxc.hook.post-stop' => 1,
466 'lxc.hook.clone' => 1,
467 'lxc.hook.destroy' => 1,
470 'lxc.start.auto' => 1,
471 'lxc.start.delay' => 1,
472 'lxc.start.order' => 1,
474 'lxc.environment' => 1,
477 our $netconf_desc = {
481 description
=> "Network interface type.",
486 format_description
=> 'string',
487 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
488 pattern
=> '[-_.\w\d]+',
492 format_description
=> 'bridge',
493 description
=> 'Bridge to attach the network device to.',
494 pattern
=> '[-_.\w\d]+',
499 format_description
=> "XX:XX:XX:XX:XX:XX",
500 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)',
501 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
506 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
507 minimum
=> 64, # minimum ethernet frame is 64 bytes
512 format
=> 'pve-ipv4-config',
513 format_description
=> 'IPv4Format/CIDR',
514 description
=> 'IPv4 address in CIDR format.',
520 format_description
=> 'GatewayIPv4',
521 description
=> 'Default gateway for IPv4 traffic.',
526 format
=> 'pve-ipv6-config',
527 format_description
=> 'IPv6Format/CIDR',
528 description
=> 'IPv6 address in CIDR format.',
534 format_description
=> 'GatewayIPv6',
535 description
=> 'Default gateway for IPv6 traffic.',
540 description
=> "Controls whether this interface's firewall rules should be used.",
547 description
=> "VLAN tag for this interface.",
552 pattern
=> qr/\d+(?:;\d+)*/,
553 format_description
=> 'vlanid[;vlanid...]',
554 description
=> "VLAN ids to pass through the interface",
559 format_description
=> 'mbps',
560 description
=> "Apply rate limiting to the interface",
564 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
566 my $MAX_LXC_NETWORKS = 10;
567 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
568 $confdesc->{"net$i"} = {
570 type
=> 'string', format
=> $netconf_desc,
571 description
=> "Specifies network interfaces for the container.",
575 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
576 sub verify_lxc_mp_string
{
577 my ($mp, $noerr) = @_;
581 # /. or /.. at the end
582 # ../ at the beginning
584 if($mp =~ m
@/\.\
.?
/@ ||
587 return undef if $noerr;
588 die "$mp contains illegal character sequences\n";
597 description
=> 'Whether to include the mount point in backups.',
598 verbose_description
=> 'Whether to include the mount point in backups '.
599 '(only used for volume mount points).',
604 format
=> 'pve-lxc-mp-string',
605 format_description
=> 'Path',
606 description
=> 'Path to the mount point as seen from inside the container '.
607 '(must not contain symlinks).',
608 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
609 "NOTE: Must not contain any symlinks for security reasons."
612 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
616 type
=> 'string', format
=> 'pve-volume-id',
617 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
620 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
621 $confdesc->{"mp$i"} = {
623 type
=> 'string', format
=> $mp_desc,
624 description
=> "Use volume as container mount point.",
629 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
630 $confdesc->{"unused$i"} = $unuseddesc;
633 sub parse_pct_config
{
634 my ($filename, $raw) = @_;
636 return undef if !defined($raw);
639 digest
=> Digest
::SHA
::sha1_hex
($raw),
643 $filename =~ m
|/lxc/(\d
+).conf
$|
644 || die "got strange filename '$filename'";
652 my @lines = split(/\n/, $raw);
653 foreach my $line (@lines) {
654 next if $line =~ m/^\s*$/;
656 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
658 $conf->{description
} = $descr if $descr;
660 $conf = $res->{snapshots
}->{$section} = {};
664 if ($line =~ m/^\#(.*)\s*$/) {
665 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
669 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
672 my $validity = $valid_lxc_conf_keys->{$key} || 0;
673 if ($validity eq 1 || $key =~ m/^lxc\.(?:cgroup|limit)\./) {
674 push @{$conf->{lxc
}}, [$key, $value];
675 } elsif (my $errmsg = $validity) {
676 warn "vm $vmid - $key: $errmsg\n";
678 warn "vm $vmid - unable to parse config: $line\n";
680 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
681 $descr .= PVE
::Tools
::decode_text
($2);
682 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
683 $conf->{snapstate
} = $1;
684 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
687 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
688 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
689 $conf->{$key} = $value;
691 warn "vm $vmid - unable to parse config: $line\n";
695 $conf->{description
} = $descr if $descr;
697 delete $res->{snapstate
}; # just to be sure
702 sub write_pct_config
{
703 my ($filename, $conf) = @_;
705 delete $conf->{snapstate
}; # just to be sure
707 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
708 my $used_volids = {};
709 foreach my $vid (@$volidlist) {
710 $used_volids->{$vid} = 1;
713 # remove 'unusedX' settings if the volume is still used
714 foreach my $key (keys %$conf) {
715 my $value = $conf->{$key};
716 if ($key =~ m/^unused/ && $used_volids->{$value}) {
717 delete $conf->{$key};
721 my $generate_raw_config = sub {
726 # add description as comment to top of file
727 my $descr = $conf->{description
} || '';
728 foreach my $cl (split(/\n/, $descr)) {
729 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
732 foreach my $key (sort keys %$conf) {
733 next if $key eq 'digest' || $key eq 'description' ||
734 $key eq 'pending' || $key eq 'snapshots' ||
735 $key eq 'snapname' || $key eq 'lxc';
736 my $value = $conf->{$key};
737 die "detected invalid newline inside property '$key'\n"
739 $raw .= "$key: $value\n";
742 if (my $lxcconf = $conf->{lxc
}) {
743 foreach my $entry (@$lxcconf) {
744 my ($k, $v) = @$entry;
752 my $raw = &$generate_raw_config($conf);
754 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
755 $raw .= "\n[$snapname]\n";
756 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
762 sub update_pct_config
{
763 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
772 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
773 $rootdir = "/proc/$pid/root";
776 my $hotplug_error = sub {
785 if (defined($delete)) {
786 foreach my $opt (@$delete) {
787 if (!exists($conf->{$opt})) {
792 if ($opt eq 'memory' || $opt eq 'rootfs') {
793 die "unable to delete required option '$opt'\n";
794 } elsif ($opt eq 'hostname') {
795 delete $conf->{$opt};
796 } elsif ($opt eq 'swap') {
797 delete $conf->{$opt};
798 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
799 "memory.memsw.limit_in_bytes", -1);
800 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
801 delete $conf->{$opt};
802 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
803 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
804 next if $hotplug_error->($opt);
805 delete $conf->{$opt};
806 } elsif ($opt eq 'cores') {
807 delete $conf->{$opt}; # rest is handled by pvestatd
808 } elsif ($opt eq 'cpulimit') {
809 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
810 delete $conf->{$opt};
811 } elsif ($opt eq 'cpuunits') {
812 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
813 delete $conf->{$opt};
814 } elsif ($opt =~ m/^net(\d)$/) {
815 delete $conf->{$opt};
818 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
819 } elsif ($opt eq 'protection') {
820 delete $conf->{$opt};
821 } elsif ($opt =~ m/^unused(\d+)$/) {
822 next if $hotplug_error->($opt);
823 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
824 push @deleted_volumes, $conf->{$opt};
825 delete $conf->{$opt};
826 } elsif ($opt =~ m/^mp(\d+)$/) {
827 next if $hotplug_error->($opt);
828 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
829 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
830 delete $conf->{$opt};
831 if ($mp->{type
} eq 'volume') {
832 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
834 } elsif ($opt eq 'unprivileged') {
835 die "unable to delete read-only option: '$opt'\n";
836 } elsif ($opt eq "replicate") {
837 delete $conf->{$opt};
839 die "implement me (delete: $opt)"
841 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
845 # There's no separate swap size to configure, there's memory and "total"
846 # memory (iow. memory+swap). This means we have to change them together.
847 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
848 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
849 if (defined($wanted_memory) || defined($wanted_swap)) {
851 my $old_memory = ($conf->{memory
} || 512);
852 my $old_swap = ($conf->{swap
} || 0);
854 $wanted_memory //= $old_memory;
855 $wanted_swap //= $old_swap;
857 my $total = $wanted_memory + $wanted_swap;
859 my $old_total = $old_memory + $old_swap;
860 if ($total > $old_total) {
861 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
862 "memory.memsw.limit_in_bytes",
863 int($total*1024*1024));
864 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
865 "memory.limit_in_bytes",
866 int($wanted_memory*1024*1024));
868 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
869 "memory.limit_in_bytes",
870 int($wanted_memory*1024*1024));
871 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
872 "memory.memsw.limit_in_bytes",
873 int($total*1024*1024));
876 $conf->{memory
} = $wanted_memory;
877 $conf->{swap
} = $wanted_swap;
879 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
882 my $used_volids = {};
883 my $check_content_type = sub {
885 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
886 my $scfg = PVE
::Storage
::config
();
887 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
888 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
889 if !$storage_config->{content
}->{rootdir
};
892 foreach my $opt (keys %$param) {
893 my $value = $param->{$opt};
894 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
895 if ($opt eq 'hostname' || $opt eq 'arch') {
896 $conf->{$opt} = $value;
897 } elsif ($opt eq 'onboot') {
898 $conf->{$opt} = $value ?
1 : 0;
899 } elsif ($opt eq 'startup') {
900 $conf->{$opt} = $value;
901 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
902 next if $hotplug_error->($opt);
903 $conf->{$opt} = $value;
904 } elsif ($opt eq 'nameserver') {
905 next if $hotplug_error->($opt);
906 my $list = PVE
::LXC
::verify_nameserver_list
($value);
907 $conf->{$opt} = $list;
908 } elsif ($opt eq 'searchdomain') {
909 next if $hotplug_error->($opt);
910 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
911 $conf->{$opt} = $list;
912 } elsif ($opt eq 'cores') {
913 $conf->{$opt} = $value;# rest is handled by pvestatd
914 } elsif ($opt eq 'cpulimit') {
916 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
918 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
920 $conf->{$opt} = $value;
921 } elsif ($opt eq 'cpuunits') {
922 $conf->{$opt} = $value;
923 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
924 } elsif ($opt eq 'description') {
925 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
926 } elsif ($opt =~ m/^net(\d+)$/) {
928 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
930 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
932 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
934 } elsif ($opt eq 'protection') {
935 $conf->{$opt} = $value ?
1 : 0;
936 } elsif ($opt =~ m/^mp(\d+)$/) {
937 next if $hotplug_error->($opt);
938 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
939 my $old = $conf->{$opt};
940 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
941 if ($mp->{type
} eq 'volume') {
942 &$check_content_type($mp);
943 $used_volids->{$mp->{volume
}} = 1;
945 $conf->{$opt} = $value;
947 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
948 if ($mp->{type
} eq 'volume') {
949 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
953 } elsif ($opt eq 'rootfs') {
954 next if $hotplug_error->($opt);
955 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
956 my $old = $conf->{$opt};
957 $conf->{$opt} = $value;
958 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
959 if ($mp->{type
} eq 'volume') {
960 &$check_content_type($mp);
961 $used_volids->{$mp->{volume
}} = 1;
964 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
965 if ($mp->{type
} eq 'volume') {
966 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
970 } elsif ($opt eq 'unprivileged') {
971 die "unable to modify read-only option: '$opt'\n";
972 } elsif ($opt eq 'ostype') {
973 next if $hotplug_error->($opt);
974 $conf->{$opt} = $value;
975 } elsif ($opt eq "replicate") {
976 die "Not all volumes are syncable, please check your config\n"
977 if !PVE
::ReplicationTools
::check_guest_volumes_syncable
($conf, 'lxc');
978 my $repl = PVE
::JSONSchema
::check_format
('pve-replicate', $value);
979 PVE
::Cluster
::check_node_exists
($repl->{target
});
980 $conf->{$opt} = $value;
982 die "implement me: $opt";
984 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
987 # Apply deletions and creations of new volumes
988 if (@deleted_volumes) {
989 my $storage_cfg = PVE
::Storage
::config
();
990 foreach my $volume (@deleted_volumes) {
991 next if $used_volids->{$volume}; # could have been re-added, too
992 # also check for references in snapshots
993 next if $class->is_volume_in_use($conf, $volume, 1);
994 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
999 my $storage_cfg = PVE
::Storage
::config
();
1000 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
1003 # This should be the last thing we do here
1004 if ($running && scalar(@nohotplug)) {
1005 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1010 my ($class, $key, $value) = @_;
1012 die "unknown setting '$key'\n" if !$confdesc->{$key};
1014 my $type = $confdesc->{$key}->{type
};
1016 if (!defined($value)) {
1017 die "got undefined value\n";
1020 if ($value =~ m/[\n\r]/) {
1021 die "property contains a line feed\n";
1024 if ($type eq 'boolean') {
1025 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1026 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1027 die "type check ('boolean') failed - got '$value'\n";
1028 } elsif ($type eq 'integer') {
1029 return int($1) if $value =~ m/^(\d+)$/;
1030 die "type check ('integer') failed - got '$value'\n";
1031 } elsif ($type eq 'number') {
1032 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1033 die "type check ('number') failed - got '$value'\n";
1034 } elsif ($type eq 'string') {
1035 if (my $fmt = $confdesc->{$key}->{format
}) {
1036 PVE
::JSONSchema
::check_format
($fmt, $value);
1041 die "internal error"
1046 # add JSON properties for create and set function
1047 sub json_config_properties
{
1048 my ($class, $prop) = @_;
1050 foreach my $opt (keys %$confdesc) {
1051 next if $opt eq 'parent' || $opt eq 'snaptime';
1052 next if $prop->{$opt};
1053 $prop->{$opt} = $confdesc->{$opt};
1059 sub __parse_ct_mountpoint_full
{
1060 my ($class, $desc, $data, $noerr) = @_;
1065 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1067 return undef if $noerr;
1071 if (defined(my $size = $res->{size
})) {
1072 $size = PVE
::JSONSchema
::parse_size
($size);
1073 if (!defined($size)) {
1074 return undef if $noerr;
1075 die "invalid size: $size\n";
1077 $res->{size
} = $size;
1080 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1085 sub parse_ct_rootfs
{
1086 my ($class, $data, $noerr) = @_;
1088 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1090 $res->{mp
} = '/' if defined($res);
1095 sub parse_ct_mountpoint
{
1096 my ($class, $data, $noerr) = @_;
1098 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1101 sub print_ct_mountpoint
{
1102 my ($class, $info, $nomp) = @_;
1103 my $skip = [ 'type' ];
1104 push @$skip, 'mp' if $nomp;
1105 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1108 sub print_lxc_network
{
1109 my ($class, $net) = @_;
1110 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1113 sub parse_lxc_network
{
1114 my ($class, $data) = @_;
1118 return $res if !$data;
1120 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1122 $res->{type
} = 'veth';
1123 if (!$res->{hwaddr
}) {
1124 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1125 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1132 my ($class, $name) = @_;
1134 return defined($confdesc->{$name});
1136 # END JSON config code
1138 sub classify_mountpoint
{
1139 my ($class, $vol) = @_;
1140 if ($vol =~ m!^/!) {
1141 return 'device' if $vol =~ m!^/dev/!;
1147 sub is_volume_in_use
{
1148 my ($class, $config, $volid, $include_snapshots) = @_;
1151 $class->foreach_mountpoint($config, sub {
1152 my ($ms, $mountpoint) = @_;
1154 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1157 my $snapshots = $config->{snapshots
};
1158 if ($include_snapshots && $snapshots) {
1159 foreach my $snap (keys %$snapshots) {
1160 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1167 sub has_dev_console
{
1168 my ($class, $conf) = @_;
1170 return !(defined($conf->{console
}) && !$conf->{console
});
1174 my ($class, $conf, $keyname) = @_;
1176 if (my $lxcconf = $conf->{lxc
}) {
1177 foreach my $entry (@$lxcconf) {
1178 my ($key, undef) = @$entry;
1179 return 1 if $key eq $keyname;
1187 my ($class, $conf) = @_;
1189 return $conf->{tty
} // $confdesc->{tty
}->{default};
1193 my ($class, $conf) = @_;
1195 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1198 sub mountpoint_names
{
1199 my ($class, $reverse) = @_;
1201 my @names = ('rootfs');
1203 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1204 push @names, "mp$i";
1207 return $reverse ?
reverse @names : @names;
1210 sub foreach_mountpoint_full
{
1211 my ($class, $conf, $reverse, $func, @param) = @_;
1213 foreach my $key ($class->mountpoint_names($reverse)) {
1214 my $value = $conf->{$key};
1215 next if !defined($value);
1216 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1217 next if !defined($mountpoint);
1219 &$func($key, $mountpoint, @param);
1223 sub foreach_mountpoint
{
1224 my ($class, $conf, $func, @param) = @_;
1226 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1229 sub foreach_mountpoint_reverse
{
1230 my ($class, $conf, $func, @param) = @_;
1232 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1235 sub get_vm_volumes
{
1236 my ($class, $conf, $excludes) = @_;
1240 $class->foreach_mountpoint($conf, sub {
1241 my ($ms, $mountpoint) = @_;
1243 return if $excludes && $ms eq $excludes;
1245 my $volid = $mountpoint->{volume
};
1246 return if !$volid || $mountpoint->{type
} ne 'volume';
1248 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1251 push @$vollist, $volid;