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