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