]>
Commit | Line | Data |
---|---|---|
f76a2828 DM |
1 | package PVE::LXC; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
d14a9a1b | 5 | use POSIX qw(EINTR); |
f76a2828 DM |
6 | |
7 | use File::Path; | |
8 | use Fcntl ':flock'; | |
9 | ||
10 | use PVE::Cluster qw(cfs_register_file cfs_read_file); | |
c65e0a6d | 11 | use PVE::Storage; |
f76a2828 DM |
12 | use PVE::SafeSyslog; |
13 | use PVE::INotify; | |
a3249355 | 14 | use PVE::JSONSchema qw(get_standard_option); |
55fa4e09 | 15 | use PVE::Tools qw($IPV6RE $IPV4RE); |
68fba17b | 16 | use PVE::Network; |
f76a2828 DM |
17 | |
18 | use Data::Dumper; | |
19 | ||
20 | cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config); | |
21 | ||
7dfc49cc DM |
22 | PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network); |
23 | sub verify_lxc_network { | |
24 | my ($value, $noerr) = @_; | |
25 | ||
26 | return $value if parse_lxc_network($value); | |
27 | ||
28 | return undef if $noerr; | |
29 | ||
30 | die "unable to parse network setting\n"; | |
31 | } | |
32 | ||
f76a2828 DM |
33 | my $nodename = PVE::INotify::nodename(); |
34 | ||
822de0c3 DM |
35 | sub parse_lxc_size { |
36 | my ($name, $value) = @_; | |
37 | ||
38 | if ($value =~ m/^(\d+)(b|k|m|g)?$/i) { | |
39 | my ($res, $unit) = ($1, lc($2 || 'b')); | |
40 | ||
41 | return $res if $unit eq 'b'; | |
42 | return $res*1024 if $unit eq 'k'; | |
43 | return $res*1024*1024 if $unit eq 'm'; | |
44 | return $res*1024*1024*1024 if $unit eq 'g'; | |
45 | } | |
46 | ||
47 | return undef; | |
48 | } | |
49 | ||
f76a2828 | 50 | my $valid_lxc_keys = { |
822de0c3 | 51 | 'lxc.arch' => 'i386|x86|i686|x86_64|amd64', |
f76a2828 DM |
52 | 'lxc.include' => 1, |
53 | 'lxc.rootfs' => 1, | |
54 | 'lxc.mount' => 1, | |
55 | 'lxc.utsname' => 1, | |
56 | ||
54664cd3 | 57 | 'lxc.id_map' => 1, |
cbb03fea | 58 | |
822de0c3 | 59 | 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size, |
44da0641 | 60 | 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size, |
b80dd50a DM |
61 | 'lxc.cgroup.cpu.cfs_period_us' => '\d+', |
62 | 'lxc.cgroup.cpu.cfs_quota_us' => '\d+', | |
63 | 'lxc.cgroup.cpu.shares' => '\d+', | |
cbb03fea | 64 | |
54664cd3 DM |
65 | # mount related |
66 | 'lxc.mount' => 1, | |
67 | 'lxc.mount.entry' => 1, | |
68 | 'lxc.mount.auto' => 1, | |
69 | ||
70 | # not used by pve | |
b80dd50a | 71 | 'lxc.tty' => '\d+', |
54664cd3 DM |
72 | 'lxc.pts' => 1, |
73 | 'lxc.haltsignal' => 1, | |
cbb03fea | 74 | 'lxc.rebootsignal' => 1, |
54664cd3 DM |
75 | 'lxc.stopsignal' => 1, |
76 | 'lxc.init_cmd' => 1, | |
77 | 'lxc.console' => 1, | |
78 | 'lxc.console.logfile' => 1, | |
79 | 'lxc.devttydir' => 1, | |
80 | 'lxc.autodev' => 1, | |
81 | 'lxc.kmsg' => 1, | |
82 | 'lxc.cap.drop' => 1, | |
83 | 'lxc.cap.keep' => 1, | |
84 | 'lxc.aa_profile' => 1, | |
85 | 'lxc.aa_allow_incomplete' => 1, | |
86 | 'lxc.se_context' => 1, | |
87 | 'lxc.loglevel' => 1, | |
88 | 'lxc.logfile' => 1, | |
89 | 'lxc.environment' => 1, | |
0d3abdf7 | 90 | 'lxc.cgroup.devices.deny' => 1, |
54664cd3 DM |
91 | |
92 | # autostart | |
93 | 'lxc.start.auto' => 1, | |
94 | 'lxc.start.delay' => 1, | |
95 | 'lxc.start.order' => 1, | |
96 | 'lxc.group' => 1, | |
97 | ||
98 | # hooks | |
99 | 'lxc.hook.pre-start' => 1, | |
100 | 'lxc.hook.pre-mount' => 1, | |
101 | 'lxc.hook.mount' => 1, | |
102 | 'lxc.hook.autodev' => 1, | |
103 | 'lxc.hook.start' => 1, | |
104 | 'lxc.hook.post-stop' => 1, | |
105 | 'lxc.hook.clone' => 1, | |
cbb03fea | 106 | |
f76a2828 | 107 | # pve related keys |
117636e5 DM |
108 | 'pve.nameserver' => sub { |
109 | my ($name, $value) = @_; | |
110 | return verify_nameserver_list($value); | |
111 | }, | |
112 | 'pve.searchdomain' => sub { | |
113 | my ($name, $value) = @_; | |
114 | return verify_searchdomain_list($value); | |
115 | }, | |
a99b3509 | 116 | 'pve.onboot' => '(0|1)', |
a3249355 DM |
117 | 'pve.startup' => sub { |
118 | my ($name, $value) = @_; | |
119 | return PVE::JSONSchema::pve_verify_startup_order($value); | |
120 | }, | |
f76a2828 | 121 | 'pve.comment' => 1, |
10fc3ba5 | 122 | 'pve.disksize' => '\d+(\.\d+)?', |
611fe3aa DM |
123 | 'pve.volid' => sub { |
124 | my ($name, $value) = @_; | |
125 | PVE::Storage::parse_volume_id($value); | |
126 | return $value; | |
127 | }, | |
a12a36e0 WL |
128 | |
129 | #pve snapshot | |
130 | 'pve.lock' => 1, | |
131 | 'pve.snaptime' => 1, | |
132 | 'pve.snapcomment' => 1, | |
133 | 'pve.parent' => 1, | |
134 | 'pve.snapstate' => 1, | |
135 | 'pve.snapname' => 1, | |
f76a2828 DM |
136 | }; |
137 | ||
93285df8 | 138 | my $valid_lxc_network_keys = { |
f76a2828 | 139 | type => 1, |
f76a2828 DM |
140 | mtu => 1, |
141 | name => 1, # ifname inside container | |
142 | 'veth.pair' => 1, # ifname at host (eth${vmid}.X) | |
143 | hwaddr => 1, | |
93285df8 DM |
144 | }; |
145 | ||
146 | my $valid_pve_network_keys = { | |
8a04b6c7 | 147 | bridge => 1, |
2b1fc2ea DM |
148 | tag => 1, |
149 | firewall => 1, | |
93285df8 DM |
150 | ip => 1, |
151 | gw => 1, | |
152 | ip6 => 1, | |
153 | gw6 => 1, | |
f76a2828 DM |
154 | }; |
155 | ||
156 | my $lxc_array_configs = { | |
157 | 'lxc.network' => 1, | |
158 | 'lxc.mount' => 1, | |
159 | 'lxc.include' => 1, | |
5b4657d0 | 160 | 'lxc.id_map' => 1, |
0d3abdf7 | 161 | 'lxc.cgroup.devices.deny' => 1, |
f76a2828 DM |
162 | }; |
163 | ||
164 | sub write_lxc_config { | |
165 | my ($filename, $data) = @_; | |
166 | ||
167 | my $raw = ""; | |
168 | ||
169 | return $raw if !$data; | |
170 | ||
63e9c5ca | 171 | my $dump_entry = sub { |
ee48a58b | 172 | my ($k, $value, $done_hash, $snapshot) = @_; |
63e9c5ca DM |
173 | return if !defined($value); |
174 | return if $done_hash->{$k}; | |
f76a2828 | 175 | $done_hash->{$k} = 1; |
63e9c5ca | 176 | if (ref($value)) { |
cbb03fea | 177 | die "got unexpected reference for '$k'" |
63e9c5ca DM |
178 | if !$lxc_array_configs->{$k}; |
179 | foreach my $v (@$value) { | |
ee48a58b | 180 | $raw .= 'snap.' if $snapshot; |
5b4657d0 DM |
181 | $raw .= "$k = $v\n"; |
182 | } | |
183 | } else { | |
ee48a58b | 184 | $raw .= 'snap.' if $snapshot; |
63e9c5ca | 185 | $raw .= "$k = $value\n"; |
5b4657d0 | 186 | } |
63e9c5ca DM |
187 | }; |
188 | ||
a12a36e0 WL |
189 | my $config_writer = sub { |
190 | my ($elem, $snapshot) = @_; | |
cbb03fea | 191 | |
a12a36e0 | 192 | my $done_hash = { digest => 1}; |
7dfc49cc | 193 | |
ee48a58b DM |
194 | if (defined(my $value = $elem->{'pve.snapname'})) { |
195 | &$dump_entry('pve.snapname', $value, $done_hash, $snapshot); | |
a12a36e0 | 196 | } |
fff3a342 | 197 | |
a12a36e0 WL |
198 | # Note: Order is important! Include defaults first, so that we |
199 | # can overwrite them later. | |
ee48a58b | 200 | &$dump_entry('lxc.include', $elem->{'lxc.include'}, $done_hash, $snapshot); |
a12a36e0 WL |
201 | |
202 | foreach my $k (sort keys %$elem) { | |
203 | next if $k !~ m/^lxc\./; | |
ee48a58b | 204 | &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot); |
a12a36e0 WL |
205 | } |
206 | foreach my $k (sort keys %$elem) { | |
207 | next if $k !~ m/^pve\./; | |
ee48a58b | 208 | &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot); |
a12a36e0 WL |
209 | } |
210 | my $network_count = 0; | |
211 | ||
212 | foreach my $k (sort keys %$elem) { | |
213 | next if $k !~ m/^net\d+$/; | |
214 | $done_hash->{$k} = 1; | |
215 | ||
216 | my $net = $elem->{$k}; | |
217 | $network_count++; | |
ee48a58b | 218 | $raw .= 'snap.' if $snapshot; |
a12a36e0 WL |
219 | $raw .= "lxc.network.type = $net->{type}\n"; |
220 | foreach my $subkey (sort keys %$net) { | |
221 | next if $subkey eq 'type'; | |
222 | if ($valid_lxc_network_keys->{$subkey}) { | |
ee48a58b | 223 | $raw .= 'snap.' if $snapshot; |
a12a36e0 WL |
224 | $raw .= "lxc.network.$subkey = $net->{$subkey}\n"; |
225 | } elsif ($valid_pve_network_keys->{$subkey}) { | |
ee48a58b | 226 | $raw .= 'snap.' if $snapshot; |
a12a36e0 WL |
227 | $raw .= "pve.network.$subkey = $net->{$subkey}\n"; |
228 | } else { | |
229 | die "found invalid network key '$subkey'"; | |
230 | } | |
93285df8 | 231 | } |
f76a2828 | 232 | } |
a12a36e0 | 233 | if (!$network_count) { |
ee48a58b | 234 | $raw .= 'snap.' if $snapshot; |
a12a36e0 WL |
235 | $raw .= "lxc.network.type = empty\n"; |
236 | } | |
237 | foreach my $k (sort keys %$elem) { | |
238 | next if $k eq 'snapshots'; | |
239 | next if $done_hash->{$k}; | |
a92f66c9 | 240 | die "found un-written value \"$k\" in config - implement this!"; |
a12a36e0 | 241 | } |
f76a2828 | 242 | |
a12a36e0 | 243 | }; |
160f0941 | 244 | |
a12a36e0 WL |
245 | &$config_writer($data); |
246 | ||
ee48a58b DM |
247 | if ($data->{snapshots}) { |
248 | my @tmp = sort { $data->{snapshots}->{$b}{'pve.snaptime'} <=> | |
a12a36e0 WL |
249 | $data->{snapshots}->{$a}{'pve.snaptime'} } |
250 | keys %{$data->{snapshots}}; | |
251 | foreach my $snapname (@tmp) { | |
252 | $raw .= "\n"; | |
253 | &$config_writer($data->{snapshots}->{$snapname}, 1); | |
254 | } | |
f76a2828 DM |
255 | } |
256 | ||
f76a2828 DM |
257 | return $raw; |
258 | } | |
259 | ||
822de0c3 DM |
260 | sub parse_lxc_option { |
261 | my ($name, $value) = @_; | |
262 | ||
263 | my $parser = $valid_lxc_keys->{$name}; | |
264 | ||
fe81a350 | 265 | die "invalid key '$name'\n" if !defined($parser); |
822de0c3 DM |
266 | |
267 | if ($parser eq '1') { | |
cbb03fea | 268 | return $value; |
822de0c3 DM |
269 | } elsif (ref($parser)) { |
270 | my $res = &$parser($name, $value); | |
271 | return $res if defined($res); | |
272 | } else { | |
273 | # assume regex | |
274 | return $value if $value =~ m/^$parser$/; | |
275 | } | |
cbb03fea | 276 | |
822de0c3 DM |
277 | die "unable to parse value '$value' for option '$name'\n"; |
278 | } | |
279 | ||
f76a2828 DM |
280 | sub parse_lxc_config { |
281 | my ($filename, $raw) = @_; | |
282 | ||
283 | return undef if !defined($raw); | |
284 | ||
285 | my $data = { | |
286 | digest => Digest::SHA::sha1_hex($raw), | |
287 | }; | |
288 | ||
289 | $filename =~ m|/lxc/(\d+)/config$| | |
290 | || die "got strange filename '$filename'"; | |
291 | ||
292 | my $vmid = $1; | |
293 | ||
a12a36e0 WL |
294 | my $split_config = sub { |
295 | my ($raw) = @_; | |
296 | my $sections = []; | |
297 | my $tmp = ''; | |
298 | while ($raw && $raw =~ s/^(.*)?(\n|$)//) { | |
299 | my $line = $1; | |
300 | if(!$line) { | |
301 | push(@{$sections},$tmp); | |
302 | $tmp = ''; | |
303 | } else { | |
304 | $tmp .= "$line\n"; | |
f76a2828 DM |
305 | } |
306 | } | |
a12a36e0 | 307 | push(@{$sections},$tmp); |
f76a2828 | 308 | |
a12a36e0 | 309 | return $sections; |
f76a2828 | 310 | }; |
7dfc49cc | 311 | |
a12a36e0 | 312 | my $sec = &$split_config($raw); |
7dfc49cc | 313 | |
a12a36e0 WL |
314 | foreach my $sec_raw (@{$sec}){ |
315 | next if $sec_raw eq ''; | |
316 | my $snapname = undef; | |
f76a2828 | 317 | |
a12a36e0 WL |
318 | my $network_counter = 0; |
319 | my $network_list = []; | |
320 | my $host_ifnames = {}; | |
f76a2828 | 321 | |
a12a36e0 WL |
322 | my $find_next_hostif_name = sub { |
323 | for (my $i = 0; $i < 10; $i++) { | |
324 | my $name = "veth${vmid}.$i"; | |
325 | if (!$host_ifnames->{$name}) { | |
326 | $host_ifnames->{$name} = 1; | |
327 | return $name; | |
328 | } | |
f76a2828 | 329 | } |
f76a2828 | 330 | |
a12a36e0 WL |
331 | die "unable to find free host_ifname"; # should not happen |
332 | }; | |
333 | ||
334 | my $push_network = sub { | |
335 | my ($netconf) = @_; | |
336 | return if !$netconf; | |
337 | push @{$network_list}, $netconf; | |
338 | $network_counter++; | |
339 | if (my $netname = $netconf->{'veth.pair'}) { | |
340 | if ($netname =~ m/^veth(\d+).(\d)$/) { | |
341 | die "wrong vmid for network interface pair\n" if $1 != $vmid; | |
342 | my $host_ifnames->{$netname} = 1; | |
343 | } else { | |
344 | die "wrong network interface pair\n"; | |
345 | } | |
5b4657d0 | 346 | } |
a12a36e0 | 347 | }; |
cbb03fea | 348 | |
a12a36e0 | 349 | my $network; |
f76a2828 | 350 | |
a12a36e0 WL |
351 | while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) { |
352 | my $line = $1; | |
f76a2828 | 353 | |
a12a36e0 WL |
354 | next if $line =~ m/^\#/; |
355 | next if $line =~ m/^\s*$/; | |
f76a2828 | 356 | |
a12a36e0 WL |
357 | if ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) { |
358 | my ($subkey, $value) = ($2, $3); | |
359 | if ($subkey eq 'type') { | |
360 | &$push_network($network); | |
361 | $network = { type => $value }; | |
362 | } elsif ($valid_lxc_network_keys->{$subkey}) { | |
363 | $network->{$subkey} = $value; | |
364 | } else { | |
365 | die "unable to parse config line: $line\n"; | |
366 | } | |
367 | next; | |
368 | } | |
369 | if ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) { | |
370 | my ($subkey, $value) = ($2, $3); | |
371 | if ($valid_pve_network_keys->{$subkey}) { | |
372 | $network->{$subkey} = $value; | |
373 | } else { | |
374 | die "unable to parse config line: $line\n"; | |
375 | } | |
376 | next; | |
377 | } | |
378 | if ($line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/) { | |
379 | my ($name, $value) = ($2, $3); | |
380 | if ($snapname) { | |
381 | $data->{snapshots}->{$snapname}->{$name} = $value; | |
382 | } | |
383 | next; | |
384 | } | |
385 | if ($line =~ m/^(snap\.)?pve\.snapname = (\w*)$/) { | |
386 | if (!$snapname) { | |
387 | $snapname = $2; | |
388 | $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname; | |
389 | } else { | |
390 | die "Configuarion broken\n"; | |
391 | } | |
392 | next; | |
393 | } | |
394 | if ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) { | |
395 | my ($name, $value) = ($2, $3); | |
396 | ||
397 | if ($lxc_array_configs->{$name}) { | |
398 | $data->{$name} = [] if !defined($data->{$name}); | |
399 | if ($snapname) { | |
400 | push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value); | |
401 | } else { | |
402 | push @{$data->{$name}}, parse_lxc_option($name, $value); | |
403 | } | |
404 | } else { | |
405 | if ($snapname) { | |
406 | die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name}); | |
407 | $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value); | |
408 | } else { | |
409 | die "multiple definitions for $name\n" if defined($data->{$name}); | |
410 | $data->{$name} = parse_lxc_option($name, $value); | |
411 | } | |
412 | } | |
f76a2828 | 413 | |
a12a36e0 WL |
414 | next; |
415 | } | |
416 | die "unable to parse config line: $line\n"; | |
417 | } | |
418 | &$push_network($network); | |
419 | ||
420 | foreach my $net (@{$network_list}) { | |
421 | next if $net->{type} eq 'empty'; # skip | |
422 | $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'}; | |
423 | $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr}; | |
424 | die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth'; | |
425 | ||
426 | if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) { | |
427 | if ($snapname) { | |
ee48a58b | 428 | $data->{snapshots}->{$snapname}->{"net$1"} = $net; |
a12a36e0 WL |
429 | } else { |
430 | $data->{"net$1"} = $net; | |
ee48a58b | 431 | } |
a12a36e0 | 432 | } |
f76a2828 | 433 | } |
7dfc49cc DM |
434 | } |
435 | ||
436 | return $data; | |
f76a2828 DM |
437 | } |
438 | ||
439 | sub config_list { | |
440 | my $vmlist = PVE::Cluster::get_vmlist(); | |
441 | my $res = {}; | |
442 | return $res if !$vmlist || !$vmlist->{ids}; | |
443 | my $ids = $vmlist->{ids}; | |
444 | ||
445 | foreach my $vmid (keys %$ids) { | |
446 | next if !$vmid; # skip CT0 | |
447 | my $d = $ids->{$vmid}; | |
448 | next if !$d->{node} || $d->{node} ne $nodename; | |
449 | next if !$d->{type} || $d->{type} ne 'lxc'; | |
450 | $res->{$vmid}->{type} = 'lxc'; | |
451 | } | |
452 | return $res; | |
453 | } | |
454 | ||
455 | sub cfs_config_path { | |
456 | my ($vmid, $node) = @_; | |
457 | ||
458 | $node = $nodename if !$node; | |
459 | return "nodes/$node/lxc/$vmid/config"; | |
460 | } | |
461 | ||
9c2d4ce9 DM |
462 | sub config_file { |
463 | my ($vmid, $node) = @_; | |
464 | ||
465 | my $cfspath = cfs_config_path($vmid, $node); | |
466 | return "/etc/pve/$cfspath"; | |
467 | } | |
468 | ||
f76a2828 DM |
469 | sub load_config { |
470 | my ($vmid) = @_; | |
471 | ||
472 | my $cfspath = cfs_config_path($vmid); | |
473 | ||
474 | my $conf = PVE::Cluster::cfs_read_file($cfspath); | |
475 | die "container $vmid does not exists\n" if !defined($conf); | |
476 | ||
477 | return $conf; | |
478 | } | |
479 | ||
5b4657d0 DM |
480 | sub create_config { |
481 | my ($vmid, $conf) = @_; | |
482 | ||
483 | my $dir = "/etc/pve/nodes/$nodename/lxc"; | |
484 | mkdir $dir; | |
485 | ||
486 | $dir .= "/$vmid"; | |
487 | mkdir($dir) || die "unable to create container configuration directory - $!\n"; | |
488 | ||
489 | write_config($vmid, $conf); | |
490 | } | |
491 | ||
492 | sub destroy_config { | |
493 | my ($vmid) = @_; | |
494 | ||
495 | my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid"; | |
496 | File::Path::rmtree($dir); | |
497 | } | |
498 | ||
f76a2828 DM |
499 | sub write_config { |
500 | my ($vmid, $conf) = @_; | |
501 | ||
502 | my $cfspath = cfs_config_path($vmid); | |
503 | ||
504 | PVE::Cluster::cfs_write_file($cfspath, $conf); | |
505 | } | |
506 | ||
9c2d4ce9 DM |
507 | my $tempcounter = 0; |
508 | sub write_temp_config { | |
509 | my ($vmid, $conf) = @_; | |
7dfc49cc | 510 | |
9c2d4ce9 DM |
511 | $tempcounter++; |
512 | my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf"; | |
513 | ||
514 | my $raw = write_lxc_config($filename, $conf); | |
515 | ||
516 | PVE::Tools::file_set_contents($filename, $raw); | |
7dfc49cc | 517 | |
9c2d4ce9 DM |
518 | return $filename; |
519 | } | |
520 | ||
d14a9a1b DM |
521 | # flock: we use one file handle per process, so lock file |
522 | # can be called multiple times and succeeds for the same process. | |
523 | ||
524 | my $lock_handles = {}; | |
525 | my $lockdir = "/run/lock/lxc"; | |
526 | ||
527 | sub lock_filename { | |
528 | my ($vmid) = @_; | |
cbb03fea | 529 | |
d14a9a1b DM |
530 | return "$lockdir/pve-config-{$vmid}.lock"; |
531 | } | |
532 | ||
533 | sub lock_aquire { | |
534 | my ($vmid, $timeout) = @_; | |
535 | ||
536 | $timeout = 10 if !$timeout; | |
537 | my $mode = LOCK_EX; | |
538 | ||
539 | my $filename = lock_filename($vmid); | |
540 | ||
f99e8278 AD |
541 | mkdir $lockdir if !-d $lockdir; |
542 | ||
d14a9a1b DM |
543 | my $lock_func = sub { |
544 | if (!$lock_handles->{$$}->{$filename}) { | |
545 | my $fh = new IO::File(">>$filename") || | |
546 | die "can't open file - $!\n"; | |
547 | $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0}; | |
548 | } | |
549 | ||
550 | if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) { | |
551 | print STDERR "trying to aquire lock..."; | |
552 | my $success; | |
553 | while(1) { | |
554 | $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode); | |
555 | # try again on EINTR (see bug #273) | |
556 | if ($success || ($! != EINTR)) { | |
557 | last; | |
558 | } | |
559 | } | |
560 | if (!$success) { | |
561 | print STDERR " failed\n"; | |
562 | die "can't aquire lock - $!\n"; | |
563 | } | |
564 | ||
565 | $lock_handles->{$$}->{$filename}->{refcount}++; | |
cbb03fea | 566 | |
d14a9a1b DM |
567 | print STDERR " OK\n"; |
568 | } | |
569 | }; | |
570 | ||
571 | eval { PVE::Tools::run_with_timeout($timeout, $lock_func); }; | |
572 | my $err = $@; | |
573 | if ($err) { | |
574 | die "can't lock file '$filename' - $err"; | |
cbb03fea | 575 | } |
d14a9a1b DM |
576 | } |
577 | ||
578 | sub lock_release { | |
579 | my ($vmid) = @_; | |
580 | ||
581 | my $filename = lock_filename($vmid); | |
582 | ||
583 | if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) { | |
584 | my $refcount = --$lock_handles->{$$}->{$filename}->{refcount}; | |
585 | if ($refcount <= 0) { | |
586 | $lock_handles->{$$}->{$filename} = undef; | |
587 | close ($fh); | |
588 | } | |
589 | } | |
590 | } | |
591 | ||
f76a2828 DM |
592 | sub lock_container { |
593 | my ($vmid, $timeout, $code, @param) = @_; | |
594 | ||
d14a9a1b | 595 | my $res; |
f76a2828 | 596 | |
d14a9a1b DM |
597 | lock_aquire($vmid, $timeout); |
598 | eval { $res = &$code(@param) }; | |
599 | my $err = $@; | |
600 | lock_release($vmid); | |
f76a2828 | 601 | |
d14a9a1b | 602 | die $err if $err; |
f76a2828 DM |
603 | |
604 | return $res; | |
605 | } | |
606 | ||
607 | my $confdesc = { | |
608 | onboot => { | |
609 | optional => 1, | |
610 | type => 'boolean', | |
611 | description => "Specifies whether a VM will be started during system bootup.", | |
612 | default => 0, | |
613 | }, | |
a3249355 | 614 | startup => get_standard_option('pve-startup-order'), |
45573f7c | 615 | cpulimit => { |
f76a2828 | 616 | optional => 1, |
8daf6a1c | 617 | type => 'number', |
45573f7c | 618 | 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.", |
44da0641 DM |
619 | minimum => 0, |
620 | maximum => 128, | |
621 | default => 0, | |
f76a2828 DM |
622 | }, |
623 | cpuunits => { | |
624 | optional => 1, | |
625 | type => 'integer', | |
626 | 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.", | |
627 | minimum => 0, | |
628 | maximum => 500000, | |
629 | default => 1000, | |
630 | }, | |
631 | memory => { | |
632 | optional => 1, | |
633 | type => 'integer', | |
634 | description => "Amount of RAM for the VM in MB.", | |
635 | minimum => 16, | |
636 | default => 512, | |
637 | }, | |
638 | swap => { | |
639 | optional => 1, | |
640 | type => 'integer', | |
641 | description => "Amount of SWAP for the VM in MB.", | |
642 | minimum => 0, | |
643 | default => 512, | |
644 | }, | |
645 | disk => { | |
646 | optional => 1, | |
647 | type => 'number', | |
648 | description => "Amount of disk space for the VM in GB. A zero indicates no limits.", | |
649 | minimum => 0, | |
10fc3ba5 | 650 | default => 4, |
f76a2828 DM |
651 | }, |
652 | hostname => { | |
653 | optional => 1, | |
654 | description => "Set a host name for the container.", | |
655 | type => 'string', | |
656 | maxLength => 255, | |
657 | }, | |
658 | description => { | |
659 | optional => 1, | |
660 | type => 'string', | |
661 | description => "Container description. Only used on the configuration web interface.", | |
662 | }, | |
663 | searchdomain => { | |
664 | optional => 1, | |
665 | type => 'string', | |
666 | description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.", | |
667 | }, | |
668 | nameserver => { | |
669 | optional => 1, | |
670 | type => 'string', | |
671 | 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.", | |
672 | }, | |
ec52ac21 DM |
673 | }; |
674 | ||
675 | my $MAX_LXC_NETWORKS = 10; | |
676 | for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) { | |
677 | $confdesc->{"net$i"} = { | |
f76a2828 DM |
678 | optional => 1, |
679 | type => 'string', format => 'pve-lxc-network', | |
b156e7cd | 680 | description => "Specifies network interfaces for the container.\n\n". |
681 | "The string should have the follow format:\n\n". | |
682 | "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n". | |
683 | "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n". | |
684 | ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n". | |
685 | ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]", | |
7dfc49cc | 686 | }; |
ec52ac21 DM |
687 | } |
688 | ||
689 | sub option_exists { | |
690 | my ($name) = @_; | |
691 | ||
692 | return defined($confdesc->{$name}); | |
693 | } | |
f76a2828 DM |
694 | |
695 | # add JSON properties for create and set function | |
696 | sub json_config_properties { | |
697 | my $prop = shift; | |
698 | ||
699 | foreach my $opt (keys %$confdesc) { | |
700 | $prop->{$opt} = $confdesc->{$opt}; | |
701 | } | |
702 | ||
703 | return $prop; | |
704 | } | |
705 | ||
822de0c3 DM |
706 | # container status helpers |
707 | ||
708 | sub list_active_containers { | |
cbb03fea | 709 | |
822de0c3 DM |
710 | my $filename = "/proc/net/unix"; |
711 | ||
712 | # similar test is used by lcxcontainers.c: list_active_containers | |
713 | my $res = {}; | |
cbb03fea | 714 | |
822de0c3 DM |
715 | my $fh = IO::File->new ($filename, "r"); |
716 | return $res if !$fh; | |
717 | ||
718 | while (defined(my $line = <$fh>)) { | |
719 | if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) { | |
720 | my $path = $1; | |
721 | if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) { | |
722 | $res->{$1} = 1; | |
723 | } | |
724 | } | |
725 | } | |
726 | ||
727 | close($fh); | |
cbb03fea | 728 | |
822de0c3 DM |
729 | return $res; |
730 | } | |
f76a2828 | 731 | |
5c752bbf DM |
732 | # warning: this is slow |
733 | sub check_running { | |
734 | my ($vmid) = @_; | |
735 | ||
736 | my $active_hash = list_active_containers(); | |
737 | ||
738 | return 1 if defined($active_hash->{$vmid}); | |
cbb03fea | 739 | |
5c752bbf DM |
740 | return undef; |
741 | } | |
742 | ||
10fc3ba5 DM |
743 | sub get_container_disk_usage { |
744 | my ($vmid) = @_; | |
745 | ||
746 | my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/']; | |
cbb03fea | 747 | |
10fc3ba5 DM |
748 | my $res = { |
749 | total => 0, | |
750 | used => 0, | |
751 | avail => 0, | |
752 | }; | |
753 | ||
754 | my $parser = sub { | |
755 | my $line = shift; | |
756 | if (my ($fsid, $total, $used, $avail) = $line =~ | |
757 | m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) { | |
758 | $res = { | |
759 | total => $total, | |
760 | used => $used, | |
761 | avail => $avail, | |
762 | }; | |
763 | } | |
764 | }; | |
765 | eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); }; | |
766 | warn $@ if $@; | |
767 | ||
768 | return $res; | |
769 | } | |
770 | ||
f76a2828 DM |
771 | sub vmstatus { |
772 | my ($opt_vmid) = @_; | |
773 | ||
774 | my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list(); | |
775 | ||
822de0c3 | 776 | my $active_hash = list_active_containers(); |
cbb03fea | 777 | |
f76a2828 | 778 | foreach my $vmid (keys %$list) { |
f76a2828 | 779 | my $d = $list->{$vmid}; |
10fc3ba5 DM |
780 | |
781 | my $running = defined($active_hash->{$vmid}); | |
cbb03fea | 782 | |
10fc3ba5 | 783 | $d->{status} = $running ? 'running' : 'stopped'; |
f76a2828 DM |
784 | |
785 | my $cfspath = cfs_config_path($vmid); | |
238a56cb | 786 | my $conf = PVE::Cluster::cfs_read_file($cfspath) || {}; |
cbb03fea | 787 | |
238a56cb DM |
788 | $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid"; |
789 | $d->{name} =~ s/[\s]//g; | |
cbb03fea | 790 | |
44da0641 DM |
791 | $d->{cpus} = 0; |
792 | ||
793 | my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'}; | |
794 | my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'}; | |
795 | ||
796 | if ($cfs_period_us && $cfs_quota_us) { | |
797 | $d->{cpus} = int($cfs_quota_us/$cfs_period_us); | |
798 | } | |
cbb03fea | 799 | |
238a56cb | 800 | $d->{disk} = 0; |
10fc3ba5 DM |
801 | $d->{maxdisk} = defined($conf->{'pve.disksize'}) ? |
802 | int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024; | |
cbb03fea | 803 | |
238a56cb | 804 | if (my $private = $conf->{'lxc.rootfs'}) { |
10fc3ba5 DM |
805 | if ($private =~ m!^/!) { |
806 | my $res = PVE::Tools::df($private, 2); | |
807 | $d->{disk} = $res->{used}; | |
808 | $d->{maxdisk} = $res->{total}; | |
809 | } elsif ($running) { | |
6ed8c6dd | 810 | if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) { |
10fc3ba5 DM |
811 | my $res = get_container_disk_usage($vmid); |
812 | $d->{disk} = $res->{used}; | |
813 | $d->{maxdisk} = $res->{total}; | |
cbb03fea | 814 | } |
10fc3ba5 | 815 | } |
238a56cb | 816 | } |
cbb03fea | 817 | |
238a56cb DM |
818 | $d->{mem} = 0; |
819 | $d->{swap} = 0; | |
820 | $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) + | |
44da0641 | 821 | ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0); |
e901d418 | 822 | |
238a56cb DM |
823 | $d->{uptime} = 0; |
824 | $d->{cpu} = 0; | |
e901d418 | 825 | |
238a56cb DM |
826 | $d->{netout} = 0; |
827 | $d->{netin} = 0; | |
f76a2828 | 828 | |
238a56cb DM |
829 | $d->{diskread} = 0; |
830 | $d->{diskwrite} = 0; | |
f76a2828 | 831 | } |
cbb03fea | 832 | |
238a56cb DM |
833 | foreach my $vmid (keys %$list) { |
834 | my $d = $list->{$vmid}; | |
835 | next if $d->{status} ne 'running'; | |
f76a2828 | 836 | |
22a77285 DM |
837 | $d->{uptime} = 100; # fixme: |
838 | ||
238a56cb DM |
839 | $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes'); |
840 | $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem}; | |
b5289322 AD |
841 | |
842 | my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1); | |
1e647c7c | 843 | my @bytes = split(/\n/, $blkio_bytes); |
b5289322 | 844 | foreach my $byte (@bytes) { |
1e647c7c DM |
845 | if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) { |
846 | $d->{diskread} = $2 if $key eq 'Read'; | |
847 | $d->{diskwrite} = $2 if $key eq 'Write'; | |
848 | } | |
b5289322 | 849 | } |
238a56cb | 850 | } |
cbb03fea | 851 | |
f76a2828 DM |
852 | return $list; |
853 | } | |
854 | ||
7dfc49cc DM |
855 | |
856 | sub print_lxc_network { | |
f76a2828 DM |
857 | my $net = shift; |
858 | ||
bedeaaf1 | 859 | die "no network name defined\n" if !$net->{name}; |
f76a2828 | 860 | |
bedeaaf1 | 861 | my $res = "name=$net->{name}"; |
7dfc49cc | 862 | |
bedeaaf1 | 863 | foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) { |
f76a2828 DM |
864 | next if !defined($net->{$k}); |
865 | $res .= ",$k=$net->{$k}"; | |
866 | } | |
7dfc49cc | 867 | |
f76a2828 DM |
868 | return $res; |
869 | } | |
870 | ||
7dfc49cc DM |
871 | sub parse_lxc_network { |
872 | my ($data) = @_; | |
873 | ||
874 | my $res = {}; | |
875 | ||
876 | return $res if !$data; | |
877 | ||
878 | foreach my $pv (split (/,/, $data)) { | |
2b1fc2ea | 879 | if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) { |
7dfc49cc DM |
880 | $res->{$1} = $2; |
881 | } else { | |
882 | return undef; | |
883 | } | |
884 | } | |
885 | ||
886 | $res->{type} = 'veth'; | |
93cdbbfb | 887 | $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr}; |
cbb03fea | 888 | |
7dfc49cc DM |
889 | return $res; |
890 | } | |
f76a2828 | 891 | |
238a56cb DM |
892 | sub read_cgroup_value { |
893 | my ($group, $vmid, $name, $full) = @_; | |
894 | ||
895 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
896 | ||
897 | return PVE::Tools::file_get_contents($path) if $full; | |
898 | ||
899 | return PVE::Tools::file_read_firstline($path); | |
900 | } | |
901 | ||
bf0b8c43 AD |
902 | sub write_cgroup_value { |
903 | my ($group, $vmid, $name, $value) = @_; | |
904 | ||
905 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
906 | PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path; | |
907 | ||
908 | } | |
909 | ||
52f1d76b DM |
910 | sub find_lxc_console_pids { |
911 | ||
912 | my $res = {}; | |
913 | ||
914 | PVE::Tools::dir_glob_foreach('/proc', '\d+', sub { | |
915 | my ($pid) = @_; | |
916 | ||
917 | my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline"); | |
918 | return if !$cmdline; | |
919 | ||
920 | my @args = split(/\0/, $cmdline); | |
921 | ||
922 | # serach for lxc-console -n <vmid> | |
cbb03fea | 923 | return if scalar(@args) != 3; |
52f1d76b DM |
924 | return if $args[1] ne '-n'; |
925 | return if $args[2] !~ m/^\d+$/; | |
926 | return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|; | |
cbb03fea | 927 | |
52f1d76b | 928 | my $vmid = $args[2]; |
cbb03fea | 929 | |
52f1d76b DM |
930 | push @{$res->{$vmid}}, $pid; |
931 | }); | |
932 | ||
933 | return $res; | |
934 | } | |
935 | ||
bedeaaf1 AD |
936 | sub find_lxc_pid { |
937 | my ($vmid) = @_; | |
938 | ||
939 | my $pid = undef; | |
940 | my $parser = sub { | |
941 | my $line = shift; | |
8b25977f | 942 | $pid = $1 if $line =~ m/^PID:\s+(\d+)$/; |
bedeaaf1 AD |
943 | }; |
944 | PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser); | |
945 | ||
8b25977f | 946 | die "unable to get PID for CT $vmid (not running?)\n" if !$pid; |
cbb03fea | 947 | |
8b25977f | 948 | return $pid; |
bedeaaf1 AD |
949 | } |
950 | ||
55fa4e09 DM |
951 | my $ipv4_reverse_mask = [ |
952 | '0.0.0.0', | |
953 | '128.0.0.0', | |
954 | '192.0.0.0', | |
955 | '224.0.0.0', | |
956 | '240.0.0.0', | |
957 | '248.0.0.0', | |
958 | '252.0.0.0', | |
959 | '254.0.0.0', | |
960 | '255.0.0.0', | |
961 | '255.128.0.0', | |
962 | '255.192.0.0', | |
963 | '255.224.0.0', | |
964 | '255.240.0.0', | |
965 | '255.248.0.0', | |
966 | '255.252.0.0', | |
967 | '255.254.0.0', | |
968 | '255.255.0.0', | |
969 | '255.255.128.0', | |
970 | '255.255.192.0', | |
971 | '255.255.224.0', | |
972 | '255.255.240.0', | |
973 | '255.255.248.0', | |
974 | '255.255.252.0', | |
975 | '255.255.254.0', | |
976 | '255.255.255.0', | |
977 | '255.255.255.128', | |
978 | '255.255.255.192', | |
979 | '255.255.255.224', | |
980 | '255.255.255.240', | |
981 | '255.255.255.248', | |
982 | '255.255.255.252', | |
983 | '255.255.255.254', | |
984 | '255.255.255.255', | |
985 | ]; | |
cbb03fea DM |
986 | |
987 | # Note: we cannot use Net:IP, because that only allows strict | |
55fa4e09 DM |
988 | # CIDR networks |
989 | sub parse_ipv4_cidr { | |
990 | my ($cidr, $noerr) = @_; | |
991 | ||
992 | if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) { | |
993 | return { address => $1, netmask => $ipv4_reverse_mask->[$2] }; | |
994 | } | |
cbb03fea | 995 | |
55fa4e09 | 996 | return undef if $noerr; |
cbb03fea | 997 | |
55fa4e09 DM |
998 | die "unable to parse ipv4 address/mask\n"; |
999 | } | |
93285df8 | 1000 | |
a12a36e0 WL |
1001 | sub check_lock { |
1002 | my ($conf) = @_; | |
1003 | ||
1004 | die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'}; | |
1005 | } | |
1006 | ||
b80dd50a DM |
1007 | sub lxc_conf_to_pve { |
1008 | my ($vmid, $lxc_conf) = @_; | |
1009 | ||
1010 | my $properties = json_config_properties(); | |
1011 | ||
1012 | my $conf = { digest => $lxc_conf->{digest} }; | |
1013 | ||
1014 | foreach my $k (keys %$properties) { | |
1015 | ||
1016 | if ($k eq 'description') { | |
1017 | if (my $raw = $lxc_conf->{'pve.comment'}) { | |
1018 | $conf->{$k} = PVE::Tools::decode_text($raw); | |
1019 | } | |
a99b3509 DM |
1020 | } elsif ($k eq 'onboot') { |
1021 | $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'}; | |
a3249355 DM |
1022 | } elsif ($k eq 'startup') { |
1023 | $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'}; | |
b80dd50a DM |
1024 | } elsif ($k eq 'hostname') { |
1025 | $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'}; | |
ffa1d001 DM |
1026 | } elsif ($k eq 'nameserver') { |
1027 | $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'}; | |
1028 | } elsif ($k eq 'searchdomain') { | |
1029 | $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'}; | |
b80dd50a DM |
1030 | } elsif ($k eq 'memory') { |
1031 | if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) { | |
1032 | $conf->{$k} = int($value / (1024*1024)); | |
1033 | } | |
1034 | } elsif ($k eq 'swap') { | |
1035 | if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) { | |
1036 | my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0; | |
1037 | $conf->{$k} = int(($value -$mem) / (1024*1024)); | |
1038 | } | |
45573f7c | 1039 | } elsif ($k eq 'cpulimit') { |
b80dd50a DM |
1040 | my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'}; |
1041 | my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'}; | |
cbb03fea | 1042 | |
b80dd50a | 1043 | if ($cfs_period_us && $cfs_quota_us) { |
8daf6a1c | 1044 | $conf->{$k} = $cfs_quota_us/$cfs_period_us; |
b80dd50a DM |
1045 | } else { |
1046 | $conf->{$k} = 0; | |
1047 | } | |
1048 | } elsif ($k eq 'cpuunits') { | |
1049 | $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024; | |
10fc3ba5 DM |
1050 | } elsif ($k eq 'disk') { |
1051 | $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ? | |
1052 | $lxc_conf->{'pve.disksize'} : 0; | |
b80dd50a DM |
1053 | } elsif ($k =~ m/^net\d$/) { |
1054 | my $net = $lxc_conf->{$k}; | |
1055 | next if !$net; | |
1056 | $conf->{$k} = print_lxc_network($net); | |
1057 | } | |
1058 | } | |
cbb03fea | 1059 | |
a12a36e0 WL |
1060 | if (my $parent = $lxc_conf->{'pve.parent'}) { |
1061 | $conf->{parent} = $lxc_conf->{'pve.parent'}; | |
1062 | } | |
1063 | ||
1064 | if (my $parent = $lxc_conf->{'pve.snapcomment'}) { | |
1065 | $conf->{description} = $lxc_conf->{'pve.snapcomment'}; | |
1066 | } | |
1067 | ||
1068 | if (my $parent = $lxc_conf->{'pve.snaptime'}) { | |
1069 | $conf->{snaptime} = $lxc_conf->{'pve.snaptime'}; | |
1070 | } | |
1071 | ||
b80dd50a DM |
1072 | return $conf; |
1073 | } | |
1074 | ||
117636e5 DM |
1075 | # verify and cleanup nameserver list (replace \0 with ' ') |
1076 | sub verify_nameserver_list { | |
1077 | my ($nameserver_list) = @_; | |
1078 | ||
1079 | my @list = (); | |
1080 | foreach my $server (PVE::Tools::split_list($nameserver_list)) { | |
1081 | PVE::JSONSchema::pve_verify_ip($server); | |
1082 | push @list, $server; | |
1083 | } | |
1084 | ||
1085 | return join(' ', @list); | |
1086 | } | |
1087 | ||
1088 | sub verify_searchdomain_list { | |
1089 | my ($searchdomain_list) = @_; | |
1090 | ||
1091 | my @list = (); | |
1092 | foreach my $server (PVE::Tools::split_list($searchdomain_list)) { | |
1093 | # todo: should we add checks for valid dns domains? | |
1094 | push @list, $server; | |
1095 | } | |
1096 | ||
1097 | return join(' ', @list); | |
1098 | } | |
1099 | ||
93285df8 DM |
1100 | sub update_lxc_config { |
1101 | my ($vmid, $conf, $running, $param, $delete) = @_; | |
1102 | ||
bf0b8c43 AD |
1103 | my @nohotplug; |
1104 | ||
cbb03fea DM |
1105 | my $rootdir; |
1106 | if ($running) { | |
bedeaaf1 | 1107 | my $pid = find_lxc_pid($vmid); |
cbb03fea | 1108 | $rootdir = "/proc/$pid/root"; |
bedeaaf1 AD |
1109 | } |
1110 | ||
93285df8 DM |
1111 | if (defined($delete)) { |
1112 | foreach my $opt (@$delete) { | |
1113 | if ($opt eq 'hostname' || $opt eq 'memory') { | |
1114 | die "unable to delete required option '$opt'\n"; | |
1115 | } elsif ($opt eq 'swap') { | |
44da0641 | 1116 | delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}; |
bf0b8c43 | 1117 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1); |
93285df8 DM |
1118 | } elsif ($opt eq 'description') { |
1119 | delete $conf->{'pve.comment'}; | |
a99b3509 DM |
1120 | } elsif ($opt eq 'onboot') { |
1121 | delete $conf->{'pve.onboot'}; | |
a3249355 DM |
1122 | } elsif ($opt eq 'startup') { |
1123 | delete $conf->{'pve.startup'}; | |
ffa1d001 DM |
1124 | } elsif ($opt eq 'nameserver') { |
1125 | delete $conf->{'pve.nameserver'}; | |
bf0b8c43 AD |
1126 | push @nohotplug, $opt; |
1127 | next if $running; | |
ffa1d001 DM |
1128 | } elsif ($opt eq 'searchdomain') { |
1129 | delete $conf->{'pve.searchdomain'}; | |
bf0b8c43 AD |
1130 | push @nohotplug, $opt; |
1131 | next if $running; | |
68fba17b | 1132 | } elsif ($opt =~ m/^net(\d)$/) { |
93285df8 | 1133 | delete $conf->{$opt}; |
68fba17b AD |
1134 | next if !$running; |
1135 | my $netid = $1; | |
1136 | PVE::Network::veth_delete("veth${vmid}.$netid"); | |
93285df8 DM |
1137 | } else { |
1138 | die "implement me" | |
1139 | } | |
bf0b8c43 | 1140 | PVE::LXC::write_config($vmid, $conf) if $running; |
93285df8 DM |
1141 | } |
1142 | } | |
1143 | ||
1144 | foreach my $opt (keys %$param) { | |
1145 | my $value = $param->{$opt}; | |
1146 | if ($opt eq 'hostname') { | |
1147 | $conf->{'lxc.utsname'} = $value; | |
a99b3509 DM |
1148 | } elsif ($opt eq 'onboot') { |
1149 | $conf->{'pve.onboot'} = $value ? 1 : 0; | |
a3249355 DM |
1150 | } elsif ($opt eq 'startup') { |
1151 | $conf->{'pve.startup'} = $value; | |
ffa1d001 | 1152 | } elsif ($opt eq 'nameserver') { |
117636e5 | 1153 | my $list = verify_nameserver_list($value); |
c325b32f | 1154 | $conf->{'pve.nameserver'} = $list; |
bf0b8c43 AD |
1155 | push @nohotplug, $opt; |
1156 | next if $running; | |
ffa1d001 | 1157 | } elsif ($opt eq 'searchdomain') { |
117636e5 | 1158 | my $list = verify_searchdomain_list($value); |
c325b32f | 1159 | $conf->{'pve.searchdomain'} = $list; |
bf0b8c43 AD |
1160 | push @nohotplug, $opt; |
1161 | next if $running; | |
93285df8 DM |
1162 | } elsif ($opt eq 'memory') { |
1163 | $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024; | |
bf0b8c43 | 1164 | write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024); |
93285df8 | 1165 | } elsif ($opt eq 'swap') { |
44da0641 DM |
1166 | my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'}; |
1167 | $mem = $param->{memory}*1024*1024 if $param->{memory}; | |
1168 | $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024; | |
bf0b8c43 | 1169 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024); |
3a43e41a | 1170 | |
45573f7c | 1171 | } elsif ($opt eq 'cpulimit') { |
44da0641 DM |
1172 | if ($value > 0) { |
1173 | my $cfs_period_us = 100000; | |
1174 | $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us; | |
1175 | $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value; | |
bf0b8c43 | 1176 | write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value); |
44da0641 DM |
1177 | } else { |
1178 | delete $conf->{'lxc.cgroup.cpu.cfs_period_us'}; | |
1179 | delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'}; | |
bf0b8c43 | 1180 | write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1); |
44da0641 | 1181 | } |
b80dd50a | 1182 | } elsif ($opt eq 'cpuunits') { |
cbb03fea | 1183 | $conf->{'lxc.cgroup.cpu.shares'} = $value; |
bf0b8c43 | 1184 | write_cgroup_value("cpu", $vmid, "cpu.shares", $value); |
93285df8 DM |
1185 | } elsif ($opt eq 'description') { |
1186 | $conf->{'pve.comment'} = PVE::Tools::encode_text($value); | |
10fc3ba5 DM |
1187 | } elsif ($opt eq 'disk') { |
1188 | $conf->{'pve.disksize'} = $value; | |
bf0b8c43 AD |
1189 | push @nohotplug, $opt; |
1190 | next if $running; | |
93285df8 DM |
1191 | } elsif ($opt =~ m/^net(\d+)$/) { |
1192 | my $netid = $1; | |
1193 | my $net = PVE::LXC::parse_lxc_network($value); | |
1194 | $net->{'veth.pair'} = "veth${vmid}.$netid"; | |
cbb03fea | 1195 | if (!$running) { |
bedeaaf1 | 1196 | $conf->{$opt} = $net; |
cbb03fea | 1197 | } else { |
bedeaaf1 AD |
1198 | update_net($vmid, $conf, $opt, $net, $netid, $rootdir); |
1199 | } | |
93285df8 | 1200 | } else { |
a92f66c9 | 1201 | die "implement me: $opt"; |
93285df8 | 1202 | } |
bf0b8c43 | 1203 | PVE::LXC::write_config($vmid, $conf) if $running; |
93285df8 | 1204 | } |
bf0b8c43 | 1205 | |
5cfa0567 DM |
1206 | if ($running && scalar(@nohotplug)) { |
1207 | die "unable to modify " . join(',', @nohotplug) . " while container is running\n"; | |
1208 | } | |
93285df8 | 1209 | } |
c325b32f DM |
1210 | |
1211 | sub get_primary_ips { | |
1212 | my ($conf) = @_; | |
1213 | ||
1214 | # return data from net0 | |
cbb03fea | 1215 | |
c325b32f DM |
1216 | my $net = $conf->{net0}; |
1217 | return undef if !$net; | |
1218 | ||
1219 | my $ipv4 = $net->{ip}; | |
1220 | $ipv4 =~ s!/\d+$!! if $ipv4; | |
1221 | my $ipv6 = $net->{ip}; | |
1222 | $ipv6 =~ s!/\d+$!! if $ipv6; | |
cbb03fea | 1223 | |
c325b32f DM |
1224 | return ($ipv4, $ipv6); |
1225 | } | |
148d1cb4 DM |
1226 | |
1227 | sub destory_lxc_container { | |
1228 | my ($storage_cfg, $vmid, $conf) = @_; | |
1229 | ||
1230 | if (my $volid = $conf->{'pve.volid'}) { | |
1231 | ||
1232 | my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid); | |
1233 | die "got strange volid (containe is not owner!)\n" if $vmid != $owner; | |
1234 | ||
1235 | PVE::Storage::vdisk_free($storage_cfg, $volid); | |
1236 | ||
1237 | destroy_config($vmid); | |
1238 | ||
1239 | } else { | |
1240 | my $cmd = ['lxc-destroy', '-n', $vmid ]; | |
1241 | ||
1242 | PVE::Tools::run_command($cmd); | |
1243 | } | |
1244 | } | |
68fba17b | 1245 | |
93cdbbfb AD |
1246 | my $safe_num_ne = sub { |
1247 | my ($a, $b) = @_; | |
1248 | ||
1249 | return 0 if !defined($a) && !defined($b); | |
1250 | return 1 if !defined($a); | |
1251 | return 1 if !defined($b); | |
1252 | ||
1253 | return $a != $b; | |
1254 | }; | |
1255 | ||
1256 | my $safe_string_ne = sub { | |
1257 | my ($a, $b) = @_; | |
1258 | ||
1259 | return 0 if !defined($a) && !defined($b); | |
1260 | return 1 if !defined($a); | |
1261 | return 1 if !defined($b); | |
1262 | ||
1263 | return $a ne $b; | |
1264 | }; | |
1265 | ||
1266 | sub update_net { | |
bedeaaf1 | 1267 | my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_; |
93cdbbfb AD |
1268 | |
1269 | my $veth = $newnet->{'veth.pair'}; | |
cbb03fea | 1270 | my $vethpeer = $veth . "p"; |
93cdbbfb AD |
1271 | my $eth = $newnet->{name}; |
1272 | ||
bedeaaf1 AD |
1273 | if ($conf->{$opt}) { |
1274 | if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) || | |
cbb03fea | 1275 | &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) { |
93cdbbfb AD |
1276 | |
1277 | PVE::Network::veth_delete($veth); | |
bedeaaf1 AD |
1278 | delete $conf->{$opt}; |
1279 | PVE::LXC::write_config($vmid, $conf); | |
93cdbbfb | 1280 | |
bedeaaf1 AD |
1281 | hotplug_net($vmid, $conf, $opt, $newnet); |
1282 | ||
1283 | } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) || | |
1284 | &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) || | |
1285 | &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) { | |
1286 | ||
1287 | if ($conf->{$opt}->{bridge}){ | |
1288 | PVE::Network::tap_unplug($veth); | |
1289 | delete $conf->{$opt}->{bridge}; | |
1290 | delete $conf->{$opt}->{tag}; | |
1291 | delete $conf->{$opt}->{firewall}; | |
1292 | PVE::LXC::write_config($vmid, $conf); | |
1293 | } | |
93cdbbfb | 1294 | |
93cdbbfb | 1295 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}); |
bedeaaf1 AD |
1296 | $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge}; |
1297 | $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag}; | |
1298 | $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall}; | |
1299 | PVE::LXC::write_config($vmid, $conf); | |
93cdbbfb AD |
1300 | } |
1301 | } else { | |
bedeaaf1 | 1302 | hotplug_net($vmid, $conf, $opt, $newnet); |
93cdbbfb AD |
1303 | } |
1304 | ||
bedeaaf1 | 1305 | update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir); |
93cdbbfb AD |
1306 | } |
1307 | ||
1308 | sub hotplug_net { | |
bedeaaf1 | 1309 | my ($vmid, $conf, $opt, $newnet) = @_; |
93cdbbfb AD |
1310 | |
1311 | my $veth = $newnet->{'veth.pair'}; | |
cbb03fea | 1312 | my $vethpeer = $veth . "p"; |
93cdbbfb AD |
1313 | my $eth = $newnet->{name}; |
1314 | ||
1315 | PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr}); | |
1316 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}); | |
1317 | ||
cbb03fea | 1318 | # attach peer in container |
93cdbbfb AD |
1319 | my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ]; |
1320 | PVE::Tools::run_command($cmd); | |
1321 | ||
cbb03fea | 1322 | # link up peer in container |
93cdbbfb AD |
1323 | $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ]; |
1324 | PVE::Tools::run_command($cmd); | |
bedeaaf1 AD |
1325 | |
1326 | $conf->{$opt}->{type} = 'veth'; | |
1327 | $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge}; | |
1328 | $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag}; | |
1329 | $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall}; | |
1330 | $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr}; | |
1331 | $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name}; | |
1332 | $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'}; | |
1333 | ||
1334 | delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip}; | |
1335 | delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6}; | |
1336 | delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw}; | |
1337 | delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6}; | |
1338 | ||
1339 | PVE::LXC::write_config($vmid, $conf); | |
93cdbbfb AD |
1340 | } |
1341 | ||
68a05bb3 | 1342 | sub update_ipconfig { |
bedeaaf1 AD |
1343 | my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_; |
1344 | ||
1345 | my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir); | |
1346 | ||
84e0c123 WB |
1347 | my $optdata = $conf->{$opt}; |
1348 | my $deleted = []; | |
1349 | my $added = []; | |
1350 | my $netcmd = sub { | |
1351 | my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_]; | |
1352 | PVE::Tools::run_command($cmd); | |
1353 | }; | |
2bfd1615 | 1354 | |
84e0c123 | 1355 | my $change_ip_config = sub { |
f39002a6 DM |
1356 | my ($ipversion) = @_; |
1357 | ||
1358 | my $family_opt = "-$ipversion"; | |
1359 | my $suffix = $ipversion == 4 ? '' : $ipversion; | |
84e0c123 WB |
1360 | my $gw= "gw$suffix"; |
1361 | my $ip= "ip$suffix"; | |
bedeaaf1 | 1362 | |
84e0c123 WB |
1363 | my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip}); |
1364 | my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw}); | |
bedeaaf1 | 1365 | |
84e0c123 | 1366 | return if !$change_ip && !$change_gw; |
68a05bb3 | 1367 | |
84e0c123 WB |
1368 | # step 1: add new IP, if this fails we cancel |
1369 | if ($change_ip && $newnet->{$ip}) { | |
1370 | eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); }; | |
1371 | if (my $err = $@) { | |
1372 | warn $err; | |
1373 | return; | |
1374 | } | |
bedeaaf1 | 1375 | } |
bedeaaf1 | 1376 | |
84e0c123 WB |
1377 | # step 2: replace gateway |
1378 | # If this fails we delete the added IP and cancel. | |
1379 | # If it succeeds we save the config and delete the old IP, ignoring | |
1380 | # errors. The config is then saved. | |
1381 | # Note: 'ip route replace' can add | |
1382 | if ($change_gw) { | |
1383 | if ($newnet->{$gw}) { | |
1384 | eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); }; | |
1385 | if (my $err = $@) { | |
1386 | warn $err; | |
1387 | # the route was not replaced, the old IP is still available | |
1388 | # rollback (delete new IP) and cancel | |
1389 | if ($change_ip) { | |
1390 | eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); }; | |
1391 | warn $@ if $@; # no need to die here | |
1392 | } | |
1393 | return; | |
1394 | } | |
1395 | } else { | |
1396 | eval { &$netcmd($family_opt, 'route', 'del', 'default'); }; | |
1397 | # if the route was not deleted, the guest might have deleted it manually | |
1398 | # warn and continue | |
1399 | warn $@ if $@; | |
1400 | } | |
2bfd1615 | 1401 | } |
2bfd1615 | 1402 | |
84e0c123 WB |
1403 | # from this point on we safe the configuration |
1404 | # step 3: delete old IP ignoring errors | |
1405 | if ($change_ip && $optdata->{$ip}) { | |
1406 | eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); }; | |
1407 | warn $@ if $@; # no need to die here | |
bedeaaf1 AD |
1408 | } |
1409 | ||
84e0c123 WB |
1410 | foreach my $property ($ip, $gw) { |
1411 | if ($newnet->{$property}) { | |
1412 | $optdata->{$property} = $newnet->{$property}; | |
1413 | } else { | |
1414 | delete $optdata->{$property}; | |
1415 | } | |
bedeaaf1 | 1416 | } |
84e0c123 WB |
1417 | PVE::LXC::write_config($vmid, $conf); |
1418 | $lxc_setup->setup_network($conf); | |
1419 | }; | |
bedeaaf1 | 1420 | |
f39002a6 DM |
1421 | &$change_ip_config(4); |
1422 | &$change_ip_config(6); | |
489e960d WL |
1423 | |
1424 | } | |
1425 | ||
a92f66c9 WL |
1426 | # Internal snapshots |
1427 | ||
1428 | # NOTE: Snapshot create/delete involves several non-atomic | |
1429 | # action, and can take a long time. | |
1430 | # So we try to avoid locking the file and use 'lock' variable | |
1431 | # inside the config file instead. | |
1432 | ||
1433 | my $snapshot_copy_config = sub { | |
1434 | my ($source, $dest) = @_; | |
1435 | ||
1436 | foreach my $k (keys %$source) { | |
1437 | next if $k eq 'snapshots'; | |
1438 | next if $k eq 'pve.snapstate'; | |
1439 | next if $k eq 'pve.snaptime'; | |
1440 | next if $k eq 'pve.lock'; | |
1441 | next if $k eq 'digest'; | |
1442 | next if $k eq 'pve.comment'; | |
1443 | ||
1444 | $dest->{$k} = $source->{$k}; | |
1445 | } | |
1446 | }; | |
1447 | ||
1448 | my $snapshot_prepare = sub { | |
1449 | my ($vmid, $snapname, $comment) = @_; | |
1450 | ||
1451 | my $snap; | |
1452 | ||
1453 | my $updatefn = sub { | |
1454 | ||
1455 | my $conf = load_config($vmid); | |
1456 | ||
1457 | check_lock($conf); | |
1458 | ||
1459 | $conf->{'pve.lock'} = 'snapshot'; | |
1460 | ||
1461 | die "snapshot name '$snapname' already used\n" | |
1462 | if defined($conf->{snapshots}->{$snapname}); | |
1463 | ||
1464 | my $storecfg = PVE::Storage::config(); | |
1465 | die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg); | |
1466 | ||
1467 | $snap = $conf->{snapshots}->{$snapname} = {}; | |
1468 | ||
1469 | &$snapshot_copy_config($conf, $snap); | |
1470 | ||
1471 | $snap->{'pve.snapstate'} = "prepare"; | |
1472 | $snap->{'pve.snaptime'} = time(); | |
1473 | $snap->{'pve.snapname'} = $snapname; | |
1474 | $snap->{'pve.snapcomment'} = $comment if $comment; | |
1475 | $conf->{snapshots}->{$snapname} = $snap; | |
1476 | ||
1477 | PVE::LXC::write_config($vmid, $conf); | |
1478 | }; | |
1479 | ||
1480 | lock_container($vmid, 10, $updatefn); | |
1481 | ||
1482 | return $snap; | |
1483 | }; | |
1484 | ||
1485 | my $snapshot_commit = sub { | |
1486 | my ($vmid, $snapname) = @_; | |
1487 | ||
1488 | my $updatefn = sub { | |
1489 | ||
1490 | my $conf = load_config($vmid); | |
1491 | ||
1492 | die "missing snapshot lock\n" | |
1493 | if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot'); | |
1494 | ||
1495 | die "snapshot '$snapname' does not exist\n" | |
1496 | if !defined($conf->{snapshots}->{$snapname}); | |
1497 | ||
1498 | die "wrong snapshot state\n" | |
1499 | if !($conf->{snapshots}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots}->{$snapname}->{'pve.snapstate'} eq "prepare"); | |
1500 | ||
1501 | delete $conf->{snapshots}->{$snapname}->{'pve.snapstate'}; | |
1502 | delete $conf->{'pve.lock'}; | |
1503 | $conf->{'pve.parent'} = $snapname; | |
1504 | ||
1505 | PVE::LXC::write_config($vmid, $conf); | |
1506 | ||
1507 | }; | |
1508 | ||
1509 | lock_container($vmid, 10 ,$updatefn); | |
1510 | }; | |
1511 | ||
1512 | sub has_feature { | |
1513 | my ($feature, $conf, $storecfg, $snapname) = @_; | |
1514 | #Fixme add other drives if necessary. | |
1515 | my $err; | |
1516 | my $volid = $conf->{'pve.volid'}; | |
1517 | $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname); | |
1518 | ||
1519 | return $err ? 0 : 1; | |
1520 | } | |
1521 | ||
489e960d WL |
1522 | sub snapshot_create { |
1523 | my ($vmid, $snapname, $comment) = @_; | |
1524 | ||
a92f66c9 WL |
1525 | my $snap = &$snapshot_prepare($vmid, $snapname, $comment); |
1526 | ||
1527 | my $config = load_config($vmid); | |
1528 | ||
1529 | my $cmd = "/usr/bin/lxc-freeze -n $vmid"; | |
1530 | my $running = check_running($vmid); | |
1531 | eval { | |
1532 | if ($running) { | |
1533 | PVE::Tools::run_command($cmd); | |
1534 | }; | |
1535 | ||
1536 | my $storecfg = PVE::Storage::config(); | |
1537 | my $volid = $config->{'pve.volid'}; | |
1538 | ||
1539 | $cmd = "/usr/bin/lxc-unfreeze -n $vmid"; | |
1540 | if ($running) { | |
1541 | PVE::Tools::run_command($cmd); | |
1542 | }; | |
489e960d | 1543 | |
a92f66c9 WL |
1544 | PVE::Storage::volume_snapshot($storecfg, $volid, $snapname); |
1545 | &$snapshot_commit($vmid, $snapname); | |
1546 | }; | |
1547 | if(my $err = $@) { | |
31429832 | 1548 | snapshot_delete($vmid, $snapname, 1); |
a92f66c9 WL |
1549 | die "$err\n"; |
1550 | } | |
68a05bb3 AD |
1551 | } |
1552 | ||
57ccb3f8 WL |
1553 | sub snapshot_delete { |
1554 | my ($vmid, $snapname, $force) = @_; | |
1555 | ||
31429832 WL |
1556 | my $snap; |
1557 | ||
1558 | my $conf; | |
1559 | ||
1560 | my $updatefn = sub { | |
1561 | ||
1562 | $conf = load_config($vmid); | |
1563 | ||
1564 | $snap = $conf->{snapshots}->{$snapname}; | |
1565 | ||
1566 | check_lock($conf); | |
1567 | ||
1568 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1569 | ||
1570 | $snap->{'pve.snapstate'} = 'delete'; | |
1571 | ||
1572 | PVE::LXC::write_config($vmid, $conf); | |
1573 | }; | |
1574 | ||
1575 | lock_container($vmid, 10, $updatefn); | |
1576 | ||
1577 | my $storecfg = PVE::Storage::config(); | |
1578 | ||
1579 | my $del_snap = sub { | |
1580 | ||
1581 | check_lock($conf); | |
1582 | ||
1583 | if ($conf->{'pve.parent'} eq $snapname) { | |
1584 | if ($conf->{snapshots}->{$snapname}->{'pve.snapname'}) { | |
1585 | $conf->{'pve.parent'} = $conf->{snapshots}->{$snapname}->{'pve.parent'}; | |
1586 | } else { | |
1587 | delete $conf->{'pve.parent'}; | |
1588 | } | |
1589 | } | |
1590 | ||
1591 | delete $conf->{snapshots}->{$snapname}; | |
1592 | ||
1593 | PVE::LXC::write_config($vmid, $conf); | |
1594 | }; | |
1595 | ||
1596 | my $volid = $conf->{snapshots}->{$snapname}->{'pve.volid'}; | |
1597 | ||
1598 | eval { | |
1599 | PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname); | |
1600 | }; | |
1601 | my $err = $@; | |
1602 | ||
1603 | if(!$err || ($err && $force)) { | |
1604 | lock_container($vmid, 10, $del_snap); | |
1605 | if ($err) { | |
1606 | die "Can't delete snapshot: $vmid $snapname $err\n"; | |
1607 | } | |
1608 | } | |
57ccb3f8 WL |
1609 | } |
1610 | ||
723157f6 WL |
1611 | sub snapshot_rollback { |
1612 | my ($vmid, $snapname) = @_; | |
1613 | ||
6860ba0c WL |
1614 | my $storecfg = PVE::Storage::config(); |
1615 | ||
1616 | my $conf = load_config($vmid); | |
1617 | ||
1618 | my $snap = $conf->{snapshots}->{$snapname}; | |
1619 | ||
1620 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1621 | ||
1622 | PVE::Storage::volume_rollback_is_possible($storecfg, $snap->{'pve.volid'}, | |
1623 | $snapname); | |
1624 | ||
1625 | my $updatefn = sub { | |
1626 | ||
1627 | die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" if $snap->{snapstate}; | |
1628 | ||
1629 | check_lock($conf); | |
1630 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1631 | ||
1632 | my $authuser = $rpcenv->get_user(); | |
1633 | ||
1634 | eval { | |
1635 | lxc_stop($vmid, $authuser, $rpcenv); | |
1636 | }; | |
1637 | ||
1638 | die "unable to rollback vm $vmid: vm is running\n" | |
1639 | if check_running($vmid); | |
1640 | ||
1641 | $conf->{'pve.lock'} = 'rollback'; | |
1642 | ||
1643 | my $forcemachine; | |
1644 | ||
1645 | # copy snapshot config to current config | |
1646 | ||
1647 | my $tmp_conf = $conf; | |
1648 | &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf); | |
1649 | print Dumper $conf, $tmp_conf; | |
1650 | $conf->{snapshots} = $tmp_conf->{snapshots}; | |
1651 | delete $conf->{'pve.snaptime'}; | |
1652 | delete $conf->{'pve.snapname'}; | |
1653 | $conf->{'pve.parent'} = $snapname; | |
1654 | ||
1655 | PVE::LXC::write_config($vmid, $conf); | |
1656 | ||
1657 | }; | |
1658 | ||
1659 | my $unlockfn = sub { | |
1660 | delete $conf->{'pve.lock'}; | |
1661 | PVE::LXC::write_config($vmid, $conf); | |
1662 | }; | |
1663 | ||
1664 | lock_container($vmid, 10, $updatefn); | |
1665 | ||
1666 | PVE::Storage::volume_snapshot_rollback($storecfg, $conf->{'pve.volid'}, $snapname); | |
1667 | ||
1668 | lock_container($vmid, 5, $unlockfn); | |
723157f6 | 1669 | } |
f76a2828 | 1670 | 1; |