]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
Extend lxc config with options for snapshot
[pve-container.git] / src / PVE / LXC.pm
CommitLineData
f76a2828
DM
1package PVE::LXC;
2
3use strict;
4use warnings;
d14a9a1b 5use POSIX qw(EINTR);
f76a2828
DM
6
7use File::Path;
8use Fcntl ':flock';
9
10use PVE::Cluster qw(cfs_register_file cfs_read_file);
c65e0a6d 11use PVE::Storage;
f76a2828
DM
12use PVE::SafeSyslog;
13use PVE::INotify;
a3249355 14use PVE::JSONSchema qw(get_standard_option);
55fa4e09 15use PVE::Tools qw($IPV6RE $IPV4RE);
68fba17b 16use PVE::Network;
f76a2828
DM
17
18use Data::Dumper;
19
20cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
21
7dfc49cc
DM
22PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
23sub 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
f76a2828
DM
33my $nodename = PVE::INotify::nodename();
34
822de0c3
DM
35sub 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
f76a2828 50my $valid_lxc_keys = {
822de0c3 51 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
f76a2828
DM
52 'lxc.include' => 1,
53 'lxc.rootfs' => 1,
54 'lxc.mount' => 1,
55 'lxc.utsname' => 1,
56
54664cd3 57 'lxc.id_map' => 1,
cbb03fea 58
822de0c3 59 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
44da0641 60 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
b80dd50a
DM
61 'lxc.cgroup.cpu.cfs_period_us' => '\d+',
62 'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
63 'lxc.cgroup.cpu.shares' => '\d+',
cbb03fea 64
54664cd3
DM
65 # mount related
66 'lxc.mount' => 1,
67 'lxc.mount.entry' => 1,
68 'lxc.mount.auto' => 1,
69
70 # not used by pve
b80dd50a 71 'lxc.tty' => '\d+',
54664cd3
DM
72 'lxc.pts' => 1,
73 'lxc.haltsignal' => 1,
cbb03fea 74 'lxc.rebootsignal' => 1,
54664cd3
DM
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,
0d3abdf7 90 'lxc.cgroup.devices.deny' => 1,
54664cd3
DM
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,
cbb03fea 106
f76a2828 107 # pve related keys
117636e5
DM
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 },
a99b3509 116 'pve.onboot' => '(0|1)',
a3249355
DM
117 'pve.startup' => sub {
118 my ($name, $value) = @_;
119 return PVE::JSONSchema::pve_verify_startup_order($value);
120 },
f76a2828 121 'pve.comment' => 1,
10fc3ba5 122 'pve.disksize' => '\d+(\.\d+)?',
611fe3aa
DM
123 'pve.volid' => sub {
124 my ($name, $value) = @_;
125 PVE::Storage::parse_volume_id($value);
126 return $value;
127 },
a12a36e0
WL
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,
f76a2828
DM
136};
137
93285df8 138my $valid_lxc_network_keys = {
f76a2828 139 type => 1,
f76a2828
DM
140 mtu => 1,
141 name => 1, # ifname inside container
142 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
143 hwaddr => 1,
93285df8
DM
144};
145
146my $valid_pve_network_keys = {
8a04b6c7 147 bridge => 1,
2b1fc2ea
DM
148 tag => 1,
149 firewall => 1,
93285df8
DM
150 ip => 1,
151 gw => 1,
152 ip6 => 1,
153 gw6 => 1,
f76a2828
DM
154};
155
156my $lxc_array_configs = {
157 'lxc.network' => 1,
158 'lxc.mount' => 1,
159 'lxc.include' => 1,
5b4657d0 160 'lxc.id_map' => 1,
0d3abdf7 161 'lxc.cgroup.devices.deny' => 1,
f76a2828
DM
162};
163
164sub write_lxc_config {
165 my ($filename, $data) = @_;
166
167 my $raw = "";
168
169 return $raw if !$data;
170
171 my $done_hash = { digest => 1};
7dfc49cc 172
63e9c5ca 173 my $dump_entry = sub {
a12a36e0
WL
174 my ($k, $elem, $done_hash, $snap) = @_;
175 my $value = $elem->{$k};
63e9c5ca
DM
176 return if !defined($value);
177 return if $done_hash->{$k};
f76a2828 178 $done_hash->{$k} = 1;
63e9c5ca 179 if (ref($value)) {
cbb03fea 180 die "got unexpected reference for '$k'"
63e9c5ca
DM
181 if !$lxc_array_configs->{$k};
182 foreach my $v (@$value) {
a12a36e0 183 $raw .= "snap\." if $snap;
5b4657d0
DM
184 $raw .= "$k = $v\n";
185 }
186 } else {
a12a36e0 187 $raw .= "snap\." if $snap;
63e9c5ca 188 $raw .= "$k = $value\n";
5b4657d0 189 }
63e9c5ca
DM
190 };
191
a12a36e0
WL
192 my $config_writer = sub {
193 my ($elem, $snapshot) = @_;
cbb03fea 194
a12a36e0 195 my $done_hash = { digest => 1};
7dfc49cc 196
a12a36e0
WL
197 if ($elem->{'pve.snapname'}) {
198 &$dump_entry('pve.snapname', $elem, $done_hash, $snapshot);
199 }
fff3a342 200
a12a36e0
WL
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 }
93285df8 234 }
f76a2828 235 }
a12a36e0
WL
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 }
f76a2828 245
a12a36e0 246 };
160f0941 247
a12a36e0
WL
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 }
f76a2828
DM
258 }
259
f76a2828
DM
260 return $raw;
261}
262
822de0c3
DM
263sub parse_lxc_option {
264 my ($name, $value) = @_;
265
266 my $parser = $valid_lxc_keys->{$name};
267
fe81a350 268 die "invalid key '$name'\n" if !defined($parser);
822de0c3
DM
269
270 if ($parser eq '1') {
cbb03fea 271 return $value;
822de0c3
DM
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 }
cbb03fea 279
822de0c3
DM
280 die "unable to parse value '$value' for option '$name'\n";
281}
282
f76a2828
DM
283sub 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
a12a36e0
WL
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";
f76a2828
DM
308 }
309 }
a12a36e0 310 push(@{$sections},$tmp);
f76a2828 311
a12a36e0 312 return $sections;
f76a2828 313 };
7dfc49cc 314
f76a2828 315
a12a36e0 316 my $sec = &$split_config($raw);
7dfc49cc 317
a12a36e0
WL
318 foreach my $sec_raw (@{$sec}){
319 next if $sec_raw eq '';
320 my $snapname = undef;
f76a2828 321
a12a36e0
WL
322 my $network_counter = 0;
323 my $network_list = [];
324 my $host_ifnames = {};
f76a2828 325
a12a36e0
WL
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 }
f76a2828 333 }
f76a2828 334
a12a36e0
WL
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 }
5b4657d0 350 }
a12a36e0 351 };
cbb03fea 352
a12a36e0 353 my $network;
f76a2828 354
a12a36e0
WL
355 while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) {
356 my $line = $1;
f76a2828 357
a12a36e0
WL
358 next if $line =~ m/^\#/;
359 next if $line =~ m/^\s*$/;
f76a2828 360
a12a36e0
WL
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 }
f76a2828 417
a12a36e0
WL
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 }
f76a2828 437 }
a12a36e0 438
7dfc49cc
DM
439 }
440
441 return $data;
f76a2828
DM
442}
443
444sub 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
460sub cfs_config_path {
461 my ($vmid, $node) = @_;
462
463 $node = $nodename if !$node;
464 return "nodes/$node/lxc/$vmid/config";
465}
466
9c2d4ce9
DM
467sub config_file {
468 my ($vmid, $node) = @_;
469
470 my $cfspath = cfs_config_path($vmid, $node);
471 return "/etc/pve/$cfspath";
472}
473
f76a2828
DM
474sub 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
5b4657d0
DM
485sub 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
497sub destroy_config {
498 my ($vmid) = @_;
499
500 my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
501 File::Path::rmtree($dir);
502}
503
f76a2828
DM
504sub 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
9c2d4ce9
DM
512my $tempcounter = 0;
513sub write_temp_config {
514 my ($vmid, $conf) = @_;
7dfc49cc 515
9c2d4ce9
DM
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);
7dfc49cc 522
9c2d4ce9
DM
523 return $filename;
524}
525
d14a9a1b
DM
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
529my $lock_handles = {};
530my $lockdir = "/run/lock/lxc";
531
532sub lock_filename {
533 my ($vmid) = @_;
cbb03fea 534
d14a9a1b
DM
535 return "$lockdir/pve-config-{$vmid}.lock";
536}
537
538sub 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
f99e8278
AD
546 mkdir $lockdir if !-d $lockdir;
547
d14a9a1b
DM
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}++;
cbb03fea 571
d14a9a1b
DM
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";
cbb03fea 580 }
d14a9a1b
DM
581}
582
583sub 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
f76a2828
DM
597sub lock_container {
598 my ($vmid, $timeout, $code, @param) = @_;
599
d14a9a1b 600 my $res;
f76a2828 601
d14a9a1b
DM
602 lock_aquire($vmid, $timeout);
603 eval { $res = &$code(@param) };
604 my $err = $@;
605 lock_release($vmid);
f76a2828 606
d14a9a1b 607 die $err if $err;
f76a2828
DM
608
609 return $res;
610}
611
612my $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 },
a3249355 619 startup => get_standard_option('pve-startup-order'),
45573f7c 620 cpulimit => {
f76a2828 621 optional => 1,
8daf6a1c 622 type => 'number',
45573f7c 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.",
44da0641
DM
624 minimum => 0,
625 maximum => 128,
626 default => 0,
f76a2828
DM
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,
10fc3ba5 655 default => 4,
f76a2828
DM
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 },
ec52ac21
DM
678};
679
680my $MAX_LXC_NETWORKS = 10;
681for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
682 $confdesc->{"net$i"} = {
f76a2828
DM
683 optional => 1,
684 type => 'string', format => 'pve-lxc-network',
b156e7cd 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>]",
7dfc49cc 691 };
ec52ac21
DM
692}
693
694sub option_exists {
695 my ($name) = @_;
696
697 return defined($confdesc->{$name});
698}
f76a2828
DM
699
700# add JSON properties for create and set function
701sub 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
822de0c3
DM
711# container status helpers
712
713sub list_active_containers {
cbb03fea 714
822de0c3
DM
715 my $filename = "/proc/net/unix";
716
717 # similar test is used by lcxcontainers.c: list_active_containers
718 my $res = {};
cbb03fea 719
822de0c3
DM
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);
cbb03fea 733
822de0c3
DM
734 return $res;
735}
f76a2828 736
5c752bbf
DM
737# warning: this is slow
738sub check_running {
739 my ($vmid) = @_;
740
741 my $active_hash = list_active_containers();
742
743 return 1 if defined($active_hash->{$vmid});
cbb03fea 744
5c752bbf
DM
745 return undef;
746}
747
10fc3ba5
DM
748sub get_container_disk_usage {
749 my ($vmid) = @_;
750
751 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
cbb03fea 752
10fc3ba5
DM
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
f76a2828
DM
776sub vmstatus {
777 my ($opt_vmid) = @_;
778
779 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
780
822de0c3 781 my $active_hash = list_active_containers();
cbb03fea 782
f76a2828 783 foreach my $vmid (keys %$list) {
f76a2828 784 my $d = $list->{$vmid};
10fc3ba5
DM
785
786 my $running = defined($active_hash->{$vmid});
cbb03fea 787
10fc3ba5 788 $d->{status} = $running ? 'running' : 'stopped';
f76a2828
DM
789
790 my $cfspath = cfs_config_path($vmid);
238a56cb 791 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
cbb03fea 792
238a56cb
DM
793 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
794 $d->{name} =~ s/[\s]//g;
cbb03fea 795
44da0641
DM
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 }
cbb03fea 804
238a56cb 805 $d->{disk} = 0;
10fc3ba5
DM
806 $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
807 int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
cbb03fea 808
238a56cb 809 if (my $private = $conf->{'lxc.rootfs'}) {
10fc3ba5
DM
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) {
6ed8c6dd 815 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
10fc3ba5
DM
816 my $res = get_container_disk_usage($vmid);
817 $d->{disk} = $res->{used};
818 $d->{maxdisk} = $res->{total};
cbb03fea 819 }
10fc3ba5 820 }
238a56cb 821 }
cbb03fea 822
238a56cb
DM
823 $d->{mem} = 0;
824 $d->{swap} = 0;
825 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
44da0641 826 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
e901d418 827
238a56cb
DM
828 $d->{uptime} = 0;
829 $d->{cpu} = 0;
e901d418 830
238a56cb
DM
831 $d->{netout} = 0;
832 $d->{netin} = 0;
f76a2828 833
238a56cb
DM
834 $d->{diskread} = 0;
835 $d->{diskwrite} = 0;
f76a2828 836 }
cbb03fea 837
238a56cb
DM
838 foreach my $vmid (keys %$list) {
839 my $d = $list->{$vmid};
840 next if $d->{status} ne 'running';
f76a2828 841
22a77285
DM
842 $d->{uptime} = 100; # fixme:
843
238a56cb
DM
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};
b5289322
AD
846
847 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
1e647c7c 848 my @bytes = split(/\n/, $blkio_bytes);
b5289322 849 foreach my $byte (@bytes) {
1e647c7c
DM
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 }
b5289322 854 }
238a56cb 855 }
cbb03fea 856
f76a2828
DM
857 return $list;
858}
859
7dfc49cc
DM
860
861sub print_lxc_network {
f76a2828
DM
862 my $net = shift;
863
bedeaaf1 864 die "no network name defined\n" if !$net->{name};
f76a2828 865
bedeaaf1 866 my $res = "name=$net->{name}";
7dfc49cc 867
bedeaaf1 868 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
869 next if !defined($net->{$k});
870 $res .= ",$k=$net->{$k}";
871 }
7dfc49cc 872
f76a2828
DM
873 return $res;
874}
875
7dfc49cc
DM
876sub parse_lxc_network {
877 my ($data) = @_;
878
879 my $res = {};
880
881 return $res if !$data;
882
883 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 884 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
885 $res->{$1} = $2;
886 } else {
887 return undef;
888 }
889 }
890
891 $res->{type} = 'veth';
93cdbbfb 892 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
cbb03fea 893
7dfc49cc
DM
894 return $res;
895}
f76a2828 896
238a56cb
DM
897sub 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
bf0b8c43
AD
907sub 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
52f1d76b
DM
915sub 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>
cbb03fea 928 return if scalar(@args) != 3;
52f1d76b
DM
929 return if $args[1] ne '-n';
930 return if $args[2] !~ m/^\d+$/;
931 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
cbb03fea 932
52f1d76b 933 my $vmid = $args[2];
cbb03fea 934
52f1d76b
DM
935 push @{$res->{$vmid}}, $pid;
936 });
937
938 return $res;
939}
940
bedeaaf1
AD
941sub find_lxc_pid {
942 my ($vmid) = @_;
943
944 my $pid = undef;
945 my $parser = sub {
946 my $line = shift;
8b25977f 947 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
bedeaaf1
AD
948 };
949 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
950
8b25977f 951 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
cbb03fea 952
8b25977f 953 return $pid;
bedeaaf1
AD
954}
955
55fa4e09
DM
956my $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];
cbb03fea
DM
991
992# Note: we cannot use Net:IP, because that only allows strict
55fa4e09
DM
993# CIDR networks
994sub 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 }
cbb03fea 1000
55fa4e09 1001 return undef if $noerr;
cbb03fea 1002
55fa4e09
DM
1003 die "unable to parse ipv4 address/mask\n";
1004}
93285df8 1005
a12a36e0
WL
1006sub check_lock {
1007 my ($conf) = @_;
1008
1009 die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
1010}
1011
b80dd50a
DM
1012sub 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 }
a99b3509
DM
1025 } elsif ($k eq 'onboot') {
1026 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
a3249355
DM
1027 } elsif ($k eq 'startup') {
1028 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
b80dd50a
DM
1029 } elsif ($k eq 'hostname') {
1030 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
ffa1d001
DM
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'};
b80dd50a
DM
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 }
45573f7c 1044 } elsif ($k eq 'cpulimit') {
b80dd50a
DM
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'};
cbb03fea 1047
b80dd50a 1048 if ($cfs_period_us && $cfs_quota_us) {
8daf6a1c 1049 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
b80dd50a
DM
1050 } else {
1051 $conf->{$k} = 0;
1052 }
1053 } elsif ($k eq 'cpuunits') {
1054 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
10fc3ba5
DM
1055 } elsif ($k eq 'disk') {
1056 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
1057 $lxc_conf->{'pve.disksize'} : 0;
b80dd50a
DM
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 }
cbb03fea 1064
a12a36e0
WL
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
b80dd50a
DM
1077 return $conf;
1078}
1079
117636e5
DM
1080# verify and cleanup nameserver list (replace \0 with ' ')
1081sub 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
1093sub 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
93285df8
DM
1105sub update_lxc_config {
1106 my ($vmid, $conf, $running, $param, $delete) = @_;
1107
bf0b8c43
AD
1108 my @nohotplug;
1109
cbb03fea
DM
1110 my $rootdir;
1111 if ($running) {
bedeaaf1 1112 my $pid = find_lxc_pid($vmid);
cbb03fea 1113 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1114 }
1115
93285df8
DM
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') {
44da0641 1121 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
bf0b8c43 1122 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
93285df8
DM
1123 } elsif ($opt eq 'description') {
1124 delete $conf->{'pve.comment'};
a99b3509
DM
1125 } elsif ($opt eq 'onboot') {
1126 delete $conf->{'pve.onboot'};
a3249355
DM
1127 } elsif ($opt eq 'startup') {
1128 delete $conf->{'pve.startup'};
ffa1d001
DM
1129 } elsif ($opt eq 'nameserver') {
1130 delete $conf->{'pve.nameserver'};
bf0b8c43
AD
1131 push @nohotplug, $opt;
1132 next if $running;
ffa1d001
DM
1133 } elsif ($opt eq 'searchdomain') {
1134 delete $conf->{'pve.searchdomain'};
bf0b8c43
AD
1135 push @nohotplug, $opt;
1136 next if $running;
68fba17b 1137 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1138 delete $conf->{$opt};
68fba17b
AD
1139 next if !$running;
1140 my $netid = $1;
1141 PVE::Network::veth_delete("veth${vmid}.$netid");
93285df8
DM
1142 } else {
1143 die "implement me"
1144 }
bf0b8c43 1145 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8
DM
1146 }
1147 }
1148
1149 foreach my $opt (keys %$param) {
1150 my $value = $param->{$opt};
1151 if ($opt eq 'hostname') {
1152 $conf->{'lxc.utsname'} = $value;
a99b3509
DM
1153 } elsif ($opt eq 'onboot') {
1154 $conf->{'pve.onboot'} = $value ? 1 : 0;
a3249355
DM
1155 } elsif ($opt eq 'startup') {
1156 $conf->{'pve.startup'} = $value;
ffa1d001 1157 } elsif ($opt eq 'nameserver') {
117636e5 1158 my $list = verify_nameserver_list($value);
c325b32f 1159 $conf->{'pve.nameserver'} = $list;
bf0b8c43
AD
1160 push @nohotplug, $opt;
1161 next if $running;
ffa1d001 1162 } elsif ($opt eq 'searchdomain') {
117636e5 1163 my $list = verify_searchdomain_list($value);
c325b32f 1164 $conf->{'pve.searchdomain'} = $list;
bf0b8c43
AD
1165 push @nohotplug, $opt;
1166 next if $running;
93285df8
DM
1167 } elsif ($opt eq 'memory') {
1168 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
bf0b8c43 1169 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
93285df8 1170 } elsif ($opt eq 'swap') {
44da0641
DM
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;
bf0b8c43 1174 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
3a43e41a 1175
45573f7c 1176 } elsif ($opt eq 'cpulimit') {
44da0641
DM
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;
bf0b8c43 1181 write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
44da0641
DM
1182 } else {
1183 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1184 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
bf0b8c43 1185 write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
44da0641 1186 }
b80dd50a 1187 } elsif ($opt eq 'cpuunits') {
cbb03fea 1188 $conf->{'lxc.cgroup.cpu.shares'} = $value;
bf0b8c43 1189 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8
DM
1190 } elsif ($opt eq 'description') {
1191 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
10fc3ba5
DM
1192 } elsif ($opt eq 'disk') {
1193 $conf->{'pve.disksize'} = $value;
bf0b8c43
AD
1194 push @nohotplug, $opt;
1195 next if $running;
93285df8
DM
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";
cbb03fea 1200 if (!$running) {
bedeaaf1 1201 $conf->{$opt} = $net;
cbb03fea 1202 } else {
bedeaaf1
AD
1203 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1204 }
93285df8
DM
1205 } else {
1206 die "implement me"
1207 }
bf0b8c43 1208 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8 1209 }
bf0b8c43 1210
5cfa0567
DM
1211 if ($running && scalar(@nohotplug)) {
1212 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1213 }
93285df8 1214}
c325b32f
DM
1215
1216sub get_primary_ips {
1217 my ($conf) = @_;
1218
1219 # return data from net0
cbb03fea 1220
c325b32f
DM
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;
cbb03fea 1228
c325b32f
DM
1229 return ($ipv4, $ipv6);
1230}
148d1cb4
DM
1231
1232sub 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}
68fba17b 1250
93cdbbfb
AD
1251my $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
1261my $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
1271sub update_net {
bedeaaf1 1272 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb
AD
1273
1274 my $veth = $newnet->{'veth.pair'};
cbb03fea 1275 my $vethpeer = $veth . "p";
93cdbbfb
AD
1276 my $eth = $newnet->{name};
1277
bedeaaf1
AD
1278 if ($conf->{$opt}) {
1279 if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
cbb03fea 1280 &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
93cdbbfb
AD
1281
1282 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1283 delete $conf->{$opt};
1284 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1285
bedeaaf1
AD
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 }
93cdbbfb 1299
93cdbbfb 1300 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
bedeaaf1
AD
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);
93cdbbfb
AD
1305 }
1306 } else {
bedeaaf1 1307 hotplug_net($vmid, $conf, $opt, $newnet);
93cdbbfb
AD
1308 }
1309
bedeaaf1 1310 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1311}
1312
1313sub hotplug_net {
bedeaaf1 1314 my ($vmid, $conf, $opt, $newnet) = @_;
93cdbbfb
AD
1315
1316 my $veth = $newnet->{'veth.pair'};
cbb03fea 1317 my $vethpeer = $veth . "p";
93cdbbfb
AD
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
cbb03fea 1323 # attach peer in container
93cdbbfb
AD
1324 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1325 PVE::Tools::run_command($cmd);
1326
cbb03fea 1327 # link up peer in container
93cdbbfb
AD
1328 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1329 PVE::Tools::run_command($cmd);
bedeaaf1
AD
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);
93cdbbfb
AD
1345}
1346
68a05bb3 1347sub update_ipconfig {
bedeaaf1
AD
1348 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1349
1350 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1351
84e0c123
WB
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 };
2bfd1615 1359
84e0c123 1360 my $change_ip_config = sub {
f39002a6
DM
1361 my ($ipversion) = @_;
1362
1363 my $family_opt = "-$ipversion";
1364 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1365 my $gw= "gw$suffix";
1366 my $ip= "ip$suffix";
bedeaaf1 1367
84e0c123
WB
1368 my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
1369 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
bedeaaf1 1370
84e0c123 1371 return if !$change_ip && !$change_gw;
68a05bb3 1372
84e0c123
WB
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 }
bedeaaf1 1380 }
bedeaaf1 1381
84e0c123
WB
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 }
2bfd1615 1406 }
2bfd1615 1407
84e0c123
WB
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
bedeaaf1
AD
1413 }
1414
84e0c123
WB
1415 foreach my $property ($ip, $gw) {
1416 if ($newnet->{$property}) {
1417 $optdata->{$property} = $newnet->{$property};
1418 } else {
1419 delete $optdata->{$property};
1420 }
bedeaaf1 1421 }
84e0c123
WB
1422 PVE::LXC::write_config($vmid, $conf);
1423 $lxc_setup->setup_network($conf);
1424 };
bedeaaf1 1425
f39002a6
DM
1426 &$change_ip_config(4);
1427 &$change_ip_config(6);
68a05bb3
AD
1428}
1429
f76a2828 14301;