]>
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; | |
b71c4686 | 9 | use PVE::Tools qw(run_command extract_param $IPV6RE $IPV4RE); |
339e4159 DM |
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; | |
52878b0a | 15 | use Digest::SHA; |
e761984e | 16 | use Encode; |
aff192e6 | 17 | |
0618d446 | 18 | use constant SCRIPT_EXT => qw (start stop mount umount premount postumount); |
aabf3add | 19 | |
339e4159 DM |
20 | my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); |
21 | my $nodename = PVE::INotify::nodename(); | |
aabf3add DM |
22 | my $global_vzconf = read_global_vz_config(); |
23 | my $res_unlimited = LONG_MAX; | |
aff192e6 | 24 | |
339e4159 DM |
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}; | |
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 |
41 | sub 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 |
48 | sub config_file { |
49 | my ($vmid, $node) = @_; | |
50 | ||
51 | my $cfspath = cfs_config_path($vmid, $node); | |
52 | return "/etc/pve/$cfspath"; | |
53 | } | |
c3163e37 DM |
54 | |
55 | sub 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 | 66 | sub 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 |
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 | ||
2d590018 DM |
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 | ||
0618d446 DM |
103 | sub 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 |
115 | sub 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 | 126 | sub 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 |
153 | sub 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 | ||
177 | sub 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 | 198 | my $last_proc_vestat = {}; |
aff192e6 | 199 | |
339e4159 | 200 | sub 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 | ||
341 | my $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 | |
432 | sub 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 | 443 | sub 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 | 495 | sub 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 |
549 | sub 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 |
565 | PVE::JSONSchema::register_format('pve-openvz-netif', \&verify_netif); |
566 | sub 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 | ||
576 | sub 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 | ||
586 | sub 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 | ||
602 | sub 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 | ||
629 | sub 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 | ||
645 | sub 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 | ||
661 | sub 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 | ||
677 | sub 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 | ||
691 | sub 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 | ||
705 | sub 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 | ||
720 | sub 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 | ||
729 | sub 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) |
740 | sub 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 |
750 | my $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 | ||
814 | sub 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 | ||
855 | cfs_register_file('/openvz/', \&parse_ovz_config); | |
856 | ||
857 | sub 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 | |
883 | sub 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 | ||
893 | sub 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 |
913 | sub 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 | 940 | sub 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 | ||
1170 | sub 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 | 1207 | sub 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 | 1218 | sub 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 |
1242 | sub 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 | ||
1254 | sub 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 |
1266 | sub 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 | ||
1304 | sub 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 | } |