]> git.proxmox.com Git - pve-container.git/blame - src/pve-update-lxc-config
implement linked clones
[pve-container.git] / src / pve-update-lxc-config
CommitLineData
4968d0f7
WB
1#!/usr/bin/perl
2
b94d3e0b
DM
3# update old beta1 config
4# todo: remove this script after 4.0 Release
5
4968d0f7
WB
6use strict;
7use warnings;
8
b94d3e0b 9use PVE::Tools;
4968d0f7
WB
10use PVE::JSONSchema qw(get_standard_option);
11use PVE::Network;
12
13sub parse_volume_id {
14 my ($volid, $noerr) = @_;
15
16 if ($volid =~ m/^([a-z][a-z0-9\-\_\.]*[a-z0-9]):(.+)$/i) {
17 return wantarray ? ($1, $2) : $1;
18 }
19 return undef if $noerr;
20 die "unable to parse volume ID '$volid'\n";
21}
22
23sub parse_lxc_size {
24 my ($name, $value) = @_;
25
26 if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
27 my ($res, $unit) = ($1, lc($2 || 'b'));
28
29 return $res if $unit eq 'b';
30 return $res*1024 if $unit eq 'k';
31 return $res*1024*1024 if $unit eq 'm';
32 return $res*1024*1024*1024 if $unit eq 'g';
33 }
34
35 return undef;
36}
37
38sub verify_searchdomain_list {
39 my ($searchdomain_list) = @_;
40
41 my @list = ();
42 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
43 # todo: should we add checks for valid dns domains?
44 push @list, $server;
45 }
46
47 return join(' ', @list);
48}
49
50sub verify_nameserver_list {
51 my ($nameserver_list) = @_;
52
53 my @list = ();
54 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
55 PVE::JSONSchema::pve_verify_ip($server);
56 push @list, $server;
57 }
58
59 return join(' ', @list);
60}
61
62my $valid_lxc_keys = {
63 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
64 'lxc.include' => 1,
65 'lxc.rootfs' => 1,
66 'lxc.mount' => 1,
67 'lxc.utsname' => 1,
68
69 'lxc.id_map' => 1,
70
71 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
72 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
73 'lxc.cgroup.cpu.cfs_period_us' => '\d+',
74 'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
75 'lxc.cgroup.cpu.shares' => '\d+',
76
77 # mount related
78 'lxc.mount' => 1,
79 'lxc.mount.entry' => 1,
80 'lxc.mount.auto' => 1,
81
82 # security related
83 'lxc.seccomp' => 1,
84
85 # not used by pve
86 'lxc.tty' => '\d+',
87 'lxc.pts' => 1,
88 'lxc.haltsignal' => 1,
89 'lxc.rebootsignal' => 1,
90 'lxc.stopsignal' => 1,
91 'lxc.init_cmd' => 1,
92 'lxc.console' => 1,
93 'lxc.console.logfile' => 1,
94 'lxc.devttydir' => 1,
95 'lxc.autodev' => 1,
96 'lxc.kmsg' => 1,
97 'lxc.cap.drop' => 1,
98 'lxc.cap.keep' => 1,
99 'lxc.aa_profile' => 1,
100 'lxc.aa_allow_incomplete' => 1,
101 'lxc.se_context' => 1,
102 'lxc.loglevel' => 1,
103 'lxc.logfile' => 1,
104 'lxc.environment' => 1,
105 'lxc.cgroup.devices.deny' => 1,
106
107 # autostart
108 'lxc.start.auto' => 1,
109 'lxc.start.delay' => 1,
110 'lxc.start.order' => 1,
111 'lxc.group' => 1,
112
113 # hooks
114 'lxc.hook.pre-start' => 1,
115 'lxc.hook.pre-mount' => 1,
116 'lxc.hook.mount' => 1,
117 'lxc.hook.autodev' => 1,
118 'lxc.hook.start' => 1,
119 'lxc.hook.post-stop' => 1,
120 'lxc.hook.clone' => 1,
121
122 # pve related keys
123 'pve.nameserver' => sub {
124 my ($name, $value) = @_;
125 return verify_nameserver_list($value);
126 },
127 'pve.searchdomain' => sub {
128 my ($name, $value) = @_;
129 return verify_searchdomain_list($value);
130 },
131 'pve.onboot' => '(0|1)',
132 'pve.startup' => sub {
133 my ($name, $value) = @_;
134 return PVE::JSONSchema::pve_verify_startup_order($value);
135 },
136 'pve.comment' => 1,
137 'pve.disksize' => '\d+(\.\d+)?',
138 'pve.volid' => sub {
139 my ($name, $value) = @_;
140 parse_volume_id($value);
141 return $value;
142 },
143
144 #pve snapshot
145 'pve.lock' => 1,
146 'pve.snaptime' => 1,
147 'pve.snapcomment' => 1,
148 'pve.parent' => 1,
149 'pve.snapstate' => 1,
150 'pve.snapname' => 1,
151};
152
153my $valid_lxc_network_keys = {
154 type => 1,
155 mtu => 1,
156 name => 1, # ifname inside container
157 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
158 hwaddr => 1,
159};
160
161my $valid_pve_network_keys = {
162 bridge => 1,
163 tag => 1,
164 firewall => 1,
165 ip => 1,
166 gw => 1,
167 ip6 => 1,
168 gw6 => 1,
169};
170
171my $lxc_array_configs = {
172 'lxc.network' => 1,
173 'lxc.mount' => 1,
174 'lxc.include' => 1,
175 'lxc.id_map' => 1,
176 'lxc.cgroup.devices.deny' => 1,
177};
178
179sub parse_lxc_option {
180 my ($name, $value) = @_;
181
182 my $parser = $valid_lxc_keys->{$name};
183
184 die "invalid key '$name'\n" if !defined($parser);
185
186 if ($parser eq '1') {
187 return $value;
188 } elsif (ref($parser)) {
189 my $res = &$parser($name, $value);
190 return $res if defined($res);
191 } else {
192 # assume regex
193 return $value if $value =~ m/^$parser$/;
194 }
195
196 die "unable to parse value '$value' for option '$name'\n";
197}
198
199sub parse_lxc_config {
200 my ($filename, $raw) = @_;
201
202 return undef if !defined($raw);
203
204 my $data = {
205 #digest => Digest::SHA::sha1_hex($raw),
206 };
207
208 $filename =~ m|/lxc/(\d+)/config$|
209 || die "got strange filename '$filename'";
210
211 # Note: restore pass filename "lxc/0/config"
212 my $vmid = $1;
213
214 my $check_net_vmid = sub {
215 my ($netvmid) = @_;
216 $vmid ||= $netvmid;
217 die "wrong vmid for network interface pair\n" if $vmid != $netvmid;
218 };
219
220 my $network_counter = 0;
221 my $network_list = [];
222 my $host_ifnames = {};
223 my $snapname;
224 my $network;
225
226 my $push_network = sub {
227 my ($netconf) = @_;
228 return if !$netconf;
229 push @{$network_list}, $netconf;
230 $network_counter++;
231 if (my $netname = $netconf->{'veth.pair'}) {
232 if ($netname =~ m/^veth(\d+).(\d)$/) {
233 &$check_net_vmid($1);
234 $host_ifnames->{$netname} = 1;
235 } else {
236 die "wrong network interface pair\n";
237 }
238 }
239 };
240
241 my $finalize_section = sub {
242 &$push_network($network); # flush
243
244 foreach my $net (@{$network_list}) {
245 next if $net->{type} eq 'empty'; # skip
246 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
247 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
248 die "undefined veth network pair'\n" if !$net->{'veth.pair'};
249
250 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
251 if ($snapname) {
252 $data->{snapshots}->{$snapname}->{"net$1"} = $net;
253 } else {
254 $data->{"net$1"} = $net;
255 }
256 }
257 }
258
259 # reset helper vars
260 $network_counter = 0;
261 $network_list = [];
262 $host_ifnames = {};
263 $network = undef;
264 };
265
266 while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
267 my $line = $1;
268 next if $line =~ m/^\s*$/; # skip empty lines
269 next if $line =~ m/^#/; # skip comments
270
271 # snap.pve.snapname starts new sections
272 if ($line =~ m/^(snap\.)?pve\.snapname\s*=\s*(\w*)\s*$/) {
273 my $value = $2;
274
275 &$finalize_section();
276
277 $snapname = $value;
278 $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
279
280 } elsif ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
281 my ($subkey, $value) = ($2, $3);
282 if ($subkey eq 'type') {
283 &$push_network($network);
284 $network = { type => $value };
285 } elsif ($valid_lxc_network_keys->{$subkey}) {
286 $network->{$subkey} = $value;
287 } else {
288 die "unable to parse config line: $line\n";
289 }
290 } elsif ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
291 my ($subkey, $value) = ($2, $3);
292 if ($valid_pve_network_keys->{$subkey}) {
293 $network->{$subkey} = $value;
294 } else {
295 die "unable to parse config line: $line\n";
296 }
297 } elsif ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
298 my ($name, $value) = ($2, $3);
299
300 if ($lxc_array_configs->{$name}) {
301 $data->{$name} = [] if !defined($data->{$name});
302 if ($snapname) {
303 push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value);
304 } else {
305 push @{$data->{$name}}, parse_lxc_option($name, $value);
306 }
307 } else {
308 if ($snapname) {
309 die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
310 $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
311 } else {
312 die "multiple definitions for $name\n" if defined($data->{$name});
313 $data->{$name} = parse_lxc_option($name, $value);
314 }
315 }
316 } else {
317 die "unable to parse config line: $line\n";
318 }
319 }
320
321 &$finalize_section();
322
323 return $data;
324}
325
326# Same confdesc entries are unchanged
327# Deleted ones have `deprecated => 1`
328# New ones `new => 1`
329# New required ones `required => 1`
330my $confdesc = {
331 onboot => {
332 optional => 1,
333 type => 'boolean',
334 description => "Specifies whether a VM will be started during system bootup.",
335 default => 0,
336 },
337 startup => get_standard_option('pve-startup-order'),
338 arch => {
339 optional => 1,
340 type => 'string',
341 enum => ['amd64', 'i386'],
342 description => "OS architecture type.",
343 default => 'amd64',
344 new => 1,
345 required => 1,
346 },
347 ostype => {
348 optional => 1,
349 type => 'string',
350 enum => ['debian', 'ubuntu', 'centos'],
351 description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
352 new => 1,
353 required => 1,
354 },
355 tty => {
356 optional => 1,
357 type => 'integer',
358 description => "Specify the number of tty available to the container",
359 minimum => 0,
360 maximum => 6,
361 default => 4,
362 new => 1,
363 },
364 cpulimit => {
365 optional => 1,
366 type => 'number',
367 description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
368 minimum => 0,
369 maximum => 128,
370 default => 0,
371 },
372 cpuunits => {
373 optional => 1,
374 type => 'integer',
375 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.",
376 minimum => 0,
377 maximum => 500000,
378 default => 1000,
379 },
380 memory => {
381 optional => 1,
382 type => 'integer',
383 description => "Amount of RAM for the VM in MB.",
384 minimum => 16,
385 default => 512,
386 },
387 swap => {
388 optional => 1,
389 type => 'integer',
390 description => "Amount of SWAP for the VM in MB.",
391 minimum => 0,
392 default => 512,
393 },
394 disk => {
395 optional => 1,
396 type => 'number',
397 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
398 minimum => 0,
399 default => 4,
400 deprecated => 1,
401 },
402 hostname => {
403 optional => 1,
404 description => "Set a host name for the container.",
405 type => 'string',
406 maxLength => 255,
407 },
408 description => {
409 optional => 1,
410 type => 'string',
411 description => "Container description. Only used on the configuration web interface.",
412 },
413 searchdomain => {
414 optional => 1,
415 type => 'string',
416 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
417 },
418 nameserver => {
419 optional => 1,
420 type => 'string',
421 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.",
422 },
423 rootfs => { #get_standard_option('pve-ct-rootfs'),
424 type => 'string', format => 'pve-ct-mountpoint',
425 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
426 description => "Use volume as container root.",
427 optional => 1,
428 new => 1,
429 required => 1,
430 },
431# parent => {
432# optional => 1,
433# type => 'string', format => 'pve-configid',
434# maxLength => 40,
435# description => "Parent snapshot name. This is used internally, and should not be modified.",
436# new => 1,
437# },
438# snaptime => {
439# optional => 1,
440# description => "Timestamp for snapshots.",
441# type => 'integer',
442# minimum => 0,
443# new => 1,
444# },
445};
446
447my $MAX_LXC_NETWORKS = 10;
448for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
449 $confdesc->{"net$i"} = {
450 optional => 1,
451 type => 'string', format => 'pve-lxc-network',
452 description => "Specifies network interfaces for the container.\n\n".
453 "The string should have the follow format:\n\n".
454 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
455 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
456 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
457 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
458 };
459}
460
461sub json_config_properties {
462 my $prop = shift;
463
464 foreach my $opt (keys %$confdesc) {
465 $prop->{$opt} = $confdesc->{$opt};
466 }
467
468 return $prop;
469}
470
471sub print_lxc_network {
472 my $net = shift;
473
474 die "no network name defined\n" if !$net->{name};
475
476 my $res = "name=$net->{name}";
477
478 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
479 next if !defined($net->{$k});
480 $res .= ",$k=$net->{$k}";
481 }
482
483 return $res;
484}
485
486sub lxc_conf_to_pve {
487 my ($vmid, $lxc_conf) = @_;
488
489 my $properties = json_config_properties();
490
491 my $conf = {}; # digest => $lxc_conf->{digest} };
492
493 foreach my $k (keys %$properties) {
494
495 if ($k eq 'description') {
496 if (my $raw = $lxc_conf->{'pve.comment'}) {
497 $conf->{$k} = PVE::Tools::decode_text($raw);
498 }
499 } elsif ($k eq 'onboot') {
500 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
501 } elsif ($k eq 'startup') {
502 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
503 } elsif ($k eq 'arch') {
504 $conf->{$k} = $lxc_conf->{'lxc.arch'} if $lxc_conf->{'lxc.arch'};
505 } elsif ($k eq 'ostype') {
506 foreach (@{$lxc_conf->{'lxc.include'}}) {
507 if (m@^\s*/usr/share/lxc/config/(.*)\.common\.conf\s*$@) {
508 $conf->{$k} = $1;
509 }
510 }
511 } elsif ($k eq 'tty') {
512 $conf->{$k} = $lxc_conf->{'lxc.tty'} if $lxc_conf->{'lxc.tty'};
513 } elsif ($k eq 'rootfs') {
514 $conf->{$k} = $lxc_conf->{'pve.volid'} if $lxc_conf->{'pve.volid'};
515 } elsif ($k eq 'hostname') {
516 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
517 } elsif ($k eq 'nameserver') {
518 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
519 } elsif ($k eq 'searchdomain') {
520 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
521 } elsif ($k eq 'memory') {
522 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
523 $conf->{$k} = int($value / (1024*1024));
524 }
525 } elsif ($k eq 'swap') {
526 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
527 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
528 $conf->{$k} = int(($value - $mem) / (1024*1024));
529 }
530 } elsif ($k eq 'cpulimit') {
531 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
532 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
533
534 if ($cfs_period_us && $cfs_quota_us) {
535 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
536 } else {
537 $conf->{$k} = 0;
538 }
539 } elsif ($k eq 'cpuunits') {
540 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
541 } elsif ($k eq 'disk') {
542 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
543 $lxc_conf->{'pve.disksize'} : 0;
544 } elsif ($k =~ m/^net\d$/) {
545 my $net = $lxc_conf->{$k};
546 next if !$net;
547 $conf->{$k} = print_lxc_network($net);
548 }
549 }
550
551 if (my $parent = $lxc_conf->{'pve.parent'}) {
552 $conf->{parent} = $lxc_conf->{'pve.parent'};
553 }
554
555 if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
556 $conf->{description} = $lxc_conf->{'pve.snapcomment'};
557 }
558
559 if (my $parent = $lxc_conf->{'pve.snaptime'}) {
560 $conf->{snaptime} = $lxc_conf->{'pve.snaptime'};
561 }
562
563 return $conf;
564}
565
566sub convert($) {
567 my ($vmid) = @_;
568 my $out = "/etc/pve/lxc/${vmid}.conf";
569 my $old = "/etc/pve/lxc/${vmid}/config";
570
b94d3e0b 571 return if -f $out;
4968d0f7
WB
572
573 print "converting $old to $out\n";
574
575 open my $fh, '<', $old or die "failed to open config $old: $!";
576 my $raw = do { local $/; <$fh> };
577 close $fh;
578
579 my $data = parse_lxc_config($old, $raw);
580 my $new = lxc_conf_to_pve($vmid, $data);
581
582 delete $new->{$_} for grep { $confdesc->{$_}->{deprecated} } keys %$new;
583
584 foreach my $required (grep { $confdesc->{$_}->{required} } keys %$confdesc) {
585 if (!$new->{$required}) {
586 die "failed to create required config key '$required'";
587 }
588 }
589
590 open $fh, '>', $out or die "failed to open output file $out: $!";
591 print {$fh} "$_: $new->{$_}\n" for sort keys %$new;
592 close $fh;
593}
594
595chdir '/etc/pve/lxc' or die "failed to change directory to /etc/pve/lxc: $!";
596for (<*>) {
597 next if !/^\d+$/ || ! -d $_;
598 eval { convert($_); };
599 warn $@ if $@;
600}