]> git.proxmox.com Git - pve-manager.git/blob - PVE/OpenVZ.pm
2b9297940c9333651d5b38787a6de361eed7da15
[pve-manager.git] / PVE / OpenVZ.pm
1 package PVE::OpenVZ;
2
3 use strict;
4 use LockFile::Simple;
5 use File::stat qw();
6 use POSIX qw (LONG_MAX);
7 use IO::Dir;
8 use IO::File;
9 use PVE::Tools qw(extract_param $IPV6RE $IPV4RE);
10 use PVE::ProcFSTools;
11 use PVE::Cluster qw(cfs_register_file cfs_read_file);
12 use PVE::SafeSyslog;
13 use PVE::INotify;
14 use PVE::JSONSchema;
15 use Digest::SHA;
16 use Encode;
17
18 use constant SCRIPT_EXT => qw (start stop mount umount premount postumount);
19
20 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
21 my $nodename = PVE::INotify::nodename();
22 my $global_vzconf = read_global_vz_config();
23 my $res_unlimited = LONG_MAX;
24
25 sub config_list {
26 my $vmlist = PVE::Cluster::get_vmlist();
27 my $res = {};
28 return $res if !$vmlist || !$vmlist->{ids};
29 my $ids = $vmlist->{ids};
30
31 foreach my $vmid (keys %$ids) {
32 next if !$vmid; # skip VE0
33 my $d = $ids->{$vmid};
34 next if !$d->{node} || $d->{node} ne $nodename;
35 next if !$d->{type} || $d->{type} ne 'openvz';
36 $res->{$vmid}->{type} = 'openvz';
37 }
38 return $res;
39 }
40
41 sub cfs_config_path {
42 my ($vmid, $node) = @_;
43
44 $node = $nodename if !$node;
45 return "nodes/$node/openvz/$vmid.conf";
46 }
47
48 sub config_file {
49 my ($vmid, $node) = @_;
50
51 my $cfspath = cfs_config_path($vmid, $node);
52 return "/etc/pve/$cfspath";
53 }
54
55 sub load_config {
56 my ($vmid) = @_;
57
58 my $cfspath = cfs_config_path($vmid);
59
60 my $conf = PVE::Cluster::cfs_read_file($cfspath);
61 die "container $vmid does not exists\n" if !defined($conf);
62
63 return $conf;
64 }
65
66 sub check_mounted {
67 my ($conf, $vmid) = @_;
68
69 my $root = get_rootdir($conf, $vmid);
70 return (-d "$root/etc" || -d "$root/proc");
71 }
72
73 # warning: this is slow
74 sub check_running {
75 my ($vmid) = @_;
76
77 if (my $fh = new IO::File ("/proc/vz/vestat", "r")) {
78 while (defined (my $line = <$fh>)) {
79 if ($line =~ m/^\s*(\d+)\s+/) {
80 if ($vmid == $1) {
81 close($fh);
82 return 1;
83 }
84 }
85 }
86 close($fh);
87 }
88 return undef;
89 }
90
91 sub get_privatedir {
92 my ($conf, $vmid) = @_;
93
94 my $private = $global_vzconf->{privatedir};
95 if ($conf->{ve_private} && $conf->{ve_private}->{value}) {
96 $private = $conf->{ve_private}->{value};
97 }
98 $private =~ s/\$VEID/$vmid/;
99
100 return $private;
101 }
102
103 sub get_rootdir {
104 my ($conf, $vmid) = @_;
105
106 my $root = $global_vzconf->{rootdir};
107 if ($conf && $conf->{ve_root} && $conf->{ve_root}->{value}) {
108 $root = $conf->{ve_root}->{value};
109 }
110 $root =~ s/\$VEID/$vmid/;
111
112 return $root;
113 }
114
115 sub read_user_beancounters {
116 my $ubc = {};
117
118 if (my $fh = IO::File->new ("/proc/bc/resources", "r")) {
119 my $vmid;
120 while (defined (my $line = <$fh>)) {
121 if ($line =~ m|\s*((\d+):\s*)?([a-z]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$|) {
122 $vmid = $2 if defined($2);
123 next if !defined($vmid);
124 my ($name, $held, $maxheld, $bar, $lim, $failcnt) = (lc($3), $4, $5, $6, $7, $8);
125 next if $name eq 'dummy';
126 $ubc->{$vmid}->{failcntsum} += $failcnt;
127 $ubc->{$vmid}->{$name} = {
128 held => $held,
129 maxheld => $maxheld,
130 bar => $bar,
131 lim => $lim,
132 failcnt => $failcnt,
133 };
134 }
135 }
136 close($fh);
137 }
138
139 return $ubc;
140 }
141
142 sub read_container_network_usage {
143 my ($vmid) = @_;
144
145 my $recv = 0;
146 my $trmt = 0;
147
148 my $netparser = sub {
149 my $line = shift;
150 if ($line =~ m/^\s*(.*):\s*(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+/) {
151 return if $1 eq 'lo';
152 $recv += $2;
153 $trmt += $3;
154 }
155 };
156
157 # fixme: can we get that info directly (with vzctl exec)?
158 my $cmd = ['/usr/sbin/vzctl', 'exec', $vmid, '/bin/cat', '/proc/net/dev'];
159 eval { PVE::Tools::run_command($cmd, outfunc => $netparser); };
160 my $err = $@;
161 syslog('err', $err) if $err;
162
163 return ($recv, $trmt);
164 };
165
166 sub read_container_blkio_stat {
167 my ($vmid) = @_;
168
169 my $read = 0;
170 my $write = 0;
171
172 my $filename = "/proc/vz/beancounter/$vmid/blkio.io_service_bytes";
173 if (my $fh = IO::File->new ($filename, "r")) {
174
175 while (defined (my $line = <$fh>)) {
176 if ($line =~ m/^\S+\s+Read\s+(\d+)$/) {
177 $read += $1;
178 } elsif ($line =~ m/^\S+\s+Write\s+(\d+)$/) {
179 $write += $1;
180 }
181 }
182 }
183
184 return ($read, $write);
185 };
186
187 my $last_proc_vestat = {};
188
189 sub vmstatus {
190 my ($opt_vmid) = @_;
191
192 my $list = $opt_vmid ? { $opt_vmid => { type => 'openvz' }} : config_list();
193
194 my $cpucount = $cpuinfo->{cpus} || 1;
195
196 foreach my $vmid (keys %$list) {
197 next if $opt_vmid && ($vmid ne $opt_vmid);
198
199 my $d = $list->{$vmid};
200 $d->{status} = 'stopped';
201
202 my $cfspath = cfs_config_path($vmid);
203 if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
204 $d->{name} = $conf->{hostname}->{value} || "CT$vmid";
205 $d->{name} =~ s/[\s]//g;
206
207 $d->{cpus} = $conf->{cpus}->{value} || 1;
208 $d->{cpus} = $cpucount if $d->{cpus} > $cpucount;
209
210 $d->{disk} = 0;
211 $d->{maxdisk} = int($conf->{diskspace}->{bar} * 1024);
212
213 $d->{mem} = 0;
214 $d->{swap} = 0;
215
216 ($d->{maxmem}, $d->{maxswap}) = ovz_config_extract_mem_swap($conf);
217
218 $d->{nproc} = 0;
219 $d->{failcnt} = 0;
220
221 $d->{uptime} = 0;
222 $d->{cpu} = 0;
223
224 $d->{netout} = 0;
225 $d->{netin} = 0;
226
227 $d->{diskread} = 0;
228 $d->{diskwrite} = 0;
229
230 if (my $ip = $conf->{ip_address}->{value}) {
231 $ip =~ s/,;/ /g;
232 $d->{ip} = (split(/\s+/, $ip))[0];
233 } else {
234 $d->{ip} = '-';
235 }
236
237 $d->{status} = 'mounted' if check_mounted($conf, $vmid);
238
239 } else {
240 delete $list->{$vmid};
241 }
242 }
243
244 my $maxpages = ($res_unlimited / 4096);
245 my $ubchash = read_user_beancounters();
246 foreach my $vmid (keys %$ubchash) {
247 my $d = $list->{$vmid};
248 my $ubc = $ubchash->{$vmid};
249 if ($d && defined($d->{status}) && $ubc) {
250 $d->{failcnt} = $ubc->{failcntsum};
251 $d->{mem} = $ubc->{physpages}->{held} * 4096;
252 if ($ubc->{swappages}->{held} < $maxpages) {
253 $d->{swap} = $ubc->{swappages}->{held} * 4096
254 }
255 $d->{nproc} = $ubc->{numproc}->{held};
256 }
257 }
258
259 if (my $fh = IO::File->new ("/proc/vz/vzquota", "r")) {
260 while (defined (my $line = <$fh>)) {
261 if ($line =~ m|^(\d+):\s+\S+/private/\d+$|) {
262 my $vmid = $1;
263 my $d = $list->{$vmid};
264 if ($d && defined($d->{status})) {
265 $line = <$fh>;
266 if ($line =~ m|^\s*1k-blocks\s+(\d+)\s+(\d+)\s|) {
267 $d->{disk} = int ($1 * 1024);
268 $d->{maxdisk} = int ($2 * 1024);
269 }
270 }
271 }
272 }
273 close($fh);
274 }
275
276 # Note: OpenVZ does not use POSIX::_SC_CLK_TCK
277 my $hz = 1000;
278
279 # see http://wiki.openvz.org/Vestat
280 if (my $fh = new IO::File ("/proc/vz/vestat", "r")) {
281 while (defined (my $line = <$fh>)) {
282 if ($line =~ m/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+/) {
283 my $vmid = $1;
284 my $user = $2;
285 my $nice = $3;
286 my $system = $4;
287 my $ut = $5;
288 my $sum = $8*$cpucount; # uptime in jiffies * cpus = available jiffies
289 my $used = $9; # used time in jiffies
290
291 my $uptime = int ($ut / $hz);
292
293 my $d = $list->{$vmid};
294 next if !($d && defined($d->{status}));
295
296 $d->{status} = 'running';
297 $d->{uptime} = $uptime;
298
299 if (!defined ($last_proc_vestat->{$vmid}) ||
300 ($last_proc_vestat->{$vmid}->{sum} > $sum)) {
301 $last_proc_vestat->{$vmid} = { used => 0, sum => 0, cpu => 0 };
302 }
303
304 my $diff = $sum - $last_proc_vestat->{$vmid}->{sum};
305
306 if ($diff > 1000) { # don't update too often
307 my $useddiff = $used - $last_proc_vestat->{$vmid}->{used};
308 my $cpu = (($useddiff/$diff) * $cpucount) / $d->{cpus};
309 $last_proc_vestat->{$vmid}->{sum} = $sum;
310 $last_proc_vestat->{$vmid}->{used} = $used;
311 $last_proc_vestat->{$vmid}->{cpu} = $d->{cpu} = $cpu;
312 } else {
313 $d->{cpu} = $last_proc_vestat->{$vmid}->{cpu};
314 }
315 }
316 }
317 close($fh);
318 }
319
320 foreach my $vmid (keys %$list) {
321 my $d = $list->{$vmid};
322 next if !$d || !$d->{status} || $d->{status} ne 'running';
323 ($d->{netin}, $d->{netout}) = read_container_network_usage($vmid);
324 ($d->{diskread}, $d->{diskwrite}) = read_container_blkio_stat($vmid);
325 }
326
327 return $list;
328 }
329
330 my $confdesc = {
331 onboot => {
332 optional => 1,
333 type => 'boolean',
334 description => "Specifies whether a VM will be started during system bootup.",
335 default => 0,
336 },
337 cpus => {
338 optional => 1,
339 type => 'integer',
340 description => "The number of CPUs for this container.",
341 minimum => 1,
342 default => 1,
343 },
344 cpuunits => {
345 optional => 1,
346 type => 'integer',
347 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.",
348 minimum => 0,
349 maximum => 500000,
350 default => 1000,
351 },
352 memory => {
353 optional => 1,
354 type => 'integer',
355 description => "Amount of RAM for the VM in MB.",
356 minimum => 16,
357 default => 512,
358 },
359 swap => {
360 optional => 1,
361 type => 'integer',
362 description => "Amount of SWAP for the VM in MB.",
363 minimum => 0,
364 default => 512,
365 },
366 disk => {
367 optional => 1,
368 type => 'number',
369 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
370 minimum => 0,
371 default => 2,
372 },
373 quotatime => {
374 optional => 1,
375 type => 'integer',
376 description => "Set quota grace period (seconds).",
377 minimum => 0,
378 default => 0,
379 },
380 quotaugidlimit => {
381 optional => 1,
382 type => 'integer',
383 description => "Set maximum number of user/group IDs in a container for which disk quota inside the container will be accounted. If this value is set to 0, user and group quotas inside the container will not.",
384 minimum => 0,
385 default => 0,
386 },
387 hostname => {
388 optional => 1,
389 description => "Set a host name for the container.",
390 type => 'string',
391 maxLength => 255,
392 },
393 description => {
394 optional => 1,
395 type => 'string',
396 description => "Container description. Only used on the configuration web interface.",
397 },
398 searchdomain => {
399 optional => 1,
400 type => 'string',
401 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
402 },
403 nameserver => {
404 optional => 1,
405 type => 'string',
406 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.",
407 },
408 ip_address => {
409 optional => 1,
410 type => 'string',
411 description => "Specifies the address the container will be assigned.",
412 },
413 netif => {
414 optional => 1,
415 type => 'string', format => 'pve-openvz-netif',
416 description => "Specifies network interfaces for the container.",
417 },
418 };
419
420 # add JSON properties for create and set function
421 sub json_config_properties {
422 my $prop = shift;
423
424 foreach my $opt (keys %$confdesc) {
425 $prop->{$opt} = $confdesc->{$opt};
426 }
427
428 return $prop;
429 }
430
431 # read global vz.conf
432 sub read_global_vz_config {
433
434 my $res = {
435 rootdir => '/var/lib/vz/root/$VEID', # note '$VEID' is a place holder
436 privatedir => '/var/lib/vz/private/$VEID', # note '$VEID' is a place holder
437 dumpdir => '/var/lib/vz/dump',
438 lockdir => '/var/lib/vz/lock',
439 };
440
441 my $filename = "/etc/vz/vz.conf";
442
443 return $res if ! -f $filename;
444
445 my $data = PVE::Tools::file_get_contents($filename);
446
447 if ($data =~ m/^\s*VE_PRIVATE=(.*)$/m) {
448 my $dir = $1;
449 $dir =~ s/^\"(.*)\"/$1/;
450 if ($dir !~ m/\$VEID/) {
451 warn "VE_PRIVATE does not contain '\$VEID' ('$dir')\n";
452 } else {
453 $res->{privatedir} = $dir;
454 }
455 }
456 if ($data =~ m/^\s*VE_ROOT=(.*)$/m) {
457 my $dir = $1;
458 $dir =~ s/^\"(.*)\"/$1/;
459 if ($dir !~ m/\$VEID/) {
460 warn "VE_ROOT does not contain '\$VEID' ('$dir')\n";
461 } else {
462 $res->{rootdir} = $dir;
463 }
464 }
465 if ($data =~ m/^\s*DUMPDIR=(.*)$/m) {
466 my $dir = $1;
467 $dir =~ s/^\"(.*)\"/$1/;
468 $dir =~ s|/\$VEID$||;
469 $res->{dumpdir} = $dir;
470 }
471 if ($data =~ m/^\s*LOCKDIR=(.*)$/m) {
472 my $dir = $1;
473 $dir =~ s/^\"(.*)\"/$1/;
474 $res->{lockdir} = $dir;
475 }
476
477 return $res;
478 };
479
480 sub parse_netif {
481 my ($data, $vmid) = @_;
482
483 my $res = {};
484 return $res if !$data;
485
486 my $host_ifnames = {};
487
488 my $find_next_hostif_name = sub {
489 for (my $i = 0; $i < 100; $i++) {
490 my $name = "veth${vmid}.$i";
491 if (!$host_ifnames->{$name}) {
492 $host_ifnames->{$name} = 1;
493 return $name;
494 }
495 }
496
497 die "unable to find free host_ifname"; # should not happen
498 };
499
500 foreach my $iface (split (/;/, $data)) {
501 my $d = {};
502 foreach my $pv (split (/,/, $iface)) {
503 if ($pv =~ m/^(ifname|mac|bridge|host_ifname|host_mac|mac_filter)=(.+)$/) {
504 if ($1 eq 'host_ifname') {
505 $d->{$1} = $2;
506 $host_ifnames->{$2} = $1;
507 } elsif ($1 eq 'mac_filter') {
508 $d->{$1} = parse_boolean('mac_filter', $2);
509 } else {
510 $d->{$1} = $2;
511 }
512 }
513 }
514 if ($d->{ifname}) {
515 $d->{mac} = PVE::Tools::random_ether_addr() if !$d->{mac};
516 $d->{host_mac} = PVE::Tools::random_ether_addr() if !$d->{host_mac};
517 $d->{raw} = print_netif($d);
518 $res->{$d->{ifname}} = $d;
519 } else {
520 return undef;
521 }
522 }
523
524 foreach my $iface (keys %$res) {
525 my $d = $res->{$iface};
526 if ($vmid && !$d->{host_ifname}) {
527 $d->{host_ifname} = &$find_next_hostif_name($iface);
528 }
529 }
530
531 return $res;
532 }
533
534 sub print_netif {
535 my $net = shift;
536
537 my $res = "ifname=$net->{ifname}";
538 $res .= ",mac=$net->{mac}" if $net->{mac};
539 $res .= ",host_ifname=$net->{host_ifname}" if $net->{host_ifname};
540 $res .= ",host_mac=$net->{host_mac}" if $net->{host_mac};
541 $res .= ",bridge=$net->{bridge}" if $net->{bridge};
542
543 if (defined($net->{mac_filter}) && !$net->{mac_filter}) {
544 $res .= ",mac_filter=off"; # 'on' is the default
545 }
546
547 return $res;
548 }
549
550 PVE::JSONSchema::register_format('pve-openvz-netif', \&verify_netif);
551 sub verify_netif {
552 my ($value, $noerr) = @_;
553
554 return $value if parse_netif($value);
555
556 return undef if $noerr;
557
558 die "unable to parse --netif value";
559 }
560
561 sub parse_res_num_ignore {
562 my ($key, $text) = @_;
563
564 if ($text =~ m/^(\d+|unlimited)(:.*)?$/) {
565 return { bar => $1 eq 'unlimited' ? $res_unlimited : $1 };
566 }
567
568 return undef;
569 }
570
571 sub parse_res_num_num {
572 my ($key, $text) = @_;
573
574 if ($text =~ m/^(\d+|unlimited)(:(\d+|unlimited))?$/) {
575 my $res = { bar => $1 eq 'unlimited' ? $res_unlimited : $1 };
576 if (defined($3)) {
577 $res->{lim} = $3 eq 'unlimited' ? $res_unlimited : $3;
578 } else {
579 $res->{lim} = $res->{bar};
580 }
581 return $res;
582 }
583
584 return undef;
585 }
586
587 sub parse_res_bar_limit {
588 my ($text, $base) = @_;
589
590 return $res_unlimited if $text eq 'unlimited';
591
592 if ($text =~ m/^(\d+)([TGMKP])?$/i) {
593 my $val = $1;
594 my $mult = $2 ? lc($2) : '';
595 if ($mult eq 'k') {
596 $val = $val * 1024;
597 } elsif ($mult eq 'm') {
598 $val = $val * 1024 * 1024;
599 } elsif ($mult eq 'g') {
600 $val = $val * 1024 * 1024 * 1024;
601 } elsif ($mult eq 't') {
602 $val = $val * 1024 * 1024 * 1024 * 1024;
603 } elsif ($mult eq 'p') {
604 $val = $val * 4096;
605 } else {
606 return $val;
607 }
608 return int($val/$base);
609 }
610
611 return undef;
612 }
613
614 sub parse_res_bytes_bytes {
615 my ($key, $text) = @_;
616
617 my @a = split(/:/, $text);
618 $a[1] = $a[0] if !defined($a[1]);
619
620 my $bar = parse_res_bar_limit($a[0], 1);
621 my $lim = parse_res_bar_limit($a[1], 1);
622
623 if (defined($bar) && defined($lim)) {
624 return { bar => $bar, lim => $lim };
625 }
626
627 return undef;
628 }
629
630 sub parse_res_block_block {
631 my ($key, $text) = @_;
632
633 my @a = split(/:/, $text);
634 $a[1] = $a[0] if !defined($a[1]);
635
636 my $bar = parse_res_bar_limit($a[0], 1024);
637 my $lim = parse_res_bar_limit($a[1], 1024);
638
639 if (defined($bar) && defined($lim)) {
640 return { bar => $bar, lim => $lim };
641 }
642
643 return undef;
644 }
645
646 sub parse_res_pages_pages {
647 my ($key, $text) = @_;
648
649 my @a = split(/:/, $text);
650 $a[1] = $a[0] if !defined($a[1]);
651
652 my $bar = parse_res_bar_limit($a[0], 4096);
653 my $lim = parse_res_bar_limit($a[1], 4096);
654
655 if (defined($bar) && defined($lim)) {
656 return { bar => $bar, lim => $lim };
657 }
658
659 return undef;
660 }
661
662 sub parse_res_pages_unlimited {
663 my ($key, $text) = @_;
664
665 my @a = split(/:/, $text);
666
667 my $bar = parse_res_bar_limit($a[0], 4096);
668
669 if (defined($bar)) {
670 return { bar => $bar, lim => $res_unlimited };
671 }
672
673 return undef;
674 }
675
676 sub parse_res_pages_ignore {
677 my ($key, $text) = @_;
678
679 my @a = split(/:/, $text);
680
681 my $bar = parse_res_bar_limit($a[0], 4096);
682
683 if (defined($bar)) {
684 return { bar => $bar };
685 }
686
687 return undef;
688 }
689
690 sub parse_res_ignore_pages {
691 my ($key, $text) = @_;
692
693 my @a = split(/:/, $text);
694 $a[1] = $a[0] if !defined($a[1]);
695
696 my $lim = parse_res_bar_limit($a[1] , 4096);
697
698 if (defined($lim)) {
699 return { bar => 0, lim => $lim };
700 }
701
702 return undef;
703 }
704
705 sub parse_boolean {
706 my ($key, $text) = @_;
707
708 return { value => 1 } if $text =~ m/^(yes|true|on|1)$/i;
709 return { value => 0 } if $text =~ m/^(no|false|off|0)$/i;
710
711 return undef;
712 };
713
714 sub parse_integer {
715 my ($key, $text) = @_;
716
717 if ($text =~ m/^(\d+)$/) {
718 return { value => int($1) };
719 }
720
721 return undef;
722 };
723
724 my $ovz_ressources = {
725 numproc => \&parse_res_num_ignore,
726 numtcpsock => \&parse_res_num_ignore,
727 numothersock => \&parse_res_num_ignore,
728 numfile => \&parse_res_num_ignore,
729 numflock => \&parse_res_num_num,
730 numpty => \&parse_res_num_ignore,
731 numsiginfo => \&parse_res_num_ignore,
732 numiptent => \&parse_res_num_ignore,
733
734 vmguarpages => \&parse_res_pages_unlimited,
735 oomguarpages => \&parse_res_pages_unlimited,
736 lockedpages => \&parse_res_pages_ignore,
737 privvmpages => \&parse_res_pages_pages,
738 shmpages => \&parse_res_pages_ignore,
739 physpages => \&parse_res_pages_pages,
740 swappages => \&parse_res_ignore_pages,
741
742 kmemsize => \&parse_res_bytes_bytes,
743 tcpsndbuf => \&parse_res_bytes_bytes,
744 tcprcvbuf => \&parse_res_bytes_bytes,
745 othersockbuf => \&parse_res_bytes_bytes,
746 dgramrcvbuf => \&parse_res_bytes_bytes,
747 dcachesize => \&parse_res_bytes_bytes,
748
749 disk_quota => \&parse_boolean,
750 diskspace => \&parse_res_block_block,
751 diskinodes => \&parse_res_num_num,
752 quotatime => \&parse_integer,
753 quotaugidlimit => \&parse_integer,
754
755 cpuunits => \&parse_integer,
756 cpulimit => \&parse_integer,
757 cpus => \&parse_integer,
758 cpumask => 'string',
759 meminfo => 'string',
760 iptables => 'string',
761
762 ip_address => 'string',
763 netif => 'string',
764 hostname => 'string',
765 nameserver => 'string',
766 searchdomain => 'string',
767
768 name => 'string',
769 description => 'string',
770 onboot => \&parse_boolean,
771 initlog => \&parse_boolean,
772 bootorder => \&parse_integer,
773 ostemplate => 'string',
774 ve_root => 'string',
775 ve_private => 'string',
776 disabled => \&parse_boolean,
777 origin_sample => 'string',
778 noatime => \&parse_boolean,
779 capability => 'string',
780 devnodes => 'string',
781 devices => 'string',
782 pci => 'string',
783 features => 'string',
784 ioprio => \&parse_integer,
785
786 };
787
788 sub parse_ovz_config {
789 my ($filename, $raw) = @_;
790
791 return undef if !defined($raw);
792
793 my $data = {
794 digest => Digest::SHA::sha1_hex($raw),
795 };
796
797 $filename =~ m|/openvz/(\d+)\.conf$|
798 || die "got strange filename '$filename'";
799
800 my $vmid = $1;
801
802 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
803 my $line = $1;
804
805 next if $line =~ m/^\#/;
806 next if $line =~ m/^\s*$/;
807
808 if ($line =~ m/^\s*([A-Z][A-Z0-9_]*)\s*=\s*\"(.*)\"\s*$/i) {
809 my $name = lc($1);
810 my $text = $2;
811
812 my $parser = $ovz_ressources->{$name};
813 if (!$parser || !ref($parser)) {
814 $data->{$name}->{value} = $text;
815 next;
816 } else {
817 if (my $res = &$parser($name, $text)) {
818 $data->{$name} = $res;
819 next;
820 }
821 }
822 }
823 die "unable to parse config line: $line\n";
824 }
825
826 return $data;
827 }
828
829 cfs_register_file('/openvz/', \&parse_ovz_config);
830
831 sub format_res_value {
832 my ($key, $value) = @_;
833
834 return 'unlimited' if $value == $res_unlimited;
835
836 return 0 if $value == 0;
837
838 if ($key =~ m/pages$/) {
839 my $bytes = $value * 4096;
840 my $mb = int ($bytes / (1024 * 1024));
841 return "${mb}M" if $mb * 1024 * 1024 == $bytes;
842 } elsif ($key =~ m/space$/) {
843 my $bytes = $value * 1024;
844 my $gb = int ($bytes / (1024 * 1024 * 1024));
845 return "${gb}G" if $gb * 1024 * 1024 * 1024 == $bytes;
846 my $mb = int ($bytes / (1024 * 1024));
847 return "${mb}M" if $mb * 1024 * 1024 == $bytes;
848 } elsif ($key =~ m/size$/) {
849 my $bytes = $value;
850 my $mb = int ($bytes / (1024 * 1024));
851 return "${mb}M" if $mb * 1024 * 1024 == $bytes;
852 }
853
854 return $value;
855 }
856
857 sub format_res_bar_lim {
858 my ($key, $data) = @_;
859
860 if (defined($data->{lim}) && ($data->{lim} ne $data->{bar})) {
861 return format_res_value($key, $data->{bar}) . ":" . format_res_value($key, $data->{lim});
862 } else {
863 return format_res_value($key, $data->{bar});
864 }
865 }
866
867 sub create_config_line {
868 my ($key, $data) = @_;
869
870 my $text;
871
872 if (defined($data->{value})) {
873 if ($confdesc->{$key} && $confdesc->{$key}->{type} eq 'boolean') {
874 my $txt = $data->{value} ? 'yes' : 'no';
875 $text .= uc($key) . "=\"$txt\"\n";
876 } else {
877 $text .= uc($key) . "=\"$data->{value}\"\n";
878 }
879 } elsif (defined($data->{bar})) {
880 my $tmp = format_res_bar_lim($key, $data);
881 $text .= uc($key) . "=\"$tmp\"\n";
882 }
883 }
884
885 sub ovz_config_extract_mem_swap {
886 my ($veconf, $unit) = @_;
887
888 $unit = 1 if !$unit;
889
890 my ($mem, $swap) = (int((512*1024*1024 + $unit - 1)/$unit), 0);
891
892 my $maxpages = ($res_unlimited / 4096);
893
894 if ($veconf->{swappages}) {
895 if ($veconf->{physpages} && $veconf->{physpages}->{lim} &&
896 ($veconf->{physpages}->{lim} < $maxpages)) {
897 $mem = int(($veconf->{physpages}->{lim} * 4096 + $unit - 1) / $unit);
898 }
899 if ($veconf->{swappages}->{lim} && ($veconf->{swappages}->{lim} < $maxpages)) {
900 $swap = int (($veconf->{swappages}->{lim} * 4096 + $unit - 1) / $unit);
901 }
902 } else {
903 if ($veconf->{vmguarpages} && $veconf->{vmguarpages}->{bar} &&
904 ($veconf->{vmguarpages}->{bar} < $maxpages)) {
905 $mem = int(($veconf->{vmguarpages}->{bar} * 4096 + $unit - 1) / $unit);
906 }
907 }
908
909 return ($mem, $swap);
910 }
911
912 sub update_ovz_config {
913 my ($vmid, $veconf, $param) = @_;
914
915 my $changes = [];
916
917 # test if barrier or limit changed
918 my $push_bl_changes = sub {
919 my ($name, $bar, $lim) = @_;
920 my $old = format_res_bar_lim($name, $veconf->{$name})
921 if $veconf->{$name} && defined($veconf->{$name}->{bar});
922 my $new = format_res_bar_lim($name, { bar => $bar, lim => $lim });
923 if (!$old || ($old ne $new)) {
924 $veconf->{$name}->{bar} = $bar;
925 $veconf->{$name}->{lim} = $lim;
926 push @$changes, "--$name", $new;
927 }
928 };
929
930 my ($mem, $swap) = ovz_config_extract_mem_swap($veconf, 1024*1024);
931 my $disk = ($veconf->{diskspace}->{bar} || $res_unlimited) / (1024*1024);
932 my $cpuunits = $veconf->{cpuunits}->{value} || 1000;
933 my $quotatime = $veconf->{quotatime}->{value} || 0;
934 my $quotaugidlimit = $veconf->{quotaugidlimit}->{value} || 0;
935 my $cpus = $veconf->{cpus}->{value} || 1;
936
937 if ($param->{memory}) {
938 $mem = $param->{memory};
939 }
940
941 if (defined ($param->{swap})) {
942 $swap = $param->{swap};
943 }
944
945 if ($param->{disk}) {
946 $disk = $param->{disk};
947 }
948
949 if ($param->{cpuunits}) {
950 $cpuunits = $param->{cpuunits};
951 }
952
953 if (defined($param->{quotatime})) {
954 $quotatime = $param->{quotatime};
955 }
956
957 if (defined($param->{quotaugidlimit})) {
958 $quotaugidlimit = $param->{quotaugidlimit};
959 }
960
961 if ($param->{cpus}) {
962 $cpus = $param->{cpus};
963 }
964
965 # memory related parameter
966
967 &$push_bl_changes('vmguarpages', 0, $res_unlimited);
968 &$push_bl_changes('oomguarpages', 0, $res_unlimited);
969 &$push_bl_changes('privvmpages', $res_unlimited, $res_unlimited);
970
971 # lock half of $mem
972 my $lockedpages = int($mem*1024/8);
973 &$push_bl_changes('lockedpages', $lockedpages, undef);
974
975 my $kmemsize = int($mem/2);
976 &$push_bl_changes('kmemsize', int($kmemsize/1.1)*1024*1024, $kmemsize*1024*1024);
977
978 my $dcachesize = int($mem/4);
979 &$push_bl_changes('dcachesize', int($dcachesize/1.1)*1024*1024, $dcachesize*1024*1024);
980
981 my $physpages = int($mem*1024/4);
982 &$push_bl_changes('physpages', 0, $physpages);
983
984 my $swappages = int($swap*1024/4);
985 &$push_bl_changes('swappages', 0, $swappages);
986
987
988 # disk quota parameters
989 if (!$disk || ($disk * 1.1) >= ($res_unlimited / (1024 * 1024))) {
990 &$push_bl_changes('diskspace', $res_unlimited, $res_unlimited);
991 &$push_bl_changes('diskinodes', $res_unlimited, $res_unlimited);
992 } else {
993 my $diskspace = int ($disk * 1024 * 1024);
994 my $diskspace_lim = int ($diskspace * 1.1);
995 &$push_bl_changes('diskspace', $diskspace, $diskspace_lim);
996 my $diskinodes = int ($disk * 200000);
997 my $diskinodes_lim = int ($disk * 220000);
998 &$push_bl_changes('diskinodes', $diskinodes, $diskinodes_lim);
999 }
1000 if ($veconf->{'quotatime'}->{value} != $quotatime) {
1001 $veconf->{'quotatime'}->{value} = $quotatime;
1002 push @$changes, '--quotatime', "$quotatime";
1003 }
1004
1005 if ($veconf->{'quotaugidlimit'}->{value} != $quotaugidlimit) {
1006 $veconf->{'quotaugidlimit'}->{value} = $quotaugidlimit;
1007 push @$changes, '--quotaugidlimit', "$quotaugidlimit";
1008 }
1009
1010 # cpu settings
1011
1012 if ($veconf->{'cpuunits'}->{value} != $cpuunits) {
1013 $veconf->{'cpuunits'}->{value} = $cpuunits;
1014 push @$changes, '--cpuunits', "$cpuunits";
1015 }
1016
1017 if ($veconf->{'cpus'}->{value} != $cpus) {
1018 $veconf->{'cpus'}->{value} = $cpus;
1019 push @$changes, '--cpus', "$cpus";
1020 }
1021
1022 my $cond_set_boolean = sub {
1023 my ($name) = @_;
1024
1025 return if !defined($param->{$name});
1026
1027 my $newvalue = $param->{$name} ? 1 : 0;
1028 my $oldvalue = $veconf->{$name}->{value};
1029 if (!defined($oldvalue) || ($oldvalue ne $newvalue)) {
1030 $veconf->{$name}->{value} = $newvalue;
1031 push @$changes, "--$name", $newvalue ? 'yes' : 'no';
1032 }
1033 };
1034
1035 my $cond_set_value = sub {
1036 my ($name, $newvalue) = @_;
1037
1038 $newvalue = defined($newvalue) ? $newvalue : $param->{$name};
1039 return if !defined($newvalue);
1040
1041 my $oldvalue = $veconf->{$name}->{value};
1042 if (!defined($oldvalue) || ($oldvalue ne $newvalue)) {
1043 $veconf->{$name}->{value} = $newvalue;
1044 push @$changes, "--$name", $newvalue;
1045 }
1046 };
1047
1048 &$cond_set_boolean('onboot');
1049
1050 &$cond_set_value('hostname');
1051
1052 &$cond_set_value('searchdomain');
1053
1054 if ($param->{'description'}) {
1055 &$cond_set_value('description', PVE::Tools::encode_text($param->{'description'}));
1056 }
1057
1058 if (defined($param->{ip_address})) {
1059 my $iphash = {};
1060 if (defined($veconf->{'ip_address'}) && $veconf->{'ip_address'}->{value}) {
1061 foreach my $ip (split (/\s+/, $veconf->{ip_address}->{value})) {
1062 $iphash->{$ip} = 1;
1063 }
1064 }
1065 my $newhash = {};
1066 foreach my $ip (PVE::Tools::split_list($param->{'ip_address'})) {
1067 next if $ip !~ m!^(?:$IPV6RE|$IPV4RE)(?:/\d+)?$!;
1068 $newhash->{$ip} = 1;
1069 if (!$iphash->{$ip}) {
1070 push @$changes, '--ipadd', $ip;
1071 $iphash->{$ip} = 1; # only add once
1072 }
1073 }
1074 foreach my $ip (keys %$iphash) {
1075 if (!$newhash->{$ip}) {
1076 push @$changes, '--ipdel', $ip;
1077 }
1078 }
1079 $veconf->{'ip_address'}->{value} = join(' ', keys %$iphash);
1080 }
1081
1082 if (defined($param->{netif})) {
1083 my $ifaces = {};
1084 if (defined ($veconf->{netif}) && $veconf->{netif}->{value}) {
1085 $ifaces = parse_netif($veconf->{netif}->{value}, $vmid);
1086 }
1087 my $newif = parse_netif($param->{netif}, $vmid);
1088
1089 foreach my $ifname (sort keys %$ifaces) {
1090 if (!$newif->{$ifname}) {
1091 push @$changes, '--netif_del', $ifname;
1092 }
1093 }
1094
1095 my $newvalue = '';
1096 foreach my $ifname (sort keys %$newif) {
1097 $newvalue .= ';' if $newvalue;
1098
1099 $newvalue .= print_netif($newif->{$ifname});
1100
1101 my $ifadd = $ifname;
1102 $ifadd .= $newif->{$ifname}->{mac} ? ",$newif->{$ifname}->{mac}" : ',';
1103 $ifadd .= $newif->{$ifname}->{host_ifname} ? ",$newif->{$ifname}->{host_ifname}" : ',';
1104 $ifadd .= $newif->{$ifname}->{host_mac} ? ",$newif->{$ifname}->{host_mac}" : ',';
1105 $ifadd .= $newif->{$ifname}->{bridge} ? ",$newif->{$ifname}->{bridge}" : '';
1106
1107 # not possible with current vzctl
1108 #$ifadd .= $newif->{$ifname}->{mac_filter} ? ",$newif->{$ifname}->{mac_filter}" : '';
1109
1110 if (!$ifaces->{$ifname} || ($ifaces->{$ifname}->{raw} ne $newif->{$ifname}->{raw})) {
1111 push @$changes, '--netif_add', $ifadd;
1112 }
1113 }
1114 $veconf->{netif}->{value} = $newvalue;
1115 }
1116
1117 if (defined($param->{'nameserver'})) {
1118 # remove duplicates
1119 my $nshash = {};
1120 my $newvalue = '';
1121 foreach my $ns (PVE::Tools::split_list($param->{'nameserver'})) {
1122 if (!$nshash->{$ns}) {
1123 push @$changes, '--nameserver', $ns;
1124 $nshash->{$ns} = 1;
1125 $newvalue .= $newvalue ? " $ns" : $ns;
1126 }
1127 }
1128 $veconf->{'nameserver'}->{value} = $newvalue if $newvalue;
1129 }
1130
1131 # foreach my $nv (@$changes) { print "CHANGE: $nv\n"; }
1132
1133 return $changes;
1134 }
1135
1136 sub generate_raw_config {
1137 my ($raw, $conf) = @_;
1138
1139 my $text = '';
1140
1141 my $found = {};
1142
1143 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
1144 my $line = $1;
1145
1146 if ($line =~ m/^\#/ || $line =~ m/^\s*$/) {
1147 $text .= "$line\n";
1148 next;
1149 }
1150
1151 if ($line =~ m/^\s*([A-Z][A-Z0-9_]*)\s*=\s*\"(.*)\"\s*$/i) {
1152 my $name = lc($1);
1153 if ($conf->{$name}) {
1154 $found->{$name} = 1;
1155 if (my $line = create_config_line($name, $conf->{$name})) {
1156 $text .= $line;
1157 }
1158 }
1159 }
1160 }
1161
1162 foreach my $key (keys %$conf) {
1163 next if $found->{$key};
1164 next if $key eq 'digest';
1165 if (my $line = create_config_line($key, $conf->{$key})) {
1166 $text .= $line;
1167 }
1168 }
1169
1170 return $text;
1171 }
1172
1173 sub create_lock_manager {
1174 my ($max) = @_;
1175
1176 return LockFile::Simple->make(-format => '%f',
1177 -autoclean => 1,
1178 -max => defined($max) ? $max : 60,
1179 -delay => 1,
1180 -stale => 1,
1181 -nfs => 0);
1182 }
1183
1184 sub lock_container {
1185 my ($vmid, $max, $code, @param) = @_;
1186
1187 my $filename = $global_vzconf->{lockdir} . "/${vmid}.lck";
1188 my $lock;
1189 my $res;
1190
1191 eval {
1192
1193 my $lockmgr = create_lock_manager($max);
1194
1195 $lock = $lockmgr->lock($filename) || die "can't lock container $vmid\n";
1196
1197 $res = &$code(@param);
1198 };
1199 my $err = $@;
1200
1201 $lock->release() if $lock;
1202
1203 die $err if $err;
1204
1205 return $res;
1206 }
1207
1208 sub replacepw {
1209 my ($file, $epw) = @_;
1210
1211 my $tmpfile = "$file.$$";
1212
1213 eval {
1214 open (SRC, "<$file") ||
1215 die "unable to open file '$file' - $!";
1216
1217 my $st = File::stat::stat(\*SRC) ||
1218 die "unable to stat file - $!";
1219
1220 open (DST, ">$tmpfile") ||
1221 die "unable to open file '$tmpfile' - $!";
1222
1223 # copy owner and permissions
1224 chmod $st->mode, \*DST;
1225 chown $st->uid, $st->gid, \*DST;
1226
1227 while (defined (my $line = <SRC>)) {
1228 $line =~ s/^root:[^:]*:/root:${epw}:/;
1229 print DST $line;
1230 }
1231 };
1232
1233 my $err = $@;
1234
1235 close (SRC);
1236 close (DST);
1237
1238 if ($err) {
1239 unlink $tmpfile;
1240 } else {
1241 rename $tmpfile, $file;
1242 unlink $tmpfile; # in case rename fails
1243 }
1244 }
1245
1246 sub set_rootpasswd {
1247 my ($privatedir, $opt_rootpasswd) = @_;
1248
1249 my $pwfile = "$privatedir/etc/passwd";
1250
1251 return if ! -f $pwfile;
1252
1253 my $shadow = "$privatedir/etc/shadow";
1254
1255 if ($opt_rootpasswd !~ m/^\$/) {
1256 my $time = substr (Digest::SHA::sha1_base64 (time), 0, 8);
1257 $opt_rootpasswd = crypt(encode("utf8", $opt_rootpasswd), "\$1\$$time\$");
1258 };
1259
1260 if (-f $shadow) {
1261 replacepw ($shadow, $opt_rootpasswd);
1262 replacepw ($pwfile, 'x');
1263 } else {
1264 replacepw ($pwfile, $opt_rootpasswd);
1265 }
1266 }