]>
Commit | Line | Data |
---|---|---|
f76a2828 DM |
1 | package PVE::LXC; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
d14a9a1b | 5 | use POSIX qw(EINTR); |
f76a2828 DM |
6 | |
7 | use File::Path; | |
8 | use Fcntl ':flock'; | |
9 | ||
10 | use PVE::Cluster qw(cfs_register_file cfs_read_file); | |
c65e0a6d | 11 | use PVE::Storage; |
f76a2828 DM |
12 | use PVE::SafeSyslog; |
13 | use PVE::INotify; | |
a3249355 | 14 | use PVE::JSONSchema qw(get_standard_option); |
55fa4e09 | 15 | use PVE::Tools qw($IPV6RE $IPV4RE); |
68fba17b | 16 | use PVE::Network; |
f76a2828 DM |
17 | |
18 | use Data::Dumper; | |
19 | ||
27916659 DM |
20 | my $nodename = PVE::INotify::nodename(); |
21 | ||
22 | cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config); | |
f76a2828 | 23 | |
7dfc49cc DM |
24 | PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network); |
25 | sub verify_lxc_network { | |
26 | my ($value, $noerr) = @_; | |
27 | ||
28 | return $value if parse_lxc_network($value); | |
29 | ||
30 | return undef if $noerr; | |
31 | ||
32 | die "unable to parse network setting\n"; | |
33 | } | |
34 | ||
27916659 DM |
35 | PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint); |
36 | sub verify_ct_mountpoint { | |
37 | my ($value, $noerr) = @_; | |
822de0c3 | 38 | |
27916659 | 39 | return $value if parse_ct_mountpoint($value); |
822de0c3 | 40 | |
27916659 | 41 | return undef if $noerr; |
822de0c3 | 42 | |
27916659 | 43 | die "unable to parse CT mountpoint options\n"; |
822de0c3 DM |
44 | } |
45 | ||
27916659 DM |
46 | PVE::JSONSchema::register_standard_option('pve-ct-rootfs', { |
47 | type => 'string', format => 'pve-ct-mountpoint', | |
48 | typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]', | |
8fbd2935 | 49 | description => "Use volume as container root.", |
27916659 DM |
50 | optional => 1, |
51 | }); | |
52 | ||
53 | my $confdesc = { | |
09d3ec42 DM |
54 | lock => { |
55 | optional => 1, | |
56 | type => 'string', | |
57 | description => "Lock/unlock the VM.", | |
58 | enum => [qw(migrate backup snapshot rollback)], | |
59 | }, | |
27916659 DM |
60 | onboot => { |
61 | optional => 1, | |
62 | type => 'boolean', | |
63 | description => "Specifies whether a VM will be started during system bootup.", | |
64 | default => 0, | |
117636e5 | 65 | }, |
27916659 | 66 | startup => get_standard_option('pve-startup-order'), |
bb1ac2de DM |
67 | template => { |
68 | optional => 1, | |
69 | type => 'boolean', | |
70 | description => "Enable/disable Template.", | |
71 | default => 0, | |
72 | }, | |
27916659 DM |
73 | arch => { |
74 | optional => 1, | |
75 | type => 'string', | |
76 | enum => ['amd64', 'i386'], | |
77 | description => "OS architecture type.", | |
78 | default => 'amd64', | |
117636e5 | 79 | }, |
27916659 DM |
80 | ostype => { |
81 | optional => 1, | |
82 | type => 'string', | |
83 | enum => ['debian', 'ubuntu', 'centos'], | |
84 | description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.", | |
a3249355 | 85 | }, |
27916659 DM |
86 | tty => { |
87 | optional => 1, | |
88 | type => 'integer', | |
89 | description => "Specify the number of tty available to the container", | |
90 | minimum => 0, | |
91 | maximum => 6, | |
92 | default => 4, | |
611fe3aa | 93 | }, |
27916659 DM |
94 | cpulimit => { |
95 | optional => 1, | |
96 | type => 'number', | |
97 | description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.", | |
98 | minimum => 0, | |
99 | maximum => 128, | |
100 | default => 0, | |
101 | }, | |
102 | cpuunits => { | |
103 | optional => 1, | |
104 | type => 'integer', | |
105 | 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 weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.", | |
106 | minimum => 0, | |
107 | maximum => 500000, | |
81bee809 | 108 | default => 1024, |
27916659 DM |
109 | }, |
110 | memory => { | |
111 | optional => 1, | |
112 | type => 'integer', | |
113 | description => "Amount of RAM for the VM in MB.", | |
114 | minimum => 16, | |
115 | default => 512, | |
116 | }, | |
117 | swap => { | |
118 | optional => 1, | |
119 | type => 'integer', | |
120 | description => "Amount of SWAP for the VM in MB.", | |
121 | minimum => 0, | |
122 | default => 512, | |
123 | }, | |
124 | hostname => { | |
125 | optional => 1, | |
126 | description => "Set a host name for the container.", | |
127 | type => 'string', | |
128 | maxLength => 255, | |
129 | }, | |
130 | description => { | |
131 | optional => 1, | |
132 | type => 'string', | |
133 | description => "Container description. Only used on the configuration web interface.", | |
134 | }, | |
135 | searchdomain => { | |
136 | optional => 1, | |
137 | type => 'string', | |
138 | description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.", | |
139 | }, | |
140 | nameserver => { | |
141 | optional => 1, | |
142 | type => 'string', | |
143 | description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.", | |
144 | }, | |
145 | rootfs => get_standard_option('pve-ct-rootfs'), | |
09d3ec42 DM |
146 | parent => { |
147 | optional => 1, | |
148 | type => 'string', format => 'pve-configid', | |
149 | maxLength => 40, | |
150 | description => "Parent snapshot name. This is used internally, and should not be modified.", | |
151 | }, | |
152 | snaptime => { | |
153 | optional => 1, | |
154 | description => "Timestamp for snapshots.", | |
155 | type => 'integer', | |
156 | minimum => 0, | |
157 | }, | |
f76a2828 DM |
158 | }; |
159 | ||
e576f689 DM |
160 | my $valid_lxc_conf_keys = { |
161 | 'lxc.include' => 1, | |
162 | 'lxc.arch' => 1, | |
163 | 'lxc.utsname' => 1, | |
164 | 'lxc.haltsignal' => 1, | |
165 | 'lxc.rebootsignal' => 1, | |
166 | 'lxc.stopsignal' => 1, | |
167 | 'lxc.init_cmd' => 1, | |
168 | 'lxc.network.type' => 1, | |
169 | 'lxc.network.flags' => 1, | |
170 | 'lxc.network.link' => 1, | |
171 | 'lxc.network.mtu' => 1, | |
172 | 'lxc.network.name' => 1, | |
173 | 'lxc.network.hwaddr' => 1, | |
174 | 'lxc.network.ipv4' => 1, | |
175 | 'lxc.network.ipv4.gateway' => 1, | |
176 | 'lxc.network.ipv6' => 1, | |
177 | 'lxc.network.ipv6.gateway' => 1, | |
178 | 'lxc.network.script.up' => 1, | |
179 | 'lxc.network.script.down' => 1, | |
180 | 'lxc.pts' => 1, | |
181 | 'lxc.console.logfile' => 1, | |
182 | 'lxc.console' => 1, | |
183 | 'lxc.tty' => 1, | |
184 | 'lxc.devttydir' => 1, | |
185 | 'lxc.hook.autodev' => 1, | |
186 | 'lxc.autodev' => 1, | |
187 | 'lxc.kmsg' => 1, | |
188 | 'lxc.mount' => 1, | |
189 | 'lxc.mount.entry' => 1, | |
190 | 'lxc.mount.auto' => 1, | |
191 | 'lxc.rootfs' => 1, | |
192 | 'lxc.rootfs.mount' => 1, | |
193 | 'lxc.rootfs.options' => 1, | |
194 | # lxc.cgroup.* | |
195 | 'lxc.cap.drop' => 1, | |
196 | 'lxc.cap.keep' => 1, | |
197 | 'lxc.aa_profile' => 1, | |
198 | 'lxc.aa_allow_incomplete' => 1, | |
199 | 'lxc.se_context' => 1, | |
200 | 'lxc.seccomp' => 1, | |
201 | 'lxc.id_map' => 1, | |
202 | 'lxc.hook.pre-start' => 1, | |
203 | 'lxc.hook.pre-mount' => 1, | |
204 | 'lxc.hook.mount' => 1, | |
205 | 'lxc.hook.start' => 1, | |
206 | 'lxc.hook.post-stop' => 1, | |
207 | 'lxc.hook.clone' => 1, | |
208 | 'lxc.hook.destroy' => 1, | |
209 | 'lxc.loglevel' => 1, | |
210 | 'lxc.logfile' => 1, | |
211 | 'lxc.start.auto' => 1, | |
212 | 'lxc.start.delay' => 1, | |
213 | 'lxc.start.order' => 1, | |
214 | 'lxc.group' => 1, | |
215 | 'lxc.environment' => 1, | |
216 | 'lxc.' => 1, | |
217 | 'lxc.' => 1, | |
218 | 'lxc.' => 1, | |
219 | 'lxc.' => 1, | |
220 | }; | |
221 | ||
27916659 DM |
222 | my $MAX_LXC_NETWORKS = 10; |
223 | for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) { | |
224 | $confdesc->{"net$i"} = { | |
225 | optional => 1, | |
226 | type => 'string', format => 'pve-lxc-network', | |
227 | description => "Specifies network interfaces for the container.\n\n". | |
228 | "The string should have the follow format:\n\n". | |
229 | "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n". | |
230 | "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n". | |
231 | ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n". | |
232 | ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]", | |
233 | }; | |
90bc31f7 DM |
234 | } |
235 | ||
27916659 DM |
236 | sub write_pct_config { |
237 | my ($filename, $conf) = @_; | |
f76a2828 | 238 | |
27916659 | 239 | delete $conf->{snapstate}; # just to be sure |
f76a2828 | 240 | |
27916659 DM |
241 | my $generate_raw_config = sub { |
242 | my ($conf) = @_; | |
f76a2828 | 243 | |
27916659 | 244 | my $raw = ''; |
cbb03fea | 245 | |
27916659 DM |
246 | # add description as comment to top of file |
247 | my $descr = $conf->{description} || ''; | |
248 | foreach my $cl (split(/\n/, $descr)) { | |
249 | $raw .= '#' . PVE::Tools::encode_text($cl) . "\n"; | |
a12a36e0 | 250 | } |
fff3a342 | 251 | |
27916659 | 252 | foreach my $key (sort keys %$conf) { |
09d3ec42 | 253 | next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || |
e576f689 | 254 | $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc'; |
27916659 | 255 | $raw .= "$key: $conf->{$key}\n"; |
a12a36e0 | 256 | } |
e576f689 DM |
257 | |
258 | if (my $lxcconf = $conf->{lxc}) { | |
259 | foreach my $entry (@$lxcconf) { | |
260 | my ($k, $v) = @$entry; | |
261 | $raw .= "$k: $v\n"; | |
262 | } | |
263 | } | |
264 | ||
27916659 | 265 | return $raw; |
a12a36e0 | 266 | }; |
160f0941 | 267 | |
27916659 | 268 | my $raw = &$generate_raw_config($conf); |
a12a36e0 | 269 | |
27916659 DM |
270 | foreach my $snapname (sort keys %{$conf->{snapshots}}) { |
271 | $raw .= "\n[$snapname]\n"; | |
272 | $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname}); | |
f76a2828 DM |
273 | } |
274 | ||
f76a2828 DM |
275 | return $raw; |
276 | } | |
277 | ||
27916659 DM |
278 | sub check_type { |
279 | my ($key, $value) = @_; | |
822de0c3 | 280 | |
27916659 | 281 | die "unknown setting '$key'\n" if !$confdesc->{$key}; |
822de0c3 | 282 | |
27916659 DM |
283 | my $type = $confdesc->{$key}->{type}; |
284 | ||
285 | if (!defined($value)) { | |
286 | die "got undefined value\n"; | |
287 | } | |
288 | ||
289 | if ($value =~ m/[\n\r]/) { | |
290 | die "property contains a line feed\n"; | |
291 | } | |
822de0c3 | 292 | |
27916659 DM |
293 | if ($type eq 'boolean') { |
294 | return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i); | |
295 | return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i); | |
296 | die "type check ('boolean') failed - got '$value'\n"; | |
297 | } elsif ($type eq 'integer') { | |
298 | return int($1) if $value =~ m/^(\d+)$/; | |
299 | die "type check ('integer') failed - got '$value'\n"; | |
300 | } elsif ($type eq 'number') { | |
301 | return $value if $value =~ m/^(\d+)(\.\d+)?$/; | |
302 | die "type check ('number') failed - got '$value'\n"; | |
303 | } elsif ($type eq 'string') { | |
304 | if (my $fmt = $confdesc->{$key}->{format}) { | |
305 | PVE::JSONSchema::check_format($fmt, $value); | |
306 | return $value; | |
307 | } | |
cbb03fea | 308 | return $value; |
822de0c3 | 309 | } else { |
27916659 | 310 | die "internal error" |
822de0c3 | 311 | } |
822de0c3 DM |
312 | } |
313 | ||
27916659 | 314 | sub parse_pct_config { |
f76a2828 DM |
315 | my ($filename, $raw) = @_; |
316 | ||
317 | return undef if !defined($raw); | |
318 | ||
27916659 | 319 | my $res = { |
f76a2828 | 320 | digest => Digest::SHA::sha1_hex($raw), |
27916659 | 321 | snapshots => {}, |
f76a2828 DM |
322 | }; |
323 | ||
27916659 | 324 | $filename =~ m|/lxc/(\d+).conf$| |
f76a2828 DM |
325 | || die "got strange filename '$filename'"; |
326 | ||
327 | my $vmid = $1; | |
328 | ||
27916659 DM |
329 | my $conf = $res; |
330 | my $descr = ''; | |
331 | my $section = ''; | |
332 | ||
333 | my @lines = split(/\n/, $raw); | |
334 | foreach my $line (@lines) { | |
335 | next if $line =~ m/^\s*$/; | |
336 | ||
337 | if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) { | |
338 | $section = $1; | |
339 | $conf->{description} = $descr if $descr; | |
340 | $descr = ''; | |
341 | $conf = $res->{snapshots}->{$section} = {}; | |
342 | next; | |
a12a36e0 | 343 | } |
a12a36e0 | 344 | |
27916659 DM |
345 | if ($line =~ m/^\#(.*)\s*$/) { |
346 | $descr .= PVE::Tools::decode_text($1) . "\n"; | |
347 | next; | |
f76a2828 | 348 | } |
5d186e16 | 349 | |
b43a097e | 350 | if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) { |
e576f689 DM |
351 | my $key = $1; |
352 | my $value = $3; | |
353 | if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) { | |
354 | push @{$conf->{lxc}}, [$key, $value]; | |
355 | } else { | |
356 | warn "vm $vmid - unable to parse config: $line\n"; | |
357 | } | |
358 | } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) { | |
27916659 DM |
359 | $descr .= PVE::Tools::decode_text($2); |
360 | } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) { | |
361 | $conf->{snapstate} = $1; | |
362 | } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) { | |
363 | my $key = $1; | |
5d186e16 | 364 | my $value = $2; |
27916659 DM |
365 | eval { $value = check_type($key, $value); }; |
366 | warn "vm $vmid - unable to parse value of '$key' - $@" if $@; | |
367 | $conf->{$key} = $value; | |
5d186e16 | 368 | } else { |
27916659 | 369 | warn "vm $vmid - unable to parse config: $line\n"; |
5d186e16 | 370 | } |
7dfc49cc DM |
371 | } |
372 | ||
27916659 | 373 | $conf->{description} = $descr if $descr; |
5d186e16 | 374 | |
27916659 DM |
375 | delete $res->{snapstate}; # just to be sure |
376 | ||
377 | return $res; | |
f76a2828 DM |
378 | } |
379 | ||
380 | sub config_list { | |
381 | my $vmlist = PVE::Cluster::get_vmlist(); | |
382 | my $res = {}; | |
383 | return $res if !$vmlist || !$vmlist->{ids}; | |
384 | my $ids = $vmlist->{ids}; | |
385 | ||
386 | foreach my $vmid (keys %$ids) { | |
387 | next if !$vmid; # skip CT0 | |
388 | my $d = $ids->{$vmid}; | |
389 | next if !$d->{node} || $d->{node} ne $nodename; | |
390 | next if !$d->{type} || $d->{type} ne 'lxc'; | |
391 | $res->{$vmid}->{type} = 'lxc'; | |
392 | } | |
393 | return $res; | |
394 | } | |
395 | ||
396 | sub cfs_config_path { | |
397 | my ($vmid, $node) = @_; | |
398 | ||
399 | $node = $nodename if !$node; | |
27916659 | 400 | return "nodes/$node/lxc/$vmid.conf"; |
f76a2828 DM |
401 | } |
402 | ||
9c2d4ce9 DM |
403 | sub config_file { |
404 | my ($vmid, $node) = @_; | |
405 | ||
406 | my $cfspath = cfs_config_path($vmid, $node); | |
407 | return "/etc/pve/$cfspath"; | |
408 | } | |
409 | ||
f76a2828 DM |
410 | sub load_config { |
411 | my ($vmid) = @_; | |
412 | ||
413 | my $cfspath = cfs_config_path($vmid); | |
414 | ||
415 | my $conf = PVE::Cluster::cfs_read_file($cfspath); | |
416 | die "container $vmid does not exists\n" if !defined($conf); | |
417 | ||
418 | return $conf; | |
419 | } | |
420 | ||
5b4657d0 DM |
421 | sub create_config { |
422 | my ($vmid, $conf) = @_; | |
423 | ||
424 | my $dir = "/etc/pve/nodes/$nodename/lxc"; | |
425 | mkdir $dir; | |
426 | ||
5b4657d0 DM |
427 | write_config($vmid, $conf); |
428 | } | |
429 | ||
430 | sub destroy_config { | |
431 | my ($vmid) = @_; | |
432 | ||
27916659 | 433 | unlink config_file($vmid, $nodename); |
5b4657d0 DM |
434 | } |
435 | ||
f76a2828 DM |
436 | sub write_config { |
437 | my ($vmid, $conf) = @_; | |
438 | ||
439 | my $cfspath = cfs_config_path($vmid); | |
440 | ||
441 | PVE::Cluster::cfs_write_file($cfspath, $conf); | |
442 | } | |
443 | ||
d14a9a1b DM |
444 | # flock: we use one file handle per process, so lock file |
445 | # can be called multiple times and succeeds for the same process. | |
446 | ||
447 | my $lock_handles = {}; | |
448 | my $lockdir = "/run/lock/lxc"; | |
449 | ||
450 | sub lock_filename { | |
451 | my ($vmid) = @_; | |
cbb03fea | 452 | |
d14a9a1b DM |
453 | return "$lockdir/pve-config-{$vmid}.lock"; |
454 | } | |
455 | ||
456 | sub lock_aquire { | |
457 | my ($vmid, $timeout) = @_; | |
458 | ||
459 | $timeout = 10 if !$timeout; | |
460 | my $mode = LOCK_EX; | |
461 | ||
462 | my $filename = lock_filename($vmid); | |
463 | ||
f99e8278 AD |
464 | mkdir $lockdir if !-d $lockdir; |
465 | ||
d14a9a1b DM |
466 | my $lock_func = sub { |
467 | if (!$lock_handles->{$$}->{$filename}) { | |
468 | my $fh = new IO::File(">>$filename") || | |
469 | die "can't open file - $!\n"; | |
470 | $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0}; | |
471 | } | |
472 | ||
473 | if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) { | |
474 | print STDERR "trying to aquire lock..."; | |
475 | my $success; | |
476 | while(1) { | |
477 | $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode); | |
478 | # try again on EINTR (see bug #273) | |
479 | if ($success || ($! != EINTR)) { | |
480 | last; | |
481 | } | |
482 | } | |
483 | if (!$success) { | |
484 | print STDERR " failed\n"; | |
485 | die "can't aquire lock - $!\n"; | |
486 | } | |
487 | ||
488 | $lock_handles->{$$}->{$filename}->{refcount}++; | |
cbb03fea | 489 | |
d14a9a1b DM |
490 | print STDERR " OK\n"; |
491 | } | |
492 | }; | |
493 | ||
494 | eval { PVE::Tools::run_with_timeout($timeout, $lock_func); }; | |
495 | my $err = $@; | |
496 | if ($err) { | |
497 | die "can't lock file '$filename' - $err"; | |
cbb03fea | 498 | } |
d14a9a1b DM |
499 | } |
500 | ||
501 | sub lock_release { | |
502 | my ($vmid) = @_; | |
503 | ||
504 | my $filename = lock_filename($vmid); | |
505 | ||
506 | if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) { | |
507 | my $refcount = --$lock_handles->{$$}->{$filename}->{refcount}; | |
508 | if ($refcount <= 0) { | |
509 | $lock_handles->{$$}->{$filename} = undef; | |
510 | close ($fh); | |
511 | } | |
512 | } | |
513 | } | |
514 | ||
f76a2828 DM |
515 | sub lock_container { |
516 | my ($vmid, $timeout, $code, @param) = @_; | |
517 | ||
d14a9a1b | 518 | my $res; |
f76a2828 | 519 | |
d14a9a1b DM |
520 | lock_aquire($vmid, $timeout); |
521 | eval { $res = &$code(@param) }; | |
522 | my $err = $@; | |
523 | lock_release($vmid); | |
f76a2828 | 524 | |
d14a9a1b | 525 | die $err if $err; |
f76a2828 DM |
526 | |
527 | return $res; | |
528 | } | |
529 | ||
ec52ac21 DM |
530 | sub option_exists { |
531 | my ($name) = @_; | |
532 | ||
533 | return defined($confdesc->{$name}); | |
534 | } | |
f76a2828 DM |
535 | |
536 | # add JSON properties for create and set function | |
537 | sub json_config_properties { | |
538 | my $prop = shift; | |
539 | ||
540 | foreach my $opt (keys %$confdesc) { | |
09d3ec42 | 541 | next if $opt eq 'parent' || $opt eq 'snaptime'; |
27916659 DM |
542 | next if $prop->{$opt}; |
543 | $prop->{$opt} = $confdesc->{$opt}; | |
544 | } | |
545 | ||
546 | return $prop; | |
547 | } | |
548 | ||
549 | sub json_config_properties_no_rootfs { | |
550 | my $prop = shift; | |
551 | ||
552 | foreach my $opt (keys %$confdesc) { | |
553 | next if $prop->{$opt}; | |
09d3ec42 | 554 | next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs'; |
f76a2828 DM |
555 | $prop->{$opt} = $confdesc->{$opt}; |
556 | } | |
557 | ||
558 | return $prop; | |
559 | } | |
560 | ||
822de0c3 DM |
561 | # container status helpers |
562 | ||
563 | sub list_active_containers { | |
cbb03fea | 564 | |
822de0c3 DM |
565 | my $filename = "/proc/net/unix"; |
566 | ||
567 | # similar test is used by lcxcontainers.c: list_active_containers | |
568 | my $res = {}; | |
cbb03fea | 569 | |
822de0c3 DM |
570 | my $fh = IO::File->new ($filename, "r"); |
571 | return $res if !$fh; | |
572 | ||
573 | while (defined(my $line = <$fh>)) { | |
574 | if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) { | |
575 | my $path = $1; | |
27916659 | 576 | if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) { |
822de0c3 DM |
577 | $res->{$1} = 1; |
578 | } | |
579 | } | |
580 | } | |
581 | ||
582 | close($fh); | |
cbb03fea | 583 | |
822de0c3 DM |
584 | return $res; |
585 | } | |
f76a2828 | 586 | |
5c752bbf DM |
587 | # warning: this is slow |
588 | sub check_running { | |
589 | my ($vmid) = @_; | |
590 | ||
591 | my $active_hash = list_active_containers(); | |
592 | ||
593 | return 1 if defined($active_hash->{$vmid}); | |
cbb03fea | 594 | |
5c752bbf DM |
595 | return undef; |
596 | } | |
597 | ||
10fc3ba5 DM |
598 | sub get_container_disk_usage { |
599 | my ($vmid) = @_; | |
600 | ||
601 | my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/']; | |
cbb03fea | 602 | |
10fc3ba5 DM |
603 | my $res = { |
604 | total => 0, | |
605 | used => 0, | |
606 | avail => 0, | |
607 | }; | |
608 | ||
609 | my $parser = sub { | |
610 | my $line = shift; | |
611 | if (my ($fsid, $total, $used, $avail) = $line =~ | |
612 | m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) { | |
613 | $res = { | |
614 | total => $total, | |
615 | used => $used, | |
616 | avail => $avail, | |
617 | }; | |
618 | } | |
619 | }; | |
620 | eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); }; | |
621 | warn $@ if $@; | |
622 | ||
623 | return $res; | |
624 | } | |
625 | ||
f76a2828 DM |
626 | sub vmstatus { |
627 | my ($opt_vmid) = @_; | |
628 | ||
629 | my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list(); | |
630 | ||
822de0c3 | 631 | my $active_hash = list_active_containers(); |
cbb03fea | 632 | |
f76a2828 | 633 | foreach my $vmid (keys %$list) { |
f76a2828 | 634 | my $d = $list->{$vmid}; |
10fc3ba5 DM |
635 | |
636 | my $running = defined($active_hash->{$vmid}); | |
cbb03fea | 637 | |
10fc3ba5 | 638 | $d->{status} = $running ? 'running' : 'stopped'; |
f76a2828 DM |
639 | |
640 | my $cfspath = cfs_config_path($vmid); | |
238a56cb | 641 | my $conf = PVE::Cluster::cfs_read_file($cfspath) || {}; |
cbb03fea | 642 | |
27916659 | 643 | $d->{name} = $conf->{'hostname'} || "CT$vmid"; |
238a56cb | 644 | $d->{name} =~ s/[\s]//g; |
cbb03fea | 645 | |
27916659 | 646 | $d->{cpus} = $conf->{cpulimit} // 0; |
44da0641 | 647 | |
27916659 DM |
648 | if ($running) { |
649 | my $res = get_container_disk_usage($vmid); | |
650 | $d->{disk} = $res->{used}; | |
651 | $d->{maxdisk} = $res->{total}; | |
652 | } else { | |
653 | $d->{disk} = 0; | |
654 | # use 4GB by default ?? | |
655 | if (my $rootfs = $conf->{rootfs}) { | |
656 | my $rootinfo = parse_ct_mountpoint($rootfs); | |
657 | $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024; | |
658 | } else { | |
659 | $d->{maxdisk} = 4*1024*1024*1024; | |
10fc3ba5 | 660 | } |
238a56cb | 661 | } |
cbb03fea | 662 | |
238a56cb DM |
663 | $d->{mem} = 0; |
664 | $d->{swap} = 0; | |
95df9a12 DM |
665 | $d->{maxmem} = ($conf->{memory}||512)*1024*1024; |
666 | $d->{maxswap} = ($conf->{swap}//0)*1024*1024; | |
e901d418 | 667 | |
238a56cb DM |
668 | $d->{uptime} = 0; |
669 | $d->{cpu} = 0; | |
e901d418 | 670 | |
238a56cb DM |
671 | $d->{netout} = 0; |
672 | $d->{netin} = 0; | |
f76a2828 | 673 | |
238a56cb DM |
674 | $d->{diskread} = 0; |
675 | $d->{diskwrite} = 0; | |
bb1ac2de DM |
676 | |
677 | $d->{template} = is_template($conf); | |
f76a2828 | 678 | } |
cbb03fea | 679 | |
238a56cb DM |
680 | foreach my $vmid (keys %$list) { |
681 | my $d = $list->{$vmid}; | |
682 | next if $d->{status} ne 'running'; | |
f76a2828 | 683 | |
22a77285 DM |
684 | $d->{uptime} = 100; # fixme: |
685 | ||
238a56cb DM |
686 | $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes'); |
687 | $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem}; | |
b5289322 AD |
688 | |
689 | my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1); | |
1e647c7c | 690 | my @bytes = split(/\n/, $blkio_bytes); |
b5289322 | 691 | foreach my $byte (@bytes) { |
1e647c7c DM |
692 | if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) { |
693 | $d->{diskread} = $2 if $key eq 'Read'; | |
694 | $d->{diskwrite} = $2 if $key eq 'Write'; | |
695 | } | |
b5289322 | 696 | } |
238a56cb | 697 | } |
cbb03fea | 698 | |
f76a2828 DM |
699 | return $list; |
700 | } | |
701 | ||
27916659 DM |
702 | my $parse_size = sub { |
703 | my ($value) = @_; | |
704 | ||
705 | return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/; | |
706 | my ($size, $unit) = ($1, $3); | |
707 | if ($unit) { | |
708 | if ($unit eq 'K') { | |
709 | $size = $size * 1024; | |
710 | } elsif ($unit eq 'M') { | |
711 | $size = $size * 1024 * 1024; | |
712 | } elsif ($unit eq 'G') { | |
713 | $size = $size * 1024 * 1024 * 1024; | |
714 | } | |
715 | } | |
716 | return int($size); | |
717 | }; | |
718 | ||
719 | sub parse_ct_mountpoint { | |
720 | my ($data) = @_; | |
721 | ||
722 | $data //= ''; | |
723 | ||
724 | my $res = {}; | |
725 | ||
726 | foreach my $p (split (/,/, $data)) { | |
727 | next if $p =~ m/^\s*$/; | |
728 | ||
729 | if ($p =~ m/^(volume|backup|size)=(.+)$/) { | |
730 | my ($k, $v) = ($1, $2); | |
731 | return undef if defined($res->{$k}); | |
dada5f33 | 732 | $res->{$k} = $v; |
27916659 DM |
733 | } else { |
734 | if (!$res->{volume} && $p !~ m/=/) { | |
735 | $res->{volume} = $p; | |
736 | } else { | |
737 | return undef; | |
738 | } | |
739 | } | |
740 | } | |
741 | ||
742 | return undef if !$res->{volume}; | |
743 | ||
744 | return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/; | |
745 | ||
746 | if ($res->{size}) { | |
747 | return undef if !defined($res->{size} = &$parse_size($res->{size})); | |
748 | } | |
749 | ||
750 | return $res; | |
751 | } | |
7dfc49cc | 752 | |
dde7b02b | 753 | sub print_ct_mountpoint { |
bb1ac2de DM |
754 | my ($info) = @_; |
755 | ||
756 | my $opts = ''; | |
757 | ||
758 | die "missing volume\n" if !$info->{volume}; | |
759 | ||
760 | foreach my $o ('size', 'backup') { | |
7092c9f1 | 761 | $opts .= ",$o=$info->{$o}" if defined($info->{$o}); |
bb1ac2de DM |
762 | } |
763 | ||
764 | return "$info->{volume}$opts"; | |
765 | } | |
766 | ||
7dfc49cc | 767 | sub print_lxc_network { |
f76a2828 DM |
768 | my $net = shift; |
769 | ||
bedeaaf1 | 770 | die "no network name defined\n" if !$net->{name}; |
f76a2828 | 771 | |
bedeaaf1 | 772 | my $res = "name=$net->{name}"; |
7dfc49cc | 773 | |
bedeaaf1 | 774 | foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) { |
f76a2828 DM |
775 | next if !defined($net->{$k}); |
776 | $res .= ",$k=$net->{$k}"; | |
777 | } | |
7dfc49cc | 778 | |
f76a2828 DM |
779 | return $res; |
780 | } | |
781 | ||
7dfc49cc DM |
782 | sub parse_lxc_network { |
783 | my ($data) = @_; | |
784 | ||
785 | my $res = {}; | |
786 | ||
787 | return $res if !$data; | |
788 | ||
789 | foreach my $pv (split (/,/, $data)) { | |
2b1fc2ea | 790 | if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) { |
7dfc49cc DM |
791 | $res->{$1} = $2; |
792 | } else { | |
793 | return undef; | |
794 | } | |
795 | } | |
796 | ||
797 | $res->{type} = 'veth'; | |
93cdbbfb | 798 | $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr}; |
cbb03fea | 799 | |
7dfc49cc DM |
800 | return $res; |
801 | } | |
f76a2828 | 802 | |
238a56cb DM |
803 | sub read_cgroup_value { |
804 | my ($group, $vmid, $name, $full) = @_; | |
805 | ||
806 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
807 | ||
808 | return PVE::Tools::file_get_contents($path) if $full; | |
809 | ||
810 | return PVE::Tools::file_read_firstline($path); | |
811 | } | |
812 | ||
bf0b8c43 AD |
813 | sub write_cgroup_value { |
814 | my ($group, $vmid, $name, $value) = @_; | |
815 | ||
816 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
817 | PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path; | |
818 | ||
819 | } | |
820 | ||
52f1d76b DM |
821 | sub find_lxc_console_pids { |
822 | ||
823 | my $res = {}; | |
824 | ||
825 | PVE::Tools::dir_glob_foreach('/proc', '\d+', sub { | |
826 | my ($pid) = @_; | |
827 | ||
828 | my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline"); | |
829 | return if !$cmdline; | |
830 | ||
831 | my @args = split(/\0/, $cmdline); | |
832 | ||
833 | # serach for lxc-console -n <vmid> | |
cbb03fea | 834 | return if scalar(@args) != 3; |
52f1d76b DM |
835 | return if $args[1] ne '-n'; |
836 | return if $args[2] !~ m/^\d+$/; | |
837 | return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|; | |
cbb03fea | 838 | |
52f1d76b | 839 | my $vmid = $args[2]; |
cbb03fea | 840 | |
52f1d76b DM |
841 | push @{$res->{$vmid}}, $pid; |
842 | }); | |
843 | ||
844 | return $res; | |
845 | } | |
846 | ||
bedeaaf1 AD |
847 | sub find_lxc_pid { |
848 | my ($vmid) = @_; | |
849 | ||
850 | my $pid = undef; | |
851 | my $parser = sub { | |
852 | my $line = shift; | |
8b25977f | 853 | $pid = $1 if $line =~ m/^PID:\s+(\d+)$/; |
bedeaaf1 AD |
854 | }; |
855 | PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser); | |
856 | ||
8b25977f | 857 | die "unable to get PID for CT $vmid (not running?)\n" if !$pid; |
cbb03fea | 858 | |
8b25977f | 859 | return $pid; |
bedeaaf1 AD |
860 | } |
861 | ||
55fa4e09 DM |
862 | my $ipv4_reverse_mask = [ |
863 | '0.0.0.0', | |
864 | '128.0.0.0', | |
865 | '192.0.0.0', | |
866 | '224.0.0.0', | |
867 | '240.0.0.0', | |
868 | '248.0.0.0', | |
869 | '252.0.0.0', | |
870 | '254.0.0.0', | |
871 | '255.0.0.0', | |
872 | '255.128.0.0', | |
873 | '255.192.0.0', | |
874 | '255.224.0.0', | |
875 | '255.240.0.0', | |
876 | '255.248.0.0', | |
877 | '255.252.0.0', | |
878 | '255.254.0.0', | |
879 | '255.255.0.0', | |
880 | '255.255.128.0', | |
881 | '255.255.192.0', | |
882 | '255.255.224.0', | |
883 | '255.255.240.0', | |
884 | '255.255.248.0', | |
885 | '255.255.252.0', | |
886 | '255.255.254.0', | |
887 | '255.255.255.0', | |
888 | '255.255.255.128', | |
889 | '255.255.255.192', | |
890 | '255.255.255.224', | |
891 | '255.255.255.240', | |
892 | '255.255.255.248', | |
893 | '255.255.255.252', | |
894 | '255.255.255.254', | |
895 | '255.255.255.255', | |
896 | ]; | |
cbb03fea DM |
897 | |
898 | # Note: we cannot use Net:IP, because that only allows strict | |
55fa4e09 DM |
899 | # CIDR networks |
900 | sub parse_ipv4_cidr { | |
901 | my ($cidr, $noerr) = @_; | |
902 | ||
903 | if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) { | |
904 | return { address => $1, netmask => $ipv4_reverse_mask->[$2] }; | |
905 | } | |
cbb03fea | 906 | |
55fa4e09 | 907 | return undef if $noerr; |
cbb03fea | 908 | |
55fa4e09 DM |
909 | die "unable to parse ipv4 address/mask\n"; |
910 | } | |
93285df8 | 911 | |
a12a36e0 WL |
912 | sub check_lock { |
913 | my ($conf) = @_; | |
914 | ||
27916659 | 915 | die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'}; |
a12a36e0 WL |
916 | } |
917 | ||
27916659 | 918 | sub update_lxc_config { |
c628ffa1 | 919 | my ($storage_cfg, $vmid, $conf) = @_; |
b80dd50a | 920 | |
bb1ac2de DM |
921 | my $dir = "/var/lib/lxc/$vmid"; |
922 | ||
923 | if ($conf->{template}) { | |
924 | ||
925 | unlink "$dir/config"; | |
926 | ||
927 | return; | |
928 | } | |
929 | ||
27916659 | 930 | my $raw = ''; |
b80dd50a | 931 | |
27916659 DM |
932 | die "missing 'arch' - internal error" if !$conf->{arch}; |
933 | $raw .= "lxc.arch = $conf->{arch}\n"; | |
b80dd50a | 934 | |
27916659 DM |
935 | my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error"; |
936 | if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') { | |
937 | $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n"; | |
938 | } else { | |
939 | die "implement me"; | |
940 | } | |
b80dd50a | 941 | |
27916659 DM |
942 | my $ttycount = $conf->{tty} // 4; |
943 | $raw .= "lxc.tty = $ttycount\n"; | |
cbb03fea | 944 | |
27916659 DM |
945 | my $utsname = $conf->{hostname} || "CT$vmid"; |
946 | $raw .= "lxc.utsname = $utsname\n"; | |
cbb03fea | 947 | |
27916659 DM |
948 | my $memory = $conf->{memory} || 512; |
949 | my $swap = $conf->{swap} // 0; | |
950 | ||
951 | my $lxcmem = int($memory*1024*1024); | |
952 | $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n"; | |
a12a36e0 | 953 | |
27916659 DM |
954 | my $lxcswap = int(($memory + $swap)*1024*1024); |
955 | $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n"; | |
956 | ||
957 | if (my $cpulimit = $conf->{cpulimit}) { | |
958 | $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n"; | |
959 | my $value = int(100000*$cpulimit); | |
960 | $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n"; | |
a12a36e0 WL |
961 | } |
962 | ||
27916659 DM |
963 | my $shares = $conf->{cpuunits} || 1024; |
964 | $raw .= "lxc.cgroup.cpu.shares = $shares\n"; | |
965 | ||
966 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs}); | |
c628ffa1 DM |
967 | my $volid = $rootinfo->{volume}; |
968 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid); | |
969 | ||
970 | my $scfg = PVE::Storage::storage_config($storage_cfg, $storage); | |
971 | if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { | |
972 | my $rootfs = PVE::Storage::path($storage_cfg, $volid); | |
973 | $raw .= "lxc.rootfs = loop:$rootfs\n"; | |
974 | } elsif ($scfg->{type} eq 'zfspool') { | |
975 | my $rootfs = PVE::Storage::path($storage_cfg, $volid); | |
976 | $raw .= "lxc.rootfs = $rootfs\n"; | |
63f2ddfb | 977 | } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') { |
644f2f8f DM |
978 | my $rootdev = PVE::Storage::path($storage_cfg, $volid); |
979 | $raw .= "lxc.rootfs = $rootdev\n"; | |
980 | } else { | |
981 | die "unsupported storage type '$scfg->{type}'\n"; | |
c628ffa1 | 982 | } |
27916659 DM |
983 | |
984 | my $netcount = 0; | |
985 | foreach my $k (keys %$conf) { | |
986 | next if $k !~ m/^net(\d+)$/; | |
987 | my $ind = $1; | |
a16d94c8 | 988 | my $d = parse_lxc_network($conf->{$k}); |
27916659 DM |
989 | $netcount++; |
990 | $raw .= "lxc.network.type = veth\n"; | |
18862537 | 991 | $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n"; |
27916659 DM |
992 | $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr}); |
993 | $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name}); | |
994 | $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu}); | |
a12a36e0 WL |
995 | } |
996 | ||
e576f689 DM |
997 | if (my $lxcconf = $conf->{lxc}) { |
998 | foreach my $entry (@$lxcconf) { | |
999 | my ($k, $v) = @$entry; | |
1000 | $netcount++ if $k eq 'lxc.network.type'; | |
1001 | $raw .= "$k = $v\n"; | |
1002 | } | |
1003 | } | |
27916659 | 1004 | |
e576f689 DM |
1005 | $raw .= "lxc.network.type = empty\n" if !$netcount; |
1006 | ||
27916659 DM |
1007 | File::Path::mkpath("$dir/rootfs"); |
1008 | ||
1009 | PVE::Tools::file_set_contents("$dir/config", $raw); | |
b80dd50a DM |
1010 | } |
1011 | ||
117636e5 DM |
1012 | # verify and cleanup nameserver list (replace \0 with ' ') |
1013 | sub verify_nameserver_list { | |
1014 | my ($nameserver_list) = @_; | |
1015 | ||
1016 | my @list = (); | |
1017 | foreach my $server (PVE::Tools::split_list($nameserver_list)) { | |
1018 | PVE::JSONSchema::pve_verify_ip($server); | |
1019 | push @list, $server; | |
1020 | } | |
1021 | ||
1022 | return join(' ', @list); | |
1023 | } | |
1024 | ||
1025 | sub verify_searchdomain_list { | |
1026 | my ($searchdomain_list) = @_; | |
1027 | ||
1028 | my @list = (); | |
1029 | foreach my $server (PVE::Tools::split_list($searchdomain_list)) { | |
1030 | # todo: should we add checks for valid dns domains? | |
1031 | push @list, $server; | |
1032 | } | |
1033 | ||
1034 | return join(' ', @list); | |
1035 | } | |
1036 | ||
27916659 | 1037 | sub update_pct_config { |
93285df8 DM |
1038 | my ($vmid, $conf, $running, $param, $delete) = @_; |
1039 | ||
bf0b8c43 AD |
1040 | my @nohotplug; |
1041 | ||
cbb03fea DM |
1042 | my $rootdir; |
1043 | if ($running) { | |
bedeaaf1 | 1044 | my $pid = find_lxc_pid($vmid); |
cbb03fea | 1045 | $rootdir = "/proc/$pid/root"; |
bedeaaf1 AD |
1046 | } |
1047 | ||
93285df8 DM |
1048 | if (defined($delete)) { |
1049 | foreach my $opt (@$delete) { | |
27916659 | 1050 | if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') { |
93285df8 DM |
1051 | die "unable to delete required option '$opt'\n"; |
1052 | } elsif ($opt eq 'swap') { | |
27916659 | 1053 | delete $conf->{$opt}; |
bf0b8c43 | 1054 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1); |
27916659 DM |
1055 | } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') { |
1056 | delete $conf->{$opt}; | |
1057 | } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain') { | |
1058 | delete $conf->{$opt}; | |
bf0b8c43 AD |
1059 | push @nohotplug, $opt; |
1060 | next if $running; | |
68fba17b | 1061 | } elsif ($opt =~ m/^net(\d)$/) { |
93285df8 | 1062 | delete $conf->{$opt}; |
68fba17b AD |
1063 | next if !$running; |
1064 | my $netid = $1; | |
18862537 | 1065 | PVE::Network::veth_delete("veth${vmid}i$netid"); |
93285df8 DM |
1066 | } else { |
1067 | die "implement me" | |
1068 | } | |
bf0b8c43 | 1069 | PVE::LXC::write_config($vmid, $conf) if $running; |
93285df8 DM |
1070 | } |
1071 | } | |
1072 | ||
be6383d7 WB |
1073 | # There's no separate swap size to configure, there's memory and "total" |
1074 | # memory (iow. memory+swap). This means we have to change them together. | |
27916659 DM |
1075 | my $wanted_memory = PVE::Tools::extract_param($param, 'memory'); |
1076 | my $wanted_swap = PVE::Tools::extract_param($param, 'swap'); | |
be6383d7 | 1077 | if (defined($wanted_memory) || defined($wanted_swap)) { |
27916659 DM |
1078 | |
1079 | $wanted_memory //= ($conf->{memory} || 512); | |
1080 | $wanted_swap //= ($conf->{swap} || 0); | |
1081 | ||
1082 | my $total = $wanted_memory + $wanted_swap; | |
1083 | if ($running) { | |
1084 | write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024)); | |
1085 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024)); | |
be6383d7 | 1086 | } |
27916659 DM |
1087 | $conf->{memory} = $wanted_memory; |
1088 | $conf->{swap} = $wanted_swap; | |
1089 | ||
1090 | PVE::LXC::write_config($vmid, $conf) if $running; | |
be6383d7 WB |
1091 | } |
1092 | ||
93285df8 DM |
1093 | foreach my $opt (keys %$param) { |
1094 | my $value = $param->{$opt}; | |
1095 | if ($opt eq 'hostname') { | |
27916659 | 1096 | $conf->{$opt} = $value; |
a99b3509 | 1097 | } elsif ($opt eq 'onboot') { |
27916659 | 1098 | $conf->{$opt} = $value ? 1 : 0; |
a3249355 | 1099 | } elsif ($opt eq 'startup') { |
27916659 | 1100 | $conf->{$opt} = $value; |
e576f689 DM |
1101 | } elsif ($opt eq 'tty') { |
1102 | $conf->{$opt} = $value; | |
1103 | push @nohotplug, $opt; | |
1104 | next if $running; | |
ffa1d001 | 1105 | } elsif ($opt eq 'nameserver') { |
117636e5 | 1106 | my $list = verify_nameserver_list($value); |
27916659 | 1107 | $conf->{$opt} = $list; |
bf0b8c43 AD |
1108 | push @nohotplug, $opt; |
1109 | next if $running; | |
ffa1d001 | 1110 | } elsif ($opt eq 'searchdomain') { |
117636e5 | 1111 | my $list = verify_searchdomain_list($value); |
27916659 | 1112 | $conf->{$opt} = $list; |
bf0b8c43 AD |
1113 | push @nohotplug, $opt; |
1114 | next if $running; | |
45573f7c | 1115 | } elsif ($opt eq 'cpulimit') { |
27916659 DM |
1116 | $conf->{$opt} = $value; |
1117 | push @nohotplug, $opt; # fixme: hotplug | |
1118 | next; | |
b80dd50a | 1119 | } elsif ($opt eq 'cpuunits') { |
27916659 | 1120 | $conf->{$opt} = $value; |
bf0b8c43 | 1121 | write_cgroup_value("cpu", $vmid, "cpu.shares", $value); |
93285df8 | 1122 | } elsif ($opt eq 'description') { |
27916659 | 1123 | $conf->{$opt} = PVE::Tools::encode_text($value); |
93285df8 DM |
1124 | } elsif ($opt =~ m/^net(\d+)$/) { |
1125 | my $netid = $1; | |
a16d94c8 | 1126 | my $net = parse_lxc_network($value); |
27916659 DM |
1127 | if (!$running) { |
1128 | $conf->{$opt} = print_lxc_network($net); | |
cbb03fea | 1129 | } else { |
bedeaaf1 AD |
1130 | update_net($vmid, $conf, $opt, $net, $netid, $rootdir); |
1131 | } | |
93285df8 | 1132 | } else { |
a92f66c9 | 1133 | die "implement me: $opt"; |
93285df8 | 1134 | } |
bf0b8c43 | 1135 | PVE::LXC::write_config($vmid, $conf) if $running; |
93285df8 | 1136 | } |
bf0b8c43 | 1137 | |
5cfa0567 DM |
1138 | if ($running && scalar(@nohotplug)) { |
1139 | die "unable to modify " . join(',', @nohotplug) . " while container is running\n"; | |
1140 | } | |
93285df8 | 1141 | } |
c325b32f DM |
1142 | |
1143 | sub get_primary_ips { | |
1144 | my ($conf) = @_; | |
1145 | ||
1146 | # return data from net0 | |
cbb03fea | 1147 | |
27916659 | 1148 | return undef if !defined($conf->{net0}); |
a16d94c8 | 1149 | my $net = parse_lxc_network($conf->{net0}); |
c325b32f DM |
1150 | |
1151 | my $ipv4 = $net->{ip}; | |
db78a181 WB |
1152 | if ($ipv4) { |
1153 | if ($ipv4 =~ /^(dhcp|manual)$/) { | |
1154 | $ipv4 = undef | |
1155 | } else { | |
1156 | $ipv4 =~ s!/\d+$!!; | |
1157 | } | |
1158 | } | |
65e5eaa3 | 1159 | my $ipv6 = $net->{ip6}; |
db78a181 WB |
1160 | if ($ipv6) { |
1161 | if ($ipv6 =~ /^(dhcp|manual)$/) { | |
1162 | $ipv6 = undef; | |
1163 | } else { | |
1164 | $ipv6 =~ s!/\d+$!!; | |
1165 | } | |
1166 | } | |
cbb03fea | 1167 | |
c325b32f DM |
1168 | return ($ipv4, $ipv6); |
1169 | } | |
148d1cb4 | 1170 | |
ef241384 | 1171 | |
27916659 | 1172 | sub destroy_lxc_container { |
148d1cb4 DM |
1173 | my ($storage_cfg, $vmid, $conf) = @_; |
1174 | ||
27916659 DM |
1175 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs}); |
1176 | if (defined($rootinfo->{volume})) { | |
1177 | my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume}); | |
1178 | PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;; | |
148d1cb4 | 1179 | } |
27916659 DM |
1180 | rmdir "/var/lib/lxc/$vmid/rootfs"; |
1181 | unlink "/var/lib/lxc/$vmid/config"; | |
1182 | rmdir "/var/lib/lxc/$vmid"; | |
1183 | destroy_config($vmid); | |
1184 | ||
1185 | #my $cmd = ['lxc-destroy', '-n', $vmid ]; | |
1186 | #PVE::Tools::run_command($cmd); | |
148d1cb4 | 1187 | } |
68fba17b | 1188 | |
ef241384 DM |
1189 | sub vm_stop_cleanup { |
1190 | my ($storeage_cfg, $vmid, $conf, $keepActive) = @_; | |
1191 | ||
1192 | eval { | |
1193 | if (!$keepActive) { | |
1194 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs}); | |
1195 | PVE::Storage::deactivate_volumes($storeage_cfg, [$rootinfo->{volume}]); | |
1196 | } | |
1197 | }; | |
1198 | warn $@ if $@; # avoid errors - just warn | |
1199 | } | |
1200 | ||
93cdbbfb AD |
1201 | my $safe_num_ne = sub { |
1202 | my ($a, $b) = @_; | |
1203 | ||
1204 | return 0 if !defined($a) && !defined($b); | |
1205 | return 1 if !defined($a); | |
1206 | return 1 if !defined($b); | |
1207 | ||
1208 | return $a != $b; | |
1209 | }; | |
1210 | ||
1211 | my $safe_string_ne = sub { | |
1212 | my ($a, $b) = @_; | |
1213 | ||
1214 | return 0 if !defined($a) && !defined($b); | |
1215 | return 1 if !defined($a); | |
1216 | return 1 if !defined($b); | |
1217 | ||
1218 | return $a ne $b; | |
1219 | }; | |
1220 | ||
1221 | sub update_net { | |
bedeaaf1 | 1222 | my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_; |
93cdbbfb | 1223 | |
18862537 WB |
1224 | if ($newnet->{type} ne 'veth') { |
1225 | # for when there are physical interfaces | |
1226 | die "cannot update interface of type $newnet->{type}"; | |
1227 | } | |
1228 | ||
1229 | my $veth = "veth${vmid}i${netid}"; | |
93cdbbfb AD |
1230 | my $eth = $newnet->{name}; |
1231 | ||
18862537 WB |
1232 | if (my $oldnetcfg = $conf->{$opt}) { |
1233 | my $oldnet = parse_lxc_network($oldnetcfg); | |
1234 | ||
1235 | if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) || | |
1236 | &$safe_string_ne($oldnet->{name}, $newnet->{name})) { | |
93cdbbfb | 1237 | |
18862537 | 1238 | PVE::Network::veth_delete($veth); |
bedeaaf1 AD |
1239 | delete $conf->{$opt}; |
1240 | PVE::LXC::write_config($vmid, $conf); | |
93cdbbfb | 1241 | |
18862537 | 1242 | hotplug_net($vmid, $conf, $opt, $newnet, $netid); |
bedeaaf1 | 1243 | |
18862537 WB |
1244 | } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) || |
1245 | &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) || | |
1246 | &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) { | |
bedeaaf1 | 1247 | |
18862537 | 1248 | if ($oldnet->{bridge}) { |
bedeaaf1 | 1249 | PVE::Network::tap_unplug($veth); |
18862537 WB |
1250 | foreach (qw(bridge tag firewall)) { |
1251 | delete $oldnet->{$_}; | |
1252 | } | |
1253 | $conf->{$opt} = print_lxc_network($oldnet); | |
bedeaaf1 AD |
1254 | PVE::LXC::write_config($vmid, $conf); |
1255 | } | |
93cdbbfb | 1256 | |
18862537 WB |
1257 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}); |
1258 | foreach (qw(bridge tag firewall)) { | |
1259 | $oldnet->{$_} = $newnet->{$_} if $newnet->{$_}; | |
1260 | } | |
1261 | $conf->{$opt} = print_lxc_network($oldnet); | |
bedeaaf1 | 1262 | PVE::LXC::write_config($vmid, $conf); |
93cdbbfb AD |
1263 | } |
1264 | } else { | |
18862537 | 1265 | hotplug_net($vmid, $conf, $opt, $newnet, $netid); |
93cdbbfb AD |
1266 | } |
1267 | ||
bedeaaf1 | 1268 | update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir); |
93cdbbfb AD |
1269 | } |
1270 | ||
1271 | sub hotplug_net { | |
18862537 | 1272 | my ($vmid, $conf, $opt, $newnet, $netid) = @_; |
93cdbbfb | 1273 | |
18862537 | 1274 | my $veth = "veth${vmid}i${netid}"; |
cbb03fea | 1275 | my $vethpeer = $veth . "p"; |
93cdbbfb AD |
1276 | my $eth = $newnet->{name}; |
1277 | ||
1278 | PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr}); | |
1279 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}); | |
1280 | ||
cbb03fea | 1281 | # attach peer in container |
93cdbbfb AD |
1282 | my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ]; |
1283 | PVE::Tools::run_command($cmd); | |
1284 | ||
cbb03fea | 1285 | # link up peer in container |
93cdbbfb AD |
1286 | $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ]; |
1287 | PVE::Tools::run_command($cmd); | |
bedeaaf1 | 1288 | |
18862537 WB |
1289 | my $done = { type => 'veth' }; |
1290 | foreach (qw(bridge tag firewall hwaddr name)) { | |
1291 | $done->{$_} = $newnet->{$_} if $newnet->{$_}; | |
1292 | } | |
1293 | $conf->{$opt} = print_lxc_network($done); | |
bedeaaf1 AD |
1294 | |
1295 | PVE::LXC::write_config($vmid, $conf); | |
93cdbbfb AD |
1296 | } |
1297 | ||
68a05bb3 | 1298 | sub update_ipconfig { |
bedeaaf1 AD |
1299 | my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_; |
1300 | ||
1301 | my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir); | |
1302 | ||
18862537 | 1303 | my $optdata = parse_lxc_network($conf->{$opt}); |
84e0c123 WB |
1304 | my $deleted = []; |
1305 | my $added = []; | |
8d723477 WB |
1306 | my $nscmd = sub { |
1307 | my $cmdargs = shift; | |
1308 | PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs); | |
84e0c123 | 1309 | }; |
8d723477 | 1310 | my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) }; |
2bfd1615 | 1311 | |
84e0c123 | 1312 | my $change_ip_config = sub { |
f39002a6 DM |
1313 | my ($ipversion) = @_; |
1314 | ||
1315 | my $family_opt = "-$ipversion"; | |
1316 | my $suffix = $ipversion == 4 ? '' : $ipversion; | |
84e0c123 WB |
1317 | my $gw= "gw$suffix"; |
1318 | my $ip= "ip$suffix"; | |
bedeaaf1 | 1319 | |
6178b0dd WB |
1320 | my $newip = $newnet->{$ip}; |
1321 | my $newgw = $newnet->{$gw}; | |
1322 | my $oldip = $optdata->{$ip}; | |
1323 | ||
1324 | my $change_ip = &$safe_string_ne($oldip, $newip); | |
1325 | my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw); | |
bedeaaf1 | 1326 | |
84e0c123 | 1327 | return if !$change_ip && !$change_gw; |
68a05bb3 | 1328 | |
84e0c123 | 1329 | # step 1: add new IP, if this fails we cancel |
6178b0dd | 1330 | if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) { |
8d723477 | 1331 | eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); }; |
84e0c123 WB |
1332 | if (my $err = $@) { |
1333 | warn $err; | |
1334 | return; | |
1335 | } | |
bedeaaf1 | 1336 | } |
bedeaaf1 | 1337 | |
84e0c123 WB |
1338 | # step 2: replace gateway |
1339 | # If this fails we delete the added IP and cancel. | |
1340 | # If it succeeds we save the config and delete the old IP, ignoring | |
1341 | # errors. The config is then saved. | |
1342 | # Note: 'ip route replace' can add | |
1343 | if ($change_gw) { | |
6178b0dd | 1344 | if ($newgw) { |
8d723477 | 1345 | eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); }; |
84e0c123 WB |
1346 | if (my $err = $@) { |
1347 | warn $err; | |
1348 | # the route was not replaced, the old IP is still available | |
1349 | # rollback (delete new IP) and cancel | |
1350 | if ($change_ip) { | |
8d723477 | 1351 | eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); }; |
84e0c123 WB |
1352 | warn $@ if $@; # no need to die here |
1353 | } | |
1354 | return; | |
1355 | } | |
1356 | } else { | |
8d723477 | 1357 | eval { &$ipcmd($family_opt, 'route', 'del', 'default'); }; |
84e0c123 WB |
1358 | # if the route was not deleted, the guest might have deleted it manually |
1359 | # warn and continue | |
1360 | warn $@ if $@; | |
1361 | } | |
2bfd1615 | 1362 | } |
2bfd1615 | 1363 | |
6178b0dd | 1364 | # from this point on we save the configuration |
84e0c123 | 1365 | # step 3: delete old IP ignoring errors |
6178b0dd | 1366 | if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) { |
8d723477 WB |
1367 | # We need to enable promote_secondaries, otherwise our newly added |
1368 | # address will be removed along with the old one. | |
1369 | my $promote = 0; | |
1370 | eval { | |
1371 | if ($ipversion == 4) { | |
1372 | &$nscmd({ outfunc => sub { $promote = int(shift) } }, | |
1373 | 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries"); | |
1374 | &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1"); | |
1375 | } | |
1376 | &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth); | |
1377 | }; | |
84e0c123 | 1378 | warn $@ if $@; # no need to die here |
8d723477 WB |
1379 | |
1380 | if ($ipversion == 4) { | |
1381 | &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote"); | |
1382 | } | |
bedeaaf1 AD |
1383 | } |
1384 | ||
84e0c123 WB |
1385 | foreach my $property ($ip, $gw) { |
1386 | if ($newnet->{$property}) { | |
1387 | $optdata->{$property} = $newnet->{$property}; | |
1388 | } else { | |
1389 | delete $optdata->{$property}; | |
1390 | } | |
bedeaaf1 | 1391 | } |
18862537 | 1392 | $conf->{$opt} = print_lxc_network($optdata); |
84e0c123 WB |
1393 | PVE::LXC::write_config($vmid, $conf); |
1394 | $lxc_setup->setup_network($conf); | |
1395 | }; | |
bedeaaf1 | 1396 | |
f39002a6 DM |
1397 | &$change_ip_config(4); |
1398 | &$change_ip_config(6); | |
489e960d WL |
1399 | |
1400 | } | |
1401 | ||
a92f66c9 WL |
1402 | # Internal snapshots |
1403 | ||
1404 | # NOTE: Snapshot create/delete involves several non-atomic | |
1405 | # action, and can take a long time. | |
1406 | # So we try to avoid locking the file and use 'lock' variable | |
1407 | # inside the config file instead. | |
1408 | ||
1409 | my $snapshot_copy_config = sub { | |
1410 | my ($source, $dest) = @_; | |
1411 | ||
1412 | foreach my $k (keys %$source) { | |
1413 | next if $k eq 'snapshots'; | |
09d3ec42 DM |
1414 | next if $k eq 'snapstate'; |
1415 | next if $k eq 'snaptime'; | |
1416 | next if $k eq 'vmstate'; | |
1417 | next if $k eq 'lock'; | |
a92f66c9 | 1418 | next if $k eq 'digest'; |
09d3ec42 | 1419 | next if $k eq 'description'; |
a92f66c9 WL |
1420 | |
1421 | $dest->{$k} = $source->{$k}; | |
1422 | } | |
1423 | }; | |
1424 | ||
1425 | my $snapshot_prepare = sub { | |
1426 | my ($vmid, $snapname, $comment) = @_; | |
1427 | ||
1428 | my $snap; | |
1429 | ||
1430 | my $updatefn = sub { | |
1431 | ||
1432 | my $conf = load_config($vmid); | |
1433 | ||
bb1ac2de DM |
1434 | die "you can't take a snapshot if it's a template\n" |
1435 | if is_template($conf); | |
1436 | ||
a92f66c9 WL |
1437 | check_lock($conf); |
1438 | ||
09d3ec42 | 1439 | $conf->{lock} = 'snapshot'; |
a92f66c9 WL |
1440 | |
1441 | die "snapshot name '$snapname' already used\n" | |
1442 | if defined($conf->{snapshots}->{$snapname}); | |
1443 | ||
1444 | my $storecfg = PVE::Storage::config(); | |
1445 | die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg); | |
1446 | ||
1447 | $snap = $conf->{snapshots}->{$snapname} = {}; | |
1448 | ||
1449 | &$snapshot_copy_config($conf, $snap); | |
1450 | ||
09d3ec42 DM |
1451 | $snap->{'snapstate'} = "prepare"; |
1452 | $snap->{'snaptime'} = time(); | |
1453 | $snap->{'description'} = $comment if $comment; | |
a92f66c9 WL |
1454 | $conf->{snapshots}->{$snapname} = $snap; |
1455 | ||
1456 | PVE::LXC::write_config($vmid, $conf); | |
1457 | }; | |
1458 | ||
1459 | lock_container($vmid, 10, $updatefn); | |
1460 | ||
1461 | return $snap; | |
1462 | }; | |
1463 | ||
1464 | my $snapshot_commit = sub { | |
1465 | my ($vmid, $snapname) = @_; | |
1466 | ||
1467 | my $updatefn = sub { | |
1468 | ||
1469 | my $conf = load_config($vmid); | |
1470 | ||
1471 | die "missing snapshot lock\n" | |
09d3ec42 | 1472 | if !($conf->{lock} && $conf->{lock} eq 'snapshot'); |
a92f66c9 | 1473 | |
27916659 | 1474 | die "snapshot '$snapname' does not exist\n" |
a92f66c9 WL |
1475 | if !defined($conf->{snapshots}->{$snapname}); |
1476 | ||
1477 | die "wrong snapshot state\n" | |
09d3ec42 DM |
1478 | if !($conf->{snapshots}->{$snapname}->{'snapstate'} && |
1479 | $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare"); | |
a92f66c9 | 1480 | |
09d3ec42 DM |
1481 | delete $conf->{snapshots}->{$snapname}->{'snapstate'}; |
1482 | delete $conf->{lock}; | |
1483 | $conf->{parent} = $snapname; | |
a92f66c9 WL |
1484 | |
1485 | PVE::LXC::write_config($vmid, $conf); | |
a92f66c9 WL |
1486 | }; |
1487 | ||
1488 | lock_container($vmid, 10 ,$updatefn); | |
1489 | }; | |
1490 | ||
1491 | sub has_feature { | |
1492 | my ($feature, $conf, $storecfg, $snapname) = @_; | |
09d3ec42 | 1493 | |
a92f66c9 WL |
1494 | #Fixme add other drives if necessary. |
1495 | my $err; | |
09d3ec42 DM |
1496 | |
1497 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs}); | |
1498 | $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname); | |
a92f66c9 WL |
1499 | |
1500 | return $err ? 0 : 1; | |
1501 | } | |
1502 | ||
489e960d WL |
1503 | sub snapshot_create { |
1504 | my ($vmid, $snapname, $comment) = @_; | |
1505 | ||
a92f66c9 WL |
1506 | my $snap = &$snapshot_prepare($vmid, $snapname, $comment); |
1507 | ||
09d3ec42 | 1508 | my $conf = load_config($vmid); |
a92f66c9 WL |
1509 | |
1510 | my $cmd = "/usr/bin/lxc-freeze -n $vmid"; | |
1511 | my $running = check_running($vmid); | |
1512 | eval { | |
1513 | if ($running) { | |
1514 | PVE::Tools::run_command($cmd); | |
1515 | }; | |
1516 | ||
1517 | my $storecfg = PVE::Storage::config(); | |
09d3ec42 DM |
1518 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs}); |
1519 | my $volid = $rootinfo->{volume}; | |
a92f66c9 WL |
1520 | |
1521 | $cmd = "/usr/bin/lxc-unfreeze -n $vmid"; | |
1522 | if ($running) { | |
1523 | PVE::Tools::run_command($cmd); | |
1524 | }; | |
489e960d | 1525 | |
a92f66c9 WL |
1526 | PVE::Storage::volume_snapshot($storecfg, $volid, $snapname); |
1527 | &$snapshot_commit($vmid, $snapname); | |
1528 | }; | |
1529 | if(my $err = $@) { | |
31429832 | 1530 | snapshot_delete($vmid, $snapname, 1); |
a92f66c9 WL |
1531 | die "$err\n"; |
1532 | } | |
68a05bb3 AD |
1533 | } |
1534 | ||
57ccb3f8 WL |
1535 | sub snapshot_delete { |
1536 | my ($vmid, $snapname, $force) = @_; | |
1537 | ||
31429832 WL |
1538 | my $snap; |
1539 | ||
1540 | my $conf; | |
1541 | ||
1542 | my $updatefn = sub { | |
1543 | ||
1544 | $conf = load_config($vmid); | |
1545 | ||
bb1ac2de DM |
1546 | die "you can't delete a snapshot if vm is a template\n" |
1547 | if is_template($conf); | |
1548 | ||
31429832 WL |
1549 | $snap = $conf->{snapshots}->{$snapname}; |
1550 | ||
1551 | check_lock($conf); | |
1552 | ||
1553 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1554 | ||
09d3ec42 | 1555 | $snap->{snapstate} = 'delete'; |
31429832 WL |
1556 | |
1557 | PVE::LXC::write_config($vmid, $conf); | |
1558 | }; | |
1559 | ||
1560 | lock_container($vmid, 10, $updatefn); | |
1561 | ||
1562 | my $storecfg = PVE::Storage::config(); | |
1563 | ||
1564 | my $del_snap = sub { | |
1565 | ||
1566 | check_lock($conf); | |
1567 | ||
09d3ec42 DM |
1568 | if ($conf->{parent} eq $snapname) { |
1569 | if ($conf->{snapshots}->{$snapname}->{snapname}) { | |
1570 | $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent}; | |
31429832 | 1571 | } else { |
09d3ec42 | 1572 | delete $conf->{parent}; |
31429832 WL |
1573 | } |
1574 | } | |
1575 | ||
1576 | delete $conf->{snapshots}->{$snapname}; | |
1577 | ||
1578 | PVE::LXC::write_config($vmid, $conf); | |
1579 | }; | |
1580 | ||
09d3ec42 DM |
1581 | my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs}; |
1582 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs); | |
1583 | my $volid = $rootinfo->{volume}; | |
31429832 WL |
1584 | |
1585 | eval { | |
1586 | PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname); | |
1587 | }; | |
1588 | my $err = $@; | |
1589 | ||
1590 | if(!$err || ($err && $force)) { | |
1591 | lock_container($vmid, 10, $del_snap); | |
1592 | if ($err) { | |
1593 | die "Can't delete snapshot: $vmid $snapname $err\n"; | |
1594 | } | |
1595 | } | |
57ccb3f8 WL |
1596 | } |
1597 | ||
723157f6 WL |
1598 | sub snapshot_rollback { |
1599 | my ($vmid, $snapname) = @_; | |
1600 | ||
6860ba0c WL |
1601 | my $storecfg = PVE::Storage::config(); |
1602 | ||
1603 | my $conf = load_config($vmid); | |
1604 | ||
bb1ac2de DM |
1605 | die "you can't rollback if vm is a template\n" if is_template($conf); |
1606 | ||
6860ba0c WL |
1607 | my $snap = $conf->{snapshots}->{$snapname}; |
1608 | ||
1609 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1610 | ||
09d3ec42 DM |
1611 | my $rootfs = $snap->{rootfs}; |
1612 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs); | |
1613 | my $volid = $rootinfo->{volume}; | |
1614 | ||
1615 | PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname); | |
6860ba0c WL |
1616 | |
1617 | my $updatefn = sub { | |
1618 | ||
09d3ec42 DM |
1619 | die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" |
1620 | if $snap->{snapstate}; | |
6860ba0c WL |
1621 | |
1622 | check_lock($conf); | |
6860ba0c | 1623 | |
b935932a | 1624 | system("lxc-stop -n $vmid --kill") if check_running($vmid); |
6860ba0c WL |
1625 | |
1626 | die "unable to rollback vm $vmid: vm is running\n" | |
1627 | if check_running($vmid); | |
1628 | ||
09d3ec42 | 1629 | $conf->{lock} = 'rollback'; |
6860ba0c WL |
1630 | |
1631 | my $forcemachine; | |
1632 | ||
1633 | # copy snapshot config to current config | |
1634 | ||
1635 | my $tmp_conf = $conf; | |
1636 | &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf); | |
6860ba0c | 1637 | $conf->{snapshots} = $tmp_conf->{snapshots}; |
09d3ec42 DM |
1638 | delete $conf->{snaptime}; |
1639 | delete $conf->{snapname}; | |
1640 | $conf->{parent} = $snapname; | |
6860ba0c WL |
1641 | |
1642 | PVE::LXC::write_config($vmid, $conf); | |
6860ba0c WL |
1643 | }; |
1644 | ||
1645 | my $unlockfn = sub { | |
09d3ec42 | 1646 | delete $conf->{lock}; |
6860ba0c WL |
1647 | PVE::LXC::write_config($vmid, $conf); |
1648 | }; | |
1649 | ||
1650 | lock_container($vmid, 10, $updatefn); | |
1651 | ||
09d3ec42 | 1652 | PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname); |
6860ba0c WL |
1653 | |
1654 | lock_container($vmid, 5, $unlockfn); | |
723157f6 | 1655 | } |
b935932a | 1656 | |
bb1ac2de DM |
1657 | sub template_create { |
1658 | my ($vmid, $conf) = @_; | |
1659 | ||
1660 | my $storecfg = PVE::Storage::config(); | |
1661 | ||
1662 | my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs}); | |
1663 | my $volid = $rootinfo->{volume}; | |
1664 | ||
1665 | die "Template feature is not available for '$volid'\n" | |
1666 | if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid); | |
1667 | ||
1668 | PVE::Storage::activate_volumes($storecfg, [$volid]); | |
1669 | ||
1670 | my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid); | |
1671 | $rootinfo->{volume} = $template_volid; | |
dde7b02b | 1672 | $conf->{rootfs} = print_ct_mountpoint($rootinfo); |
bb1ac2de DM |
1673 | |
1674 | write_config($vmid, $conf); | |
1675 | } | |
1676 | ||
1677 | sub is_template { | |
1678 | my ($conf) = @_; | |
1679 | ||
1680 | return 1 if defined $conf->{template} && $conf->{template} == 1; | |
1681 | } | |
1682 | ||
f76a2828 | 1683 | 1; |