]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
Extend lxc config with options for snapshot
[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 $done_hash = { digest => 1};
172
173 my $dump_entry = sub {
174 my ($k, $elem, $done_hash, $snap) = @_;
175 my $value = $elem->{$k};
176 return if !defined($value);
177 return if $done_hash->{$k};
178 $done_hash->{$k} = 1;
179 if (ref($value)) {
180 die "got unexpected reference for '$k'"
181 if !$lxc_array_configs->{$k};
182 foreach my $v (@$value) {
183 $raw .= "snap\." if $snap;
184 $raw .= "$k = $v\n";
185 }
186 } else {
187 $raw .= "snap\." if $snap;
188 $raw .= "$k = $value\n";
189 }
190 };
191
192 my $config_writer = sub {
193 my ($elem, $snapshot) = @_;
194
195 my $done_hash = { digest => 1};
196
197 if ($elem->{'pve.snapname'}) {
198 &$dump_entry('pve.snapname', $elem, $done_hash, $snapshot);
199 }
200
201 # Note: Order is important! Include defaults first, so that we
202 # can overwrite them later.
203 &$dump_entry('lxc.include', $elem, $done_hash, $snapshot);
204
205 foreach my $k (sort keys %$elem) {
206 next if $k !~ m/^lxc\./;
207 &$dump_entry($k, $elem, $done_hash, $snapshot);
208 }
209 foreach my $k (sort keys %$elem) {
210 next if $k !~ m/^pve\./;
211 &$dump_entry($k, $elem, $done_hash, $snapshot);
212 }
213 my $network_count = 0;
214
215 foreach my $k (sort keys %$elem) {
216 next if $k !~ m/^net\d+$/;
217 $done_hash->{$k} = 1;
218
219 my $net = $elem->{$k};
220 $network_count++;
221 $raw .= "snap\." if $snapshot;
222 $raw .= "lxc.network.type = $net->{type}\n";
223 foreach my $subkey (sort keys %$net) {
224 next if $subkey eq 'type';
225 if ($valid_lxc_network_keys->{$subkey}) {
226 $raw .= "snap\." if $snapshot;
227 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
228 } elsif ($valid_pve_network_keys->{$subkey}) {
229 $raw .= "snap\." if $snapshot;
230 $raw .= "pve.network.$subkey = $net->{$subkey}\n";
231 } else {
232 die "found invalid network key '$subkey'";
233 }
234 }
235 }
236 if (!$network_count) {
237 $raw .= "snap\." if $snapshot;
238 $raw .= "lxc.network.type = empty\n";
239 }
240 foreach my $k (sort keys %$elem) {
241 next if $k eq 'snapshots';
242 next if $done_hash->{$k};
243 die "found un-written value in config - implement this!";
244 }
245
246 };
247
248 &$config_writer($data);
249
250 if ($data->{snapshots}){
251 my @tmp = sort { $data->{snapshots}->{$b}{'pve.snaptime'} <=>
252 $data->{snapshots}->{$a}{'pve.snaptime'} }
253 keys %{$data->{snapshots}};
254 foreach my $snapname (@tmp) {
255 $raw .= "\n";
256 &$config_writer($data->{snapshots}->{$snapname}, 1);
257 }
258 }
259
260 return $raw;
261 }
262
263 sub parse_lxc_option {
264 my ($name, $value) = @_;
265
266 my $parser = $valid_lxc_keys->{$name};
267
268 die "invalid key '$name'\n" if !defined($parser);
269
270 if ($parser eq '1') {
271 return $value;
272 } elsif (ref($parser)) {
273 my $res = &$parser($name, $value);
274 return $res if defined($res);
275 } else {
276 # assume regex
277 return $value if $value =~ m/^$parser$/;
278 }
279
280 die "unable to parse value '$value' for option '$name'\n";
281 }
282
283 sub parse_lxc_config {
284 my ($filename, $raw) = @_;
285
286 return undef if !defined($raw);
287
288 my $data = {
289 digest => Digest::SHA::sha1_hex($raw),
290 };
291
292 $filename =~ m|/lxc/(\d+)/config$|
293 || die "got strange filename '$filename'";
294
295 my $vmid = $1;
296
297 my $split_config = sub {
298 my ($raw) = @_;
299 my $sections = [];
300 my $tmp = '';
301 while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
302 my $line = $1;
303 if(!$line) {
304 push(@{$sections},$tmp);
305 $tmp = '';
306 } else {
307 $tmp .= "$line\n";
308 }
309 }
310 push(@{$sections},$tmp);
311
312 return $sections;
313 };
314
315
316 my $sec = &$split_config($raw);
317
318 foreach my $sec_raw (@{$sec}){
319 next if $sec_raw eq '';
320 my $snapname = undef;
321
322 my $network_counter = 0;
323 my $network_list = [];
324 my $host_ifnames = {};
325
326 my $find_next_hostif_name = sub {
327 for (my $i = 0; $i < 10; $i++) {
328 my $name = "veth${vmid}.$i";
329 if (!$host_ifnames->{$name}) {
330 $host_ifnames->{$name} = 1;
331 return $name;
332 }
333 }
334
335 die "unable to find free host_ifname"; # should not happen
336 };
337
338 my $push_network = sub {
339 my ($netconf) = @_;
340 return if !$netconf;
341 push @{$network_list}, $netconf;
342 $network_counter++;
343 if (my $netname = $netconf->{'veth.pair'}) {
344 if ($netname =~ m/^veth(\d+).(\d)$/) {
345 die "wrong vmid for network interface pair\n" if $1 != $vmid;
346 my $host_ifnames->{$netname} = 1;
347 } else {
348 die "wrong network interface pair\n";
349 }
350 }
351 };
352
353 my $network;
354
355 while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) {
356 my $line = $1;
357
358 next if $line =~ m/^\#/;
359 next if $line =~ m/^\s*$/;
360
361 if ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
362 my ($subkey, $value) = ($2, $3);
363 if ($subkey eq 'type') {
364 &$push_network($network);
365 $network = { type => $value };
366 } elsif ($valid_lxc_network_keys->{$subkey}) {
367 $network->{$subkey} = $value;
368 } else {
369 die "unable to parse config line: $line\n";
370 }
371 next;
372 }
373 if ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
374 my ($subkey, $value) = ($2, $3);
375 if ($valid_pve_network_keys->{$subkey}) {
376 $network->{$subkey} = $value;
377 } else {
378 die "unable to parse config line: $line\n";
379 }
380 next;
381 }
382 if ($line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/) {
383 my ($name, $value) = ($2, $3);
384 if ($snapname) {
385 $data->{snapshots}->{$snapname}->{$name} = $value;
386 }
387 next;
388 }
389 if ($line =~ m/^(snap\.)?pve\.snapname = (\w*)$/) {
390 if (!$snapname) {
391 $snapname = $2;
392 $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
393 } else {
394 die "Configuarion broken\n";
395 }
396 next;
397 }
398 if ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
399 my ($name, $value) = ($2, $3);
400
401 if ($lxc_array_configs->{$name}) {
402 $data->{$name} = [] if !defined($data->{$name});
403 if ($snapname) {
404 push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value);
405 } else {
406 push @{$data->{$name}}, parse_lxc_option($name, $value);
407 }
408 } else {
409 if ($snapname) {
410 die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
411 $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
412 } else {
413 die "multiple definitions for $name\n" if defined($data->{$name});
414 $data->{$name} = parse_lxc_option($name, $value);
415 }
416 }
417
418 next;
419 }
420 die "unable to parse config line: $line\n";
421 }
422 &$push_network($network);
423
424 foreach my $net (@{$network_list}) {
425 next if $net->{type} eq 'empty'; # skip
426 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
427 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
428 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
429
430 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
431 if ($snapname) {
432 $data->{snapshots}->{$snapname}->{"net$1"} = $net
433 } else {
434 $data->{"net$1"} = $net;
435 }
436 }
437 }
438
439 }
440
441 return $data;
442 }
443
444 sub config_list {
445 my $vmlist = PVE::Cluster::get_vmlist();
446 my $res = {};
447 return $res if !$vmlist || !$vmlist->{ids};
448 my $ids = $vmlist->{ids};
449
450 foreach my $vmid (keys %$ids) {
451 next if !$vmid; # skip CT0
452 my $d = $ids->{$vmid};
453 next if !$d->{node} || $d->{node} ne $nodename;
454 next if !$d->{type} || $d->{type} ne 'lxc';
455 $res->{$vmid}->{type} = 'lxc';
456 }
457 return $res;
458 }
459
460 sub cfs_config_path {
461 my ($vmid, $node) = @_;
462
463 $node = $nodename if !$node;
464 return "nodes/$node/lxc/$vmid/config";
465 }
466
467 sub config_file {
468 my ($vmid, $node) = @_;
469
470 my $cfspath = cfs_config_path($vmid, $node);
471 return "/etc/pve/$cfspath";
472 }
473
474 sub load_config {
475 my ($vmid) = @_;
476
477 my $cfspath = cfs_config_path($vmid);
478
479 my $conf = PVE::Cluster::cfs_read_file($cfspath);
480 die "container $vmid does not exists\n" if !defined($conf);
481
482 return $conf;
483 }
484
485 sub create_config {
486 my ($vmid, $conf) = @_;
487
488 my $dir = "/etc/pve/nodes/$nodename/lxc";
489 mkdir $dir;
490
491 $dir .= "/$vmid";
492 mkdir($dir) || die "unable to create container configuration directory - $!\n";
493
494 write_config($vmid, $conf);
495 }
496
497 sub destroy_config {
498 my ($vmid) = @_;
499
500 my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
501 File::Path::rmtree($dir);
502 }
503
504 sub write_config {
505 my ($vmid, $conf) = @_;
506
507 my $cfspath = cfs_config_path($vmid);
508
509 PVE::Cluster::cfs_write_file($cfspath, $conf);
510 }
511
512 my $tempcounter = 0;
513 sub write_temp_config {
514 my ($vmid, $conf) = @_;
515
516 $tempcounter++;
517 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
518
519 my $raw = write_lxc_config($filename, $conf);
520
521 PVE::Tools::file_set_contents($filename, $raw);
522
523 return $filename;
524 }
525
526 # flock: we use one file handle per process, so lock file
527 # can be called multiple times and succeeds for the same process.
528
529 my $lock_handles = {};
530 my $lockdir = "/run/lock/lxc";
531
532 sub lock_filename {
533 my ($vmid) = @_;
534
535 return "$lockdir/pve-config-{$vmid}.lock";
536 }
537
538 sub lock_aquire {
539 my ($vmid, $timeout) = @_;
540
541 $timeout = 10 if !$timeout;
542 my $mode = LOCK_EX;
543
544 my $filename = lock_filename($vmid);
545
546 mkdir $lockdir if !-d $lockdir;
547
548 my $lock_func = sub {
549 if (!$lock_handles->{$$}->{$filename}) {
550 my $fh = new IO::File(">>$filename") ||
551 die "can't open file - $!\n";
552 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
553 }
554
555 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
556 print STDERR "trying to aquire lock...";
557 my $success;
558 while(1) {
559 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
560 # try again on EINTR (see bug #273)
561 if ($success || ($! != EINTR)) {
562 last;
563 }
564 }
565 if (!$success) {
566 print STDERR " failed\n";
567 die "can't aquire lock - $!\n";
568 }
569
570 $lock_handles->{$$}->{$filename}->{refcount}++;
571
572 print STDERR " OK\n";
573 }
574 };
575
576 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
577 my $err = $@;
578 if ($err) {
579 die "can't lock file '$filename' - $err";
580 }
581 }
582
583 sub lock_release {
584 my ($vmid) = @_;
585
586 my $filename = lock_filename($vmid);
587
588 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
589 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
590 if ($refcount <= 0) {
591 $lock_handles->{$$}->{$filename} = undef;
592 close ($fh);
593 }
594 }
595 }
596
597 sub lock_container {
598 my ($vmid, $timeout, $code, @param) = @_;
599
600 my $res;
601
602 lock_aquire($vmid, $timeout);
603 eval { $res = &$code(@param) };
604 my $err = $@;
605 lock_release($vmid);
606
607 die $err if $err;
608
609 return $res;
610 }
611
612 my $confdesc = {
613 onboot => {
614 optional => 1,
615 type => 'boolean',
616 description => "Specifies whether a VM will be started during system bootup.",
617 default => 0,
618 },
619 startup => get_standard_option('pve-startup-order'),
620 cpulimit => {
621 optional => 1,
622 type => 'number',
623 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.",
624 minimum => 0,
625 maximum => 128,
626 default => 0,
627 },
628 cpuunits => {
629 optional => 1,
630 type => 'integer',
631 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.",
632 minimum => 0,
633 maximum => 500000,
634 default => 1000,
635 },
636 memory => {
637 optional => 1,
638 type => 'integer',
639 description => "Amount of RAM for the VM in MB.",
640 minimum => 16,
641 default => 512,
642 },
643 swap => {
644 optional => 1,
645 type => 'integer',
646 description => "Amount of SWAP for the VM in MB.",
647 minimum => 0,
648 default => 512,
649 },
650 disk => {
651 optional => 1,
652 type => 'number',
653 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
654 minimum => 0,
655 default => 4,
656 },
657 hostname => {
658 optional => 1,
659 description => "Set a host name for the container.",
660 type => 'string',
661 maxLength => 255,
662 },
663 description => {
664 optional => 1,
665 type => 'string',
666 description => "Container description. Only used on the configuration web interface.",
667 },
668 searchdomain => {
669 optional => 1,
670 type => 'string',
671 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
672 },
673 nameserver => {
674 optional => 1,
675 type => 'string',
676 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.",
677 },
678 };
679
680 my $MAX_LXC_NETWORKS = 10;
681 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
682 $confdesc->{"net$i"} = {
683 optional => 1,
684 type => 'string', format => 'pve-lxc-network',
685 description => "Specifies network interfaces for the container.\n\n".
686 "The string should have the follow format:\n\n".
687 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
688 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
689 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
690 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
691 };
692 }
693
694 sub option_exists {
695 my ($name) = @_;
696
697 return defined($confdesc->{$name});
698 }
699
700 # add JSON properties for create and set function
701 sub json_config_properties {
702 my $prop = shift;
703
704 foreach my $opt (keys %$confdesc) {
705 $prop->{$opt} = $confdesc->{$opt};
706 }
707
708 return $prop;
709 }
710
711 # container status helpers
712
713 sub list_active_containers {
714
715 my $filename = "/proc/net/unix";
716
717 # similar test is used by lcxcontainers.c: list_active_containers
718 my $res = {};
719
720 my $fh = IO::File->new ($filename, "r");
721 return $res if !$fh;
722
723 while (defined(my $line = <$fh>)) {
724 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
725 my $path = $1;
726 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
727 $res->{$1} = 1;
728 }
729 }
730 }
731
732 close($fh);
733
734 return $res;
735 }
736
737 # warning: this is slow
738 sub check_running {
739 my ($vmid) = @_;
740
741 my $active_hash = list_active_containers();
742
743 return 1 if defined($active_hash->{$vmid});
744
745 return undef;
746 }
747
748 sub get_container_disk_usage {
749 my ($vmid) = @_;
750
751 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
752
753 my $res = {
754 total => 0,
755 used => 0,
756 avail => 0,
757 };
758
759 my $parser = sub {
760 my $line = shift;
761 if (my ($fsid, $total, $used, $avail) = $line =~
762 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
763 $res = {
764 total => $total,
765 used => $used,
766 avail => $avail,
767 };
768 }
769 };
770 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
771 warn $@ if $@;
772
773 return $res;
774 }
775
776 sub vmstatus {
777 my ($opt_vmid) = @_;
778
779 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
780
781 my $active_hash = list_active_containers();
782
783 foreach my $vmid (keys %$list) {
784 my $d = $list->{$vmid};
785
786 my $running = defined($active_hash->{$vmid});
787
788 $d->{status} = $running ? 'running' : 'stopped';
789
790 my $cfspath = cfs_config_path($vmid);
791 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
792
793 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
794 $d->{name} =~ s/[\s]//g;
795
796 $d->{cpus} = 0;
797
798 my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
799 my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
800
801 if ($cfs_period_us && $cfs_quota_us) {
802 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
803 }
804
805 $d->{disk} = 0;
806 $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
807 int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
808
809 if (my $private = $conf->{'lxc.rootfs'}) {
810 if ($private =~ m!^/!) {
811 my $res = PVE::Tools::df($private, 2);
812 $d->{disk} = $res->{used};
813 $d->{maxdisk} = $res->{total};
814 } elsif ($running) {
815 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
816 my $res = get_container_disk_usage($vmid);
817 $d->{disk} = $res->{used};
818 $d->{maxdisk} = $res->{total};
819 }
820 }
821 }
822
823 $d->{mem} = 0;
824 $d->{swap} = 0;
825 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
826 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
827
828 $d->{uptime} = 0;
829 $d->{cpu} = 0;
830
831 $d->{netout} = 0;
832 $d->{netin} = 0;
833
834 $d->{diskread} = 0;
835 $d->{diskwrite} = 0;
836 }
837
838 foreach my $vmid (keys %$list) {
839 my $d = $list->{$vmid};
840 next if $d->{status} ne 'running';
841
842 $d->{uptime} = 100; # fixme:
843
844 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
845 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
846
847 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
848 my @bytes = split(/\n/, $blkio_bytes);
849 foreach my $byte (@bytes) {
850 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
851 $d->{diskread} = $2 if $key eq 'Read';
852 $d->{diskwrite} = $2 if $key eq 'Write';
853 }
854 }
855 }
856
857 return $list;
858 }
859
860
861 sub print_lxc_network {
862 my $net = shift;
863
864 die "no network name defined\n" if !$net->{name};
865
866 my $res = "name=$net->{name}";
867
868 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
869 next if !defined($net->{$k});
870 $res .= ",$k=$net->{$k}";
871 }
872
873 return $res;
874 }
875
876 sub parse_lxc_network {
877 my ($data) = @_;
878
879 my $res = {};
880
881 return $res if !$data;
882
883 foreach my $pv (split (/,/, $data)) {
884 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
885 $res->{$1} = $2;
886 } else {
887 return undef;
888 }
889 }
890
891 $res->{type} = 'veth';
892 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
893
894 return $res;
895 }
896
897 sub read_cgroup_value {
898 my ($group, $vmid, $name, $full) = @_;
899
900 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
901
902 return PVE::Tools::file_get_contents($path) if $full;
903
904 return PVE::Tools::file_read_firstline($path);
905 }
906
907 sub write_cgroup_value {
908 my ($group, $vmid, $name, $value) = @_;
909
910 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
911 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
912
913 }
914
915 sub find_lxc_console_pids {
916
917 my $res = {};
918
919 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
920 my ($pid) = @_;
921
922 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
923 return if !$cmdline;
924
925 my @args = split(/\0/, $cmdline);
926
927 # serach for lxc-console -n <vmid>
928 return if scalar(@args) != 3;
929 return if $args[1] ne '-n';
930 return if $args[2] !~ m/^\d+$/;
931 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
932
933 my $vmid = $args[2];
934
935 push @{$res->{$vmid}}, $pid;
936 });
937
938 return $res;
939 }
940
941 sub find_lxc_pid {
942 my ($vmid) = @_;
943
944 my $pid = undef;
945 my $parser = sub {
946 my $line = shift;
947 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
948 };
949 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
950
951 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
952
953 return $pid;
954 }
955
956 my $ipv4_reverse_mask = [
957 '0.0.0.0',
958 '128.0.0.0',
959 '192.0.0.0',
960 '224.0.0.0',
961 '240.0.0.0',
962 '248.0.0.0',
963 '252.0.0.0',
964 '254.0.0.0',
965 '255.0.0.0',
966 '255.128.0.0',
967 '255.192.0.0',
968 '255.224.0.0',
969 '255.240.0.0',
970 '255.248.0.0',
971 '255.252.0.0',
972 '255.254.0.0',
973 '255.255.0.0',
974 '255.255.128.0',
975 '255.255.192.0',
976 '255.255.224.0',
977 '255.255.240.0',
978 '255.255.248.0',
979 '255.255.252.0',
980 '255.255.254.0',
981 '255.255.255.0',
982 '255.255.255.128',
983 '255.255.255.192',
984 '255.255.255.224',
985 '255.255.255.240',
986 '255.255.255.248',
987 '255.255.255.252',
988 '255.255.255.254',
989 '255.255.255.255',
990 ];
991
992 # Note: we cannot use Net:IP, because that only allows strict
993 # CIDR networks
994 sub parse_ipv4_cidr {
995 my ($cidr, $noerr) = @_;
996
997 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
998 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
999 }
1000
1001 return undef if $noerr;
1002
1003 die "unable to parse ipv4 address/mask\n";
1004 }
1005
1006 sub check_lock {
1007 my ($conf) = @_;
1008
1009 die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
1010 }
1011
1012 sub lxc_conf_to_pve {
1013 my ($vmid, $lxc_conf) = @_;
1014
1015 my $properties = json_config_properties();
1016
1017 my $conf = { digest => $lxc_conf->{digest} };
1018
1019 foreach my $k (keys %$properties) {
1020
1021 if ($k eq 'description') {
1022 if (my $raw = $lxc_conf->{'pve.comment'}) {
1023 $conf->{$k} = PVE::Tools::decode_text($raw);
1024 }
1025 } elsif ($k eq 'onboot') {
1026 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
1027 } elsif ($k eq 'startup') {
1028 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
1029 } elsif ($k eq 'hostname') {
1030 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
1031 } elsif ($k eq 'nameserver') {
1032 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
1033 } elsif ($k eq 'searchdomain') {
1034 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
1035 } elsif ($k eq 'memory') {
1036 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
1037 $conf->{$k} = int($value / (1024*1024));
1038 }
1039 } elsif ($k eq 'swap') {
1040 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
1041 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
1042 $conf->{$k} = int(($value -$mem) / (1024*1024));
1043 }
1044 } elsif ($k eq 'cpulimit') {
1045 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
1046 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1047
1048 if ($cfs_period_us && $cfs_quota_us) {
1049 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
1050 } else {
1051 $conf->{$k} = 0;
1052 }
1053 } elsif ($k eq 'cpuunits') {
1054 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
1055 } elsif ($k eq 'disk') {
1056 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
1057 $lxc_conf->{'pve.disksize'} : 0;
1058 } elsif ($k =~ m/^net\d$/) {
1059 my $net = $lxc_conf->{$k};
1060 next if !$net;
1061 $conf->{$k} = print_lxc_network($net);
1062 }
1063 }
1064
1065 if (my $parent = $lxc_conf->{'pve.parent'}) {
1066 $conf->{parent} = $lxc_conf->{'pve.parent'};
1067 }
1068
1069 if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
1070 $conf->{description} = $lxc_conf->{'pve.snapcomment'};
1071 }
1072
1073 if (my $parent = $lxc_conf->{'pve.snaptime'}) {
1074 $conf->{snaptime} = $lxc_conf->{'pve.snaptime'};
1075 }
1076
1077 return $conf;
1078 }
1079
1080 # verify and cleanup nameserver list (replace \0 with ' ')
1081 sub verify_nameserver_list {
1082 my ($nameserver_list) = @_;
1083
1084 my @list = ();
1085 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1086 PVE::JSONSchema::pve_verify_ip($server);
1087 push @list, $server;
1088 }
1089
1090 return join(' ', @list);
1091 }
1092
1093 sub verify_searchdomain_list {
1094 my ($searchdomain_list) = @_;
1095
1096 my @list = ();
1097 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1098 # todo: should we add checks for valid dns domains?
1099 push @list, $server;
1100 }
1101
1102 return join(' ', @list);
1103 }
1104
1105 sub update_lxc_config {
1106 my ($vmid, $conf, $running, $param, $delete) = @_;
1107
1108 my @nohotplug;
1109
1110 my $rootdir;
1111 if ($running) {
1112 my $pid = find_lxc_pid($vmid);
1113 $rootdir = "/proc/$pid/root";
1114 }
1115
1116 if (defined($delete)) {
1117 foreach my $opt (@$delete) {
1118 if ($opt eq 'hostname' || $opt eq 'memory') {
1119 die "unable to delete required option '$opt'\n";
1120 } elsif ($opt eq 'swap') {
1121 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
1122 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1123 } elsif ($opt eq 'description') {
1124 delete $conf->{'pve.comment'};
1125 } elsif ($opt eq 'onboot') {
1126 delete $conf->{'pve.onboot'};
1127 } elsif ($opt eq 'startup') {
1128 delete $conf->{'pve.startup'};
1129 } elsif ($opt eq 'nameserver') {
1130 delete $conf->{'pve.nameserver'};
1131 push @nohotplug, $opt;
1132 next if $running;
1133 } elsif ($opt eq 'searchdomain') {
1134 delete $conf->{'pve.searchdomain'};
1135 push @nohotplug, $opt;
1136 next if $running;
1137 } elsif ($opt =~ m/^net(\d)$/) {
1138 delete $conf->{$opt};
1139 next if !$running;
1140 my $netid = $1;
1141 PVE::Network::veth_delete("veth${vmid}.$netid");
1142 } else {
1143 die "implement me"
1144 }
1145 PVE::LXC::write_config($vmid, $conf) if $running;
1146 }
1147 }
1148
1149 foreach my $opt (keys %$param) {
1150 my $value = $param->{$opt};
1151 if ($opt eq 'hostname') {
1152 $conf->{'lxc.utsname'} = $value;
1153 } elsif ($opt eq 'onboot') {
1154 $conf->{'pve.onboot'} = $value ? 1 : 0;
1155 } elsif ($opt eq 'startup') {
1156 $conf->{'pve.startup'} = $value;
1157 } elsif ($opt eq 'nameserver') {
1158 my $list = verify_nameserver_list($value);
1159 $conf->{'pve.nameserver'} = $list;
1160 push @nohotplug, $opt;
1161 next if $running;
1162 } elsif ($opt eq 'searchdomain') {
1163 my $list = verify_searchdomain_list($value);
1164 $conf->{'pve.searchdomain'} = $list;
1165 push @nohotplug, $opt;
1166 next if $running;
1167 } elsif ($opt eq 'memory') {
1168 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
1169 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
1170 } elsif ($opt eq 'swap') {
1171 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
1172 $mem = $param->{memory}*1024*1024 if $param->{memory};
1173 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
1174 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
1175
1176 } elsif ($opt eq 'cpulimit') {
1177 if ($value > 0) {
1178 my $cfs_period_us = 100000;
1179 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
1180 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
1181 write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
1182 } else {
1183 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1184 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1185 write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
1186 }
1187 } elsif ($opt eq 'cpuunits') {
1188 $conf->{'lxc.cgroup.cpu.shares'} = $value;
1189 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1190 } elsif ($opt eq 'description') {
1191 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
1192 } elsif ($opt eq 'disk') {
1193 $conf->{'pve.disksize'} = $value;
1194 push @nohotplug, $opt;
1195 next if $running;
1196 } elsif ($opt =~ m/^net(\d+)$/) {
1197 my $netid = $1;
1198 my $net = PVE::LXC::parse_lxc_network($value);
1199 $net->{'veth.pair'} = "veth${vmid}.$netid";
1200 if (!$running) {
1201 $conf->{$opt} = $net;
1202 } else {
1203 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1204 }
1205 } else {
1206 die "implement me"
1207 }
1208 PVE::LXC::write_config($vmid, $conf) if $running;
1209 }
1210
1211 if ($running && scalar(@nohotplug)) {
1212 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1213 }
1214 }
1215
1216 sub get_primary_ips {
1217 my ($conf) = @_;
1218
1219 # return data from net0
1220
1221 my $net = $conf->{net0};
1222 return undef if !$net;
1223
1224 my $ipv4 = $net->{ip};
1225 $ipv4 =~ s!/\d+$!! if $ipv4;
1226 my $ipv6 = $net->{ip};
1227 $ipv6 =~ s!/\d+$!! if $ipv6;
1228
1229 return ($ipv4, $ipv6);
1230 }
1231
1232 sub destory_lxc_container {
1233 my ($storage_cfg, $vmid, $conf) = @_;
1234
1235 if (my $volid = $conf->{'pve.volid'}) {
1236
1237 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
1238 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1239
1240 PVE::Storage::vdisk_free($storage_cfg, $volid);
1241
1242 destroy_config($vmid);
1243
1244 } else {
1245 my $cmd = ['lxc-destroy', '-n', $vmid ];
1246
1247 PVE::Tools::run_command($cmd);
1248 }
1249 }
1250
1251 my $safe_num_ne = sub {
1252 my ($a, $b) = @_;
1253
1254 return 0 if !defined($a) && !defined($b);
1255 return 1 if !defined($a);
1256 return 1 if !defined($b);
1257
1258 return $a != $b;
1259 };
1260
1261 my $safe_string_ne = sub {
1262 my ($a, $b) = @_;
1263
1264 return 0 if !defined($a) && !defined($b);
1265 return 1 if !defined($a);
1266 return 1 if !defined($b);
1267
1268 return $a ne $b;
1269 };
1270
1271 sub update_net {
1272 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1273
1274 my $veth = $newnet->{'veth.pair'};
1275 my $vethpeer = $veth . "p";
1276 my $eth = $newnet->{name};
1277
1278 if ($conf->{$opt}) {
1279 if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
1280 &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
1281
1282 PVE::Network::veth_delete($veth);
1283 delete $conf->{$opt};
1284 PVE::LXC::write_config($vmid, $conf);
1285
1286 hotplug_net($vmid, $conf, $opt, $newnet);
1287
1288 } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) ||
1289 &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) ||
1290 &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) {
1291
1292 if ($conf->{$opt}->{bridge}){
1293 PVE::Network::tap_unplug($veth);
1294 delete $conf->{$opt}->{bridge};
1295 delete $conf->{$opt}->{tag};
1296 delete $conf->{$opt}->{firewall};
1297 PVE::LXC::write_config($vmid, $conf);
1298 }
1299
1300 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
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 PVE::LXC::write_config($vmid, $conf);
1305 }
1306 } else {
1307 hotplug_net($vmid, $conf, $opt, $newnet);
1308 }
1309
1310 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1311 }
1312
1313 sub hotplug_net {
1314 my ($vmid, $conf, $opt, $newnet) = @_;
1315
1316 my $veth = $newnet->{'veth.pair'};
1317 my $vethpeer = $veth . "p";
1318 my $eth = $newnet->{name};
1319
1320 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1321 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1322
1323 # attach peer in container
1324 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1325 PVE::Tools::run_command($cmd);
1326
1327 # link up peer in container
1328 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1329 PVE::Tools::run_command($cmd);
1330
1331 $conf->{$opt}->{type} = 'veth';
1332 $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
1333 $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
1334 $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
1335 $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
1336 $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
1337 $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
1338
1339 delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
1340 delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
1341 delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
1342 delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
1343
1344 PVE::LXC::write_config($vmid, $conf);
1345 }
1346
1347 sub update_ipconfig {
1348 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1349
1350 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1351
1352 my $optdata = $conf->{$opt};
1353 my $deleted = [];
1354 my $added = [];
1355 my $netcmd = sub {
1356 my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
1357 PVE::Tools::run_command($cmd);
1358 };
1359
1360 my $change_ip_config = sub {
1361 my ($ipversion) = @_;
1362
1363 my $family_opt = "-$ipversion";
1364 my $suffix = $ipversion == 4 ? '' : $ipversion;
1365 my $gw= "gw$suffix";
1366 my $ip= "ip$suffix";
1367
1368 my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
1369 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
1370
1371 return if !$change_ip && !$change_gw;
1372
1373 # step 1: add new IP, if this fails we cancel
1374 if ($change_ip && $newnet->{$ip}) {
1375 eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
1376 if (my $err = $@) {
1377 warn $err;
1378 return;
1379 }
1380 }
1381
1382 # step 2: replace gateway
1383 # If this fails we delete the added IP and cancel.
1384 # If it succeeds we save the config and delete the old IP, ignoring
1385 # errors. The config is then saved.
1386 # Note: 'ip route replace' can add
1387 if ($change_gw) {
1388 if ($newnet->{$gw}) {
1389 eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
1390 if (my $err = $@) {
1391 warn $err;
1392 # the route was not replaced, the old IP is still available
1393 # rollback (delete new IP) and cancel
1394 if ($change_ip) {
1395 eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
1396 warn $@ if $@; # no need to die here
1397 }
1398 return;
1399 }
1400 } else {
1401 eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
1402 # if the route was not deleted, the guest might have deleted it manually
1403 # warn and continue
1404 warn $@ if $@;
1405 }
1406 }
1407
1408 # from this point on we safe the configuration
1409 # step 3: delete old IP ignoring errors
1410 if ($change_ip && $optdata->{$ip}) {
1411 eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
1412 warn $@ if $@; # no need to die here
1413 }
1414
1415 foreach my $property ($ip, $gw) {
1416 if ($newnet->{$property}) {
1417 $optdata->{$property} = $newnet->{$property};
1418 } else {
1419 delete $optdata->{$property};
1420 }
1421 }
1422 PVE::LXC::write_config($vmid, $conf);
1423 $lxc_setup->setup_network($conf);
1424 };
1425
1426 &$change_ip_config(4);
1427 &$change_ip_config(6);
1428 }
1429
1430 1;