2013-10-27 21:59:46 +01:00
# Copyright (C) 2013 Red Hat, Inc.
2013-03-17 17:06:52 -04:00
#
# 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
2013-10-27 21:59:47 +01:00
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
2013-03-17 17:06:52 -04:00
#
# 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.
#
2014-02-05 19:09:26 -05:00
from distutils . spawn import find_executable
import logging
2013-03-17 17:06:52 -04:00
import os
2014-02-05 19:09:26 -05:00
import re
import shutil
import subprocess
import tempfile
2013-03-17 17:06:52 -04:00
2014-02-05 19:09:26 -05:00
from virtinst import StoragePool
2013-03-17 17:06:52 -04:00
2013-03-17 18:18:22 -04:00
2014-02-05 19:09:26 -05:00
class parser_class ( object ) :
2013-03-17 17:06:52 -04:00
"""
Base class for particular config file format definitions of
a VM instance .
Warning : this interface is not ( yet ) considered stable and may
change at will .
"""
@staticmethod
def identify_file ( input_file ) :
"""
Return True if the given file is of this format .
"""
raise NotImplementedError
@staticmethod
2014-02-05 19:09:26 -05:00
def export_libvirt ( conn , input_file ) :
2013-03-17 17:06:52 -04:00
"""
2014-02-05 19:09:26 -05:00
Import a configuration file and turn it into a libvirt Guest object
2013-03-17 17:06:52 -04:00
"""
raise NotImplementedError
2014-09-12 15:59:22 -04:00
def _get_parsers ( ) :
from . vmx import vmx_parser
from . ovf import ovf_parser
return [ vmx_parser , ovf_parser ]
2014-02-05 19:09:26 -05:00
2013-03-17 17:06:52 -04:00
2014-02-05 19:09:26 -05:00
def _is_test ( ) :
return bool ( os . getenv ( " VIRTINST_TEST_SUITE " ) )
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
2014-02-05 19:09:26 -05:00
def _find_parser_by_name ( input_name ) :
2013-03-17 17:06:52 -04:00
"""
Return the parser of the given name .
"""
2014-09-12 15:59:22 -04:00
parsers = [ p for p in _get_parsers ( ) if p . name == input_name ]
2013-03-17 17:06:52 -04:00
if len ( parsers ) :
return parsers [ 0 ]
2014-02-05 19:09:26 -05:00
raise RuntimeError ( _ ( " No parser found for type ' %s ' " ) % input_name )
2013-03-17 17:06:52 -04:00
2013-04-13 14:34:52 -04:00
2014-02-05 19:09:26 -05:00
def _find_parser_by_file ( input_file ) :
2013-03-17 17:06:52 -04:00
"""
Return the parser that is capable of comprehending the given file .
"""
2014-09-12 15:59:22 -04:00
for p in _get_parsers ( ) :
2013-03-17 17:06:52 -04:00
if p . identify_file ( input_file ) :
return p
2014-02-05 19:09:26 -05:00
raise RuntimeError ( _ ( " Don ' t know how to parse file %s " ) % input_file )
2013-03-17 17:06:52 -04:00
2013-04-13 14:34:52 -04:00
2014-02-05 19:09:26 -05:00
def _run_cmd ( cmd ) :
2013-03-17 17:06:52 -04:00
"""
2014-02-05 19:09:26 -05:00
Return the exit status and output to stdout and stderr .
2013-03-17 17:06:52 -04:00
"""
2014-02-05 19:09:26 -05:00
logging . debug ( " Running command: %s " , " " . join ( cmd ) )
proc = subprocess . Popen ( cmd , stderr = subprocess . PIPE ,
stdout = subprocess . PIPE ,
close_fds = True )
stdout , stderr = proc . communicate ( )
ret = proc . wait ( )
2013-03-17 17:06:52 -04:00
2014-02-05 19:09:26 -05:00
logging . debug ( " stdout= %s " , stdout )
logging . debug ( " stderr= %s " , stderr )
2013-04-13 14:34:52 -04:00
2014-02-05 19:09:26 -05:00
if ret == 0 :
return
out = stdout
if stderr :
if out :
out + = " \n "
out + = stderr
raise RuntimeError ( " %s : failed with exit status %d : %s " %
( " " . join ( cmd ) , ret , out ) )
2013-03-17 17:06:52 -04:00
2013-04-13 14:34:52 -04:00
2014-02-05 19:09:26 -05:00
def _find_input ( input_file , parser , print_cb ) :
2013-03-17 17:06:52 -04:00
"""
2014-02-05 19:09:26 -05:00
Given the input file , determine if its a directory , archive , etc
2013-03-17 17:06:52 -04:00
"""
2014-02-05 19:09:26 -05:00
force_clean = [ ]
2013-03-17 17:06:52 -04:00
2014-02-05 19:09:26 -05:00
try :
ext = os . path . splitext ( input_file ) [ 1 ]
tempdir = None
if ext and ext [ 1 : ] in [ " zip " , " gz " , " ova " ,
" tar " , " bz2 " , " bzip2 " , " 7z " , " xz " ] :
basedir = " /var/tmp "
if _is_test ( ) :
tempdir = os . path . join ( basedir , " virt-convert-tmp " )
else :
tempdir = tempfile . mkdtemp (
prefix = " virt-convert-tmp " , dir = basedir )
2013-04-13 14:34:52 -04:00
2014-02-05 19:09:26 -05:00
base = os . path . basename ( input_file )
2014-03-05 10:31:59 +08:00
# check if 'unar' command existed.
if not find_executable ( " unar " ) :
raise RuntimeError ( _ ( " %s appears to be an archive, "
" but ' unar ' is not installed. "
" Please either install ' unar ' , or extract the archive "
" yourself and point virt-convert at "
" the extracted directory. " ) % base )
2014-02-05 19:09:26 -05:00
cmd = [ " unar " , " -o " , tempdir , base ]
print_cb ( _ ( " %s appears to be an archive, running: %s " ) %
( base , " " . join ( cmd ) ) )
cmd [ - 1 ] = input_file
_run_cmd ( cmd )
force_clean . append ( tempdir )
input_file = tempdir
if not os . path . isdir ( input_file ) :
if not parser :
parser = _find_parser_by_file ( input_file )
return input_file , parser , force_clean
2014-09-12 15:59:22 -04:00
parsers = parser and [ parser ] or _get_parsers ( )
2014-02-05 19:09:26 -05:00
for root , ignore , files in os . walk ( input_file ) :
for p in parsers :
for f in [ f for f in files if f . endswith ( p . suffix ) ] :
path = os . path . join ( root , f )
if p . identify_file ( path ) :
return path , p , force_clean
raise RuntimeError ( " Could not find parser for file %s " % input_file )
except :
for f in force_clean :
shutil . rmtree ( f )
raise
class VirtConverter ( object ) :
2013-03-17 17:06:52 -04:00
"""
2014-02-05 19:09:26 -05:00
Public interface for actually performing the conversion
2013-03-17 17:06:52 -04:00
"""
2014-05-31 16:39:57 -04:00
def __init__ ( self , conn , input_file , print_cb = - 1 , input_name = None ) :
2014-02-05 19:09:26 -05:00
self . conn = conn
self . _err_clean = [ ]
self . _force_clean = [ ]
2013-03-17 17:06:52 -04:00
2014-05-31 17:37:19 -04:00
if print_cb == - 1 or print_cb is None :
2014-02-05 19:09:26 -05:00
def cb ( msg ) :
2014-05-31 17:37:19 -04:00
if print_cb == - 1 :
print msg
self . print_cb = cb
else :
self . print_cb = print_cb
2013-03-17 17:06:52 -04:00
2014-02-05 19:09:26 -05:00
parser = None
if input_name :
parser = _find_parser_by_name ( input_name )
2014-09-20 18:26:37 -04:00
input_file = os . path . abspath ( input_file )
2014-02-05 19:09:26 -05:00
logging . debug ( " converter __init__ with input= %s parser= %s " ,
input_file , parser )
( self . _input_file ,
self . parser ,
self . _force_clean ) = _find_input ( input_file , parser , self . print_cb )
2014-05-31 16:05:29 -04:00
self . _top_dir = os . path . dirname ( os . path . abspath ( self . _input_file ) )
2014-02-05 19:09:26 -05:00
logging . debug ( " converter not input_file= %s parser= %s " ,
self . _input_file , self . parser )
cwd = os . getcwd ( )
try :
os . chdir ( self . _top_dir )
self . _guest = self . parser . export_libvirt ( self . conn ,
self . _input_file )
self . _guest . add_default_devices ( )
finally :
os . chdir ( cwd )
def __del__ ( self ) :
for f in self . _force_clean :
shutil . rmtree ( f )
def get_guest ( self ) :
return self . _guest
def cleanup ( self ) :
"""
Remove any generated output .
"""
for path in self . _err_clean :
if os . path . isfile ( path ) :
os . remove ( path )
if os . path . isdir ( path ) :
shutil . rmtree ( path )
def _copy_file ( self , absin , absout , dry ) :
self . print_cb ( " Copying %s to %s " % ( os . path . basename ( absin ) , absout ) )
if not dry :
shutil . copy ( absin , absout )
def _qemu_convert ( self , absin , absout , disk_format , dry ) :
"""
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 .
"""
binnames = [ " qemu-img " , " kvm-img " ]
if _is_test ( ) :
executable = " /usr/bin/qemu-img "
else :
for binname in binnames :
executable = find_executable ( binname )
if executable :
break
if executable is None :
raise RuntimeError ( _ ( " None of %s tools found. " ) % binnames )
base = os . path . basename ( absin )
cmd = [ executable , " convert " , " -O " , disk_format , base , absout ]
self . print_cb ( " Running %s " % " " . join ( cmd ) )
if dry :
return
cmd [ 4 ] = absin
_run_cmd ( cmd )
def convert_disks ( self , disk_format , destdir = None , dry = False ) :
"""
Convert a disk into the requested format if possible , in the
given output directory . Raises RuntimeError or other failures .
"""
if disk_format == " none " :
disk_format = None
if destdir is None :
2014-05-31 16:30:00 -04:00
destdir = StoragePool . get_default_path ( self . conn , build = not dry )
2014-02-05 19:09:26 -05:00
guest = self . get_guest ( )
for disk in guest . get_devices ( " disk " ) :
if disk . device != " disk " :
continue
if disk_format and disk . driver_type == disk_format :
logging . debug ( " path= %s is already in requested format= %s " ,
disk . path , disk_format )
disk_format = None
basepath = os . path . splitext ( os . path . basename ( disk . path ) ) [ 0 ]
newpath = re . sub ( r ' \ s ' , ' _ ' , basepath )
if disk_format :
newpath + = ( " . " + disk_format )
newpath = os . path . join ( destdir , newpath )
if os . path . exists ( newpath ) :
raise RuntimeError ( _ ( " New path name ' %s ' already exists " ) %
newpath )
if not disk_format or disk_format == " none " :
self . _copy_file ( disk . path , newpath , dry )
else :
self . _qemu_convert ( disk . path , newpath , disk_format , dry )
disk . driver_type = disk_format
disk . path = newpath
self . _err_clean . append ( newpath )