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