2013-03-18 01:06:52 +04:00
#
# Copyright 2013 Red Hat, Inc.
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# 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., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
import subprocess
import shutil
import errno
import sys
import os
import re
import logging
DISK_FORMAT_NONE = 0
DISK_FORMAT_RAW = 1
DISK_FORMAT_VMDK = 2
DISK_FORMAT_VDISK = 3
DISK_FORMAT_QCOW = 4
DISK_FORMAT_QCOW2 = 5
DISK_FORMAT_COW = 6
DISK_FORMAT_VDI = 7
DISK_TYPE_DISK = 0
DISK_TYPE_CDROM = 1
DISK_TYPE_ISO = 2
CSUM_SHA1 = 0
CSUM_SHA256 = 1
disk_suffixes = {
DISK_FORMAT_RAW : " .raw " ,
DISK_FORMAT_VMDK : " .vmdk " ,
DISK_FORMAT_VDISK : " .vdisk " ,
DISK_FORMAT_QCOW : " .qcow " ,
DISK_FORMAT_QCOW2 : " .qcow2 " ,
DISK_FORMAT_COW : " .cow " ,
DISK_FORMAT_VDI : " .vdi " ,
}
qemu_formats = {
DISK_FORMAT_RAW : " raw " ,
DISK_FORMAT_VMDK : " vmdk " ,
DISK_FORMAT_VDISK : " vdisk " ,
DISK_FORMAT_QCOW : " qcow " ,
DISK_FORMAT_QCOW2 : " qcow2 " ,
DISK_FORMAT_COW : " cow " ,
DISK_FORMAT_VDI : " vdi " ,
}
disk_format_names = {
" none " : DISK_FORMAT_NONE ,
" raw " : DISK_FORMAT_RAW ,
" vmdk " : DISK_FORMAT_VMDK ,
" vdisk " : DISK_FORMAT_VDISK ,
" qcow " : DISK_FORMAT_QCOW ,
" qcow2 " : DISK_FORMAT_QCOW2 ,
" cow " : DISK_FORMAT_COW ,
" vdi " : DISK_FORMAT_VDI ,
}
checksum_types = {
CSUM_SHA1 : " sha1 " ,
CSUM_SHA256 : " sha256 " ,
}
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def ensuredirs ( path ) :
"""
Make sure that all the containing directories of the given file
path exist .
"""
try :
os . makedirs ( os . path . dirname ( path ) )
except OSError , e :
if e . errno != errno . EEXIST :
raise
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def run_cmd ( cmd ) :
"""
Return the exit status and output to stdout and stderr .
"""
logging . debug ( " Running command: %s " , " " . join ( cmd ) )
proc = subprocess . Popen ( cmd , stderr = subprocess . PIPE ,
stdout = subprocess . PIPE ,
close_fds = True )
ret = proc . wait ( )
return ret , proc . stdout . readlines ( ) , proc . stderr . readlines ( )
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def run_vdiskadm ( args ) :
""" Run vdiskadm, returning the output. """
2013-04-13 22:34:52 +04:00
ret , stdout , stderr = run_cmd ( [ " /usr/sbin/vdiskadm " ] + args )
2013-03-18 01:06:52 +04:00
if ret != 0 :
raise RuntimeError ( " Disk conversion failed with "
" exit status %d : %s " % ( ret , " " . join ( stderr ) ) )
if len ( stderr ) :
print >> sys . stderr , stderr
return stdout
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
class disk ( object ) :
""" Definition of an individual disk instance. """
2013-04-12 16:26:21 +04:00
def __init__ ( self , path = None , fmt = DISK_FORMAT_NONE , bus = " ide " ,
typ = DISK_TYPE_DISK ) :
2013-03-18 01:06:52 +04:00
self . path = path
2013-04-12 16:26:21 +04:00
self . format = fmt
2013-03-18 01:06:52 +04:00
self . bus = bus
2013-04-12 16:26:21 +04:00
self . type = typ
2013-03-18 01:06:52 +04:00
self . clean = [ ]
self . csum_dict = { }
def cleanup ( self ) :
"""
Remove any generated output .
"""
for path in self . clean :
if os . path . isfile ( path ) :
os . remove ( path )
if os . path . isdir ( path ) :
os . removedirs ( path )
self . clean = [ ]
def copy_file ( self , infile , outfile ) :
""" Copy an individual file. """
2013-04-13 22:34:52 +04:00
self . clean + = [ outfile ]
2013-03-18 01:06:52 +04:00
ensuredirs ( outfile )
shutil . copy ( infile , outfile )
def out_file ( self , out_format ) :
""" Return the relative path of the output file. """
if not out_format :
return self . path
relout = self . path . replace ( disk_suffixes [ self . format ] ,
disk_suffixes [ out_format ] )
return re . sub ( r ' \ s ' , ' _ ' , relout )
def vdisk_convert ( self , absin , absout ) :
"""
Import the given disk into vdisk , including any sub - files as
necessary .
"""
2013-04-13 22:34:52 +04:00
stdout = run_vdiskadm ( [ " import " , " -fnp " , absin , absout ] )
2013-03-18 01:06:52 +04:00
for item in stdout :
ignore , path = item . strip ( ) . split ( ' : ' , 1 )
2013-04-13 22:34:52 +04:00
self . clean + = [ os . path . join ( absout , path ) ]
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
run_vdiskadm ( [ " import " , " -fp " , absin , absout ] )
2013-03-18 01:06:52 +04:00
def qemu_convert ( self , absin , absout , out_format ) :
"""
Use qemu - img to convert the given disk . Note that at least some
version of qemu - img cannot handle multi - file VMDKs , so this can
easily go wrong .
Gentoo , Debian , and Ubuntu ( potentially others ) install kvm - img
with kvm and qemu - img with qemu . Both would work .
"""
2013-04-13 22:34:52 +04:00
self . clean + = [ absout ]
2013-03-18 01:06:52 +04:00
ret , ignore , stderr = run_cmd ( [ " qemu-img " , " convert " , " -O " ,
qemu_formats [ out_format ] , absin , absout ] )
if ret == 127 :
ret , ignore , stderr = run_cmd ( [ " kvm-img " , " convert " , " -O " ,
qemu_formats [ out_format ] , absin , absout ] )
if ret != 0 :
raise RuntimeError ( " Disk conversion failed with "
" exit status %d : %s " % ( ret , " " . join ( stderr ) ) )
if len ( stderr ) :
print >> sys . stderr , stderr
def copy ( self , indir , outdir , out_format ) :
"""
If needed , copy top - level disk files to outdir . If the copy is
done , then self . path is updated as needed .
Returns ( input_in_outdir , need_conversion )
"""
need_conversion = ( out_format != DISK_FORMAT_NONE and
self . format != out_format )
if os . path . isabs ( self . path ) :
return True , need_conversion
relin = self . path
absin = os . path . join ( indir , relin )
relout = self . out_file ( self . format )
absout = os . path . join ( outdir , relout )
#
# If we're going to use vdiskadm, it's much smarter; don't
# attempt any copies.
#
if out_format == DISK_FORMAT_VDISK :
return False , True
#
# If we're using the same directory, just account for any spaces
# in the disk filename and we're done.
#
if indir == outdir :
if relin != relout :
# vdisks cannot have spaces
if self . format == DISK_FORMAT_VDISK :
raise RuntimeError ( " Disk conversion failed: "
" invalid vdisk ' %s ' " % self . path )
2013-04-13 22:34:52 +04:00
self . clean + = [ absout ]
2013-03-18 01:06:52 +04:00
self . copy_file ( absin , absout )
self . path = relout
return True , need_conversion
#
# If we're not performing any conversion, just copy the file.
# XXX: This can go wrong for multi-part disks!
#
if not need_conversion :
2013-04-13 22:34:52 +04:00
self . clean + = [ absout ]
2013-03-18 01:06:52 +04:00
self . copy_file ( absin , absout )
self . path = relout
return True , False
#
# We're doing a conversion step, so we can rely upon convert()
# to place something in outdir.
#
return False , True
def convert ( self , indir , outdir , output_format ) :
"""
Convert a disk into the requested format if possible , in the
given output directory . Raises RuntimeError or other failures .
"""
if self . type != DISK_TYPE_DISK :
return
out_format = disk_format_names [ output_format ]
if not ( out_format == DISK_FORMAT_NONE or
out_format == DISK_FORMAT_VDISK or
out_format == DISK_FORMAT_RAW or
out_format == DISK_FORMAT_VMDK or
out_format == DISK_FORMAT_QCOW or
out_format == DISK_FORMAT_QCOW2 or
out_format == DISK_FORMAT_COW ) :
raise NotImplementedError ( _ ( " Cannot convert to disk format %s " ) %
output_format )
indir = os . path . normpath ( os . path . abspath ( indir ) )
outdir = os . path . normpath ( os . path . abspath ( outdir ) )
input_in_outdir , need_conversion = self . copy ( indir , outdir , out_format )
if not need_conversion :
assert ( input_in_outdir )
return
if os . path . isabs ( self . path ) :
raise NotImplementedError ( _ ( " Cannot convert disk with absolute "
" path %s " ) % self . path )
if input_in_outdir :
indir = outdir
relin = self . path
absin = os . path . join ( indir , relin )
relout = self . out_file ( out_format )
absout = os . path . join ( outdir , relout )
ensuredirs ( absout )
if os . getenv ( " VIRTCONV_TEST_NO_DISK_CONVERSION " ) :
self . format = out_format
self . path = self . out_file ( self . format )
return
if out_format == DISK_FORMAT_VDISK :
self . vdisk_convert ( absin , absout )
else :
self . qemu_convert ( absin , absout , out_format )
self . format = out_format
self . path = relout
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def disk_formats ( ) :
"""
Return a list of supported disk formats .
"""
return disk_format_names . keys ( )