]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
vmstatus: add dummy values
[pve-container.git] / src / PVE / LXC.pm
CommitLineData
f76a2828
DM
1package PVE::LXC;
2
3use strict;
4use warnings;
5
6use File::Path;
7use Fcntl ':flock';
8
9use PVE::Cluster qw(cfs_register_file cfs_read_file);
10use PVE::SafeSyslog;
11use PVE::INotify;
12
13use Data::Dumper;
14
15cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
16
7dfc49cc
DM
17PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
18sub verify_lxc_network {
19 my ($value, $noerr) = @_;
20
21 return $value if parse_lxc_network($value);
22
23 return undef if $noerr;
24
25 die "unable to parse network setting\n";
26}
27
f76a2828
DM
28my $nodename = PVE::INotify::nodename();
29
30my $valid_lxc_keys = {
31 'lxc.arch' => 1,
32 'lxc.include' => 1,
33 'lxc.rootfs' => 1,
34 'lxc.mount' => 1,
35 'lxc.utsname' => 1,
36
37 'lxc.cgroup.memory.limit_in_bytes' => 1,
38
39 # pve related keys
40 'pve.comment' => 1,
41};
42
43my $valid_network_keys = {
44 type => 1,
45 flags => 1,
46 link => 1,
47 mtu => 1,
48 name => 1, # ifname inside container
49 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
50 hwaddr => 1,
51 ipv4 => 1,
52 'ipv4.gateway' => 1,
53 ipv6 => 1,
54 'ipv6.gateway' => 1,
55};
56
57my $lxc_array_configs = {
58 'lxc.network' => 1,
59 'lxc.mount' => 1,
60 'lxc.include' => 1,
61};
62
63sub write_lxc_config {
64 my ($filename, $data) = @_;
65
66 my $raw = "";
67
68 return $raw if !$data;
69
70 my $done_hash = { digest => 1};
7dfc49cc 71
f76a2828
DM
72 foreach my $k (sort keys %$data) {
73 next if $k !~ m/^lxc\./;
74 $done_hash->{$k} = 1;
75 $raw .= "$k = $data->{$k}\n";
76 }
7dfc49cc 77
f76a2828
DM
78 foreach my $k (sort keys %$data) {
79 next if $k !~ m/^net\d+$/;
80 $done_hash->{$k} = 1;
81 my $net = $data->{$k};
82 $raw .= "lxc.network.type = $net->{type}\n";
83 foreach my $subkey (sort keys %$net) {
84 next if $subkey eq 'type';
85 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
86 }
87 }
88
89 foreach my $k (sort keys %$data) {
90 next if $done_hash->{$k};
91 die "found un-written value in config - implement this!";
92 }
93
f76a2828
DM
94 return $raw;
95}
96
97sub parse_lxc_config {
98 my ($filename, $raw) = @_;
99
100 return undef if !defined($raw);
101
102 my $data = {
103 digest => Digest::SHA::sha1_hex($raw),
104 };
105
106 $filename =~ m|/lxc/(\d+)/config$|
107 || die "got strange filename '$filename'";
108
109 my $vmid = $1;
110
111 my $network_counter = 0;
112 my $network_list = [];
113 my $host_ifnames = {};
114
115 my $find_next_hostif_name = sub {
116 for (my $i = 0; $i < 10; $i++) {
117 my $name = "veth${vmid}.$i";
118 if (!$host_ifnames->{$name}) {
119 $host_ifnames->{$name} = 1;
120 return $name;
121 }
122 }
123
124 die "unable to find free host_ifname"; # should not happen
125 };
7dfc49cc 126
f76a2828
DM
127 my $push_network = sub {
128 my ($netconf) = @_;
129 return if !$netconf;
130 push @{$network_list}, $netconf;
131 $network_counter++;
132 if (my $netname = $netconf->{'veth.pair'}) {
133 if ($netname =~ m/^veth(\d+).(\d)$/) {
134 die "wrong vmid for network interface pair\n" if $1 != $vmid;
135 my $host_ifnames->{$netname} = 1;
136 } else {
137 die "wrong network interface pair\n";
138 }
139 }
140 };
141
142 my $network;
7dfc49cc 143
f76a2828
DM
144 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
145 my $line = $1;
146
147 next if $line =~ m/^\#/;
148 next if $line =~ m/^\s*$/;
149
150 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
151 my ($subkey, $value) = ($1, $2);
152 if ($subkey eq 'type') {
153 &$push_network($network);
154 $network = { type => $value };
155 } elsif ($valid_network_keys->{$subkey}) {
156 $network->{$subkey} = $value;
157 } else {
158 die "unable to parse config line: $line\n";
159 }
7dfc49cc 160
f76a2828
DM
161 next;
162 }
163 if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
164 my ($name, $value) = ($1, $2);
165 $data->{$name} = $value;
166 next;
167 }
168 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S+)\s*$/) {
169 my ($name, $value) = ($1, $2);
170
7dfc49cc 171 die "inavlid key '$name'\n" if !$valid_lxc_keys->{$name};
f76a2828
DM
172
173 die "multiple definitions for $name\n" if defined($data->{$name});
174
175 $data->{$name} = $value;
176 next;
177 }
178
179 die "unable to parse config line: $line\n";
180 }
181
182 &$push_network($network);
183
184 foreach my $net (@{$network_list}) {
185 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
186 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
187 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
188
189 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
190 $data->{"net$1"} = $net;
191 }
7dfc49cc
DM
192 }
193
194 return $data;
f76a2828
DM
195}
196
197sub config_list {
198 my $vmlist = PVE::Cluster::get_vmlist();
199 my $res = {};
200 return $res if !$vmlist || !$vmlist->{ids};
201 my $ids = $vmlist->{ids};
202
203 foreach my $vmid (keys %$ids) {
204 next if !$vmid; # skip CT0
205 my $d = $ids->{$vmid};
206 next if !$d->{node} || $d->{node} ne $nodename;
207 next if !$d->{type} || $d->{type} ne 'lxc';
208 $res->{$vmid}->{type} = 'lxc';
209 }
210 return $res;
211}
212
213sub cfs_config_path {
214 my ($vmid, $node) = @_;
215
216 $node = $nodename if !$node;
217 return "nodes/$node/lxc/$vmid/config";
218}
219
9c2d4ce9
DM
220sub config_file {
221 my ($vmid, $node) = @_;
222
223 my $cfspath = cfs_config_path($vmid, $node);
224 return "/etc/pve/$cfspath";
225}
226
f76a2828
DM
227sub load_config {
228 my ($vmid) = @_;
229
230 my $cfspath = cfs_config_path($vmid);
231
232 my $conf = PVE::Cluster::cfs_read_file($cfspath);
233 die "container $vmid does not exists\n" if !defined($conf);
234
235 return $conf;
236}
237
238sub write_config {
239 my ($vmid, $conf) = @_;
240
241 my $cfspath = cfs_config_path($vmid);
242
243 PVE::Cluster::cfs_write_file($cfspath, $conf);
244}
245
9c2d4ce9
DM
246my $tempcounter = 0;
247sub write_temp_config {
248 my ($vmid, $conf) = @_;
7dfc49cc 249
9c2d4ce9
DM
250 $tempcounter++;
251 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
252
253 my $raw = write_lxc_config($filename, $conf);
254
255 PVE::Tools::file_set_contents($filename, $raw);
7dfc49cc 256
9c2d4ce9
DM
257 return $filename;
258}
259
f76a2828
DM
260sub lock_container {
261 my ($vmid, $timeout, $code, @param) = @_;
262
263 my $lockdir = "/run/lock/lxc";
264 my $lockfile = "$lockdir/pve-config-{$vmid}.lock";
265
266 File::Path::make_path($lockdir);
267
268 my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param);
269
270 die $@ if $@;
271
272 return $res;
273}
274
275my $confdesc = {
276 onboot => {
277 optional => 1,
278 type => 'boolean',
279 description => "Specifies whether a VM will be started during system bootup.",
280 default => 0,
281 },
282 cpus => {
283 optional => 1,
284 type => 'integer',
285 description => "The number of CPUs for this container.",
286 minimum => 1,
287 default => 1,
288 },
289 cpuunits => {
290 optional => 1,
291 type => 'integer',
292 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.",
293 minimum => 0,
294 maximum => 500000,
295 default => 1000,
296 },
297 memory => {
298 optional => 1,
299 type => 'integer',
300 description => "Amount of RAM for the VM in MB.",
301 minimum => 16,
302 default => 512,
303 },
304 swap => {
305 optional => 1,
306 type => 'integer',
307 description => "Amount of SWAP for the VM in MB.",
308 minimum => 0,
309 default => 512,
310 },
311 disk => {
312 optional => 1,
313 type => 'number',
314 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
315 minimum => 0,
316 default => 2,
317 },
318 hostname => {
319 optional => 1,
320 description => "Set a host name for the container.",
321 type => 'string',
322 maxLength => 255,
323 },
324 description => {
325 optional => 1,
326 type => 'string',
327 description => "Container description. Only used on the configuration web interface.",
328 },
329 searchdomain => {
330 optional => 1,
331 type => 'string',
332 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
333 },
334 nameserver => {
335 optional => 1,
336 type => 'string',
337 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.",
338 },
ec52ac21
DM
339};
340
341my $MAX_LXC_NETWORKS = 10;
342for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
343 $confdesc->{"net$i"} = {
f76a2828
DM
344 optional => 1,
345 type => 'string', format => 'pve-lxc-network',
346 description => "Specifies network interfaces for the container.",
7dfc49cc 347 };
ec52ac21
DM
348}
349
350sub option_exists {
351 my ($name) = @_;
352
353 return defined($confdesc->{$name});
354}
f76a2828
DM
355
356# add JSON properties for create and set function
357sub json_config_properties {
358 my $prop = shift;
359
360 foreach my $opt (keys %$confdesc) {
361 $prop->{$opt} = $confdesc->{$opt};
362 }
363
364 return $prop;
365}
366
367
368sub vmstatus {
369 my ($opt_vmid) = @_;
370
371 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
372
373 foreach my $vmid (keys %$list) {
374 next if $opt_vmid && ($vmid ne $opt_vmid);
375
376 my $d = $list->{$vmid};
377 $d->{status} = 'stopped';
378
379 my $cfspath = cfs_config_path($vmid);
380 if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
f76a2828
DM
381 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
382 $d->{name} =~ s/[\s]//g;
e901d418
DM
383
384 $d->{cpus} = 1;
385
386 $d->{disk} = 0;
387 $d->{maxdisk} = 1;
388
389 $d->{mem} = 0;
390 $d->{maxmem} = 1024;
391
392 $d->{uptime} = 0;
393 $d->{cpu} = 0;
394
395 $d->{netout} = 0;
396 $d->{netin} = 0;
397
398 $d->{diskread} = 0;
399 $d->{diskwrite} = 0;
f76a2828
DM
400
401 }
402 }
403
404 return $list;
405}
406
7dfc49cc
DM
407
408sub print_lxc_network {
f76a2828
DM
409 my $net = shift;
410
7dfc49cc 411 die "no network link defined\n" if !$net->{link};
f76a2828 412
7dfc49cc
DM
413 my $res = "link=$net->{link}";
414
415 foreach my $k (qw(hwaddr mtu name ipv4 ipv4.gateway ipv6 ipv6.gateway)) {
f76a2828
DM
416 next if !defined($net->{$k});
417 $res .= ",$k=$net->{$k}";
418 }
7dfc49cc 419
f76a2828
DM
420 return $res;
421}
422
7dfc49cc
DM
423sub parse_lxc_network {
424 my ($data) = @_;
425
426 my $res = {};
427
428 return $res if !$data;
429
430 foreach my $pv (split (/,/, $data)) {
431 if ($pv =~ m/^(link|hwaddr|mtu|name|ipv4|ipv6|ipv4\.gateway|ipv6\.gateway)=(\S+)$/) {
432 $res->{$1} = $2;
433 } else {
434 return undef;
435 }
436 }
437
438 $res->{type} = 'veth';
439 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
440
441 return $res;
442}
f76a2828
DM
443
4441;