]> git.proxmox.com Git - qemu-server.git/blob - PVE/VZDump/QemuServer.pm
fix cluster_lock_storage() call
[qemu-server.git] / PVE / VZDump / QemuServer.pm
1 package PVE::VZDump::QemuServer;
2
3 use strict;
4 use warnings;
5 use File::Path;
6 use File::Basename;
7 use PVE::INotify;
8 use PVE::VZDump;
9 use PVE::Cluster qw(cfs_read_file);
10 use PVE::Tools;
11 use PVE::Storage::Plugin;
12 use PVE::Storage;
13 use PVE::QemuServer;
14 use IO::File;
15
16 use base qw (PVE::VZDump::Plugin);
17
18 sub new {
19 my ($class, $vzdump) = @_;
20
21 PVE::VZDump::check_bin('qm');
22
23 my $self = bless { vzdump => $vzdump };
24
25 $self->{vmlist} = PVE::QemuServer::vzlist();
26 $self->{storecfg} = PVE::Storage::config();
27
28 return $self;
29 };
30
31
32 sub type {
33 return 'qemu';
34 }
35
36 sub vmlist {
37 my ($self) = @_;
38
39 return [ keys %{$self->{vmlist}} ];
40 }
41
42 sub prepare {
43 my ($self, $task, $vmid, $mode) = @_;
44
45 $task->{disks} = [];
46
47 my $conf = $self->{vmlist}->{$vmid} = PVE::QemuServer::load_config($vmid);
48
49 $task->{hostname} = $conf->{name};
50
51 my $lvmmap = PVE::VZDump::get_lvm_mapping();
52
53 my $hostname = PVE::INotify::nodename();
54
55 my $ind = {};
56 my $mountinfo = {};
57 my $mountind = 0;
58
59 my $snapshot_count = 0;
60
61 my $vollist = [];
62 my $drivehash = {};
63 PVE::QemuServer::foreach_drive($conf, sub {
64 my ($ds, $drive) = @_;
65
66 return if PVE::QemuServer::drive_is_cdrom($drive);
67
68 if (defined($drive->{backup}) && $drive->{backup} eq "no") {
69 $self->loginfo("exclude disk '$ds' (backup=no)");
70 return;
71 }
72
73 my $volid = $drive->{file};
74
75 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
76 push @$vollist, $volid if $storeid;
77 $drivehash->{$ds} = $drive;
78 });
79
80 PVE::Storage::activate_volumes($self->{storecfg}, $vollist);
81
82 while (my ($ds, $drive) = each %$drivehash) {
83
84 my $volid = $drive->{file};
85
86 my $path;
87
88 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
89 if ($storeid) {
90 $path = PVE::Storage::path($self->{storecfg}, $volid);
91 } else {
92 $path = $volid;
93 }
94
95 next if !$path;
96
97 die "no such volume '$volid'\n" if ! -e $path;
98
99 my $diskinfo = { path => $path , volid => $volid, storeid => $storeid,
100 snappath => $path, virtdev => $ds };
101
102 if (-b $path) {
103
104 $diskinfo->{type} = 'block';
105
106 $diskinfo->{filename} = "vm-disk-$ds.raw";
107
108 if ($mode eq 'snapshot') {
109 my ($lvmvg, $lvmlv) = @{$lvmmap->{$path}} if defined ($lvmmap->{$path});
110 die ("mode failure - unable to detect lvm volume group\n") if !$lvmvg;
111
112 $ind->{$lvmvg} = 0 if !defined $ind->{$lvmvg};
113 $diskinfo->{snapname} = "vzsnap-$hostname-$ind->{$lvmvg}";
114 $diskinfo->{snapdev} = "/dev/$lvmvg/$diskinfo->{snapname}";
115 $diskinfo->{lvmvg} = $lvmvg;
116 $diskinfo->{lvmlv} = $lvmlv;
117 $diskinfo->{snappath} = $diskinfo->{snapdev};
118 $ind->{$lvmvg}++;
119
120 $snapshot_count++;
121 }
122
123 } else {
124
125 $diskinfo->{type} = 'file';
126
127 my (undef, $dir, $ext) = fileparse ($path, qr/\.[^.]*/);
128
129 $diskinfo->{filename} = "vm-disk-$ds$ext";
130
131 if ($mode eq 'snapshot') {
132
133 my ($srcdev, $lvmpath, $lvmvg, $lvmlv, $fstype) =
134 PVE::VZDump::get_lvm_device ($dir, $lvmmap);
135
136 my $targetdev = PVE::VZDump::get_lvm_device($task->{dumpdir}, $lvmmap);
137
138 die ("mode failure - unable to detect lvm volume group\n") if !$lvmvg;
139 die ("mode failure - wrong lvm mount point '$lvmpath'\n") if $dir !~ m|/?$lvmpath/?|;
140 die ("mode failure - unable to dump into snapshot (use option --dumpdir)\n")
141 if $targetdev eq $srcdev;
142
143 $ind->{$lvmvg} = 0 if !defined $ind->{$lvmvg};
144
145 my $info = $mountinfo->{$lvmpath};
146 if (!$info) {
147 my $snapname = "vzsnap-$hostname-$ind->{$lvmvg}";
148 my $snapdev = "/dev/$lvmvg/$snapname";
149 $mountinfo->{$lvmpath} = $info = {
150 snapdev => $snapdev,
151 snapname => $snapname,
152 mountpoint => "/mnt/vzsnap$mountind",
153 };
154 $ind->{$lvmvg}++;
155 $mountind++;
156
157 $snapshot_count++;
158 }
159
160 $diskinfo->{snapdev} = $info->{snapdev};
161 $diskinfo->{snapname} = $info->{snapname};
162 $diskinfo->{mountpoint} = $info->{mountpoint};
163
164 $diskinfo->{lvmvg} = $lvmvg;
165 $diskinfo->{lvmlv} = $lvmlv;
166
167 $diskinfo->{fstype} = $fstype;
168 $diskinfo->{lvmpath} = $lvmpath;
169
170 $diskinfo->{snappath} = $path;
171 $diskinfo->{snappath} =~ s|/?$lvmpath/?|$diskinfo->{mountpoint}/|;
172 }
173 }
174
175 push @{$task->{disks}}, $diskinfo;
176 }
177
178 $task->{snapshot_count} = $snapshot_count;
179 }
180
181 sub vm_status {
182 my ($self, $vmid) = @_;
183
184 my $running = PVE::QemuServer::check_running($vmid) ? 1 : 0;
185
186 return wantarray ? ($running, $running ? 'running' : 'stopped') : $running;
187 }
188
189 sub lock_vm {
190 my ($self, $vmid) = @_;
191
192 $self->cmd ("qm set $vmid --lock backup");
193 }
194
195 sub unlock_vm {
196 my ($self, $vmid) = @_;
197
198 $self->cmd ("qm unlock $vmid");
199 }
200
201 sub stop_vm {
202 my ($self, $task, $vmid) = @_;
203
204 my $opts = $self->{vzdump}->{opts};
205
206 my $wait = $opts->{stopwait} * 60;
207 # send shutdown and wait
208 $self->cmd ("qm shutdown $vmid --skiplock --keepActive --timeout $wait");
209 }
210
211 sub start_vm {
212 my ($self, $task, $vmid) = @_;
213
214 $self->cmd ("qm start $vmid --skiplock");
215 }
216
217 sub suspend_vm {
218 my ($self, $task, $vmid) = @_;
219
220 $self->cmd ("qm suspend $vmid --skiplock");
221 }
222
223 sub resume_vm {
224 my ($self, $task, $vmid) = @_;
225
226 $self->cmd ("qm resume $vmid --skiplock");
227 }
228
229 sub snapshot_alloc {
230 my ($self, $storeid, $name, $size, $srcdev) = @_;
231
232 my $cmd = "lvcreate --size ${size}M --snapshot --name '$name' '$srcdev'";
233
234 if ($storeid) {
235
236 my $scfg = PVE::Storage::storage_config($self->{storecfg}, $storeid);
237
238 # lock shared storage
239 return PVE::Storage::Plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
240 $self->cmd ($cmd);
241 });
242 } else {
243 $self->cmd ($cmd);
244 }
245 }
246
247 sub snapshot_free {
248 my ($self, $storeid, $name, $snapdev, $noerr) = @_;
249
250 my $cmd = ['lvremove', '-f', $snapdev];
251
252 # loop, because we often get 'LV in use: not deactivating'
253 # we use run_command() because we do not want to log errors here
254 my $wait = 1;
255 while(-b $snapdev) {
256 eval {
257 if ($storeid) {
258 my $scfg = PVE::Storage::storage_config($self->{storecfg}, $storeid);
259 # lock shared storage
260 return PVE::Storage::Plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
261 PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
262 });
263 } else {
264 PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
265 }
266 };
267 my $err = $@;
268 last if !$err;
269 if ($wait >= 64) {
270 $self->logerr($err);
271 die $@ if !$noerr;
272 last;
273 }
274 $self->loginfo("lvremove failed - trying again in $wait seconds") if $wait >= 8;
275 sleep($wait);
276 $wait = $wait*2;
277 }
278 }
279
280 sub snapshot {
281 my ($self, $task, $vmid) = @_;
282
283 my $opts = $self->{vzdump}->{opts};
284
285 my $mounts = {};
286
287 foreach my $di (@{$task->{disks}}) {
288 if ($di->{type} eq 'block') {
289
290 if (-b $di->{snapdev}) {
291 $self->loginfo ("trying to remove stale snapshot '$di->{snapdev}'");
292 $self->snapshot_free ($di->{storeid}, $di->{snapname}, $di->{snapdev}, 1);
293 }
294
295 $di->{cleanup_lvm} = 1;
296 $self->snapshot_alloc ($di->{storeid}, $di->{snapname}, $opts->{size},
297 "/dev/$di->{lvmvg}/$di->{lvmlv}");
298
299 } elsif ($di->{type} eq 'file') {
300
301 next if defined ($mounts->{$di->{mountpoint}}); # already mounted
302
303 if (-b $di->{snapdev}) {
304 $self->loginfo ("trying to remove stale snapshot '$di->{snapdev}'");
305
306 $self->cmd_noerr ("umount $di->{mountpoint}");
307 $self->snapshot_free ($di->{storeid}, $di->{snapname}, $di->{snapdev}, 1);
308 }
309
310 mkpath $di->{mountpoint}; # create mount point for lvm snapshot
311
312 $di->{cleanup_lvm} = 1;
313
314 $self->snapshot_alloc ($di->{storeid}, $di->{snapname}, $opts->{size},
315 "/dev/$di->{lvmvg}/$di->{lvmlv}");
316
317 my $mopts = $di->{fstype} eq 'xfs' ? "-o nouuid" : '';
318
319 $di->{snapshot_mount} = 1;
320
321 $self->cmd ("mount -n -t $di->{fstype} $mopts $di->{snapdev} $di->{mountpoint}");
322
323 $mounts->{$di->{mountpoint}} = 1;
324
325 } else {
326 die "implement me";
327 }
328 }
329 }
330
331 sub get_size {
332 my $path = shift;
333
334 if (-f $path) {
335 return -s $path;
336 } elsif (-b $path) {
337 my $fh = IO::File->new ($path, "r");
338 die "unable to open '$path' to detect device size\n" if !$fh;
339 my $size = sysseek $fh, 0, 2;
340 $fh->close();
341 die "unable to detect device size for '$path'\n" if !$size;
342 return $size;
343 }
344 }
345
346 sub assemble {
347 my ($self, $task, $vmid) = @_;
348
349 my $conffile = PVE::QemuServer::config_file ($vmid);
350
351 my $outfile = "$task->{tmpdir}/qemu-server.conf";
352
353 my $outfd;
354 my $conffd;
355
356 eval {
357
358 $outfd = IO::File->new (">$outfile") ||
359 die "unable to open '$outfile'";
360 $conffd = IO::File->new ($conffile, 'r') ||
361 die "unable open '$conffile'";
362
363 while (defined (my $line = <$conffd>)) {
364 next if $line =~ m/^\#vzdump\#/; # just to be sure
365 print $outfd $line;
366 }
367
368 foreach my $di (@{$task->{disks}}) {
369 if ($di->{type} eq 'block' || $di->{type} eq 'file') {
370 my $size = get_size ($di->{snappath});
371 my $storeid = $di->{storeid} || '';
372 print $outfd "#vzdump#map:$di->{virtdev}:$di->{filename}:$size:$storeid:\n";
373 } else {
374 die "internal error";
375 }
376 }
377 };
378 my $err = $@;
379
380 close ($outfd) if $outfd;
381 close ($conffd) if $conffd;
382
383 die $err if $err;
384 }
385
386 sub archive {
387 my ($self, $task, $vmid, $filename, $comp) = @_;
388
389 my $conffile = "$task->{tmpdir}/qemu-server.conf";
390
391 my $opts = $self->{vzdump}->{opts};
392
393 my $starttime = time ();
394
395 my $fh;
396
397 my @filea = ($conffile, 'qemu-server.conf'); # always first file in tar
398 foreach my $di (@{$task->{disks}}) {
399 if ($di->{type} eq 'block' || $di->{type} eq 'file') {
400 push @filea, $di->{snappath}, $di->{filename};
401 } else {
402 die "implement me";
403 }
404 }
405
406 my $files = join (' ', map { "'$_'" } @filea);
407
408 # no sparse file scan when we use compression
409 my $sparse = $comp ? '' : '-s';
410
411 my $cmd = "/usr/lib/qemu-server/vmtar $sparse $files";
412 my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
413 $cmd .= "|cstream -t $bwl" if $opts->{bwlimit};
414 $cmd .= "|$comp" if $comp;
415
416 if ($opts->{stdout}) {
417 $self->cmd ($cmd, output => ">&=" . fileno($opts->{stdout}));
418 } else {
419 $self->cmd ("$cmd >$filename");
420 }
421 }
422
423 sub cleanup {
424 my ($self, $task, $vmid) = @_;
425
426 foreach my $di (@{$task->{disks}}) {
427
428 if ($di->{snapshot_mount}) {
429 $self->cmd_noerr ("umount $di->{mountpoint}");
430 }
431
432 if ($di->{cleanup_lvm}) {
433 if (-b $di->{snapdev}) {
434 if ($di->{type} eq 'block') {
435 $self->snapshot_free ($di->{storeid}, $di->{snapname}, $di->{snapdev}, 1);
436 } elsif ($di->{type} eq 'file') {
437 $self->snapshot_free ($di->{storeid}, $di->{snapname}, $di->{snapdev}, 1);
438 }
439 }
440 }
441 }
442 }
443
444 1;