2015-05-01 04:05:37 +03:00
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies 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 3 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, see <http://www.gnu.org/licenses/>.
"""
VMware player / workstation server module .
"""
import os
import sys
2015-05-22 06:48:59 +03:00
import re
2015-05-01 04:05:37 +03:00
import shutil
import asyncio
import subprocess
import logging
2015-07-13 05:58:58 +03:00
import codecs
2021-06-08 05:26:33 +03:00
import ipaddress
2015-05-22 06:48:59 +03:00
2015-05-21 04:05:26 +03:00
from collections import OrderedDict
2015-05-22 06:48:59 +03:00
from gns3server . utils . interfaces import interfaces
2015-08-01 22:49:02 +03:00
from gns3server . utils . asyncio import subprocess_check_output
2019-10-09 12:02:30 +03:00
from gns3server . utils import parse_version , shlex_quote
2015-05-01 04:05:37 +03:00
log = logging . getLogger ( __name__ )
2016-04-15 18:57:06 +03:00
from gns3server . compute . base_manager import BaseManager
from gns3server . compute . vmware . vmware_vm import VMwareVM
from gns3server . compute . vmware . vmware_error import VMwareError
2015-05-01 04:05:37 +03:00
class VMware ( BaseManager ) :
2016-05-11 20:35:36 +03:00
_NODE_CLASS = VMwareVM
2015-05-01 04:05:37 +03:00
def __init__ ( self ) :
super ( ) . __init__ ( )
2016-01-26 23:23:01 +02:00
self . _vmware_inventory_lock = asyncio . Lock ( )
2015-05-01 04:05:37 +03:00
self . _vmrun_path = None
2016-10-19 12:40:16 +03:00
self . _host_type = None
2015-05-22 06:48:59 +03:00
self . _vmnets = [ ]
2021-06-08 05:26:33 +03:00
self . _vmnets_info = { }
2015-05-22 06:48:59 +03:00
self . _vmnet_start_range = 2
if sys . platform . startswith ( " win " ) :
self . _vmnet_end_range = 19
else :
self . _vmnet_end_range = 255
2015-05-01 04:05:37 +03:00
@property
def vmrun_path ( self ) :
"""
Returns the path vmrun utility .
: returns : path
"""
return self . _vmrun_path
2015-07-25 01:50:36 +03:00
@staticmethod
def _find_vmrun_registry ( regkey ) :
import winreg
try :
# default path not used, let's look in the registry
hkey = winreg . OpenKey ( winreg . HKEY_LOCAL_MACHINE , regkey )
2015-09-09 11:38:11 +03:00
install_path , _ = winreg . QueryValueEx ( hkey , " InstallPath " )
vmrun_path = os . path . join ( install_path , " vmrun.exe " )
2015-07-25 01:50:36 +03:00
winreg . CloseKey ( hkey )
if os . path . exists ( vmrun_path ) :
return vmrun_path
except OSError :
pass
return None
2015-05-01 04:05:37 +03:00
def find_vmrun ( self ) :
2015-05-11 02:21:31 +03:00
"""
Searches for vmrun .
: returns : path to vmrun
"""
2015-05-01 04:05:37 +03:00
# look for vmrun
vmrun_path = self . config . get_section_config ( " VMware " ) . get ( " vmrun_path " )
if not vmrun_path :
if sys . platform . startswith ( " win " ) :
2015-06-18 02:05:58 +03:00
vmrun_path = shutil . which ( " vmrun " )
if vmrun_path is None :
2016-02-04 04:08:41 +02:00
# look for vmrun.exe using the VMware Workstation directory listed in the registry
vmrun_path = self . _find_vmrun_registry ( r " SOFTWARE \ Wow6432Node \ VMware, Inc. \ VMware Workstation " )
2015-07-25 01:50:36 +03:00
if vmrun_path is None :
2016-02-04 04:08:41 +02:00
# look for vmrun.exe using the VIX directory listed in the registry
vmrun_path = self . _find_vmrun_registry ( r " SOFTWARE \ Wow6432Node \ VMware, Inc. \ VMware VIX " )
2015-05-01 04:05:37 +03:00
elif sys . platform . startswith ( " darwin " ) :
vmrun_path = " /Applications/VMware Fusion.app/Contents/Library/vmrun "
else :
2016-08-29 12:27:35 +03:00
vmrun_path = " vmrun "
2016-10-31 12:20:35 +02:00
if vmrun_path and not os . path . isabs ( vmrun_path ) :
2016-08-29 12:27:35 +03:00
vmrun_path = shutil . which ( vmrun_path )
2015-05-01 04:05:37 +03:00
if not vmrun_path :
2016-04-26 22:12:42 +03:00
raise VMwareError ( " Could not find VMware vmrun, please make sure it is installed " )
2015-05-01 04:05:37 +03:00
if not os . path . isfile ( vmrun_path ) :
raise VMwareError ( " vmrun {} is not accessible " . format ( vmrun_path ) )
if not os . access ( vmrun_path , os . X_OK ) :
raise VMwareError ( " vmrun is not executable " )
2016-05-17 10:28:05 +03:00
if os . path . basename ( vmrun_path ) . lower ( ) not in [ " vmrun " , " vmrun.exe " ] :
2015-05-01 04:05:37 +03:00
raise VMwareError ( " Invalid vmrun executable name {} " . format ( os . path . basename ( vmrun_path ) ) )
self . _vmrun_path = vmrun_path
return vmrun_path
2015-08-01 22:49:02 +03:00
@staticmethod
def _find_vmware_version_registry ( regkey ) :
import winreg
version = None
try :
# default path not used, let's look in the registry
hkey = winreg . OpenKey ( winreg . HKEY_LOCAL_MACHINE , regkey )
version , _ = winreg . QueryValueEx ( hkey , " ProductVersion " )
winreg . CloseKey ( hkey )
except OSError :
pass
if version is not None :
2019-01-17 13:01:58 +02:00
match = re . search ( r " ([0-9]+) \ . " , version )
2015-08-01 22:49:02 +03:00
if match :
version = match . group ( 1 )
return version
2018-10-15 13:05:49 +03:00
async def _check_vmware_player_requirements ( self , player_version ) :
2016-04-26 22:12:42 +03:00
"""
Check minimum requirements to use VMware Player .
VIX 1.13 was the release for Player 6.
VIX 1.14 was the release for Player 7.
VIX 1.15 was the release for Workstation Player 12.
2018-10-02 12:22:32 +03:00
VIX 1.17 was the release for Workstation Player 14.
2016-04-26 22:12:42 +03:00
: param player_version : VMware Player major version .
"""
player_version = int ( player_version )
if player_version < 6 :
raise VMwareError ( " Using VMware Player requires version 6 or above " )
elif player_version == 6 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.13.0 " )
2016-04-26 22:12:42 +03:00
elif player_version == 7 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.14.0 " )
2016-04-26 22:12:42 +03:00
elif player_version > = 12 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.15.0 " )
2018-10-02 12:22:32 +03:00
elif player_version > = 14 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.17.0 " )
2016-10-19 12:40:16 +03:00
self . _host_type = " player "
2016-04-26 22:12:42 +03:00
2018-10-15 13:05:49 +03:00
async def _check_vmware_workstation_requirements ( self , ws_version ) :
2016-04-26 22:12:42 +03:00
"""
Check minimum requirements to use VMware Workstation .
VIX 1.13 was the release for Workstation 10.
VIX 1.14 was the release for Workstation 11.
VIX 1.15 was the release for Workstation Pro 12.
2018-10-02 12:22:32 +03:00
VIX 1.17 was the release for Workstation Pro 14.
2016-04-26 22:12:42 +03:00
: param ws_version : VMware Workstation major version .
"""
ws_version = int ( ws_version )
if ws_version < 10 :
raise VMwareError ( " Using VMware Workstation requires version 10 or above " )
elif ws_version == 10 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.13.0 " )
2016-04-26 22:12:42 +03:00
elif ws_version == 11 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.14.0 " )
2016-04-26 22:12:42 +03:00
elif ws_version > = 12 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.15.0 " )
2018-10-02 12:22:32 +03:00
elif ws_version > = 14 :
2018-10-15 13:05:49 +03:00
await self . check_vmrun_version ( minimum_required_version = " 1.17.0 " )
2016-10-19 12:40:16 +03:00
self . _host_type = " ws "
2016-04-26 22:12:42 +03:00
2018-10-15 13:05:49 +03:00
async def check_vmware_version ( self ) :
2015-08-01 22:49:02 +03:00
"""
Check VMware version
"""
if sys . platform . startswith ( " win " ) :
# look for vmrun.exe using the directory listed in the registry
ws_version = self . _find_vmware_version_registry ( r " SOFTWARE \ Wow6432Node \ VMware, Inc. \ VMware Workstation " )
if ws_version is None :
player_version = self . _find_vmware_version_registry ( r " SOFTWARE \ Wow6432Node \ VMware, Inc. \ VMware Player " )
if player_version :
log . debug ( " VMware Player version {} detected " . format ( player_version ) )
2018-10-15 13:05:49 +03:00
await self . _check_vmware_player_requirements ( player_version )
2015-08-01 22:49:02 +03:00
else :
log . warning ( " Could not find VMware version " )
2016-10-26 11:36:34 +03:00
self . _host_type = " ws "
2015-08-01 22:49:02 +03:00
else :
log . debug ( " VMware Workstation version {} detected " . format ( ws_version ) )
2018-10-15 13:05:49 +03:00
await self . _check_vmware_workstation_requirements ( ws_version )
2015-08-01 22:49:02 +03:00
else :
if sys . platform . startswith ( " darwin " ) :
2016-02-20 19:59:06 +02:00
if not os . path . isdir ( " /Applications/VMware Fusion.app " ) :
2016-02-25 13:15:38 +02:00
raise VMwareError ( " VMware Fusion is not installed in the standard location /Applications/VMware Fusion.app " )
2016-10-19 12:40:16 +03:00
self . _host_type = " fusion "
2016-02-25 13:15:38 +02:00
return # FIXME: no version checking on Mac OS X but we support all versions of fusion
2015-08-01 22:49:02 +03:00
2015-10-13 01:27:31 +03:00
vmware_path = VMware . _get_linux_vmware_binary ( )
2015-08-07 08:05:10 +03:00
if vmware_path is None :
2015-10-13 01:27:31 +03:00
raise VMwareError ( " VMware is not installed (vmware or vmplayer executable could not be found in $PATH) " )
2015-08-07 08:05:10 +03:00
2015-08-01 22:49:02 +03:00
try :
2018-10-15 13:05:49 +03:00
output = await subprocess_check_output ( vmware_path , " -v " )
2019-01-17 13:01:58 +02:00
match = re . search ( r " VMware Workstation ([0-9]+) \ . " , output )
2015-08-01 22:49:02 +03:00
version = None
if match :
2016-04-26 22:12:42 +03:00
# VMware Workstation has been detected
2015-08-01 22:49:02 +03:00
version = match . group ( 1 )
log . debug ( " VMware Workstation version {} detected " . format ( version ) )
2018-10-15 13:05:49 +03:00
await self . _check_vmware_workstation_requirements ( version )
2019-01-17 13:01:58 +02:00
match = re . search ( r " VMware Player ([0-9]+) \ . " , output )
2015-08-01 22:49:02 +03:00
if match :
2016-04-26 22:12:42 +03:00
# VMware Player has been detected
2015-08-01 22:49:02 +03:00
version = match . group ( 1 )
log . debug ( " VMware Player version {} detected " . format ( version ) )
2018-10-15 13:05:49 +03:00
await self . _check_vmware_player_requirements ( version )
2015-08-01 22:49:02 +03:00
if version is None :
2016-02-01 15:25:15 +02:00
log . warning ( " Could not find VMware version. Output of VMware: {} " . format ( output ) )
raise VMwareError ( " Could not find VMware version. Output of VMware: {} " . format ( output ) )
2015-08-01 22:49:02 +03:00
except ( OSError , subprocess . SubprocessError ) as e :
log . error ( " Error while looking for the VMware version: {} " . format ( e ) )
2016-02-01 15:25:15 +02:00
raise VMwareError ( " Error while looking for the VMware version: {} " . format ( e ) )
2015-08-01 22:49:02 +03:00
2015-05-22 06:48:59 +03:00
@staticmethod
2015-09-20 22:19:57 +03:00
def _get_vmnet_interfaces_registry ( ) :
import winreg
vmnet_interfaces = [ ]
regkey = r " SOFTWARE \ Wow6432Node \ VMware, Inc. \ VMnetLib \ VMnetConfig "
try :
hkey = winreg . OpenKey ( winreg . HKEY_LOCAL_MACHINE , regkey )
for index in range ( winreg . QueryInfoKey ( hkey ) [ 0 ] ) :
vmnet = winreg . EnumKey ( hkey , index )
hkeyvmnet = winreg . OpenKey ( hkey , vmnet )
if winreg . QueryInfoKey ( hkeyvmnet ) [ 1 ] :
# the vmnet has not been configure if the key has no values
vmnet = vmnet . replace ( " vm " , " VM " )
2018-04-17 06:47:25 +03:00
if vmnet not in ( " VMnet0 " , " VMnet1 " , " VMnet8 " ) :
2015-09-20 22:19:57 +03:00
vmnet_interfaces . append ( vmnet )
winreg . CloseKey ( hkeyvmnet )
winreg . CloseKey ( hkey )
except OSError as e :
raise VMwareError ( " Could not read registry key {} : {} " . format ( regkey , e ) )
return vmnet_interfaces
@staticmethod
def _get_vmnet_interfaces ( ) :
if sys . platform . startswith ( " win " ) :
return VMware . _get_vmnet_interfaces_registry ( )
elif sys . platform . startswith ( " darwin " ) :
vmware_networking_file = " /Library/Preferences/VMware Fusion/networking "
else :
# location on Linux
vmware_networking_file = " /etc/vmware/networking "
2021-06-08 05:26:33 +03:00
vmnet_interfaces = { }
2015-09-20 22:19:57 +03:00
try :
with open ( vmware_networking_file , " r " , encoding = " utf-8 " ) as f :
for line in f . read ( ) . splitlines ( ) :
2019-01-17 13:01:58 +02:00
match = re . search ( r " VNET_([0-9]+)_VIRTUAL_ADAPTER " , line )
2015-09-20 22:19:57 +03:00
if match :
vmnet = " vmnet {} " . format ( match . group ( 1 ) )
2018-04-17 06:47:25 +03:00
if vmnet not in ( " vmnet0 " , " vmnet1 " , " vmnet8 " ) :
2021-06-08 05:26:33 +03:00
vmnet_interfaces [ vmnet ] = { }
with open ( vmware_networking_file , " r " , encoding = " utf-8 " ) as f :
for line in f . read ( ) . splitlines ( ) :
match = re . search ( r " VNET_([0-9]+)_HOSTONLY_SUBNET \ s+(.*) " , line )
if match :
vmnet = " vmnet {} " . format ( match . group ( 1 ) )
if vmnet in vmnet_interfaces . keys ( ) :
vmnet_interfaces [ vmnet ] [ " subnet " ] = match . group ( 2 )
match = re . search ( r " VNET_([0-9]+)_HOSTONLY_NETMASK \ s+(.*) " , line )
if match :
vmnet = " vmnet {} " . format ( match . group ( 1 ) )
if vmnet in vmnet_interfaces . keys ( ) :
vmnet_interfaces [ vmnet ] [ " netmask " ] = match . group ( 2 )
2015-09-20 22:19:57 +03:00
except OSError as e :
raise VMwareError ( " Cannot open {} : {} " . format ( vmware_networking_file , e ) )
return vmnet_interfaces
@staticmethod
def _get_vmnet_interfaces_ubridge ( ) :
2015-05-22 06:48:59 +03:00
vmnet_interfaces = [ ]
for interface in interfaces ( ) :
if sys . platform . startswith ( " win " ) :
if " netcard " in interface :
windows_name = interface [ " netcard " ]
else :
windows_name = interface [ " name " ]
2019-01-17 13:01:58 +02:00
match = re . search ( r " (VMnet[0-9]+) " , windows_name )
2015-05-22 06:48:59 +03:00
if match :
vmnet = match . group ( 1 )
2018-04-17 06:47:25 +03:00
if vmnet not in ( " VMnet0 " , " VMnet1 " , " VMnet8 " ) :
2015-05-22 06:48:59 +03:00
vmnet_interfaces . append ( vmnet )
elif interface [ " name " ] . startswith ( " vmnet " ) :
vmnet = interface [ " name " ]
2018-04-17 06:47:25 +03:00
if vmnet not in ( " vmnet0 " , " vmnet1 " , " vmnet8 " ) :
2015-05-22 06:48:59 +03:00
vmnet_interfaces . append ( interface [ " name " ] )
return vmnet_interfaces
def is_managed_vmnet ( self , vmnet ) :
self . _vmnet_start_range = self . config . get_section_config ( " VMware " ) . getint ( " vmnet_start_range " , self . _vmnet_start_range )
self . _vmnet_end_range = self . config . get_section_config ( " VMware " ) . getint ( " vmnet_end_range " , self . _vmnet_end_range )
2019-01-17 13:01:58 +02:00
match = re . search ( r " vmnet([0-9]+)$ " , vmnet , re . IGNORECASE )
2015-05-22 06:48:59 +03:00
if match :
vmnet_number = match . group ( 1 )
if self . _vmnet_start_range < = int ( vmnet_number ) < = self . _vmnet_end_range :
return True
return False
def allocate_vmnet ( self ) :
if not self . _vmnets :
2015-09-17 16:18:55 +03:00
raise VMwareError ( " No VMnet interface available between vmnet {} and vmnet {} . Go to preferences VMware / Network / Configure to add more interfaces. " . format ( self . _vmnet_start_range , self . _vmnet_end_range ) )
2015-05-22 06:48:59 +03:00
return self . _vmnets . pop ( 0 )
2021-06-08 05:26:33 +03:00
def find_bridge_interface ( self , vmnet_interface ) :
"""
Find the bridge interface that is used for the vmnet interface in VMware .
"""
if vmnet_interface in self . _vmnets_info . keys ( ) :
subnet = self . _vmnets_info [ vmnet_interface ] . get ( " subnet " , None )
netmask = self . _vmnets_info [ vmnet_interface ] . get ( " netmask " , None )
if subnet and netmask :
for interface in interfaces ( ) :
try :
network = ipaddress . ip_network ( f " { subnet } / { netmask } " )
ip = ipaddress . ip_address ( interface [ " ip_address " ] )
except ValueError :
continue
if ip in network :
return interface [ " name " ]
return None
2015-09-20 22:19:57 +03:00
def refresh_vmnet_list ( self , ubridge = True ) :
2015-05-22 06:48:59 +03:00
2015-09-20 22:19:57 +03:00
if ubridge :
# VMnet host adapters must be present when uBridge is used
vmnet_interfaces = self . _get_vmnet_interfaces_ubridge ( )
else :
vmnet_interfaces = self . _get_vmnet_interfaces ( )
2021-06-14 06:46:11 +03:00
self . _vmnets_info = vmnet_interfaces . copy ( )
2022-04-20 14:26:45 +03:00
vmnet_interfaces = list ( vmnet_interfaces . keys ( ) )
2015-05-22 06:48:59 +03:00
# remove vmnets already in use
2016-05-11 20:35:36 +03:00
for vmware_vm in self . _nodes . values ( ) :
for used_vmnet in vmware_vm . vmnets :
2015-05-22 06:48:59 +03:00
if used_vmnet in vmnet_interfaces :
log . debug ( " {} is already in use " . format ( used_vmnet ) )
vmnet_interfaces . remove ( used_vmnet )
# remove vmnets that are not managed
for vmnet in vmnet_interfaces . copy ( ) :
if vmnet in vmnet_interfaces and self . is_managed_vmnet ( vmnet ) is False :
vmnet_interfaces . remove ( vmnet )
self . _vmnets = vmnet_interfaces
2015-05-16 04:09:48 +03:00
@property
def host_type ( self ) :
"""
Returns the VMware host type .
player = VMware player
ws = VMware Workstation
fusion = VMware Fusion
: returns : host type ( string )
"""
2016-10-19 12:40:16 +03:00
return self . _host_type
2015-05-16 04:09:48 +03:00
2018-10-15 13:05:49 +03:00
async def execute ( self , subcommand , args , timeout = 120 , log_level = logging . INFO ) :
2016-11-28 21:00:20 +02:00
trial = 2
2016-12-01 10:24:52 +02:00
while True :
2016-11-28 21:00:20 +02:00
try :
2018-10-15 13:05:49 +03:00
return ( await self . _execute ( subcommand , args , timeout = timeout , log_level = log_level ) )
2016-11-28 21:00:20 +02:00
except VMwareError as e :
# We can fail to detect that it's VMware player instead of Workstation (due to marketing change Player is now Player Workstation)
if self . host_type == " ws " and " VIX_SERVICEPROVIDER_VMWARE_WORKSTATION " in str ( e ) :
self . _host_type = " player "
2018-10-15 13:05:49 +03:00
return ( await self . _execute ( subcommand , args , timeout = timeout , log_level = log_level ) )
2016-11-28 21:00:20 +02:00
else :
if trial < = 0 :
raise e
trial - = 1
2018-10-15 13:05:49 +03:00
await asyncio . sleep ( 0.5 )
2015-05-01 04:05:37 +03:00
2018-10-15 13:05:49 +03:00
async def _execute ( self , subcommand , args , timeout = 120 , log_level = logging . INFO ) :
2016-11-04 20:39:17 +02:00
if self . host_type is None :
2018-10-15 13:05:49 +03:00
await self . check_vmware_version ( )
2015-05-31 05:26:38 +03:00
2016-11-04 20:39:17 +02:00
vmrun_path = self . vmrun_path
if not vmrun_path :
vmrun_path = self . find_vmrun ( )
2015-05-31 05:26:38 +03:00
2016-11-04 20:39:17 +02:00
command = [ vmrun_path , " -T " , self . host_type , subcommand ]
command . extend ( args )
2019-10-09 12:02:30 +03:00
command_string = " " . join ( [ shlex_quote ( c ) for c in command ] )
2016-11-11 12:41:16 +02:00
log . log ( log_level , " Executing vmrun with command: {} " . format ( command_string ) )
2016-11-04 20:39:17 +02:00
try :
2018-10-15 13:05:49 +03:00
process = await asyncio . create_subprocess_exec ( * command , stdout = asyncio . subprocess . PIPE , stderr = asyncio . subprocess . PIPE )
2016-11-04 20:39:17 +02:00
except ( OSError , subprocess . SubprocessError ) as e :
raise VMwareError ( " Could not execute vmrun: {} " . format ( e ) )
2015-05-31 05:26:38 +03:00
2016-11-04 20:39:17 +02:00
try :
2018-10-15 13:05:49 +03:00
stdout_data , _ = await asyncio . wait_for ( process . communicate ( ) , timeout = timeout )
2016-11-04 20:39:17 +02:00
except asyncio . TimeoutError :
2018-03-12 08:38:50 +02:00
raise VMwareError ( " vmrun has timed out after {} seconds! \n Try to run {} in a terminal to see more details. \n \n Make sure GNS3 and VMware run under the same user and whitelist vmrun.exe in your antivirus. " . format ( timeout , command_string ) )
2016-11-04 20:39:17 +02:00
if process . returncode :
# vmrun print errors on stdout
vmrun_error = stdout_data . decode ( " utf-8 " , errors = " ignore " )
2018-03-12 08:38:50 +02:00
raise VMwareError ( " vmrun has returned an error: {} \n Try to run {} in a terminal to see more details. \n And make sure GNS3 and VMware run under the same user. " . format ( vmrun_error , command_string ) )
2016-11-04 20:39:17 +02:00
return stdout_data . decode ( " utf-8 " , errors = " ignore " ) . splitlines ( )
2015-05-02 03:47:46 +03:00
2018-10-15 13:05:49 +03:00
async def check_vmrun_version ( self , minimum_required_version = " 1.13.0 " ) :
2016-04-26 22:12:42 +03:00
"""
Checks the vmrun version .
VMware VIX library version must be at least > = 1.13 by default
VIX 1.13 was the release for VMware Fusion 6 , Workstation 10 , and Player 6.
VIX 1.14 was the release for VMware Fusion 7 , Workstation 11 and Player 7.
VIX 1.15 was the release for VMware Fusion 8 , Workstation Pro 12 and Workstation Player 12.
: param required_version : required vmrun version number
"""
2016-02-04 03:15:33 +02:00
2016-11-04 20:39:17 +02:00
vmrun_path = self . vmrun_path
if not vmrun_path :
vmrun_path = self . find_vmrun ( )
2016-02-04 03:15:33 +02:00
2016-11-04 20:39:17 +02:00
try :
2018-10-15 13:05:49 +03:00
output = await subprocess_check_output ( vmrun_path )
2019-01-17 13:01:58 +02:00
match = re . search ( r " vmrun version ([0-9 \ .]+) " , output )
2016-11-04 20:39:17 +02:00
version = None
if match :
version = match . group ( 1 )
log . debug ( " VMware vmrun version {} detected, minimum required: {} " . format ( version , minimum_required_version ) )
if parse_version ( version ) < parse_version ( minimum_required_version ) :
raise VMwareError ( " VMware vmrun executable version must be >= version {} " . format ( minimum_required_version ) )
if version is None :
log . warning ( " Could not find VMware vmrun version. Output: {} " . format ( output ) )
raise VMwareError ( " Could not find VMware vmrun version. Output: {} " . format ( output ) )
except ( OSError , subprocess . SubprocessError ) as e :
log . error ( " Error while looking for the VMware vmrun version: {} " . format ( e ) )
raise VMwareError ( " Error while looking for the VMware vmrun version: {} " . format ( e ) )
2016-02-04 03:15:33 +02:00
2018-10-15 13:05:49 +03:00
async def remove_from_vmware_inventory ( self , vmx_path ) :
2016-01-26 23:23:01 +02:00
"""
Removes a linked clone from the VMware inventory file .
: param vmx_path : path of the linked clone VMX file
"""
2018-10-15 13:05:49 +03:00
async with self . _vmware_inventory_lock :
2016-01-26 23:23:01 +02:00
inventory_path = self . get_vmware_inventory_path ( )
if os . path . exists ( inventory_path ) :
try :
inventory_pairs = self . parse_vmware_file ( inventory_path )
except OSError as e :
log . warning ( ' Could not read VMware inventory file " {} " : {} ' . format ( inventory_path , e ) )
return
vmlist_entry = None
for name , value in inventory_pairs . items ( ) :
if value == vmx_path :
vmlist_entry = name . split ( " . " , 1 ) [ 0 ]
break
if vmlist_entry is not None :
2016-02-10 20:26:40 +02:00
for name in inventory_pairs . copy ( ) . keys ( ) :
2016-01-26 23:23:01 +02:00
if name . startswith ( vmlist_entry ) :
del inventory_pairs [ name ]
try :
self . write_vmware_file ( inventory_path , inventory_pairs )
except OSError as e :
raise VMwareError ( ' Could not write VMware inventory file " {} " : {} ' . format ( inventory_path , e ) )
2015-05-11 02:21:31 +03:00
@staticmethod
2015-05-15 05:11:57 +03:00
def parse_vmware_file ( path ) :
2015-05-11 02:21:31 +03:00
"""
Parses a VMware file ( VMX , preferences or inventory ) .
: param path : path to the VMware file
: returns : dict
"""
2015-05-21 04:05:26 +03:00
pairs = OrderedDict ( )
2015-07-13 05:58:58 +03:00
encoding = " utf-8 "
# get the first line to read the .encoding value
2015-07-28 01:29:02 +03:00
with open ( path , " rb " ) as f :
line = f . readline ( ) . decode ( encoding , errors = " ignore " )
2015-07-13 05:58:58 +03:00
if line . startswith ( " #! " ) :
# skip the shebang
2015-07-28 01:29:02 +03:00
line = f . readline ( ) . decode ( encoding , errors = " ignore " )
2015-07-13 05:58:58 +03:00
try :
key , value = line . split ( ' = ' , 1 )
if key . strip ( ) . lower ( ) == " .encoding " :
file_encoding = value . strip ( ' " ' )
try :
codecs . lookup ( file_encoding )
encoding = file_encoding
except LookupError :
log . warning ( " Invalid file encoding detected in ' {} ' : {} " . format ( path , file_encoding ) )
except ValueError :
log . warning ( " Couldn ' t find file encoding in {} , using {} ... " . format ( path , encoding ) )
# read the file with the correct encoding
with open ( path , encoding = encoding , errors = " ignore " ) as f :
2015-05-11 02:21:31 +03:00
for line in f . read ( ) . splitlines ( ) :
try :
key , value = line . split ( ' = ' , 1 )
2015-06-18 17:28:13 +03:00
pairs [ key . strip ( ) . lower ( ) ] = value . strip ( ' " ' )
2015-05-11 02:21:31 +03:00
except ValueError :
continue
return pairs
2015-05-31 05:26:38 +03:00
@staticmethod
def write_vmware_file ( path , pairs ) :
"""
Write a VMware file ( excepting VMX file ) .
: param path : path to the VMware file
: param pairs : settings to write
"""
2015-07-13 05:58:58 +03:00
encoding = " utf-8 "
if " .encoding " in pairs :
file_encoding = pairs [ " .encoding " ]
try :
codecs . lookup ( file_encoding )
encoding = file_encoding
except LookupError :
log . warning ( " Invalid file encoding detected in ' {} ' : {} " . format ( path , file_encoding ) )
with open ( path , " w " , encoding = encoding , errors = " ignore " ) as f :
2015-05-31 05:26:38 +03:00
for key , value in pairs . items ( ) :
entry = ' {} = " {} " \n ' . format ( key , value )
f . write ( entry )
2015-05-21 04:05:26 +03:00
@staticmethod
def write_vmx_file ( path , pairs ) :
"""
Write a VMware VMX file .
: param path : path to the VMX file
: param pairs : settings to write
"""
2015-07-13 05:58:58 +03:00
encoding = " utf-8 "
if " .encoding " in pairs :
file_encoding = pairs [ " .encoding " ]
try :
codecs . lookup ( file_encoding )
encoding = file_encoding
except LookupError :
log . warning ( " Invalid file encoding detected in ' {} ' : {} " . format ( path , file_encoding ) )
with open ( path , " w " , encoding = encoding , errors = " ignore " ) as f :
2015-05-21 04:05:26 +03:00
if sys . platform . startswith ( " linux " ) :
# write the shebang on the first line on Linux
2015-10-02 16:04:42 +03:00
vmware_path = VMware . _get_linux_vmware_binary ( )
2015-05-21 04:05:26 +03:00
if vmware_path :
f . write ( " #! {} \n " . format ( vmware_path ) )
for key , value in pairs . items ( ) :
entry = ' {} = " {} " \n ' . format ( key , value )
f . write ( entry )
2015-05-08 01:50:37 +03:00
def _get_vms_from_inventory ( self , inventory_path ) :
2015-05-11 02:21:31 +03:00
"""
Searches for VMs by parsing a VMware inventory file .
: param inventory_path : path to the inventory file
: returns : list of VMs
"""
2015-05-02 03:47:46 +03:00
2015-05-08 01:50:37 +03:00
vm_entries = { }
2016-05-11 20:35:36 +03:00
vmware_vms = [ ]
2017-10-22 13:33:21 +03:00
log . info ( ' Searching for VMware VMs in inventory file " {} " ' . format ( inventory_path ) )
2015-05-08 01:50:37 +03:00
try :
2015-05-15 05:11:57 +03:00
pairs = self . parse_vmware_file ( inventory_path )
2015-05-11 02:21:31 +03:00
for key , value in pairs . items ( ) :
if key . startswith ( " vmlist " ) :
2015-05-08 01:50:37 +03:00
try :
2015-05-11 02:21:31 +03:00
vm_entry , variable_name = key . split ( ' . ' , 1 )
2015-05-08 01:50:37 +03:00
except ValueError :
continue
2015-06-18 17:28:13 +03:00
if vm_entry not in vm_entries :
2015-05-11 02:21:31 +03:00
vm_entries [ vm_entry ] = { }
vm_entries [ vm_entry ] [ variable_name . strip ( ) ] = value
2015-05-08 01:50:37 +03:00
except OSError as e :
log . warning ( " Could not read VMware inventory file {} : {} " . format ( inventory_path , e ) )
for vm_settings in vm_entries . values ( ) :
2015-06-18 17:28:13 +03:00
if " displayname " in vm_settings and " config " in vm_settings :
2017-04-11 16:05:31 +03:00
if os . path . exists ( vm_settings [ " config " ] ) :
log . debug ( ' Found VM named " {} " with VMX file " {} " ' . format ( vm_settings [ " displayname " ] , vm_settings [ " config " ] ) )
vmware_vms . append ( { " vmname " : vm_settings [ " displayname " ] , " vmx_path " : vm_settings [ " config " ] } )
2016-05-11 20:35:36 +03:00
return vmware_vms
2015-05-08 01:50:37 +03:00
2015-05-11 02:21:31 +03:00
def _get_vms_from_directory ( self , directory ) :
"""
Searches for VMs in a given directory .
: param directory : path to the directory
: returns : list of VMs
"""
2015-05-08 01:50:37 +03:00
2016-05-11 20:35:36 +03:00
vmware_vms = [ ]
2017-10-22 13:33:21 +03:00
log . info ( ' Searching for VMware VMs in directory " {} " ' . format ( directory ) )
2015-05-11 02:21:31 +03:00
for path , _ , filenames in os . walk ( directory ) :
2015-05-02 03:47:46 +03:00
for filename in filenames :
if os . path . splitext ( filename ) [ 1 ] == " .vmx " :
vmx_path = os . path . join ( path , filename )
2015-05-11 02:21:31 +03:00
log . debug ( ' Reading VMware VMX file " {} " ' . format ( vmx_path ) )
2015-05-02 03:47:46 +03:00
try :
2015-05-15 05:11:57 +03:00
pairs = self . parse_vmware_file ( vmx_path )
2015-06-18 17:28:13 +03:00
if " displayname " in pairs :
log . debug ( ' Found VM named " {} " ' . format ( pairs [ " displayname " ] ) )
2016-05-11 20:35:36 +03:00
vmware_vms . append ( { " vmname " : pairs [ " displayname " ] , " vmx_path " : vmx_path } )
2015-05-08 01:50:37 +03:00
except OSError as e :
2015-05-11 02:21:31 +03:00
log . warning ( ' Could not read VMware VMX file " {} " : {} ' . format ( vmx_path , e ) )
2015-05-02 03:47:46 +03:00
continue
2016-05-11 20:35:36 +03:00
return vmware_vms
2015-05-08 01:50:37 +03:00
2015-05-31 05:26:38 +03:00
@staticmethod
def get_vmware_inventory_path ( ) :
2015-05-08 01:50:37 +03:00
"""
2015-05-31 05:26:38 +03:00
Returns VMware inventory file path .
: returns : path to the inventory file
2015-05-08 01:50:37 +03:00
"""
if sys . platform . startswith ( " win " ) :
2015-05-31 05:26:38 +03:00
return os . path . expandvars ( r " % APPDATA % \ Vmware \ Inventory.vmls " )
2015-05-08 01:50:37 +03:00
elif sys . platform . startswith ( " darwin " ) :
2015-09-17 12:04:24 +03:00
return os . path . expanduser ( " ~/Library/Application Support/VMware Fusion/vmInventory " )
2015-05-08 01:50:37 +03:00
else :
2015-05-31 05:26:38 +03:00
return os . path . expanduser ( " ~/.vmware/inventory.vmls " )
2015-05-08 01:50:37 +03:00
2015-05-31 05:26:38 +03:00
@staticmethod
def get_vmware_preferences_path ( ) :
"""
Returns VMware preferences file path .
: returns : path to the preferences file
"""
if sys . platform . startswith ( " win " ) :
return os . path . expandvars ( r " % APPDATA % \ VMware \ preferences.ini " )
elif sys . platform . startswith ( " darwin " ) :
return os . path . expanduser ( " ~/Library/Preferences/VMware Fusion/preferences " )
else :
2015-06-18 17:28:13 +03:00
return os . path . expanduser ( " ~/.vmware/preferences " )
2015-05-31 05:26:38 +03:00
@staticmethod
2017-10-22 13:33:21 +03:00
def get_vmware_default_vm_paths ( ) :
2015-05-31 05:26:38 +03:00
"""
2017-10-22 13:33:21 +03:00
Returns VMware default VM directory paths .
2015-05-31 05:26:38 +03:00
: returns : path to the default VM directory
"""
if sys . platform . startswith ( " win " ) :
2016-03-29 04:37:34 +03:00
import ctypes
2016-04-28 17:35:18 +03:00
import ctypes . wintypes
2016-03-29 04:37:34 +03:00
path = ctypes . create_unicode_buffer ( ctypes . wintypes . MAX_PATH )
ctypes . windll . shell32 . SHGetFolderPathW ( None , 5 , None , 0 , path )
documents_folder = path . value
2017-10-22 13:33:21 +03:00
return [ ' {} \ My Virtual Machines ' . format ( documents_folder ) , ' {} \ Virtual Machines ' . format ( documents_folder ) ]
2015-05-31 05:26:38 +03:00
elif sys . platform . startswith ( " darwin " ) :
2017-10-22 13:33:21 +03:00
return [ os . path . expanduser ( " ~/Documents/Virtual Machines.localized " ) ]
2015-05-31 05:26:38 +03:00
else :
2017-10-22 13:33:21 +03:00
return [ os . path . expanduser ( " ~/vmware " ) ]
2015-05-31 05:26:38 +03:00
2018-10-15 13:05:49 +03:00
async def list_vms ( self ) :
2015-05-31 05:26:38 +03:00
"""
Gets VMware VM list .
"""
2015-08-01 22:49:02 +03:00
# check for the right VMware version
2018-10-15 13:05:49 +03:00
await self . check_vmware_version ( )
2017-10-18 11:27:03 +03:00
vmware_vms = [ ]
2015-05-31 05:26:38 +03:00
inventory_path = self . get_vmware_inventory_path ( )
2016-04-13 10:38:58 +03:00
if os . path . exists ( inventory_path ) and self . host_type != " player " :
2016-03-19 23:41:26 +02:00
# inventory may exist for VMware player if VMware workstation has been previously installed
2017-10-18 11:27:03 +03:00
vmware_vms = self . _get_vms_from_inventory ( inventory_path )
if not vmware_vms :
2017-10-22 13:33:21 +03:00
# backup methods when no VMware inventory file exists or for VMware player which has no inventory file
2015-05-31 05:26:38 +03:00
vmware_preferences_path = self . get_vmware_preferences_path ( )
2016-03-19 23:41:26 +02:00
pairs = { }
2015-05-11 02:21:31 +03:00
if os . path . exists ( vmware_preferences_path ) :
# the default vm path may be present in VMware preferences file.
try :
2015-05-15 05:11:57 +03:00
pairs = self . parse_vmware_file ( vmware_preferences_path )
2015-05-11 02:21:31 +03:00
except OSError as e :
log . warning ( ' Could not read VMware preferences file " {} " : {} ' . format ( vmware_preferences_path , e ) )
2016-03-19 23:41:26 +02:00
if " prefvmx.defaultvmpath " in pairs :
default_vm_path = pairs [ " prefvmx.defaultvmpath " ]
2017-10-22 13:33:21 +03:00
if not os . path . isdir ( default_vm_path ) :
raise VMwareError ( ' Could not find or access the default VM directory: " {default_vm_path} " . Please change " prefvmx.defaultvmpath= {default_vm_path} " in " {vmware_preferences_path} " ' . format ( default_vm_path = default_vm_path ,
vmware_preferences_path = vmware_preferences_path ) )
vmware_vms = self . _get_vms_from_directory ( default_vm_path )
if not vmware_vms :
# the default vm path is not in the VMware preferences file or that directory is empty
# let's search the default locations for VMs
for default_vm_path in self . get_vmware_default_vm_paths ( ) :
if os . path . isdir ( default_vm_path ) :
vmware_vms . extend ( self . _get_vms_from_directory ( default_vm_path ) )
if not vmware_vms :
log . warning ( " Could not find any VMware VM in default locations " )
# look for VMX paths in the preferences file in case not all VMs are in a default directory
2016-03-20 19:32:40 +02:00
for key , value in pairs . items ( ) :
2016-03-19 23:41:26 +02:00
m = re . match ( r ' pref.mruVM( \ d+) \ .filename ' , key )
if m :
display_name = " pref.mruVM {} .displayName " . format ( m . group ( 1 ) )
if display_name in pairs :
found = False
2016-05-11 20:35:36 +03:00
for vmware_vm in vmware_vms :
if vmware_vm [ " vmname " ] == display_name :
2016-03-19 23:41:26 +02:00
found = True
if found is False :
2016-05-11 20:35:36 +03:00
vmware_vms . append ( { " vmname " : pairs [ display_name ] , " vmx_path " : value } )
2017-10-18 11:27:03 +03:00
return vmware_vms
2015-10-02 12:04:11 +03:00
2015-10-02 16:04:42 +03:00
@staticmethod
def _get_linux_vmware_binary ( ) :
2015-10-02 12:04:11 +03:00
"""
Return the path of the vmware binary on Linux or None
"""
path = shutil . which ( " vmware " )
if path is None :
path = shutil . which ( " vmplayer " )
return path
2016-02-25 13:15:38 +02:00
if __name__ == ' __main__ ' :
loop = asyncio . get_event_loop ( )
vmware = VMware . instance ( )
2018-10-15 13:05:49 +03:00
loop . run_until_complete ( asyncio . ensure_future ( vmware . check_vmware_version ( ) ) )