]>
Commit | Line | Data |
---|---|---|
f76a2828 DM |
1 | package PVE::LXC; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use File::Path; | |
7 | use Fcntl ':flock'; | |
8 | ||
9 | use PVE::Cluster qw(cfs_register_file cfs_read_file); | |
10 | use PVE::SafeSyslog; | |
11 | use PVE::INotify; | |
a3249355 | 12 | use PVE::JSONSchema qw(get_standard_option); |
55fa4e09 | 13 | use PVE::Tools qw($IPV6RE $IPV4RE); |
f76a2828 DM |
14 | |
15 | use Data::Dumper; | |
16 | ||
17 | cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config); | |
18 | ||
7dfc49cc DM |
19 | PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network); |
20 | sub verify_lxc_network { | |
21 | my ($value, $noerr) = @_; | |
22 | ||
23 | return $value if parse_lxc_network($value); | |
24 | ||
25 | return undef if $noerr; | |
26 | ||
27 | die "unable to parse network setting\n"; | |
28 | } | |
29 | ||
f76a2828 DM |
30 | my $nodename = PVE::INotify::nodename(); |
31 | ||
822de0c3 DM |
32 | sub parse_lxc_size { |
33 | my ($name, $value) = @_; | |
34 | ||
35 | if ($value =~ m/^(\d+)(b|k|m|g)?$/i) { | |
36 | my ($res, $unit) = ($1, lc($2 || 'b')); | |
37 | ||
38 | return $res if $unit eq 'b'; | |
39 | return $res*1024 if $unit eq 'k'; | |
40 | return $res*1024*1024 if $unit eq 'm'; | |
41 | return $res*1024*1024*1024 if $unit eq 'g'; | |
42 | } | |
43 | ||
44 | return undef; | |
45 | } | |
46 | ||
f76a2828 | 47 | my $valid_lxc_keys = { |
822de0c3 | 48 | 'lxc.arch' => 'i386|x86|i686|x86_64|amd64', |
f76a2828 DM |
49 | 'lxc.include' => 1, |
50 | 'lxc.rootfs' => 1, | |
51 | 'lxc.mount' => 1, | |
52 | 'lxc.utsname' => 1, | |
53 | ||
54664cd3 | 54 | 'lxc.id_map' => 1, |
822de0c3 DM |
55 | |
56 | 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size, | |
44da0641 | 57 | 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size, |
b80dd50a DM |
58 | 'lxc.cgroup.cpu.cfs_period_us' => '\d+', |
59 | 'lxc.cgroup.cpu.cfs_quota_us' => '\d+', | |
60 | 'lxc.cgroup.cpu.shares' => '\d+', | |
822de0c3 | 61 | |
54664cd3 DM |
62 | # mount related |
63 | 'lxc.mount' => 1, | |
64 | 'lxc.mount.entry' => 1, | |
65 | 'lxc.mount.auto' => 1, | |
66 | ||
67 | # not used by pve | |
b80dd50a | 68 | 'lxc.tty' => '\d+', |
54664cd3 DM |
69 | 'lxc.pts' => 1, |
70 | 'lxc.haltsignal' => 1, | |
71 | 'lxc.rebootsignal' => 1, | |
72 | 'lxc.stopsignal' => 1, | |
73 | 'lxc.init_cmd' => 1, | |
74 | 'lxc.console' => 1, | |
75 | 'lxc.console.logfile' => 1, | |
76 | 'lxc.devttydir' => 1, | |
77 | 'lxc.autodev' => 1, | |
78 | 'lxc.kmsg' => 1, | |
79 | 'lxc.cap.drop' => 1, | |
80 | 'lxc.cap.keep' => 1, | |
81 | 'lxc.aa_profile' => 1, | |
82 | 'lxc.aa_allow_incomplete' => 1, | |
83 | 'lxc.se_context' => 1, | |
84 | 'lxc.loglevel' => 1, | |
85 | 'lxc.logfile' => 1, | |
86 | 'lxc.environment' => 1, | |
87 | ||
88 | ||
89 | # autostart | |
90 | 'lxc.start.auto' => 1, | |
91 | 'lxc.start.delay' => 1, | |
92 | 'lxc.start.order' => 1, | |
93 | 'lxc.group' => 1, | |
94 | ||
95 | # hooks | |
96 | 'lxc.hook.pre-start' => 1, | |
97 | 'lxc.hook.pre-mount' => 1, | |
98 | 'lxc.hook.mount' => 1, | |
99 | 'lxc.hook.autodev' => 1, | |
100 | 'lxc.hook.start' => 1, | |
101 | 'lxc.hook.post-stop' => 1, | |
102 | 'lxc.hook.clone' => 1, | |
103 | ||
f76a2828 | 104 | # pve related keys |
117636e5 DM |
105 | 'pve.nameserver' => sub { |
106 | my ($name, $value) = @_; | |
107 | return verify_nameserver_list($value); | |
108 | }, | |
109 | 'pve.searchdomain' => sub { | |
110 | my ($name, $value) = @_; | |
111 | return verify_searchdomain_list($value); | |
112 | }, | |
a99b3509 | 113 | 'pve.onboot' => '(0|1)', |
a3249355 DM |
114 | 'pve.startup' => sub { |
115 | my ($name, $value) = @_; | |
116 | return PVE::JSONSchema::pve_verify_startup_order($value); | |
117 | }, | |
f76a2828 DM |
118 | 'pve.comment' => 1, |
119 | }; | |
120 | ||
93285df8 | 121 | my $valid_lxc_network_keys = { |
f76a2828 | 122 | type => 1, |
f76a2828 DM |
123 | mtu => 1, |
124 | name => 1, # ifname inside container | |
125 | 'veth.pair' => 1, # ifname at host (eth${vmid}.X) | |
126 | hwaddr => 1, | |
93285df8 DM |
127 | }; |
128 | ||
129 | my $valid_pve_network_keys = { | |
8a04b6c7 | 130 | bridge => 1, |
2b1fc2ea DM |
131 | tag => 1, |
132 | firewall => 1, | |
93285df8 DM |
133 | ip => 1, |
134 | gw => 1, | |
135 | ip6 => 1, | |
136 | gw6 => 1, | |
f76a2828 DM |
137 | }; |
138 | ||
139 | my $lxc_array_configs = { | |
140 | 'lxc.network' => 1, | |
141 | 'lxc.mount' => 1, | |
142 | 'lxc.include' => 1, | |
143 | }; | |
144 | ||
145 | sub write_lxc_config { | |
146 | my ($filename, $data) = @_; | |
147 | ||
148 | my $raw = ""; | |
149 | ||
150 | return $raw if !$data; | |
151 | ||
152 | my $done_hash = { digest => 1}; | |
7dfc49cc | 153 | |
f76a2828 DM |
154 | foreach my $k (sort keys %$data) { |
155 | next if $k !~ m/^lxc\./; | |
156 | $done_hash->{$k} = 1; | |
157 | $raw .= "$k = $data->{$k}\n"; | |
158 | } | |
7dfc49cc | 159 | |
f76a2828 | 160 | foreach my $k (sort keys %$data) { |
fff3a342 DM |
161 | next if $k !~ m/^pve\./; |
162 | $done_hash->{$k} = 1; | |
163 | $raw .= "$k = $data->{$k}\n"; | |
164 | } | |
165 | ||
166 | foreach my $k (sort keys %$data) { | |
f76a2828 DM |
167 | next if $k !~ m/^net\d+$/; |
168 | $done_hash->{$k} = 1; | |
169 | my $net = $data->{$k}; | |
170 | $raw .= "lxc.network.type = $net->{type}\n"; | |
171 | foreach my $subkey (sort keys %$net) { | |
172 | next if $subkey eq 'type'; | |
93285df8 DM |
173 | if ($valid_lxc_network_keys->{$subkey}) { |
174 | $raw .= "lxc.network.$subkey = $net->{$subkey}\n"; | |
175 | } elsif ($valid_pve_network_keys->{$subkey}) { | |
176 | $raw .= "pve.network.$subkey = $net->{$subkey}\n"; | |
177 | } else { | |
178 | die "found invalid network key '$subkey'"; | |
179 | } | |
f76a2828 DM |
180 | } |
181 | } | |
182 | ||
183 | foreach my $k (sort keys %$data) { | |
184 | next if $done_hash->{$k}; | |
185 | die "found un-written value in config - implement this!"; | |
186 | } | |
187 | ||
f76a2828 DM |
188 | return $raw; |
189 | } | |
190 | ||
822de0c3 DM |
191 | sub parse_lxc_option { |
192 | my ($name, $value) = @_; | |
193 | ||
194 | my $parser = $valid_lxc_keys->{$name}; | |
195 | ||
196 | die "inavlid key '$name'\n" if !defined($parser); | |
197 | ||
198 | if ($parser eq '1') { | |
199 | return $value; | |
200 | } elsif (ref($parser)) { | |
201 | my $res = &$parser($name, $value); | |
202 | return $res if defined($res); | |
203 | } else { | |
204 | # assume regex | |
205 | return $value if $value =~ m/^$parser$/; | |
206 | } | |
207 | ||
208 | die "unable to parse value '$value' for option '$name'\n"; | |
209 | } | |
210 | ||
f76a2828 DM |
211 | sub parse_lxc_config { |
212 | my ($filename, $raw) = @_; | |
213 | ||
214 | return undef if !defined($raw); | |
215 | ||
216 | my $data = { | |
217 | digest => Digest::SHA::sha1_hex($raw), | |
218 | }; | |
219 | ||
220 | $filename =~ m|/lxc/(\d+)/config$| | |
221 | || die "got strange filename '$filename'"; | |
222 | ||
223 | my $vmid = $1; | |
224 | ||
225 | my $network_counter = 0; | |
226 | my $network_list = []; | |
227 | my $host_ifnames = {}; | |
228 | ||
229 | my $find_next_hostif_name = sub { | |
230 | for (my $i = 0; $i < 10; $i++) { | |
231 | my $name = "veth${vmid}.$i"; | |
232 | if (!$host_ifnames->{$name}) { | |
233 | $host_ifnames->{$name} = 1; | |
234 | return $name; | |
235 | } | |
236 | } | |
237 | ||
238 | die "unable to find free host_ifname"; # should not happen | |
239 | }; | |
7dfc49cc | 240 | |
f76a2828 DM |
241 | my $push_network = sub { |
242 | my ($netconf) = @_; | |
243 | return if !$netconf; | |
244 | push @{$network_list}, $netconf; | |
245 | $network_counter++; | |
246 | if (my $netname = $netconf->{'veth.pair'}) { | |
247 | if ($netname =~ m/^veth(\d+).(\d)$/) { | |
248 | die "wrong vmid for network interface pair\n" if $1 != $vmid; | |
249 | my $host_ifnames->{$netname} = 1; | |
250 | } else { | |
251 | die "wrong network interface pair\n"; | |
252 | } | |
253 | } | |
254 | }; | |
255 | ||
256 | my $network; | |
7dfc49cc | 257 | |
f76a2828 DM |
258 | while ($raw && $raw =~ s/^(.*?)(\n|$)//) { |
259 | my $line = $1; | |
260 | ||
261 | next if $line =~ m/^\#/; | |
262 | next if $line =~ m/^\s*$/; | |
263 | ||
264 | if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) { | |
265 | my ($subkey, $value) = ($1, $2); | |
266 | if ($subkey eq 'type') { | |
267 | &$push_network($network); | |
268 | $network = { type => $value }; | |
93285df8 DM |
269 | } elsif ($valid_lxc_network_keys->{$subkey}) { |
270 | $network->{$subkey} = $value; | |
271 | } else { | |
272 | die "unable to parse config line: $line\n"; | |
273 | } | |
274 | next; | |
275 | } | |
276 | if ($line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) { | |
277 | my ($subkey, $value) = ($1, $2); | |
278 | if ($valid_pve_network_keys->{$subkey}) { | |
f76a2828 DM |
279 | $network->{$subkey} = $value; |
280 | } else { | |
281 | die "unable to parse config line: $line\n"; | |
282 | } | |
f76a2828 DM |
283 | next; |
284 | } | |
285 | if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) { | |
286 | my ($name, $value) = ($1, $2); | |
287 | $data->{$name} = $value; | |
288 | next; | |
289 | } | |
54664cd3 | 290 | if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) { |
f76a2828 DM |
291 | my ($name, $value) = ($1, $2); |
292 | ||
f76a2828 DM |
293 | die "multiple definitions for $name\n" if defined($data->{$name}); |
294 | ||
822de0c3 | 295 | $data->{$name} = parse_lxc_option($name, $value); |
f76a2828 DM |
296 | next; |
297 | } | |
298 | ||
299 | die "unable to parse config line: $line\n"; | |
300 | } | |
301 | ||
302 | &$push_network($network); | |
303 | ||
304 | foreach my $net (@{$network_list}) { | |
305 | $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'}; | |
306 | $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr}; | |
307 | die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth'; | |
308 | ||
309 | if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) { | |
310 | $data->{"net$1"} = $net; | |
311 | } | |
7dfc49cc DM |
312 | } |
313 | ||
314 | return $data; | |
f76a2828 DM |
315 | } |
316 | ||
317 | sub config_list { | |
318 | my $vmlist = PVE::Cluster::get_vmlist(); | |
319 | my $res = {}; | |
320 | return $res if !$vmlist || !$vmlist->{ids}; | |
321 | my $ids = $vmlist->{ids}; | |
322 | ||
323 | foreach my $vmid (keys %$ids) { | |
324 | next if !$vmid; # skip CT0 | |
325 | my $d = $ids->{$vmid}; | |
326 | next if !$d->{node} || $d->{node} ne $nodename; | |
327 | next if !$d->{type} || $d->{type} ne 'lxc'; | |
328 | $res->{$vmid}->{type} = 'lxc'; | |
329 | } | |
330 | return $res; | |
331 | } | |
332 | ||
333 | sub cfs_config_path { | |
334 | my ($vmid, $node) = @_; | |
335 | ||
336 | $node = $nodename if !$node; | |
337 | return "nodes/$node/lxc/$vmid/config"; | |
338 | } | |
339 | ||
9c2d4ce9 DM |
340 | sub config_file { |
341 | my ($vmid, $node) = @_; | |
342 | ||
343 | my $cfspath = cfs_config_path($vmid, $node); | |
344 | return "/etc/pve/$cfspath"; | |
345 | } | |
346 | ||
f76a2828 DM |
347 | sub load_config { |
348 | my ($vmid) = @_; | |
349 | ||
350 | my $cfspath = cfs_config_path($vmid); | |
351 | ||
352 | my $conf = PVE::Cluster::cfs_read_file($cfspath); | |
353 | die "container $vmid does not exists\n" if !defined($conf); | |
354 | ||
355 | return $conf; | |
356 | } | |
357 | ||
358 | sub write_config { | |
359 | my ($vmid, $conf) = @_; | |
360 | ||
361 | my $cfspath = cfs_config_path($vmid); | |
362 | ||
363 | PVE::Cluster::cfs_write_file($cfspath, $conf); | |
364 | } | |
365 | ||
9c2d4ce9 DM |
366 | my $tempcounter = 0; |
367 | sub write_temp_config { | |
368 | my ($vmid, $conf) = @_; | |
7dfc49cc | 369 | |
9c2d4ce9 DM |
370 | $tempcounter++; |
371 | my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf"; | |
372 | ||
373 | my $raw = write_lxc_config($filename, $conf); | |
374 | ||
375 | PVE::Tools::file_set_contents($filename, $raw); | |
7dfc49cc | 376 | |
9c2d4ce9 DM |
377 | return $filename; |
378 | } | |
379 | ||
f76a2828 DM |
380 | sub lock_container { |
381 | my ($vmid, $timeout, $code, @param) = @_; | |
382 | ||
383 | my $lockdir = "/run/lock/lxc"; | |
384 | my $lockfile = "$lockdir/pve-config-{$vmid}.lock"; | |
385 | ||
386 | File::Path::make_path($lockdir); | |
387 | ||
388 | my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param); | |
389 | ||
390 | die $@ if $@; | |
391 | ||
392 | return $res; | |
393 | } | |
394 | ||
395 | my $confdesc = { | |
396 | onboot => { | |
397 | optional => 1, | |
398 | type => 'boolean', | |
399 | description => "Specifies whether a VM will be started during system bootup.", | |
400 | default => 0, | |
401 | }, | |
a3249355 | 402 | startup => get_standard_option('pve-startup-order'), |
f76a2828 DM |
403 | cpus => { |
404 | optional => 1, | |
405 | type => 'integer', | |
44da0641 DM |
406 | description => "The number of CPUs for this container (0 is unlimited).", |
407 | minimum => 0, | |
408 | maximum => 128, | |
409 | default => 0, | |
f76a2828 DM |
410 | }, |
411 | cpuunits => { | |
412 | optional => 1, | |
413 | type => 'integer', | |
414 | 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.", | |
415 | minimum => 0, | |
416 | maximum => 500000, | |
417 | default => 1000, | |
418 | }, | |
419 | memory => { | |
420 | optional => 1, | |
421 | type => 'integer', | |
422 | description => "Amount of RAM for the VM in MB.", | |
423 | minimum => 16, | |
424 | default => 512, | |
425 | }, | |
426 | swap => { | |
427 | optional => 1, | |
428 | type => 'integer', | |
429 | description => "Amount of SWAP for the VM in MB.", | |
430 | minimum => 0, | |
431 | default => 512, | |
432 | }, | |
433 | disk => { | |
434 | optional => 1, | |
435 | type => 'number', | |
436 | description => "Amount of disk space for the VM in GB. A zero indicates no limits.", | |
437 | minimum => 0, | |
438 | default => 2, | |
439 | }, | |
440 | hostname => { | |
441 | optional => 1, | |
442 | description => "Set a host name for the container.", | |
443 | type => 'string', | |
444 | maxLength => 255, | |
445 | }, | |
446 | description => { | |
447 | optional => 1, | |
448 | type => 'string', | |
449 | description => "Container description. Only used on the configuration web interface.", | |
450 | }, | |
451 | searchdomain => { | |
452 | optional => 1, | |
453 | type => 'string', | |
454 | description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.", | |
455 | }, | |
456 | nameserver => { | |
457 | optional => 1, | |
458 | type => 'string', | |
459 | 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.", | |
460 | }, | |
ec52ac21 DM |
461 | }; |
462 | ||
463 | my $MAX_LXC_NETWORKS = 10; | |
464 | for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) { | |
465 | $confdesc->{"net$i"} = { | |
f76a2828 DM |
466 | optional => 1, |
467 | type => 'string', format => 'pve-lxc-network', | |
468 | description => "Specifies network interfaces for the container.", | |
7dfc49cc | 469 | }; |
ec52ac21 DM |
470 | } |
471 | ||
472 | sub option_exists { | |
473 | my ($name) = @_; | |
474 | ||
475 | return defined($confdesc->{$name}); | |
476 | } | |
f76a2828 DM |
477 | |
478 | # add JSON properties for create and set function | |
479 | sub json_config_properties { | |
480 | my $prop = shift; | |
481 | ||
482 | foreach my $opt (keys %$confdesc) { | |
483 | $prop->{$opt} = $confdesc->{$opt}; | |
484 | } | |
485 | ||
486 | return $prop; | |
487 | } | |
488 | ||
822de0c3 DM |
489 | # container status helpers |
490 | ||
491 | sub list_active_containers { | |
492 | ||
493 | my $filename = "/proc/net/unix"; | |
494 | ||
495 | # similar test is used by lcxcontainers.c: list_active_containers | |
496 | my $res = {}; | |
497 | ||
498 | my $fh = IO::File->new ($filename, "r"); | |
499 | return $res if !$fh; | |
500 | ||
501 | while (defined(my $line = <$fh>)) { | |
502 | if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) { | |
503 | my $path = $1; | |
504 | if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) { | |
505 | $res->{$1} = 1; | |
506 | } | |
507 | } | |
508 | } | |
509 | ||
510 | close($fh); | |
511 | ||
512 | return $res; | |
513 | } | |
f76a2828 | 514 | |
5c752bbf DM |
515 | # warning: this is slow |
516 | sub check_running { | |
517 | my ($vmid) = @_; | |
518 | ||
519 | my $active_hash = list_active_containers(); | |
520 | ||
521 | return 1 if defined($active_hash->{$vmid}); | |
522 | ||
523 | return undef; | |
524 | } | |
525 | ||
f76a2828 DM |
526 | sub vmstatus { |
527 | my ($opt_vmid) = @_; | |
528 | ||
529 | my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list(); | |
530 | ||
822de0c3 DM |
531 | my $active_hash = list_active_containers(); |
532 | ||
f76a2828 | 533 | foreach my $vmid (keys %$list) { |
f76a2828 | 534 | my $d = $list->{$vmid}; |
822de0c3 | 535 | $d->{status} = $active_hash->{$vmid} ? 'running' : 'stopped'; |
f76a2828 DM |
536 | |
537 | my $cfspath = cfs_config_path($vmid); | |
238a56cb DM |
538 | my $conf = PVE::Cluster::cfs_read_file($cfspath) || {}; |
539 | ||
540 | $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid"; | |
541 | $d->{name} =~ s/[\s]//g; | |
e901d418 | 542 | |
44da0641 DM |
543 | $d->{cpus} = 0; |
544 | ||
545 | my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'}; | |
546 | my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'}; | |
547 | ||
548 | if ($cfs_period_us && $cfs_quota_us) { | |
549 | $d->{cpus} = int($cfs_quota_us/$cfs_period_us); | |
550 | } | |
238a56cb DM |
551 | |
552 | $d->{disk} = 0; | |
553 | $d->{maxdisk} = 1; | |
554 | if (my $private = $conf->{'lxc.rootfs'}) { | |
555 | my $res = PVE::Tools::df($private, 2); | |
556 | $d->{disk} = $res->{used}; | |
557 | $d->{maxdisk} = $res->{total}; | |
558 | } | |
559 | ||
560 | $d->{mem} = 0; | |
561 | $d->{swap} = 0; | |
562 | $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) + | |
44da0641 | 563 | ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0); |
e901d418 | 564 | |
238a56cb DM |
565 | $d->{uptime} = 0; |
566 | $d->{cpu} = 0; | |
e901d418 | 567 | |
238a56cb DM |
568 | $d->{netout} = 0; |
569 | $d->{netin} = 0; | |
f76a2828 | 570 | |
238a56cb DM |
571 | $d->{diskread} = 0; |
572 | $d->{diskwrite} = 0; | |
f76a2828 | 573 | } |
238a56cb DM |
574 | |
575 | foreach my $vmid (keys %$list) { | |
576 | my $d = $list->{$vmid}; | |
577 | next if $d->{status} ne 'running'; | |
f76a2828 | 578 | |
22a77285 DM |
579 | $d->{uptime} = 100; # fixme: |
580 | ||
238a56cb DM |
581 | $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes'); |
582 | $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem}; | |
583 | } | |
584 | ||
f76a2828 DM |
585 | return $list; |
586 | } | |
587 | ||
7dfc49cc DM |
588 | |
589 | sub print_lxc_network { | |
f76a2828 DM |
590 | my $net = shift; |
591 | ||
8a04b6c7 | 592 | die "no network bridge defined\n" if !$net->{bridge}; |
f76a2828 | 593 | |
8a04b6c7 | 594 | my $res = "bridge=$net->{bridge}"; |
7dfc49cc | 595 | |
2b1fc2ea | 596 | foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) { |
f76a2828 DM |
597 | next if !defined($net->{$k}); |
598 | $res .= ",$k=$net->{$k}"; | |
599 | } | |
7dfc49cc | 600 | |
f76a2828 DM |
601 | return $res; |
602 | } | |
603 | ||
7dfc49cc DM |
604 | sub parse_lxc_network { |
605 | my ($data) = @_; | |
606 | ||
607 | my $res = {}; | |
608 | ||
609 | return $res if !$data; | |
610 | ||
611 | foreach my $pv (split (/,/, $data)) { | |
2b1fc2ea | 612 | if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) { |
7dfc49cc DM |
613 | $res->{$1} = $2; |
614 | } else { | |
615 | return undef; | |
616 | } | |
617 | } | |
618 | ||
619 | $res->{type} = 'veth'; | |
620 | $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac}; | |
621 | ||
622 | return $res; | |
623 | } | |
f76a2828 | 624 | |
238a56cb DM |
625 | sub read_cgroup_value { |
626 | my ($group, $vmid, $name, $full) = @_; | |
627 | ||
628 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
629 | ||
630 | return PVE::Tools::file_get_contents($path) if $full; | |
631 | ||
632 | return PVE::Tools::file_read_firstline($path); | |
633 | } | |
634 | ||
52f1d76b DM |
635 | sub find_lxc_console_pids { |
636 | ||
637 | my $res = {}; | |
638 | ||
639 | PVE::Tools::dir_glob_foreach('/proc', '\d+', sub { | |
640 | my ($pid) = @_; | |
641 | ||
642 | my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline"); | |
643 | return if !$cmdline; | |
644 | ||
645 | my @args = split(/\0/, $cmdline); | |
646 | ||
647 | # serach for lxc-console -n <vmid> | |
648 | return if scalar(@args) != 3; | |
649 | return if $args[1] ne '-n'; | |
650 | return if $args[2] !~ m/^\d+$/; | |
651 | return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|; | |
652 | ||
653 | my $vmid = $args[2]; | |
654 | ||
655 | push @{$res->{$vmid}}, $pid; | |
656 | }); | |
657 | ||
658 | return $res; | |
659 | } | |
660 | ||
55fa4e09 DM |
661 | my $ipv4_reverse_mask = [ |
662 | '0.0.0.0', | |
663 | '128.0.0.0', | |
664 | '192.0.0.0', | |
665 | '224.0.0.0', | |
666 | '240.0.0.0', | |
667 | '248.0.0.0', | |
668 | '252.0.0.0', | |
669 | '254.0.0.0', | |
670 | '255.0.0.0', | |
671 | '255.128.0.0', | |
672 | '255.192.0.0', | |
673 | '255.224.0.0', | |
674 | '255.240.0.0', | |
675 | '255.248.0.0', | |
676 | '255.252.0.0', | |
677 | '255.254.0.0', | |
678 | '255.255.0.0', | |
679 | '255.255.128.0', | |
680 | '255.255.192.0', | |
681 | '255.255.224.0', | |
682 | '255.255.240.0', | |
683 | '255.255.248.0', | |
684 | '255.255.252.0', | |
685 | '255.255.254.0', | |
686 | '255.255.255.0', | |
687 | '255.255.255.128', | |
688 | '255.255.255.192', | |
689 | '255.255.255.224', | |
690 | '255.255.255.240', | |
691 | '255.255.255.248', | |
692 | '255.255.255.252', | |
693 | '255.255.255.254', | |
694 | '255.255.255.255', | |
695 | ]; | |
696 | ||
697 | # Note: we cannot use Net:IP, because that only allows strict | |
698 | # CIDR networks | |
699 | sub parse_ipv4_cidr { | |
700 | my ($cidr, $noerr) = @_; | |
701 | ||
702 | if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) { | |
703 | return { address => $1, netmask => $ipv4_reverse_mask->[$2] }; | |
704 | } | |
705 | ||
706 | return undef if $noerr; | |
707 | ||
708 | die "unable to parse ipv4 address/mask\n"; | |
709 | } | |
93285df8 | 710 | |
b80dd50a DM |
711 | sub lxc_conf_to_pve { |
712 | my ($vmid, $lxc_conf) = @_; | |
713 | ||
714 | my $properties = json_config_properties(); | |
715 | ||
716 | my $conf = { digest => $lxc_conf->{digest} }; | |
717 | ||
718 | foreach my $k (keys %$properties) { | |
719 | ||
720 | if ($k eq 'description') { | |
721 | if (my $raw = $lxc_conf->{'pve.comment'}) { | |
722 | $conf->{$k} = PVE::Tools::decode_text($raw); | |
723 | } | |
a99b3509 DM |
724 | } elsif ($k eq 'onboot') { |
725 | $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'}; | |
a3249355 DM |
726 | } elsif ($k eq 'startup') { |
727 | $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'}; | |
b80dd50a DM |
728 | } elsif ($k eq 'hostname') { |
729 | $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'}; | |
ffa1d001 DM |
730 | } elsif ($k eq 'nameserver') { |
731 | $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'}; | |
732 | } elsif ($k eq 'searchdomain') { | |
733 | $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'}; | |
b80dd50a DM |
734 | } elsif ($k eq 'memory') { |
735 | if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) { | |
736 | $conf->{$k} = int($value / (1024*1024)); | |
737 | } | |
738 | } elsif ($k eq 'swap') { | |
739 | if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) { | |
740 | my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0; | |
741 | $conf->{$k} = int(($value -$mem) / (1024*1024)); | |
742 | } | |
743 | } elsif ($k eq 'cpus') { | |
744 | my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'}; | |
745 | my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'}; | |
746 | ||
747 | if ($cfs_period_us && $cfs_quota_us) { | |
748 | $conf->{$k} = int($cfs_quota_us/$cfs_period_us); | |
749 | } else { | |
750 | $conf->{$k} = 0; | |
751 | } | |
752 | } elsif ($k eq 'cpuunits') { | |
753 | $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024; | |
754 | } elsif ($k =~ m/^net\d$/) { | |
755 | my $net = $lxc_conf->{$k}; | |
756 | next if !$net; | |
757 | $conf->{$k} = print_lxc_network($net); | |
758 | } | |
759 | } | |
760 | ||
761 | return $conf; | |
762 | } | |
763 | ||
117636e5 DM |
764 | # verify and cleanup nameserver list (replace \0 with ' ') |
765 | sub verify_nameserver_list { | |
766 | my ($nameserver_list) = @_; | |
767 | ||
768 | my @list = (); | |
769 | foreach my $server (PVE::Tools::split_list($nameserver_list)) { | |
770 | PVE::JSONSchema::pve_verify_ip($server); | |
771 | push @list, $server; | |
772 | } | |
773 | ||
774 | return join(' ', @list); | |
775 | } | |
776 | ||
777 | sub verify_searchdomain_list { | |
778 | my ($searchdomain_list) = @_; | |
779 | ||
780 | my @list = (); | |
781 | foreach my $server (PVE::Tools::split_list($searchdomain_list)) { | |
782 | # todo: should we add checks for valid dns domains? | |
783 | push @list, $server; | |
784 | } | |
785 | ||
786 | return join(' ', @list); | |
787 | } | |
788 | ||
93285df8 DM |
789 | sub update_lxc_config { |
790 | my ($vmid, $conf, $running, $param, $delete) = @_; | |
791 | ||
792 | # fixme: hotplug | |
793 | die "unable to modify config while container is running\n" if $running; | |
794 | ||
795 | if (defined($delete)) { | |
796 | foreach my $opt (@$delete) { | |
797 | if ($opt eq 'hostname' || $opt eq 'memory') { | |
798 | die "unable to delete required option '$opt'\n"; | |
799 | } elsif ($opt eq 'swap') { | |
44da0641 | 800 | delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}; |
93285df8 DM |
801 | } elsif ($opt eq 'description') { |
802 | delete $conf->{'pve.comment'}; | |
a99b3509 DM |
803 | } elsif ($opt eq 'onboot') { |
804 | delete $conf->{'pve.onboot'}; | |
a3249355 DM |
805 | } elsif ($opt eq 'startup') { |
806 | delete $conf->{'pve.startup'}; | |
ffa1d001 DM |
807 | } elsif ($opt eq 'nameserver') { |
808 | delete $conf->{'pve.nameserver'}; | |
809 | } elsif ($opt eq 'searchdomain') { | |
810 | delete $conf->{'pve.searchdomain'}; | |
93285df8 DM |
811 | } elsif ($opt =~ m/^net\d$/) { |
812 | delete $conf->{$opt}; | |
813 | } else { | |
814 | die "implement me" | |
815 | } | |
816 | } | |
817 | } | |
818 | ||
819 | foreach my $opt (keys %$param) { | |
820 | my $value = $param->{$opt}; | |
821 | if ($opt eq 'hostname') { | |
822 | $conf->{'lxc.utsname'} = $value; | |
a99b3509 DM |
823 | } elsif ($opt eq 'onboot') { |
824 | $conf->{'pve.onboot'} = $value ? 1 : 0; | |
a3249355 DM |
825 | } elsif ($opt eq 'startup') { |
826 | $conf->{'pve.startup'} = $value; | |
ffa1d001 | 827 | } elsif ($opt eq 'nameserver') { |
117636e5 | 828 | my $list = verify_nameserver_list($value); |
c325b32f | 829 | $conf->{'pve.nameserver'} = $list; |
ffa1d001 | 830 | } elsif ($opt eq 'searchdomain') { |
117636e5 | 831 | my $list = verify_searchdomain_list($value); |
c325b32f | 832 | $conf->{'pve.searchdomain'} = $list; |
93285df8 DM |
833 | } elsif ($opt eq 'memory') { |
834 | $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024; | |
835 | } elsif ($opt eq 'swap') { | |
44da0641 DM |
836 | my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'}; |
837 | $mem = $param->{memory}*1024*1024 if $param->{memory}; | |
838 | $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024; | |
839 | } elsif ($opt eq 'cpus') { | |
840 | if ($value > 0) { | |
841 | my $cfs_period_us = 100000; | |
842 | $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us; | |
843 | $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value; | |
844 | } else { | |
845 | delete $conf->{'lxc.cgroup.cpu.cfs_period_us'}; | |
846 | delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'}; | |
847 | } | |
b80dd50a DM |
848 | } elsif ($opt eq 'cpuunits') { |
849 | $conf->{'lxc.cgroup.cpu.shares'} = $value; | |
93285df8 DM |
850 | } elsif ($opt eq 'description') { |
851 | $conf->{'pve.comment'} = PVE::Tools::encode_text($value); | |
852 | } elsif ($opt =~ m/^net(\d+)$/) { | |
853 | my $netid = $1; | |
854 | my $net = PVE::LXC::parse_lxc_network($value); | |
855 | $net->{'veth.pair'} = "veth${vmid}.$netid"; | |
856 | $conf->{$opt} = $net; | |
857 | } else { | |
858 | die "implement me" | |
859 | } | |
860 | } | |
861 | } | |
c325b32f DM |
862 | |
863 | sub get_primary_ips { | |
864 | my ($conf) = @_; | |
865 | ||
866 | # return data from net0 | |
867 | ||
868 | my $net = $conf->{net0}; | |
869 | return undef if !$net; | |
870 | ||
871 | my $ipv4 = $net->{ip}; | |
872 | $ipv4 =~ s!/\d+$!! if $ipv4; | |
873 | my $ipv6 = $net->{ip}; | |
874 | $ipv6 =~ s!/\d+$!! if $ipv6; | |
875 | ||
876 | return ($ipv4, $ipv6); | |
877 | } | |
238a56cb | 878 | |
f76a2828 | 879 | 1; |