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);
12 use base
qw(PVE::AbstractConfig);
14 my $nodename = PVE
::INotify
::nodename
();
15 my $lock_handles = {};
16 my $lockdir = "/run/lock/lxc";
18 mkdir "/etc/pve/nodes/$nodename/lxc";
19 my $MAX_MOUNT_POINTS = 10;
20 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
22 # BEGIN implemented abstract methods from PVE::AbstractConfig
28 sub __config_max_unused_disks
{
31 return $MAX_UNUSED_DISKS;
34 sub config_file_lock
{
35 my ($class, $vmid) = @_;
37 return "$lockdir/pve-config-${vmid}.lock";
41 my ($class, $vmid, $node) = @_;
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
47 sub mountpoint_backup_enabled
{
48 my ($class, $mp_key, $mountpoint) = @_;
50 return 1 if $mp_key eq 'rootfs';
52 return 0 if $mountpoint->{type
} ne 'volume';
54 return 1 if $mountpoint->{backup
};
60 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
63 $class->foreach_mountpoint($conf, sub {
64 my ($ms, $mountpoint) = @_;
66 return if $err; # skip further test
67 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
70 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
71 $mountpoint->{volume
},
78 sub __snapshot_save_vmstate
{
79 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
80 die "implement me - snapshot_save_vmstate\n";
83 sub __snapshot_check_running
{
84 my ($class, $vmid) = @_;
85 return PVE
::LXC
::check_running
($vmid);
88 sub __snapshot_check_freeze_needed
{
89 my ($class, $vmid, $config, $save_vmstate) = @_;
91 my $ret = $class->__snapshot_check_running($vmid);
95 sub __snapshot_freeze
{
96 my ($class, $vmid, $unfreeze) = @_;
99 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
102 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
103 PVE
::LXC
::sync_container_namespace
($vmid);
107 sub __snapshot_create_vol_snapshot
{
108 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
110 my $storecfg = PVE
::Storage
::config
();
112 return if $snapname eq 'vzdump' &&
113 !$class->mountpoint_backup_enabled($ms, $mountpoint);
115 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
118 sub __snapshot_delete_remove_drive
{
119 my ($class, $snap, $remove_drive) = @_;
121 if ($remove_drive eq 'vmstate') {
122 die "implement me - saving vmstate\n";
124 my $value = $snap->{$remove_drive};
125 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
126 delete $snap->{$remove_drive};
128 $class->add_unused_volume($snap, $mountpoint->{volume
})
129 if ($mountpoint->{type
} eq 'volume');
133 sub __snapshot_delete_vmstate_file
{
134 my ($class, $snap, $force) = @_;
136 die "implement me - saving vmstate\n";
139 sub __snapshot_delete_vol_snapshot
{
140 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
142 return if $snapname eq 'vzdump' &&
143 !$class->mountpoint_backup_enabled($ms, $mountpoint);
145 my $storecfg = PVE
::Storage
::config
();
146 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
147 push @$unused, $mountpoint->{volume
};
150 sub __snapshot_rollback_vol_possible
{
151 my ($class, $mountpoint, $snapname) = @_;
153 my $storecfg = PVE
::Storage
::config
();
154 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
157 sub __snapshot_rollback_vol_rollback
{
158 my ($class, $mountpoint, $snapname) = @_;
160 my $storecfg = PVE
::Storage
::config
();
161 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
164 sub __snapshot_rollback_vm_stop
{
165 my ($class, $vmid) = @_;
167 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
168 if $class->__snapshot_check_running($vmid);
171 sub __snapshot_rollback_vm_start
{
172 my ($class, $vmid, $vmstate, $forcemachine);
174 die "implement me - save vmstate\n";
177 sub __snapshot_rollback_get_unused
{
178 my ($class, $conf, $snap) = @_;
182 $class->__snapshot_foreach_volume($conf, sub {
183 my ($vs, $volume) = @_;
185 return if $volume->{type
} ne 'volume';
188 my $volid = $volume->{volume
};
190 $class->__snapshot_foreach_volume($snap, sub {
191 my ($ms, $mountpoint) = @_;
194 return if ($mountpoint->{type
} ne 'volume');
197 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
200 push @$unused, $volid if !$found;
206 sub __snapshot_foreach_volume
{
207 my ($class, $conf, $func) = @_;
209 $class->foreach_mountpoint($conf, $func);
212 # END implemented abstract methods from PVE::AbstractConfig
214 # BEGIN JSON config code
216 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
222 format
=> 'pve-lxc-mp-string',
223 format_description
=> 'volume',
224 description
=> 'Volume, device or directory to mount into the container.',
228 format
=> 'disk-size',
229 format_description
=> 'DiskSize',
230 description
=> 'Volume size (read only value).',
235 description
=> 'Explicitly enable or disable ACL support.',
240 description
=> 'Read-only mountpoint',
245 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
250 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
251 type
=> 'string', format
=> $rootfs_desc,
252 description
=> "Use volume as container root.",
256 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
257 description
=> "The name of the snapshot.",
258 type
=> 'string', format
=> 'pve-configid',
266 description
=> "Lock/unlock the VM.",
267 enum
=> [qw(migrate backup snapshot rollback)],
272 description
=> "Specifies whether a VM will be started during system bootup.",
275 startup
=> get_standard_option
('pve-startup-order'),
279 description
=> "Enable/disable Template.",
285 enum
=> ['amd64', 'i386'],
286 description
=> "OS architecture type.",
292 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
293 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.",
298 description
=> "Attach a console device (/dev/console) to the container.",
304 description
=> "Specify the number of tty available to the container",
312 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.",
320 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.",
328 description
=> "Amount of RAM for the VM in MB.",
335 description
=> "Amount of SWAP for the VM in MB.",
341 description
=> "Set a host name for the container.",
342 type
=> 'string', format
=> 'dns-name',
348 description
=> "Container description. Only used on the configuration web interface.",
352 type
=> 'string', format
=> 'dns-name-list',
353 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
357 type
=> 'string', format
=> 'address-list',
358 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.",
360 rootfs
=> get_standard_option
('pve-ct-rootfs'),
363 type
=> 'string', format
=> 'pve-configid',
365 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
369 description
=> "Timestamp for snapshots.",
375 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).",
377 enum
=> ['shell', 'console', 'tty'],
383 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
389 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
394 my $valid_lxc_conf_keys = {
398 'lxc.haltsignal' => 1,
399 'lxc.rebootsignal' => 1,
400 'lxc.stopsignal' => 1,
402 'lxc.network.type' => 1,
403 'lxc.network.flags' => 1,
404 'lxc.network.link' => 1,
405 'lxc.network.mtu' => 1,
406 'lxc.network.name' => 1,
407 'lxc.network.hwaddr' => 1,
408 'lxc.network.ipv4' => 1,
409 'lxc.network.ipv4.gateway' => 1,
410 'lxc.network.ipv6' => 1,
411 'lxc.network.ipv6.gateway' => 1,
412 'lxc.network.script.up' => 1,
413 'lxc.network.script.down' => 1,
415 'lxc.console.logfile' => 1,
418 'lxc.devttydir' => 1,
419 'lxc.hook.autodev' => 1,
423 'lxc.mount.entry' => 1,
424 'lxc.mount.auto' => 1,
425 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
426 'lxc.rootfs.mount' => 1,
427 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
428 ', please use mountpoint options in the "rootfs" key',
432 'lxc.aa_profile' => 1,
433 'lxc.aa_allow_incomplete' => 1,
434 'lxc.se_context' => 1,
437 'lxc.hook.pre-start' => 1,
438 'lxc.hook.pre-mount' => 1,
439 'lxc.hook.mount' => 1,
440 'lxc.hook.start' => 1,
441 'lxc.hook.stop' => 1,
442 'lxc.hook.post-stop' => 1,
443 'lxc.hook.clone' => 1,
444 'lxc.hook.destroy' => 1,
447 'lxc.start.auto' => 1,
448 'lxc.start.delay' => 1,
449 'lxc.start.order' => 1,
451 'lxc.environment' => 1,
454 our $netconf_desc = {
458 description
=> "Network interface type.",
463 format_description
=> 'string',
464 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
465 pattern
=> '[-_.\w\d]+',
469 format_description
=> 'bridge',
470 description
=> 'Bridge to attach the network device to.',
471 pattern
=> '[-_.\w\d]+',
476 format_description
=> "XX:XX:XX:XX:XX:XX",
477 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)',
478 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
483 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
484 minimum
=> 64, # minimum ethernet frame is 64 bytes
489 format
=> 'pve-ipv4-config',
490 format_description
=> 'IPv4Format/CIDR',
491 description
=> 'IPv4 address in CIDR format.',
497 format_description
=> 'GatewayIPv4',
498 description
=> 'Default gateway for IPv4 traffic.',
503 format
=> 'pve-ipv6-config',
504 format_description
=> 'IPv6Format/CIDR',
505 description
=> 'IPv6 address in CIDR format.',
511 format_description
=> 'GatewayIPv6',
512 description
=> 'Default gateway for IPv6 traffic.',
517 description
=> "Controls whether this interface's firewall rules should be used.",
524 description
=> "VLAN tag for this interface.",
529 pattern
=> qr/\d+(?:;\d+)*/,
530 format_description
=> 'vlanid[;vlanid...]',
531 description
=> "VLAN ids to pass through the interface",
536 format_description
=> 'mbps',
537 description
=> "Apply rate limiting to the interface",
541 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
543 my $MAX_LXC_NETWORKS = 10;
544 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
545 $confdesc->{"net$i"} = {
547 type
=> 'string', format
=> $netconf_desc,
548 description
=> "Specifies network interfaces for the container.",
552 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
553 sub verify_lxc_mp_string
{
554 my ($mp, $noerr) = @_;
558 # /. or /.. at the end
559 # ../ at the beginning
561 if($mp =~ m
@/\.\
.?
/@ ||
564 return undef if $noerr;
565 die "$mp contains illegal character sequences\n";
574 description
=> 'Whether to include the mountpoint in backups.',
575 verbose_description
=> 'Whether to include the mountpoint in backups '.
576 '(only used for volume mountpoints).',
581 format
=> 'pve-lxc-mp-string',
582 format_description
=> 'Path',
583 description
=> 'Path to the mountpoint as seen from inside the container '.
584 '(must not contain symlinks).',
585 verbose_description
=> "Path to the mountpoint as seen from inside the container.\n\n".
586 "NOTE: Must not contain any symlinks for security reasons."
589 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
593 type
=> 'string', format
=> 'pve-volume-id',
594 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
597 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
598 $confdesc->{"mp$i"} = {
600 type
=> 'string', format
=> $mp_desc,
601 description
=> "Use volume as container mount point.",
606 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
607 $confdesc->{"unused$i"} = $unuseddesc;
610 sub parse_pct_config
{
611 my ($filename, $raw) = @_;
613 return undef if !defined($raw);
616 digest
=> Digest
::SHA
::sha1_hex
($raw),
620 $filename =~ m
|/lxc/(\d
+).conf
$|
621 || die "got strange filename '$filename'";
629 my @lines = split(/\n/, $raw);
630 foreach my $line (@lines) {
631 next if $line =~ m/^\s*$/;
633 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
635 $conf->{description
} = $descr if $descr;
637 $conf = $res->{snapshots
}->{$section} = {};
641 if ($line =~ m/^\#(.*)\s*$/) {
642 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
646 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
649 my $validity = $valid_lxc_conf_keys->{$key} || 0;
650 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
651 push @{$conf->{lxc
}}, [$key, $value];
652 } elsif (my $errmsg = $validity) {
653 warn "vm $vmid - $key: $errmsg\n";
655 warn "vm $vmid - unable to parse config: $line\n";
657 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
658 $descr .= PVE
::Tools
::decode_text
($2);
659 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
660 $conf->{snapstate
} = $1;
661 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
664 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
665 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
666 $conf->{$key} = $value;
668 warn "vm $vmid - unable to parse config: $line\n";
672 $conf->{description
} = $descr if $descr;
674 delete $res->{snapstate
}; # just to be sure
679 sub write_pct_config
{
680 my ($filename, $conf) = @_;
682 delete $conf->{snapstate
}; # just to be sure
684 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
685 my $used_volids = {};
686 foreach my $vid (@$volidlist) {
687 $used_volids->{$vid} = 1;
690 # remove 'unusedX' settings if the volume is still used
691 foreach my $key (keys %$conf) {
692 my $value = $conf->{$key};
693 if ($key =~ m/^unused/ && $used_volids->{$value}) {
694 delete $conf->{$key};
698 my $generate_raw_config = sub {
703 # add description as comment to top of file
704 my $descr = $conf->{description
} || '';
705 foreach my $cl (split(/\n/, $descr)) {
706 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
709 foreach my $key (sort keys %$conf) {
710 next if $key eq 'digest' || $key eq 'description' ||
711 $key eq 'pending' || $key eq 'snapshots' ||
712 $key eq 'snapname' || $key eq 'lxc';
713 my $value = $conf->{$key};
714 die "detected invalid newline inside property '$key'\n"
716 $raw .= "$key: $value\n";
719 if (my $lxcconf = $conf->{lxc
}) {
720 foreach my $entry (@$lxcconf) {
721 my ($k, $v) = @$entry;
729 my $raw = &$generate_raw_config($conf);
731 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
732 $raw .= "\n[$snapname]\n";
733 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
739 sub update_pct_config
{
740 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
749 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
750 $rootdir = "/proc/$pid/root";
753 my $hotplug_error = sub {
762 if (defined($delete)) {
763 foreach my $opt (@$delete) {
764 if (!exists($conf->{$opt})) {
765 warn "no such option: $opt\n";
769 if ($opt eq 'memory' || $opt eq 'rootfs') {
770 die "unable to delete required option '$opt'\n";
771 } elsif ($opt eq 'hostname') {
772 delete $conf->{$opt};
773 } elsif ($opt eq 'swap') {
774 delete $conf->{$opt};
775 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
776 "memory.memsw.limit_in_bytes", -1);
777 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
778 delete $conf->{$opt};
779 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
780 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
781 next if $hotplug_error->($opt);
782 delete $conf->{$opt};
783 } elsif ($opt eq 'cpulimit') {
784 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
785 delete $conf->{$opt};
786 } elsif ($opt eq 'cpuunits') {
787 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
788 delete $conf->{$opt};
789 } elsif ($opt =~ m/^net(\d)$/) {
790 delete $conf->{$opt};
793 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
794 } elsif ($opt eq 'protection') {
795 delete $conf->{$opt};
796 } elsif ($opt =~ m/^unused(\d+)$/) {
797 next if $hotplug_error->($opt);
798 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
799 push @deleted_volumes, $conf->{$opt};
800 delete $conf->{$opt};
801 } elsif ($opt =~ m/^mp(\d+)$/) {
802 next if $hotplug_error->($opt);
803 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
804 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
805 delete $conf->{$opt};
806 if ($mp->{type
} eq 'volume') {
807 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
809 } elsif ($opt eq 'unprivileged') {
810 die "unable to delete read-only option: '$opt'\n";
812 die "implement me (delete: $opt)"
814 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
818 # There's no separate swap size to configure, there's memory and "total"
819 # memory (iow. memory+swap). This means we have to change them together.
820 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
821 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
822 if (defined($wanted_memory) || defined($wanted_swap)) {
824 my $old_memory = ($conf->{memory
} || 512);
825 my $old_swap = ($conf->{swap
} || 0);
827 $wanted_memory //= $old_memory;
828 $wanted_swap //= $old_swap;
830 my $total = $wanted_memory + $wanted_swap;
832 my $old_total = $old_memory + $old_swap;
833 if ($total > $old_total) {
834 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
835 "memory.memsw.limit_in_bytes",
836 int($total*1024*1024));
837 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
838 "memory.limit_in_bytes",
839 int($wanted_memory*1024*1024));
841 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
842 "memory.limit_in_bytes",
843 int($wanted_memory*1024*1024));
844 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
845 "memory.memsw.limit_in_bytes",
846 int($total*1024*1024));
849 $conf->{memory
} = $wanted_memory;
850 $conf->{swap
} = $wanted_swap;
852 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
855 my $used_volids = {};
856 my $check_content_type = sub {
858 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
859 my $scfg = PVE
::Storage
::config
();
860 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
861 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
862 if !$storage_config->{content
}->{rootdir
};
865 foreach my $opt (keys %$param) {
866 my $value = $param->{$opt};
867 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
868 if ($opt eq 'hostname' || $opt eq 'arch') {
869 $conf->{$opt} = $value;
870 } elsif ($opt eq 'onboot') {
871 $conf->{$opt} = $value ?
1 : 0;
872 } elsif ($opt eq 'startup') {
873 $conf->{$opt} = $value;
874 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
875 next if $hotplug_error->($opt);
876 $conf->{$opt} = $value;
877 } elsif ($opt eq 'nameserver') {
878 next if $hotplug_error->($opt);
879 my $list = PVE
::LXC
::verify_nameserver_list
($value);
880 $conf->{$opt} = $list;
881 } elsif ($opt eq 'searchdomain') {
882 next if $hotplug_error->($opt);
883 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
884 $conf->{$opt} = $list;
885 } elsif ($opt eq 'cpulimit') {
887 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
889 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
891 $conf->{$opt} = $value;
892 } elsif ($opt eq 'cpuunits') {
893 $conf->{$opt} = $value;
894 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
895 } elsif ($opt eq 'description') {
896 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
897 } elsif ($opt =~ m/^net(\d+)$/) {
899 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
901 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
903 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
905 } elsif ($opt eq 'protection') {
906 $conf->{$opt} = $value ?
1 : 0;
907 } elsif ($opt =~ m/^mp(\d+)$/) {
908 next if $hotplug_error->($opt);
909 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
910 my $old = $conf->{$opt};
911 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
912 if ($mp->{type
} eq 'volume') {
913 &$check_content_type($mp);
914 $used_volids->{$mp->{volume
}} = 1;
916 $conf->{$opt} = $value;
918 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
919 if ($mp->{type
} eq 'volume') {
920 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
924 } elsif ($opt eq 'rootfs') {
925 next if $hotplug_error->($opt);
926 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
927 my $old = $conf->{$opt};
928 $conf->{$opt} = $value;
929 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
930 if ($mp->{type
} eq 'volume') {
931 &$check_content_type($mp);
932 $used_volids->{$mp->{volume
}} = 1;
935 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
936 if ($mp->{type
} eq 'volume') {
937 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
941 } elsif ($opt eq 'unprivileged') {
942 die "unable to modify read-only option: '$opt'\n";
943 } elsif ($opt eq 'ostype') {
944 next if $hotplug_error->($opt);
945 $conf->{$opt} = $value;
947 die "implement me: $opt";
949 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
952 # Apply deletions and creations of new volumes
953 if (@deleted_volumes) {
954 my $storage_cfg = PVE
::Storage
::config
();
955 foreach my $volume (@deleted_volumes) {
956 next if $used_volids->{$volume}; # could have been re-added, too
957 # also check for references in snapshots
958 next if $class->is_volume_in_use($conf, $volume, 1);
959 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
964 my $storage_cfg = PVE
::Storage
::config
();
965 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
968 # This should be the last thing we do here
969 if ($running && scalar(@nohotplug)) {
970 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
975 my ($class, $key, $value) = @_;
977 die "unknown setting '$key'\n" if !$confdesc->{$key};
979 my $type = $confdesc->{$key}->{type
};
981 if (!defined($value)) {
982 die "got undefined value\n";
985 if ($value =~ m/[\n\r]/) {
986 die "property contains a line feed\n";
989 if ($type eq 'boolean') {
990 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
991 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
992 die "type check ('boolean') failed - got '$value'\n";
993 } elsif ($type eq 'integer') {
994 return int($1) if $value =~ m/^(\d+)$/;
995 die "type check ('integer') failed - got '$value'\n";
996 } elsif ($type eq 'number') {
997 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
998 die "type check ('number') failed - got '$value'\n";
999 } elsif ($type eq 'string') {
1000 if (my $fmt = $confdesc->{$key}->{format
}) {
1001 PVE
::JSONSchema
::check_format
($fmt, $value);
1006 die "internal error"
1011 # add JSON properties for create and set function
1012 sub json_config_properties
{
1013 my ($class, $prop) = @_;
1015 foreach my $opt (keys %$confdesc) {
1016 next if $opt eq 'parent' || $opt eq 'snaptime';
1017 next if $prop->{$opt};
1018 $prop->{$opt} = $confdesc->{$opt};
1024 sub __parse_ct_mountpoint_full
{
1025 my ($class, $desc, $data, $noerr) = @_;
1030 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1032 return undef if $noerr;
1036 if (defined(my $size = $res->{size
})) {
1037 $size = PVE
::JSONSchema
::parse_size
($size);
1038 if (!defined($size)) {
1039 return undef if $noerr;
1040 die "invalid size: $size\n";
1042 $res->{size
} = $size;
1045 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1050 sub parse_ct_rootfs
{
1051 my ($class, $data, $noerr) = @_;
1053 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1055 $res->{mp
} = '/' if defined($res);
1060 sub parse_ct_mountpoint
{
1061 my ($class, $data, $noerr) = @_;
1063 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1066 sub print_ct_mountpoint
{
1067 my ($class, $info, $nomp) = @_;
1068 my $skip = [ 'type' ];
1069 push @$skip, 'mp' if $nomp;
1070 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1073 sub print_lxc_network
{
1074 my ($class, $net) = @_;
1075 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1078 sub parse_lxc_network
{
1079 my ($class, $data) = @_;
1083 return $res if !$data;
1085 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1087 $res->{type
} = 'veth';
1088 if (!$res->{hwaddr
}) {
1089 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1090 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1097 my ($class, $name) = @_;
1099 return defined($confdesc->{$name});
1101 # END JSON config code
1103 sub classify_mountpoint
{
1104 my ($class, $vol) = @_;
1105 if ($vol =~ m!^/!) {
1106 return 'device' if $vol =~ m!^/dev/!;
1112 sub is_volume_in_use
{
1113 my ($class, $config, $volid, $include_snapshots) = @_;
1116 $class->foreach_mountpoint($config, sub {
1117 my ($ms, $mountpoint) = @_;
1119 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1122 my $snapshots = $config->{snapshots
};
1123 if ($include_snapshots && $snapshots) {
1124 foreach my $snap (keys %$snapshots) {
1125 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1132 sub has_dev_console
{
1133 my ($class, $conf) = @_;
1135 return !(defined($conf->{console
}) && !$conf->{console
});
1139 my ($class, $conf) = @_;
1141 return $conf->{tty
} // $confdesc->{tty
}->{default};
1145 my ($class, $conf) = @_;
1147 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1150 sub mountpoint_names
{
1151 my ($class, $reverse) = @_;
1153 my @names = ('rootfs');
1155 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1156 push @names, "mp$i";
1159 return $reverse ?
reverse @names : @names;
1162 sub foreach_mountpoint_full
{
1163 my ($class, $conf, $reverse, $func, @param) = @_;
1165 foreach my $key ($class->mountpoint_names($reverse)) {
1166 my $value = $conf->{$key};
1167 next if !defined($value);
1168 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1169 next if !defined($mountpoint);
1171 &$func($key, $mountpoint, @param);
1175 sub foreach_mountpoint
{
1176 my ($class, $conf, $func, @param) = @_;
1178 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1181 sub foreach_mountpoint_reverse
{
1182 my ($class, $conf, $func, @param) = @_;
1184 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1187 sub get_vm_volumes
{
1188 my ($class, $conf, $excludes) = @_;
1192 $class->foreach_mountpoint($conf, sub {
1193 my ($ms, $mountpoint) = @_;
1195 return if $excludes && $ms eq $excludes;
1197 my $volid = $mountpoint->{volume
};
1198 return if !$volid || $mountpoint->{type
} ne 'volume';
1200 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1203 push @$vollist, $volid;