2013-10-28 00:59:46 +04:00
# Copyright (C) 2013 Red Hat, Inc.
2013-03-18 01:06:52 +04:00
#
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2013-03-18 01:06:52 +04:00
#
2017-10-20 23:06:48 +03:00
import collections
2013-03-18 01:06:52 +04:00
import logging
2014-02-06 04:09:26 +04:00
import os
import re
2013-03-18 01:06:52 +04:00
import shlex
2014-02-06 04:09:26 +04:00
import virtinst
from virtinst import util
2013-04-16 01:36:32 +04:00
2014-09-12 23:59:22 +04:00
from . formats import parser_class
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
class _VMXLine ( object ) :
"""
Class tracking an individual line in a VMX / VMDK file
"""
def __init__ ( self , content ) :
self . content = content
self . pair = None
self . is_blank = False
self . is_comment = False
self . is_disk = False
self . _parse ( )
def _parse ( self ) :
line = self . content . strip ( )
if not line :
self . is_blank = True
elif line . startswith ( " # " ) :
self . is_comment = True
elif line . startswith ( " RW " ) or line . startswith ( " RDONLY " ) :
self . is_disk = True
else :
# Expected that this will raise an error for unknown format
before_eq , after_eq = line . split ( " = " , 1 )
key = before_eq . strip ( ) . lower ( )
value = after_eq . strip ( ) . strip ( ' " ' )
self . pair = ( key , value )
def parse_disk_path ( self ) :
# format:
# RW 16777216 VMFS "test-flat.vmdk"
# RDONLY 156296322 V2I "virtual-pc-diskformat.v2i"
content = self . content . split ( " " , 3 ) [ 3 ]
if not content . startswith ( " \" " ) :
raise ValueError ( " Path was not fourth entry in VMDK storage line " )
return shlex . split ( content , " " , 1 ) [ 0 ]
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
class _VMXFile ( object ) :
"""
Class tracking a parsed VMX / VMDK format file
"""
def __init__ ( self , content ) :
self . content = content
self . lines = [ ]
self . _parse ( )
def _parse ( self ) :
for line in self . content :
try :
lineobj = _VMXLine ( line )
self . lines . append ( lineobj )
2017-05-05 19:47:21 +03:00
except Exception as e :
2013-03-18 01:06:52 +04:00
raise Exception ( _ ( " Syntax error at line %d : %s \n %s " ) %
( len ( self . lines ) + 1 , line . strip ( ) , e ) )
def pairs ( self ) :
2017-10-20 23:06:48 +03:00
ret = collections . OrderedDict ( )
2013-03-18 01:06:52 +04:00
for line in self . lines :
if line . pair :
ret [ line . pair [ 0 ] ] = line . pair [ 1 ]
return ret
2013-04-13 22:34:52 +04:00
2014-02-06 04:09:26 +04:00
def parse_vmdk ( filename ) :
2013-03-18 01:06:52 +04:00
"""
Parse a VMDK descriptor file
Reference : http : / / sanbarrow . com / vmdk - basics . html
"""
# Detect if passed file is a descriptor file
# Assume descriptor isn't larger than 10K
if not os . path . exists ( filename ) :
logging . debug ( " VMDK file ' %s ' doesn ' t exist " , filename )
return
if os . path . getsize ( filename ) > ( 10 * 1024 ) :
logging . debug ( " VMDK file ' %s ' too big to be a descriptor " , filename )
return
f = open ( filename , " r " )
content = f . readlines ( )
f . close ( )
try :
vmdkfile = _VMXFile ( content )
2017-07-24 11:26:48 +03:00
except Exception :
2013-03-18 01:06:52 +04:00
logging . exception ( " %s looked like a vmdk file, but parsing failed " ,
filename )
return
2013-04-12 00:32:00 +04:00
disklines = [ l for l in vmdkfile . lines if l . is_disk ]
2013-03-18 01:06:52 +04:00
if len ( disklines ) == 0 :
raise RuntimeError ( _ ( " Didn ' t detect a storage line in the VMDK "
" descriptor file " ) )
if len ( disklines ) > 1 :
raise RuntimeError ( _ ( " Don ' t know how to handle multistorage VMDK "
" descriptors " ) )
2014-02-06 04:09:26 +04:00
return disklines [ 0 ] . parse_disk_path ( )
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2014-02-06 04:09:26 +04:00
def parse_netdev_entry ( conn , ifaces , fullkey , value ) :
2013-03-18 01:06:52 +04:00
"""
Parse a particular key / value for a network . Throws ValueError .
"""
ignore , ignore , inst , key = re . split ( " ^(ethernet)([0-9]+). " , fullkey )
lvalue = value . lower ( )
if key == " present " and lvalue == " false " :
return
2014-02-06 04:09:26 +04:00
net = None
for checkiface in ifaces :
if getattr ( checkiface , " vmx_inst " ) == inst :
net = checkiface
break
if not net :
2018-03-20 19:18:35 +03:00
net = virtinst . DeviceInterface ( conn )
2014-02-06 04:09:26 +04:00
setattr ( net , " vmx_inst " , inst )
net . set_default_source ( )
ifaces . append ( net )
2013-03-18 01:06:52 +04:00
if key == " virtualdev " :
2014-02-06 04:09:26 +04:00
# "vlance", "vmxnet", "e1000"
if lvalue in [ " e1000 " ] :
net . model = lvalue
2013-03-18 01:06:52 +04:00
if key == " addresstype " and lvalue == " generated " :
2014-02-06 04:09:26 +04:00
# Autogenerate a MAC address, the default
pass
2013-03-18 01:06:52 +04:00
if key == " address " :
2014-02-06 04:09:26 +04:00
# we ignore .generatedAddress for auto mode
net . macaddr = lvalue
return net , inst
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2018-02-23 03:25:05 +03:00
def parse_disk_entry ( conn , disks , fullkey , value , topdir ) :
2013-03-18 01:06:52 +04:00
"""
2018-02-23 03:25:05 +03:00
Parse a particular key / value for a disk .
2013-03-18 01:06:52 +04:00
"""
# skip bus values, e.g. 'scsi0.present = "TRUE"'
if re . match ( r " ^(scsi|ide)[0-9]+[^:] " , fullkey ) :
return
2014-02-06 04:09:26 +04:00
ignore , bus , bus_nr , inst , key = re . split (
r " ^(scsi|ide)([0-9]+):([0-9]+) \ . " , fullkey )
2013-03-18 01:06:52 +04:00
lvalue = value . lower ( )
if key == " present " and lvalue == " false " :
return
# Does anyone else think it's scary that we're still doing things
# like this?
if bus == " ide " :
inst = int ( bus_nr ) * 2 + ( int ( inst ) % 2 )
elif bus == " scsi " :
inst = int ( bus_nr ) * 16 + ( int ( inst ) % 16 )
2014-02-06 04:09:26 +04:00
disk = None
for checkdisk in disks :
if checkdisk . bus == bus and getattr ( checkdisk , " vmx_inst " ) == inst :
disk = checkdisk
break
if not disk :
2018-03-20 19:18:35 +03:00
disk = virtinst . DeviceDisk ( conn )
2014-02-06 04:09:26 +04:00
disk . bus = bus
setattr ( disk , " vmx_inst " , inst )
disks . append ( disk )
2013-03-18 01:06:52 +04:00
if key == " devicetype " :
2014-02-06 04:09:26 +04:00
if ( lvalue == " atapi-cdrom " or
lvalue == " cdrom-raw " or
lvalue == " cdrom-image " ) :
disk . device = " cdrom "
2013-03-18 01:06:52 +04:00
if key == " filename " :
disk . path = value
2014-02-06 04:09:26 +04:00
fmt = " raw "
2013-03-18 01:06:52 +04:00
if lvalue . endswith ( " .vmdk " ) :
2014-02-06 04:09:26 +04:00
fmt = " vmdk "
2013-03-18 01:06:52 +04:00
# See if the filename is actually a VMDK descriptor file
2018-02-23 03:25:05 +03:00
newpath = parse_vmdk ( os . path . join ( topdir , disk . path ) )
2014-02-06 04:09:26 +04:00
if newpath :
logging . debug ( " VMDK file parsed path %s -> %s " ,
disk . path , newpath )
disk . path = newpath
2013-03-18 01:06:52 +04:00
2014-02-06 04:09:26 +04:00
disk . driver_type = fmt
2013-03-18 01:06:52 +04:00
2014-02-06 04:09:26 +04:00
class vmx_parser ( parser_class ) :
2013-03-18 01:06:52 +04:00
"""
Support for VMWare . vmx files . Note that documentation is
particularly sparse on this format , with pretty much the best
resource being http : / / sanbarrow . com / vmx . html
"""
name = " vmx "
suffix = " .vmx "
@staticmethod
def identify_file ( input_file ) :
"""
Return True if the given file is of this format .
"""
2014-02-06 04:09:26 +04:00
if os . path . getsize ( input_file ) > ( 1024 * 1024 * 2 ) :
return
2013-03-18 01:06:52 +04:00
infile = open ( input_file , " r " )
content = infile . readlines ( )
infile . close ( )
for line in content :
# some .vmx files don't bother with the header
if ( re . match ( r ' ^config.version \ s+= ' , line ) or
re . match ( r ' ^#! \ s*/usr/bin/vm(ware|player) ' , line ) ) :
return True
return False
@staticmethod
2014-02-06 04:09:26 +04:00
def export_libvirt ( conn , input_file ) :
2018-02-23 03:25:05 +03:00
topdir = os . path . dirname ( os . path . abspath ( input_file ) )
2013-03-18 01:06:52 +04:00
infile = open ( input_file , " r " )
contents = infile . readlines ( )
infile . close ( )
logging . debug ( " Importing VMX file: \n %s " , " " . join ( contents ) )
vmxfile = _VMXFile ( contents )
config = vmxfile . pairs ( )
if not config . get ( " displayname " ) :
raise ValueError ( _ ( " No displayName defined in ' %s ' " ) %
input_file )
2014-02-06 04:09:26 +04:00
name = config . get ( " displayname " )
mem = config . get ( " memsize " )
desc = config . get ( " annotation " )
vcpus = config . get ( " numvcpus " )
def _find_keys ( prefixes ) :
ret = [ ]
for key , value in config . items ( ) :
for p in util . listify ( prefixes ) :
if key . startswith ( p ) :
ret . append ( ( key , value ) )
break
return ret
disks = [ ]
for key , value in _find_keys ( [ " scsi " , " ide " ] ) :
2018-02-23 03:25:05 +03:00
parse_disk_entry ( conn , disks , key , value , topdir )
2014-02-06 04:09:26 +04:00
ifaces = [ ]
for key , value in _find_keys ( " ethernet " ) :
parse_netdev_entry ( conn , ifaces , key , value )
for disk in disks :
if disk . device == " disk " :
2013-03-18 01:06:52 +04:00
continue
# vmx files often have dross left in path for CD entries
2015-07-10 15:07:02 +03:00
if ( disk . path is None or
disk . path . lower ( ) == " auto detect " or
2013-03-18 01:06:52 +04:00
not os . path . exists ( disk . path ) ) :
2014-02-06 04:09:26 +04:00
disk . path = None
2013-03-18 01:06:52 +04:00
2018-09-07 03:28:05 +03:00
guest = virtinst . Guest ( conn )
2014-02-06 04:09:26 +04:00
guest . name = name . replace ( " " , " _ " )
guest . description = desc or None
if vcpus :
guest . vcpus = int ( vcpus )
if mem :
guest . memory = int ( mem ) * 1024
2013-03-18 01:06:52 +04:00
2014-02-06 04:09:26 +04:00
for dev in ifaces + disks :
guest . add_device ( dev )
2013-03-18 01:06:52 +04:00
2014-02-06 04:09:26 +04:00
return guest