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