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