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