We now always copy a guest during conversion, meaning this tool is no longer required. --- Build.PL | 2 +- MANIFEST | 2 - lib/Sys/VirtV2V.pm | 9 +- lib/Sys/VirtV2V/Connection.pm | 1 - po/POTFILES.in | 1 - snapshot/run-snapshot-locally | 43 -- snapshot/v2v-snapshot.pl | 931 ----------------------------------------- v2v/virt-v2v.conf | 8 +- 8 files changed, 9 insertions(+), 988 deletions(-) delete mode 100755 snapshot/run-snapshot-locally delete mode 100755 snapshot/v2v-snapshot.pl diff --git a/Build.PL b/Build.PL index 51afae8..f5e6e7c 100644 --- a/Build.PL +++ b/Build.PL @@ -217,7 +217,7 @@ my $build = $class->new ( dist_version_from => 'lib/Sys/VirtV2V.pm', confdoc_files => [ 'v2v/virt-v2v.conf.pod' ], install_path => { 'locale' => '/usr/local/share/locale' }, - script_files => [ 'snapshot/v2v-snapshot.pl', 'v2v/virt-v2v.pl' ], + script_files => [ 'v2v/virt-v2v.pl' ], meta_add => { resources => { license => "http://www.gnu.org/licenses/gpl.html", diff --git a/MANIFEST b/MANIFEST index d5debe1..d200497 100644 --- a/MANIFEST +++ b/MANIFEST @@ -31,8 +31,6 @@ po/te.po po/virt-v2v.pot po/zh_CN.po README-NLS -snapshot/run-snapshot-locally -snapshot/v2v-snapshot.pl t/001-pod.t t/002-pod-coverage.t t/003-syntax.t diff --git a/lib/Sys/VirtV2V.pm b/lib/Sys/VirtV2V.pm index a831972..3e39d64 100644 --- a/lib/Sys/VirtV2V.pm +++ b/lib/Sys/VirtV2V.pm @@ -30,8 +30,8 @@ Sys::VirtV2V - Convert a virtual guest to use KVM =head1 DESCRIPTION -Modules under Sys::VirtV2V are used by the L<virt-v2v(1)> and L<v2v-snapshot(1)> -tools. See the documentation for those tools for a more detailed description. +Modules under Sys::VirtV2V are used by the L<virt-v2v(1)> tool. See the +virt-v2v documentation for a more detailed description. The Sys::VirtV2V module provides package information for virt-v2v. @@ -60,11 +60,6 @@ Please see the file COPYING.LIB for the full license. =head1 SEE ALSO L<virt-v2v(1)>, -L<v2v-snapshot(1)>, -L<Sys::VirtV2V::GuestOS(3pm)>, -L<Sys::VirtV2V::HVSource(3pm)>, -L<Sys::VirtV2V::Converter(3pm)>, -L<Sys::VirtV2V::Connection(3pm)>, L<http://libguestfs.org/> =cut diff --git a/lib/Sys/VirtV2V/Connection.pm b/lib/Sys/VirtV2V/Connection.pm index 5b4ed8d..a211662 100644 --- a/lib/Sys/VirtV2V/Connection.pm +++ b/lib/Sys/VirtV2V/Connection.pm @@ -195,7 +195,6 @@ Please see the file COPYING.LIB for the full license. L<Sys::VirtV2V::Connection::LibVirt(3pm)>, L<Sys::VirtV2V::Connection::LibVirtXML(3pm)>, L<virt-v2v(1)>, -L<v2v-snapshot(1)>, L<http://libguestfs.org/>. =cut diff --git a/po/POTFILES.in b/po/POTFILES.in index ffdf250..d5c774a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -10,5 +10,4 @@ ../lib/Sys/VirtV2V/Target/LibVirt.pm ../lib/Sys/VirtV2V/Transfer/ESX.pm ../lib/Sys/VirtV2V/UserMessage.pm -../snapshot/v2v-snapshot.pl ../v2v/virt-v2v.pl diff --git a/snapshot/run-snapshot-locally b/snapshot/run-snapshot-locally deleted file mode 100755 index 9fd1c06..0000000 --- a/snapshot/run-snapshot-locally +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# v2v-snapshot -# Copyright (C) 2009 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -# This script sets up the environment so you can run v2v-snapshot in place -# without needing to do 'make install' first. -# -# Use it like this: -# ./run-snapshot-locally [usual v2v-snapshot args ...] -# -# It requires the environment variable VIRTV2V_ROOT to be set. If using -# libguestfs from source, LIBGUESTFS_ROOT must also be set. - -if [ -z "$VIRTV2V_ROOT" ]; then - echo "VIRTV2V_ROOT must be set" - exit 1 -fi - -PERL5LIB="$VIRTV2V_ROOT/blib/lib" - -if [ ! -z "$LIBGUESTFS_ROOT" ]; then - LD_LIBRARY_PATH="$LIBGUESTFS_ROOT/src/.libs" - LIBGUESTFS_PATH="$LIBGUESTFS_ROOT/appliance" - PERL5LIB="$PERL5LIB:$LIBGUESTFS_ROOT/perl/blib/lib:$LIBGUESTFS_ROOT/perl/blib/arch" -fi - -export PERL5LIB LD_LIBRARY_PATH LIBGUESTFS_PATH - -exec perl "$VIRTV2V_ROOT/snapshot/v2v-snapshot.pl" "$@" diff --git a/snapshot/v2v-snapshot.pl b/snapshot/v2v-snapshot.pl deleted file mode 100755 index ccbc907..0000000 --- a/snapshot/v2v-snapshot.pl +++ /dev/null @@ -1,931 +0,0 @@ -#!/usr/bin/perl -# v2v-snapshot -# Copyright (C) 2009 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -use warnings; -use strict; - -use English; - -use File::Temp qw(tempfile); -use Getopt::Long; -use Pod::Usage; - -use Locale::TextDomain 'virt-v2v'; - -use Sys::Virt; - -use Sys::VirtV2V; -use Sys::VirtV2V::ExecHelper; -use Sys::VirtV2V::Connection; -use Sys::VirtV2V::UserMessage qw(user_message); - -=encoding utf8 - -=head1 NAME - -v2v-snapshot - Convert a guest to use a qcow2 snapshot for storage - -=head1 SYNOPSIS - - v2v-snapshot guest-domain - - v2v-snapshot --rollback guest-domain - - v2v-snapshot --commit guest-domain - -=head1 DESCRIPTION - -v2v-snapshot is a tool for creating a local snapshot of a guest's storage. It is -suitable for use when making a change to a guest which might have to be rolled -back. v2v-snapshot creates a qcow2 snapshot for all a guest's block devices and -updates the guest's libvirt domain to use them. - -When a change has been tested, v2v-snapshot can either commit the change, which -will update the original storage with the changes made to the snapshot, or -rollback the change, which will remove the snapshot. In either case, the guest -will be updated again to use its original storage. - -B<v2v-snapshot can only snapshot a guest which is shutdown. It is not intended -as a long-term storage option, and is not a general snapshotting tool for -guests.> - -=head1 OPTIONS - -=over 4 - -=cut - -my $help; - -=item B<--help> - -Display brief help. - -=cut - -my $version; - -=item B<--version> - -Display version number and exit. - -=cut - -my $uri; - -=item B<--connect URI> | B<-c URI> - -Connect to libvirt using the given I<URI>. If omitted, then we connect to the -default libvirt hypervisor. - -=cut - -my $input = "libvirt"; - -=item B<--input input> | B<-i input> - -The specified guest description uses the given I<input format>. The default is -C<libvirt>. Supported options are: - -=over - -=item I<libvirt> - -Guest argument is the name of a libvirt domain. - -=item I<libvirtxml> - -Guest argument is the path to an XML file describing a libvirt domain. - -=back - -=cut - -my $outputxml; - -=item B<--outputxml file> | B<-o file> - -Write the updated domain xml to I<file> instead of directly creating the domain. -If I<file> is '-' output will be to standard out. - -This option can be useful when snapshotting or rolling back to domain XML which -can't be created on the local hypervisor, for example because Xen is not -available locally. - -=cut - -my $datadir; - -=item B<--datadir dir> | B<-d dir> - -The directory v2v-snapshot will store its data. 2 subdirectories will be created -for holding snapshot images and XML backups. It defaults to -I</var/lib/virt-v2v>, except for connections to qemu:///session, when it -defaults to ~/.virt-v2v. - -=cut - -my $force = 0; - -=item B<--force> | B<-f> - -Force an action to complete which might not be safe. Force is required to: - -=over - -Create a snapshot of a guest which already has an active snapshot (overwrites -the XML backup). - -Rollback a guest which has no XML backup (all guest metadata is lost). - -=back - -=cut - -my $commit = 0; - -=item B<--commit> - -Commit the existing snapshot to its backing store and update the guest to using -the original storage. - -=cut - -my $rollback = 0; - -=item B<--rollback> - -Remove the snapshot and restore the guest to its previous, unmodified storage. - -=back - -=cut - -# Initialise the message output prefix -Sys::VirtV2V::UserMessage->set_identifier('v2v-snapshot'); - -GetOptions ("help|?" => \$help, - "version" => \$version, - "connect|c=s" => \$uri, - "input|i=s" => \$input, - "outputxml|o=s" => \$outputxml, - "datadir|d=s" => \$datadir, - "force|f" => \$force, - "commit" => \$commit, - "rollback" => \$rollback - ) or pod2usage(2); -pod2usage(0) if($help); -pod2usage({ - -message => __"--commit and --rollback options are mutually exclusive", - -exitval => 1 -}) if($commit && $rollback); - -if ($version) { - print "$Sys::VirtV2V::VERSION\n"; - exit(0); -} - -pod2usage(user_message(__"no guest argument given")) if @ARGV == 0; - -# Get a libvirt connection -my @vmm_params = (auth => 1); -push(@vmm_params, uri => $uri) if(defined($uri)); -my $vmm = Sys::Virt->new(@vmm_params); - -# Set the default datadir depending on the connection -if (!defined($datadir)) { - if ($vmm->get_uri() eq "qemu:///session") { - # Get the current user's home directory - my (undef, undef, undef, undef, undef, # name, passwd, uid, gid, quota - undef, undef, $home, undef, undef # comment, gcos, dir, shell, expire - ) = getpwuid($UID); - - unless (defined($home)) { - print STDERR user_message(__x("Unable to get home directory ". - "for current user")); - exit(1); - } - - $datadir = File::Spec->catdir($home, ".virt-v2v"); - } - - else { - $datadir = "/var/lib/virt-v2v"; - } - - unless (-d $datadir) { - unless(mkdir($datadir)) { - print STDERR user_message(__x("Unable to create data ". - "directory {dir}: {error}", - dir => $datadir, - error => $@)); - exit(1); - } - } -} - -# Get an appropriate Connection -my $mdr = Sys::VirtV2V::Connection->instantiate($input, {}, $vmm, @ARGV); -if(!defined($mdr)) { - print STDERR user_message(__x("{input} is not a valid input format", - input => $input)); - exit(1); -} - -exit(1) unless($mdr->is_configured()); - -############################################################################### -## Start of processing - -# Get a libvirt configuration for the guest -my $dom = $mdr->get_dom(); -exit(1) unless(defined($dom)); - -my $pool = _get_pool($vmm); - -if($commit) { - _commit_guest($dom, $vmm, $pool) == 0 or exit(1); -} - -elsif($rollback) { - my $retval; - ($retval, $dom) = _rollback_guest($dom, $vmm, $pool); - - exit(1) unless($retval == 0); -} - -# No commit or rollback. Snapshot the guest -else { - _snapshot_guest($dom, $vmm, $pool) == 0 or exit(1); -} - -# Don't try to output anything if the domain is no longer defined -if(!defined($dom)) { - print user_message(__"No domain has been created."); - exit($force == 1 ? 0 : 1); -} - -# If --outputxml was given, just write the xml instead of creating the domain -if($outputxml) { - my $out; - my $error = 0; - - # Write output to a file - if('-' ne $outputxml) { - unless(open($out, '>', $outputxml)) { - print STDERR user_message(__x("Unable to open {file}: {error}", - file => $outputxml, - error => $!)); - ($out, $outputxml) = tempfile(_get_guest_name($dom).'-XXXXXX', - SUFFIX => '.xml'); - $error = 1; - } - - print $out $dom->toString(); - - close($out) or die(__x("Error closing {file}: {error}", - file => $outputxml, error => $!)); - - if($error) { - print STDERR user_message(__x("Wrote output to {file}", - file => $outputxml)); - exit(1); - } - } - - # Write output to STDOUT - else { - print $dom->toString(); - } -} - -else { - eval { - $vmm->define_domain($dom->toString()); - }; - - if($@) { - print STDERR user_message(__x("Unable to create guest: {error}", - error => $@->stringify())); - print STDERR user_message(__"Consider using the --outputxml option"); - - # Write the output to a temporary file - my ($out, $outputxml) = tempfile(_get_guest_name($dom).'-XXXXXX', - SUFFIX => '.xml'); - - print $out $dom->toString(); - - print STDERR user_message(__x("Wrote output to {file}", - file => $outputxml)); - - unless(close($out)) { - print STDERR user_message(__x("Error closing {file}: {error}", - file => $outputxml, error => $!)); - } - - exit(1); - } -} - -############################################################################### -## Helper functions - -sub _get_pool -{ - my ($vmm) = @_; - - # Look for the v2v-snapshot storage pool - my $pool; - eval { - $pool = $vmm->get_storage_pool_by_name('v2v-snapshot'); - }; - - # If it wasn't there, try creating it - if($@) { - my $snapshotdir = $datadir.'/snapshots'; - - unless (-d $snapshotdir) { - unless(mkdir($snapshotdir)) { - print STDERR user_message(__x("Unable to create snapshot ". - "directory {dir}: {error}", - dir => $snapshotdir, - error => $@)); - exit(1); - } - } - - eval { - $pool = $vmm->create_storage_pool(" - <pool type='dir'> - <name>v2v-snapshot</name> - <target> - <path>$snapshotdir</path> - </target> - </pool> - "); - }; - - # If that didn't work, give up - if($@) { - print STDERR user_message(__x("Unable to create v2v-snapshot ". - "storage pool: {error}", - error => $@->stringify())) - unless(defined($pool)); - exit(1); - } - } - - # Check that the pool is usable - my $pool_info = $pool->get_info(); - - # If it's inactive, start it - if($pool_info->{state} == Sys::Virt::StoragePool::STATE_INACTIVE) { - eval { - $pool->create(); - }; - - if($@) { - print STDERR user_message(__x("Unable to start v2v-snapshot ". - "storage pool: {error}", - error => $@->stringify())) - unless(defined($pool)); - exit(1); - } - } - - # If it's building, there's nothing to do but wait - elsif($pool_info->{state} == Sys::Virt::StoragePool::STATE_BUILDING) { - print STDERR user_message(__"v2v-snapshot storage pool is ". - "temporarily unavailable"); - exit(1); - } - - return $pool; -} - -# Get a storage volume object for a given path -sub _get_volume -{ - my ($path, $pool) = @_; - - my $vol; - my $refreshed = 0; - do { - # XXX: Shouldn't be using an undocumented API - # See RHBZ 519647. Replace with lookupByPath when it's available. - eval { - $vol = Sys::Virt::StorageVol->_new(path => $path, - connection => $vmm); - }; - - if($@) { - if($refreshed) { - my $pool_xml = $pool->get_xml_description(); - my $pool_dom = new XML::DOM::Parser->parse($pool_xml); - - die(user_message(__x("Unable to find {path} in the ". - "v2v-snapshot storage pool.", - path => $path))); - } else { - $pool->refresh(0); - $refreshed = 1; - } - } - } until(defined($vol)); - - return $vol; -} - -sub _commit_guest -{ - my ($dom, $vmm, $pool) = @_; - - # First, get a list of existing disks - foreach my $disk ($dom->findnodes('/domain/devices/disk')) { - my ($source) = $disk->findnodes('source'); - my ($target) = $disk->findnodes('target/@dev'); - - # Look for the source location - my $path; - my $src_attrs = $source->getAttributes(); - foreach my $attr qw(dev file) { - my $item = $src_attrs->getNamedItem($attr); - if(defined($item)) { - $path = $item->getNodeValue(); - - # Remove the attribute. We'll add a new one in below. - $src_attrs->removeNamedItem($attr); - - last; - } - } - - # Find the storage volume, which will include information on the backing - # store - my $vol; - eval { - $vol = _get_volume($path, $pool); - }; - if($@) { - print STDERR $@; - return -1; - } - - # Get the volume's backing store - my $vol_xml = $vol->get_xml_description(); - my $vol_dom = new XML::DOM::Parser->parse($vol_xml); - my ($backing_store) = $vol_dom->findnodes('/volume/backingStore'); - - # Skip it if it doesn't have a backing store - unless($backing_store) { - print STDERR user_message(__x("Skipping device {target} as it ". - "doesn't have a backing store", - target => $target->getNodeValue())); - next; - } - - my ($backing_path) = $backing_store->findnodes('path/text()'); - $backing_path = $backing_path->getNodeValue(); - my ($backing_format) = $backing_store->findnodes('format/@type'); - $backing_format = $backing_format->getNodeValue(); - - # Try to work out if the backing store is a file or a block device by - # interrogating its storage volume object - my $backing_type; - eval { - # XXX: See comment above about this usage of _new - my $backing_vol = Sys::Virt::StorageVol->_new(path => $path, - connection => $vmm); - $backing_type = $backing_vol->get_info()->{type}; - }; - - # Backing store isn't in a storage pool - if($@) { - # Guess based on path name - # N.B. We could stat it, but that wouldn't work for a remote - # connection - if($backing_path =~ m{^/dev/}) { - $backing_type = Sys::Virt::StorageVol::TYPE_BLOCK; - } else { - $backing_type = Sys::Virt::StorageVol::TYPE_FILE; - } - } - - # Update the domain XML with the location of the backing store - if($backing_type == Sys::Virt::StorageVol::TYPE_BLOCK) { - $source->setAttribute('dev', $backing_path); - $source->removeAttribute('file'); - } else { - $source->setAttribute('file', $backing_path); - $source->removeAttribute('dev'); - } - - # Update the domain XML with with a driver appropriate to the backing - # store - my ($driver) = $disk->findnodes('driver'); - - # Initialise the driver if it's not set - $driver ||= $disk->appendChild($dom->createElement('driver')); - - $driver->setAttribute('name', 'qemu'); - $driver->setAttribute('type', $backing_format); - - # Commit snapshot to its backing store - # XXX: There should be a libvirt API to do this - my $eh = Sys::VirtV2V::ExecHelper->run - ('/usr/bin/qemu-img', 'commit', '-f', 'qcow2', $path); - - # Check commit succeeded - if($eh->status != 0) { - print STDERR user_message(__x("Failed to commit snapshot '{path}' ". - "to backing store '{backingstore}'.". - "\nCommand output was:\n{output}", - path => $path, - backingstore => $backing_path, - output => $eh->output())); - - return -1; - } - - # Delete the snapshot volume - $vol->delete(0); - } - - # Remove the XML backup if it exists - my $xmlpath = _get_xml_path($dom); - unlink($xmlpath) if(-e $xmlpath); - - return 0; -} - -sub _snapshot_guest -{ - my ($dom, $vmm, $pool) = @_; - - my $name = _get_guest_name($dom); - - # Store a backup of the domain XML before modification - my $xmlpath = _get_xml_path($dom); - - # Error if the xml backup already exists and force not specified - if(-e $xmlpath && !$force) { - print STDERR - user_message(__x("A snapshot already exists for {guest}. You must ". - "commit it or roll it back back before creating ". - "a new snapshot.", guest => $name)); - return -1; - } - - # Keep a list files and volumes created by the snapshot process. We need to - # clean all of these up in the event of an error. - my @created_files = (); - my @created_volumes = (); - - my $ret = eval { - # Write the backup - my $xmlbackup; - open($xmlbackup, '>', $xmlpath) - or die(__x("Unable to write to {path}: {error}", - path => $xmlpath, error => $!)); - push(@created_files, $xmlpath); - - print $xmlbackup $dom->toString(); - - close($xmlbackup) - or die(__x("Error closing {path}: {error}", - path => $xmlpath, error => $!)); - - # Get a timestamp for use in naming snapshot volumes - my $time = time(); - - return _foreach_disk($dom, sub { - my ($disk, $source, $target, $path) = @_; - - # Create a new qcow2 volume in the v2v-snapshot storage pool - my $target_name = $target->getNodeValue(); - my $vol_name = "$name-$target_name-$time.qcow2"; - my $vol_xml = " - <volume> - <name>$vol_name</name> - <capacity>0</capacity> - <target> - <format type='qcow2'/> - </target> - <backingStore> - <path>$path</path> - </backingStore> - </volume> - "; - - my $vol; - eval { - $vol = $pool->create_volume($vol_xml); - }; - - if($@) { - print STDERR - user_message(__x("Unable to create storage volume: {error}", - error => $@->stringify())); - return -1; - } - - push(@created_volumes, $vol); - - # Update the source to be a "file" with the new path - $source->setAttribute("file", $vol->get_path()); - - # Remove the dev attribute in case it was set - $source->removeAttribute("dev"); - - # Also update the disk element to be a "file" - $source->getParentNode()->setAttribute('type', 'file'); - - # Replace the driver element with one which describes the qcow2 file - my ($driver) = $disk->findnodes('driver'); - - # Initialise the driver if it's not set - $driver ||= $disk->appendChild($dom->createElement('driver')); - - $driver->setAttribute('name', 'qemu'); - $driver->setAttribute('type', 'qcow2'); - - return 0; - }); - }; - - if ($@ || $ret < 0) { - foreach my $file (@created_files) { - unlink($file); # Don't check for further errors - } - - foreach my $vol (@created_volumes) { - $vol->delete(); - } - - die($@) if ($@); - } - - return $ret; -} - -sub _rollback_guest -{ - my ($dom, $vmm, $pool) = @_; - - my $name = _get_guest_name($dom); - my $xmlpath = _get_xml_path($dom); - - # Only rollback a guest without stored XML if force was specified - if(! -e $xmlpath && !$force) { - print STDERR user_message(__x("Refusing to rollback guest {name} ". - "without backed-up xml", - name => _get_guest_name($dom))); - return -1; - } - - # Delete all snapshots - _foreach_disk($dom, sub { - my ($disk, $source, $target, $path) = @_; - - # Find the storage volume, which will include information on the backing - # store - my $vol; - eval { - $vol = _get_volume($path, $pool); - }; - if($@) { - print STDERR $@; - return -1; - } - - # Check that the volume has a backing store - my $vol_xml = $vol->get_xml_description(); - my $vol_dom = new XML::DOM::Parser->parse($vol_xml); - my ($backing_store) = $vol_dom->findnodes('/volume/backingStore'); - - unless(defined($backing_store)) { - print STDERR user_message(__x("{path} is not a snapshot volume", - path => $path)); - return -1; - } - - if(-e $path && unlink($path) != 1) { - print STDERR user_message(__x("Failed to delete {file}: {error}", - file => $path, error => $!)); - return -1; - } - - return 0; - }) == 0 or return -1; - - # Load the backed-up XML - if(-e $xmlpath) { - $dom = new XML::DOM::Parser->parsefile($xmlpath); - if(unlink($xmlpath) != 1) { - print STDERR user_message(__x("Unable to delete backup xml file ". - "{file}: {error}", - file => $xmlpath, error => $!)); - return -1; - } - } - - # Or there is no backed-up XML (force must have been given) - else { - $dom = undef; - } - - # undefined the guest if it is defined - my $domain; - eval { - $domain = $vmm->get_domain_by_name($name); - }; - - # Nothing to do if the guest isn't defined - if(defined($domain)) { - my $state = $domain->get_info()->{state}; - - # Check the domain is shutoff - unless($state == Sys::Virt::Domain::STATE_SHUTOFF) { - $domain->destroy(); - - # $domain is now undefined. Get it back again - $domain = $vmm->get_domain_by_name($name); - } - - $domain->undefine(); - } - - return (0, $dom); -} - -sub _foreach_disk -{ - my ($dom, $func) = @_; - - # Get a list of existing disks - foreach my $disk ($dom->findnodes('/domain/devices/disk[@device = \'disk\']')) - { - my ($source) = $disk->findnodes('source'); - my ($target) = $disk->findnodes('target/@dev'); - - # Ignore disks with no source - next if (!defined($source)); - - # Look for the source location - my $path; - my $src_attrs = $source->getAttributes(); - foreach my $attr qw(dev file) { - my $item = $src_attrs->getNamedItem($attr); - if(defined($item)) { - $path = $item->getNodeValue(); - last; - } - } - - # Warn and ignore this source if we didn't find either - if(!defined($path)) { - print STDERR user_message(__x("invalid source element: {element}", - element => $source->toString())); - next; - } - - $func->($disk, $source, $target, $path) == 0 or return -1; - } -} - -sub _get_guest_name -{ - my ($dom) = @_; - - # Get the name of the guest from the domain description - my ($name_elem) = $dom->findnodes('/domain/name/text()'); - return $name_elem->toString(); -} - -sub _get_xml_path -{ - my ($dom) = @_; - - my $xmldir = $datadir.'/xml'; - - # Ensure it exists - unless (-d $xmldir) { - unless (mkdir($xmldir)) { - print STDERR user_message(__x("Unable to create xml storage ". - "directory {dir}: {error}", - dir => $xmldir, error => $@)); - exit(1); - } - } - - return $xmldir.'/'._get_guest_name($dom).'.xml'; -} - -=head1 EXAMPLES - -=head2 Snapshot a local guest - -=over - -This example covers snapshotting a guest which is available through libvirt on -the local machine. The guest domain's name is I<E<lt>guestE<gt>>. - -First ensure the guest is shutdown: - - # virsh destroy <guest> - -Snapshot the guest: - - # v2v-snapshot <guest> - -Start the guest, make some changes, modify hardware, test changes. To commit -the changes: - - # v2v-snapshot --commit <guest> - -Alternatively, to rollback the changes: - - # v2v-snapshot --rollback <guest> - -=back - -=head2 Snapshot an imported guest - -=over - -This example covers snapshotting a guest which has been copied from another -machine. For this to work you must have the domain XML exported from the origin -machine: - - (Origin) # virsh dumpxml <guest> > guest.xml - -You must also present all of the guest's disk images to the local machine in the -locations specified in guest.xml. If copying the images, the easiest -way to achieve this is to copy it to the same path on the local machine as it -had on the origin machine. If the storage can be remotely mounted, it should be -presented to the local machine with the same paths as on the origin machine. If -the location of the storage must change, you must manually edit guest.xml to -reflect the new paths. - -If it is possible to create the domain on the local machine: - - # v2v-snapshot -i libvirtxml guest.xml - -This command will create the imported domain locally with snapshot storage. - -If it is not possible to create the domain locally, for example because it is a -Xen domain and the local libvirt cannot manage Xen domains: - - # v2v-snapshot -o snapshot-guest.xml -i libvirtxml guest.xml - -This command does not attempt the create the domain locally, which would fail. -Instead it writes updated domain XML to I<snapshot-guest.xml>. The disks in -snapshot-guest.xml point to the newly created snapshot volumes. - -The latter method of import is intended for use when importing a Xen domain from -a origin machine for conversion with L<virt-v2v(1)>. virt-v2v should be given -I<snapshot-guest.xml> as the domain XML. - -=back - -=head1 SEE ALSO - -L<virt-v2v(1)>, -L<http://libguestfs.org/>. - -=head1 AUTHOR - -Matthew Booth <mbooth at redhat.com> - -=head1 COPYRIGHT - -Copyright (C) 2009 Red Hat Inc. - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. diff --git a/v2v/virt-v2v.conf b/v2v/virt-v2v.conf index e88d8e7..31f272e 100644 --- a/v2v/virt-v2v.conf +++ b/v2v/virt-v2v.conf @@ -51,23 +51,28 @@ <!-- Networks --> <!-- The default Xen bridge name --> + <!-- <network type='bridge' name='xenbr1'> <network type='network' name='default'/> </network> + --> <!-- The default ESX bridge name --> + <!-- <network type='bridge' name='VM Network'> <network type='network' name='default'/> </network> + --> <!-- The default libvirt network name --> + <!-- <network type='network' name='network'> <network type='network' name='default'/> </network> + --> <!-- If importing to RHEV, you may want to use the default network name 'rhevm' instead --> - <!-- <network type='bridge' name='xenbr1'> <network type='network' name='rhevm'/> </network> @@ -79,5 +84,4 @@ <network type='network' name='network'> <network type='network' name='rhevm'/> </network> - --> </virt-v2v> -- 1.6.6.1
I accidentally included a local change to virt-v2v.conf in this patch. Here it is again without that change.
We now always copy a guest during conversion, meaning this tool is no longer required. --- Build.PL | 2 +- MANIFEST | 2 - lib/Sys/VirtV2V.pm | 9 +- lib/Sys/VirtV2V/Connection.pm | 1 - po/POTFILES.in | 1 - snapshot/run-snapshot-locally | 43 -- snapshot/v2v-snapshot.pl | 931 ----------------------------------------- 7 files changed, 3 insertions(+), 986 deletions(-) delete mode 100755 snapshot/run-snapshot-locally delete mode 100755 snapshot/v2v-snapshot.pl diff --git a/Build.PL b/Build.PL index 51afae8..f5e6e7c 100644 --- a/Build.PL +++ b/Build.PL @@ -217,7 +217,7 @@ my $build = $class->new ( dist_version_from => 'lib/Sys/VirtV2V.pm', confdoc_files => [ 'v2v/virt-v2v.conf.pod' ], install_path => { 'locale' => '/usr/local/share/locale' }, - script_files => [ 'snapshot/v2v-snapshot.pl', 'v2v/virt-v2v.pl' ], + script_files => [ 'v2v/virt-v2v.pl' ], meta_add => { resources => { license => "http://www.gnu.org/licenses/gpl.html", diff --git a/MANIFEST b/MANIFEST index d5debe1..d200497 100644 --- a/MANIFEST +++ b/MANIFEST @@ -31,8 +31,6 @@ po/te.po po/virt-v2v.pot po/zh_CN.po README-NLS -snapshot/run-snapshot-locally -snapshot/v2v-snapshot.pl t/001-pod.t t/002-pod-coverage.t t/003-syntax.t diff --git a/lib/Sys/VirtV2V.pm b/lib/Sys/VirtV2V.pm index a831972..3e39d64 100644 --- a/lib/Sys/VirtV2V.pm +++ b/lib/Sys/VirtV2V.pm @@ -30,8 +30,8 @@ Sys::VirtV2V - Convert a virtual guest to use KVM =head1 DESCRIPTION -Modules under Sys::VirtV2V are used by the L<virt-v2v(1)> and L<v2v-snapshot(1)> -tools. See the documentation for those tools for a more detailed description. +Modules under Sys::VirtV2V are used by the L<virt-v2v(1)> tool. See the +virt-v2v documentation for a more detailed description. The Sys::VirtV2V module provides package information for virt-v2v. @@ -60,11 +60,6 @@ Please see the file COPYING.LIB for the full license. =head1 SEE ALSO L<virt-v2v(1)>, -L<v2v-snapshot(1)>, -L<Sys::VirtV2V::GuestOS(3pm)>, -L<Sys::VirtV2V::HVSource(3pm)>, -L<Sys::VirtV2V::Converter(3pm)>, -L<Sys::VirtV2V::Connection(3pm)>, L<http://libguestfs.org/> =cut diff --git a/lib/Sys/VirtV2V/Connection.pm b/lib/Sys/VirtV2V/Connection.pm index 5b4ed8d..a211662 100644 --- a/lib/Sys/VirtV2V/Connection.pm +++ b/lib/Sys/VirtV2V/Connection.pm @@ -195,7 +195,6 @@ Please see the file COPYING.LIB for the full license. L<Sys::VirtV2V::Connection::LibVirt(3pm)>, L<Sys::VirtV2V::Connection::LibVirtXML(3pm)>, L<virt-v2v(1)>, -L<v2v-snapshot(1)>, L<http://libguestfs.org/>. =cut diff --git a/po/POTFILES.in b/po/POTFILES.in index ffdf250..d5c774a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -10,5 +10,4 @@ ../lib/Sys/VirtV2V/Target/LibVirt.pm ../lib/Sys/VirtV2V/Transfer/ESX.pm ../lib/Sys/VirtV2V/UserMessage.pm -../snapshot/v2v-snapshot.pl ../v2v/virt-v2v.pl diff --git a/snapshot/run-snapshot-locally b/snapshot/run-snapshot-locally deleted file mode 100755 index 9fd1c06..0000000 --- a/snapshot/run-snapshot-locally +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# v2v-snapshot -# Copyright (C) 2009 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -# This script sets up the environment so you can run v2v-snapshot in place -# without needing to do 'make install' first. -# -# Use it like this: -# ./run-snapshot-locally [usual v2v-snapshot args ...] -# -# It requires the environment variable VIRTV2V_ROOT to be set. If using -# libguestfs from source, LIBGUESTFS_ROOT must also be set. - -if [ -z "$VIRTV2V_ROOT" ]; then - echo "VIRTV2V_ROOT must be set" - exit 1 -fi - -PERL5LIB="$VIRTV2V_ROOT/blib/lib" - -if [ ! -z "$LIBGUESTFS_ROOT" ]; then - LD_LIBRARY_PATH="$LIBGUESTFS_ROOT/src/.libs" - LIBGUESTFS_PATH="$LIBGUESTFS_ROOT/appliance" - PERL5LIB="$PERL5LIB:$LIBGUESTFS_ROOT/perl/blib/lib:$LIBGUESTFS_ROOT/perl/blib/arch" -fi - -export PERL5LIB LD_LIBRARY_PATH LIBGUESTFS_PATH - -exec perl "$VIRTV2V_ROOT/snapshot/v2v-snapshot.pl" "$@" diff --git a/snapshot/v2v-snapshot.pl b/snapshot/v2v-snapshot.pl deleted file mode 100755 index ccbc907..0000000 --- a/snapshot/v2v-snapshot.pl +++ /dev/null @@ -1,931 +0,0 @@ -#!/usr/bin/perl -# v2v-snapshot -# Copyright (C) 2009 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -use warnings; -use strict; - -use English; - -use File::Temp qw(tempfile); -use Getopt::Long; -use Pod::Usage; - -use Locale::TextDomain 'virt-v2v'; - -use Sys::Virt; - -use Sys::VirtV2V; -use Sys::VirtV2V::ExecHelper; -use Sys::VirtV2V::Connection; -use Sys::VirtV2V::UserMessage qw(user_message); - -=encoding utf8 - -=head1 NAME - -v2v-snapshot - Convert a guest to use a qcow2 snapshot for storage - -=head1 SYNOPSIS - - v2v-snapshot guest-domain - - v2v-snapshot --rollback guest-domain - - v2v-snapshot --commit guest-domain - -=head1 DESCRIPTION - -v2v-snapshot is a tool for creating a local snapshot of a guest's storage. It is -suitable for use when making a change to a guest which might have to be rolled -back. v2v-snapshot creates a qcow2 snapshot for all a guest's block devices and -updates the guest's libvirt domain to use them. - -When a change has been tested, v2v-snapshot can either commit the change, which -will update the original storage with the changes made to the snapshot, or -rollback the change, which will remove the snapshot. In either case, the guest -will be updated again to use its original storage. - -B<v2v-snapshot can only snapshot a guest which is shutdown. It is not intended -as a long-term storage option, and is not a general snapshotting tool for -guests.> - -=head1 OPTIONS - -=over 4 - -=cut - -my $help; - -=item B<--help> - -Display brief help. - -=cut - -my $version; - -=item B<--version> - -Display version number and exit. - -=cut - -my $uri; - -=item B<--connect URI> | B<-c URI> - -Connect to libvirt using the given I<URI>. If omitted, then we connect to the -default libvirt hypervisor. - -=cut - -my $input = "libvirt"; - -=item B<--input input> | B<-i input> - -The specified guest description uses the given I<input format>. The default is -C<libvirt>. Supported options are: - -=over - -=item I<libvirt> - -Guest argument is the name of a libvirt domain. - -=item I<libvirtxml> - -Guest argument is the path to an XML file describing a libvirt domain. - -=back - -=cut - -my $outputxml; - -=item B<--outputxml file> | B<-o file> - -Write the updated domain xml to I<file> instead of directly creating the domain. -If I<file> is '-' output will be to standard out. - -This option can be useful when snapshotting or rolling back to domain XML which -can't be created on the local hypervisor, for example because Xen is not -available locally. - -=cut - -my $datadir; - -=item B<--datadir dir> | B<-d dir> - -The directory v2v-snapshot will store its data. 2 subdirectories will be created -for holding snapshot images and XML backups. It defaults to -I</var/lib/virt-v2v>, except for connections to qemu:///session, when it -defaults to ~/.virt-v2v. - -=cut - -my $force = 0; - -=item B<--force> | B<-f> - -Force an action to complete which might not be safe. Force is required to: - -=over - -Create a snapshot of a guest which already has an active snapshot (overwrites -the XML backup). - -Rollback a guest which has no XML backup (all guest metadata is lost). - -=back - -=cut - -my $commit = 0; - -=item B<--commit> - -Commit the existing snapshot to its backing store and update the guest to using -the original storage. - -=cut - -my $rollback = 0; - -=item B<--rollback> - -Remove the snapshot and restore the guest to its previous, unmodified storage. - -=back - -=cut - -# Initialise the message output prefix -Sys::VirtV2V::UserMessage->set_identifier('v2v-snapshot'); - -GetOptions ("help|?" => \$help, - "version" => \$version, - "connect|c=s" => \$uri, - "input|i=s" => \$input, - "outputxml|o=s" => \$outputxml, - "datadir|d=s" => \$datadir, - "force|f" => \$force, - "commit" => \$commit, - "rollback" => \$rollback - ) or pod2usage(2); -pod2usage(0) if($help); -pod2usage({ - -message => __"--commit and --rollback options are mutually exclusive", - -exitval => 1 -}) if($commit && $rollback); - -if ($version) { - print "$Sys::VirtV2V::VERSION\n"; - exit(0); -} - -pod2usage(user_message(__"no guest argument given")) if @ARGV == 0; - -# Get a libvirt connection -my @vmm_params = (auth => 1); -push(@vmm_params, uri => $uri) if(defined($uri)); -my $vmm = Sys::Virt->new(@vmm_params); - -# Set the default datadir depending on the connection -if (!defined($datadir)) { - if ($vmm->get_uri() eq "qemu:///session") { - # Get the current user's home directory - my (undef, undef, undef, undef, undef, # name, passwd, uid, gid, quota - undef, undef, $home, undef, undef # comment, gcos, dir, shell, expire - ) = getpwuid($UID); - - unless (defined($home)) { - print STDERR user_message(__x("Unable to get home directory ". - "for current user")); - exit(1); - } - - $datadir = File::Spec->catdir($home, ".virt-v2v"); - } - - else { - $datadir = "/var/lib/virt-v2v"; - } - - unless (-d $datadir) { - unless(mkdir($datadir)) { - print STDERR user_message(__x("Unable to create data ". - "directory {dir}: {error}", - dir => $datadir, - error => $@)); - exit(1); - } - } -} - -# Get an appropriate Connection -my $mdr = Sys::VirtV2V::Connection->instantiate($input, {}, $vmm, @ARGV); -if(!defined($mdr)) { - print STDERR user_message(__x("{input} is not a valid input format", - input => $input)); - exit(1); -} - -exit(1) unless($mdr->is_configured()); - -############################################################################### -## Start of processing - -# Get a libvirt configuration for the guest -my $dom = $mdr->get_dom(); -exit(1) unless(defined($dom)); - -my $pool = _get_pool($vmm); - -if($commit) { - _commit_guest($dom, $vmm, $pool) == 0 or exit(1); -} - -elsif($rollback) { - my $retval; - ($retval, $dom) = _rollback_guest($dom, $vmm, $pool); - - exit(1) unless($retval == 0); -} - -# No commit or rollback. Snapshot the guest -else { - _snapshot_guest($dom, $vmm, $pool) == 0 or exit(1); -} - -# Don't try to output anything if the domain is no longer defined -if(!defined($dom)) { - print user_message(__"No domain has been created."); - exit($force == 1 ? 0 : 1); -} - -# If --outputxml was given, just write the xml instead of creating the domain -if($outputxml) { - my $out; - my $error = 0; - - # Write output to a file - if('-' ne $outputxml) { - unless(open($out, '>', $outputxml)) { - print STDERR user_message(__x("Unable to open {file}: {error}", - file => $outputxml, - error => $!)); - ($out, $outputxml) = tempfile(_get_guest_name($dom).'-XXXXXX', - SUFFIX => '.xml'); - $error = 1; - } - - print $out $dom->toString(); - - close($out) or die(__x("Error closing {file}: {error}", - file => $outputxml, error => $!)); - - if($error) { - print STDERR user_message(__x("Wrote output to {file}", - file => $outputxml)); - exit(1); - } - } - - # Write output to STDOUT - else { - print $dom->toString(); - } -} - -else { - eval { - $vmm->define_domain($dom->toString()); - }; - - if($@) { - print STDERR user_message(__x("Unable to create guest: {error}", - error => $@->stringify())); - print STDERR user_message(__"Consider using the --outputxml option"); - - # Write the output to a temporary file - my ($out, $outputxml) = tempfile(_get_guest_name($dom).'-XXXXXX', - SUFFIX => '.xml'); - - print $out $dom->toString(); - - print STDERR user_message(__x("Wrote output to {file}", - file => $outputxml)); - - unless(close($out)) { - print STDERR user_message(__x("Error closing {file}: {error}", - file => $outputxml, error => $!)); - } - - exit(1); - } -} - -############################################################################### -## Helper functions - -sub _get_pool -{ - my ($vmm) = @_; - - # Look for the v2v-snapshot storage pool - my $pool; - eval { - $pool = $vmm->get_storage_pool_by_name('v2v-snapshot'); - }; - - # If it wasn't there, try creating it - if($@) { - my $snapshotdir = $datadir.'/snapshots'; - - unless (-d $snapshotdir) { - unless(mkdir($snapshotdir)) { - print STDERR user_message(__x("Unable to create snapshot ". - "directory {dir}: {error}", - dir => $snapshotdir, - error => $@)); - exit(1); - } - } - - eval { - $pool = $vmm->create_storage_pool(" - <pool type='dir'> - <name>v2v-snapshot</name> - <target> - <path>$snapshotdir</path> - </target> - </pool> - "); - }; - - # If that didn't work, give up - if($@) { - print STDERR user_message(__x("Unable to create v2v-snapshot ". - "storage pool: {error}", - error => $@->stringify())) - unless(defined($pool)); - exit(1); - } - } - - # Check that the pool is usable - my $pool_info = $pool->get_info(); - - # If it's inactive, start it - if($pool_info->{state} == Sys::Virt::StoragePool::STATE_INACTIVE) { - eval { - $pool->create(); - }; - - if($@) { - print STDERR user_message(__x("Unable to start v2v-snapshot ". - "storage pool: {error}", - error => $@->stringify())) - unless(defined($pool)); - exit(1); - } - } - - # If it's building, there's nothing to do but wait - elsif($pool_info->{state} == Sys::Virt::StoragePool::STATE_BUILDING) { - print STDERR user_message(__"v2v-snapshot storage pool is ". - "temporarily unavailable"); - exit(1); - } - - return $pool; -} - -# Get a storage volume object for a given path -sub _get_volume -{ - my ($path, $pool) = @_; - - my $vol; - my $refreshed = 0; - do { - # XXX: Shouldn't be using an undocumented API - # See RHBZ 519647. Replace with lookupByPath when it's available. - eval { - $vol = Sys::Virt::StorageVol->_new(path => $path, - connection => $vmm); - }; - - if($@) { - if($refreshed) { - my $pool_xml = $pool->get_xml_description(); - my $pool_dom = new XML::DOM::Parser->parse($pool_xml); - - die(user_message(__x("Unable to find {path} in the ". - "v2v-snapshot storage pool.", - path => $path))); - } else { - $pool->refresh(0); - $refreshed = 1; - } - } - } until(defined($vol)); - - return $vol; -} - -sub _commit_guest -{ - my ($dom, $vmm, $pool) = @_; - - # First, get a list of existing disks - foreach my $disk ($dom->findnodes('/domain/devices/disk')) { - my ($source) = $disk->findnodes('source'); - my ($target) = $disk->findnodes('target/@dev'); - - # Look for the source location - my $path; - my $src_attrs = $source->getAttributes(); - foreach my $attr qw(dev file) { - my $item = $src_attrs->getNamedItem($attr); - if(defined($item)) { - $path = $item->getNodeValue(); - - # Remove the attribute. We'll add a new one in below. - $src_attrs->removeNamedItem($attr); - - last; - } - } - - # Find the storage volume, which will include information on the backing - # store - my $vol; - eval { - $vol = _get_volume($path, $pool); - }; - if($@) { - print STDERR $@; - return -1; - } - - # Get the volume's backing store - my $vol_xml = $vol->get_xml_description(); - my $vol_dom = new XML::DOM::Parser->parse($vol_xml); - my ($backing_store) = $vol_dom->findnodes('/volume/backingStore'); - - # Skip it if it doesn't have a backing store - unless($backing_store) { - print STDERR user_message(__x("Skipping device {target} as it ". - "doesn't have a backing store", - target => $target->getNodeValue())); - next; - } - - my ($backing_path) = $backing_store->findnodes('path/text()'); - $backing_path = $backing_path->getNodeValue(); - my ($backing_format) = $backing_store->findnodes('format/@type'); - $backing_format = $backing_format->getNodeValue(); - - # Try to work out if the backing store is a file or a block device by - # interrogating its storage volume object - my $backing_type; - eval { - # XXX: See comment above about this usage of _new - my $backing_vol = Sys::Virt::StorageVol->_new(path => $path, - connection => $vmm); - $backing_type = $backing_vol->get_info()->{type}; - }; - - # Backing store isn't in a storage pool - if($@) { - # Guess based on path name - # N.B. We could stat it, but that wouldn't work for a remote - # connection - if($backing_path =~ m{^/dev/}) { - $backing_type = Sys::Virt::StorageVol::TYPE_BLOCK; - } else { - $backing_type = Sys::Virt::StorageVol::TYPE_FILE; - } - } - - # Update the domain XML with the location of the backing store - if($backing_type == Sys::Virt::StorageVol::TYPE_BLOCK) { - $source->setAttribute('dev', $backing_path); - $source->removeAttribute('file'); - } else { - $source->setAttribute('file', $backing_path); - $source->removeAttribute('dev'); - } - - # Update the domain XML with with a driver appropriate to the backing - # store - my ($driver) = $disk->findnodes('driver'); - - # Initialise the driver if it's not set - $driver ||= $disk->appendChild($dom->createElement('driver')); - - $driver->setAttribute('name', 'qemu'); - $driver->setAttribute('type', $backing_format); - - # Commit snapshot to its backing store - # XXX: There should be a libvirt API to do this - my $eh = Sys::VirtV2V::ExecHelper->run - ('/usr/bin/qemu-img', 'commit', '-f', 'qcow2', $path); - - # Check commit succeeded - if($eh->status != 0) { - print STDERR user_message(__x("Failed to commit snapshot '{path}' ". - "to backing store '{backingstore}'.". - "\nCommand output was:\n{output}", - path => $path, - backingstore => $backing_path, - output => $eh->output())); - - return -1; - } - - # Delete the snapshot volume - $vol->delete(0); - } - - # Remove the XML backup if it exists - my $xmlpath = _get_xml_path($dom); - unlink($xmlpath) if(-e $xmlpath); - - return 0; -} - -sub _snapshot_guest -{ - my ($dom, $vmm, $pool) = @_; - - my $name = _get_guest_name($dom); - - # Store a backup of the domain XML before modification - my $xmlpath = _get_xml_path($dom); - - # Error if the xml backup already exists and force not specified - if(-e $xmlpath && !$force) { - print STDERR - user_message(__x("A snapshot already exists for {guest}. You must ". - "commit it or roll it back back before creating ". - "a new snapshot.", guest => $name)); - return -1; - } - - # Keep a list files and volumes created by the snapshot process. We need to - # clean all of these up in the event of an error. - my @created_files = (); - my @created_volumes = (); - - my $ret = eval { - # Write the backup - my $xmlbackup; - open($xmlbackup, '>', $xmlpath) - or die(__x("Unable to write to {path}: {error}", - path => $xmlpath, error => $!)); - push(@created_files, $xmlpath); - - print $xmlbackup $dom->toString(); - - close($xmlbackup) - or die(__x("Error closing {path}: {error}", - path => $xmlpath, error => $!)); - - # Get a timestamp for use in naming snapshot volumes - my $time = time(); - - return _foreach_disk($dom, sub { - my ($disk, $source, $target, $path) = @_; - - # Create a new qcow2 volume in the v2v-snapshot storage pool - my $target_name = $target->getNodeValue(); - my $vol_name = "$name-$target_name-$time.qcow2"; - my $vol_xml = " - <volume> - <name>$vol_name</name> - <capacity>0</capacity> - <target> - <format type='qcow2'/> - </target> - <backingStore> - <path>$path</path> - </backingStore> - </volume> - "; - - my $vol; - eval { - $vol = $pool->create_volume($vol_xml); - }; - - if($@) { - print STDERR - user_message(__x("Unable to create storage volume: {error}", - error => $@->stringify())); - return -1; - } - - push(@created_volumes, $vol); - - # Update the source to be a "file" with the new path - $source->setAttribute("file", $vol->get_path()); - - # Remove the dev attribute in case it was set - $source->removeAttribute("dev"); - - # Also update the disk element to be a "file" - $source->getParentNode()->setAttribute('type', 'file'); - - # Replace the driver element with one which describes the qcow2 file - my ($driver) = $disk->findnodes('driver'); - - # Initialise the driver if it's not set - $driver ||= $disk->appendChild($dom->createElement('driver')); - - $driver->setAttribute('name', 'qemu'); - $driver->setAttribute('type', 'qcow2'); - - return 0; - }); - }; - - if ($@ || $ret < 0) { - foreach my $file (@created_files) { - unlink($file); # Don't check for further errors - } - - foreach my $vol (@created_volumes) { - $vol->delete(); - } - - die($@) if ($@); - } - - return $ret; -} - -sub _rollback_guest -{ - my ($dom, $vmm, $pool) = @_; - - my $name = _get_guest_name($dom); - my $xmlpath = _get_xml_path($dom); - - # Only rollback a guest without stored XML if force was specified - if(! -e $xmlpath && !$force) { - print STDERR user_message(__x("Refusing to rollback guest {name} ". - "without backed-up xml", - name => _get_guest_name($dom))); - return -1; - } - - # Delete all snapshots - _foreach_disk($dom, sub { - my ($disk, $source, $target, $path) = @_; - - # Find the storage volume, which will include information on the backing - # store - my $vol; - eval { - $vol = _get_volume($path, $pool); - }; - if($@) { - print STDERR $@; - return -1; - } - - # Check that the volume has a backing store - my $vol_xml = $vol->get_xml_description(); - my $vol_dom = new XML::DOM::Parser->parse($vol_xml); - my ($backing_store) = $vol_dom->findnodes('/volume/backingStore'); - - unless(defined($backing_store)) { - print STDERR user_message(__x("{path} is not a snapshot volume", - path => $path)); - return -1; - } - - if(-e $path && unlink($path) != 1) { - print STDERR user_message(__x("Failed to delete {file}: {error}", - file => $path, error => $!)); - return -1; - } - - return 0; - }) == 0 or return -1; - - # Load the backed-up XML - if(-e $xmlpath) { - $dom = new XML::DOM::Parser->parsefile($xmlpath); - if(unlink($xmlpath) != 1) { - print STDERR user_message(__x("Unable to delete backup xml file ". - "{file}: {error}", - file => $xmlpath, error => $!)); - return -1; - } - } - - # Or there is no backed-up XML (force must have been given) - else { - $dom = undef; - } - - # undefined the guest if it is defined - my $domain; - eval { - $domain = $vmm->get_domain_by_name($name); - }; - - # Nothing to do if the guest isn't defined - if(defined($domain)) { - my $state = $domain->get_info()->{state}; - - # Check the domain is shutoff - unless($state == Sys::Virt::Domain::STATE_SHUTOFF) { - $domain->destroy(); - - # $domain is now undefined. Get it back again - $domain = $vmm->get_domain_by_name($name); - } - - $domain->undefine(); - } - - return (0, $dom); -} - -sub _foreach_disk -{ - my ($dom, $func) = @_; - - # Get a list of existing disks - foreach my $disk ($dom->findnodes('/domain/devices/disk[@device = \'disk\']')) - { - my ($source) = $disk->findnodes('source'); - my ($target) = $disk->findnodes('target/@dev'); - - # Ignore disks with no source - next if (!defined($source)); - - # Look for the source location - my $path; - my $src_attrs = $source->getAttributes(); - foreach my $attr qw(dev file) { - my $item = $src_attrs->getNamedItem($attr); - if(defined($item)) { - $path = $item->getNodeValue(); - last; - } - } - - # Warn and ignore this source if we didn't find either - if(!defined($path)) { - print STDERR user_message(__x("invalid source element: {element}", - element => $source->toString())); - next; - } - - $func->($disk, $source, $target, $path) == 0 or return -1; - } -} - -sub _get_guest_name -{ - my ($dom) = @_; - - # Get the name of the guest from the domain description - my ($name_elem) = $dom->findnodes('/domain/name/text()'); - return $name_elem->toString(); -} - -sub _get_xml_path -{ - my ($dom) = @_; - - my $xmldir = $datadir.'/xml'; - - # Ensure it exists - unless (-d $xmldir) { - unless (mkdir($xmldir)) { - print STDERR user_message(__x("Unable to create xml storage ". - "directory {dir}: {error}", - dir => $xmldir, error => $@)); - exit(1); - } - } - - return $xmldir.'/'._get_guest_name($dom).'.xml'; -} - -=head1 EXAMPLES - -=head2 Snapshot a local guest - -=over - -This example covers snapshotting a guest which is available through libvirt on -the local machine. The guest domain's name is I<E<lt>guestE<gt>>. - -First ensure the guest is shutdown: - - # virsh destroy <guest> - -Snapshot the guest: - - # v2v-snapshot <guest> - -Start the guest, make some changes, modify hardware, test changes. To commit -the changes: - - # v2v-snapshot --commit <guest> - -Alternatively, to rollback the changes: - - # v2v-snapshot --rollback <guest> - -=back - -=head2 Snapshot an imported guest - -=over - -This example covers snapshotting a guest which has been copied from another -machine. For this to work you must have the domain XML exported from the origin -machine: - - (Origin) # virsh dumpxml <guest> > guest.xml - -You must also present all of the guest's disk images to the local machine in the -locations specified in guest.xml. If copying the images, the easiest -way to achieve this is to copy it to the same path on the local machine as it -had on the origin machine. If the storage can be remotely mounted, it should be -presented to the local machine with the same paths as on the origin machine. If -the location of the storage must change, you must manually edit guest.xml to -reflect the new paths. - -If it is possible to create the domain on the local machine: - - # v2v-snapshot -i libvirtxml guest.xml - -This command will create the imported domain locally with snapshot storage. - -If it is not possible to create the domain locally, for example because it is a -Xen domain and the local libvirt cannot manage Xen domains: - - # v2v-snapshot -o snapshot-guest.xml -i libvirtxml guest.xml - -This command does not attempt the create the domain locally, which would fail. -Instead it writes updated domain XML to I<snapshot-guest.xml>. The disks in -snapshot-guest.xml point to the newly created snapshot volumes. - -The latter method of import is intended for use when importing a Xen domain from -a origin machine for conversion with L<virt-v2v(1)>. virt-v2v should be given -I<snapshot-guest.xml> as the domain XML. - -=back - -=head1 SEE ALSO - -L<virt-v2v(1)>, -L<http://libguestfs.org/>. - -=head1 AUTHOR - -Matthew Booth <mbooth at redhat.com> - -=head1 COPYRIGHT - -Copyright (C) 2009 Red Hat Inc. - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -- 1.6.6.1
On Wed, Mar 31, 2010 at 04:30:51PM +0100, Matthew Booth wrote:> We now always copy a guest during conversion, meaning this tool is no longer > required. > --- > Build.PL | 2 +- > MANIFEST | 2 - > lib/Sys/VirtV2V.pm | 9 +- > lib/Sys/VirtV2V/Connection.pm | 1 - > po/POTFILES.in | 1 - > snapshot/run-snapshot-locally | 43 -- > snapshot/v2v-snapshot.pl | 931 ----------------------------------------- > v2v/virt-v2v.conf | 8 +-Yup, nuke from orbit. ACK. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
Possibly Parallel Threads
- [PATCH 1/2] Refactor guest and volume creation into Sys::VirtV2V::Target::LibVirt
- [PATCH 1/6] Convert config file to XML, and translate networks/bridge for all connections
- [PATCH] Improve error message when LibvirtXML is given invalid domain XML
- [PATCH 1/9] Convert config file to XML, and translate networks/bridge for all connections
- [PATCH 1/2] Config: NFC: always create and pass round a Config object