2018-08-10 12:18:14 +03:00
#!/usr/bin/env python
#
# Copyright (C) 2018 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/>.
import sys
import logging
import asyncio
import psutil
2018-08-12 11:49:48 +03:00
import ipaddress
2018-08-10 12:18:14 +03:00
from . base_gns3_vm import BaseGNS3VM
from . gns3_vm_error import GNS3VMError
log = logging . getLogger ( __name__ )
class HyperVGNS3VM ( BaseGNS3VM ) :
_HYPERV_VM_STATE_ENABLED = 2
_HYPERV_VM_STATE_DISABLED = 3
2018-08-12 13:11:32 +03:00
_HYPERV_VM_STATE_SHUTDOWN = 4
2018-08-10 12:18:14 +03:00
_HYPERV_VM_STATE_PAUSED = 9
_WMI_JOB_STATUS_STARTED = 4096
_WMI_JOB_STATE_RUNNING = 4
_WMI_JOB_STATE_COMPLETED = 7
def __init__ ( self , controller ) :
self . _engine = " hyper-v "
super ( ) . __init__ ( controller )
self . _conn = None
self . _vm = None
self . _management = None
2020-01-23 13:04:17 +02:00
self . _wmi = None
2018-08-10 12:18:14 +03:00
def _check_requirements ( self ) :
"""
Checks if the GNS3 VM can run on Hyper - V .
"""
2019-03-12 13:17:31 +02:00
if not sys . platform . startswith ( " win " ) :
raise GNS3VMError ( " Hyper-V is only supported on Windows " )
2019-03-12 21:15:58 +02:00
if sys . getwindowsversion ( ) . platform_version [ 0 ] < 10 :
2019-03-12 17:40:37 +02:00
raise GNS3VMError ( " Windows 10/Windows Server 2016 or a later version is required to run Hyper-V with nested virtualization enabled (version {} detected) " . format ( sys . getwindowsversion ( ) . platform_version [ 0 ] ) )
2018-08-10 12:18:14 +03:00
2020-06-10 08:51:58 +03:00
is_windows_10 = sys . getwindowsversion ( ) . platform_version [ 0 ] == 10 and sys . getwindowsversion ( ) . platform_version [ 1 ] == 0
if is_windows_10 and sys . getwindowsversion ( ) . platform_version [ 2 ] < 14393 :
raise GNS3VMError ( " Hyper-V with nested virtualization is only supported on Windows 10 Anniversary Update (build 10.0.14393) or later " )
2019-03-12 21:15:58 +02:00
2018-08-12 12:49:24 +03:00
try :
2020-01-23 13:04:17 +02:00
import pythoncom
pythoncom . CoInitialize ( )
import wmi
self . _wmi = wmi
conn = self . _wmi . WMI ( )
except self . _wmi . x_wmi as e :
2018-08-12 12:49:24 +03:00
raise GNS3VMError ( " Could not connect to WMI: {} " . format ( e ) )
2018-08-10 12:18:14 +03:00
2019-03-12 13:17:31 +02:00
if not conn . Win32_ComputerSystem ( ) [ 0 ] . HypervisorPresent :
raise GNS3VMError ( " Hyper-V is not installed or activated " )
2018-08-10 12:18:14 +03:00
if conn . Win32_Processor ( ) [ 0 ] . Manufacturer != " GenuineIntel " :
2020-06-10 08:51:58 +03:00
if is_windows_10 and conn . Win32_Processor ( ) [ 0 ] . Manufacturer == " AuthenticAMD " :
if sys . getwindowsversion ( ) . platform_version [ 2 ] < 19640 :
raise GNS3VMError ( " Windows 10 (build 10.0.19640) or later is required by Hyper-V to support nested virtualization with AMD processors " )
else :
raise GNS3VMError ( " An Intel processor is required by Hyper-V to support nested virtualization on this version of Windows " )
2018-08-10 12:18:14 +03:00
2019-03-13 18:13:54 +02:00
# This is not reliable
#if not conn.Win32_Processor()[0].VirtualizationFirmwareEnabled:
# raise GNS3VMError("Nested Virtualization (VT-x) is not enabled on this system")
2018-08-10 12:18:14 +03:00
def _connect ( self ) :
"""
Connects to local host using WMI .
"""
self . _check_requirements ( )
try :
2020-01-23 13:04:17 +02:00
self . _conn = self . _wmi . WMI ( namespace = r " root \ virtualization \ v2 " )
except self . _wmi . x_wmi as e :
2018-08-12 11:49:48 +03:00
raise GNS3VMError ( " Could not connect to WMI: {} " . format ( e ) )
2018-08-10 12:18:14 +03:00
if not self . _conn . Msvm_VirtualSystemManagementService ( ) :
raise GNS3VMError ( " The Windows account running GNS3 does not have the required permissions for Hyper-V " )
self . _management = self . _conn . Msvm_VirtualSystemManagementService ( ) [ 0 ]
def _find_vm ( self , vm_name ) :
"""
Finds a Hyper - V VM .
"""
2018-08-12 12:49:24 +03:00
if self . _conn is None :
self . _connect ( )
2018-08-10 12:18:14 +03:00
vms = self . _conn . Msvm_ComputerSystem ( ElementName = vm_name )
nb_vms = len ( vms )
if nb_vms == 0 :
return None
elif nb_vms > 1 :
raise GNS3VMError ( " Duplicate VM name found for {} " . format ( vm_name ) )
else :
return vms [ 0 ]
def _is_running ( self ) :
"""
Checks if the VM is running .
"""
if self . _vm is not None and self . _vm . EnabledState == HyperVGNS3VM . _HYPERV_VM_STATE_ENABLED :
return True
return False
2018-08-12 11:49:48 +03:00
def _get_vm_setting_data ( self , vm ) :
2018-08-12 12:49:24 +03:00
"""
Gets the VM settings .
: param vm : VM instance
"""
2018-08-12 11:49:48 +03:00
vm_settings = vm . associators ( wmi_result_class = ' Msvm_VirtualSystemSettingData ' )
return [ s for s in vm_settings if s . VirtualSystemType == ' Microsoft:Hyper-V:System:Realized ' ] [ 0 ]
def _get_vm_resources ( self , vm , resource_class ) :
2018-08-12 12:49:24 +03:00
"""
Gets specific VM resource .
: param vm : VM instance
: param resource_class : resource class name
"""
2018-08-12 11:49:48 +03:00
setting_data = self . _get_vm_setting_data ( vm )
return setting_data . associators ( wmi_result_class = resource_class )
2018-08-10 12:18:14 +03:00
def _set_vcpus_ram ( self , vcpus , ram ) :
"""
Set the number of vCPU cores and amount of RAM for the GNS3 VM .
: param vcpus : number of vCPU cores
: param ram : amount of RAM
"""
available_vcpus = psutil . cpu_count ( logical = False )
if vcpus > available_vcpus :
raise GNS3VMError ( " You have allocated too many vCPUs for the GNS3 VM! (max available is {} vCPUs) " . format ( available_vcpus ) )
try :
2018-08-12 12:49:24 +03:00
mem_settings = self . _get_vm_resources ( self . _vm , ' Msvm_MemorySettingData ' ) [ 0 ]
cpu_settings = self . _get_vm_resources ( self . _vm , ' Msvm_ProcessorSettingData ' ) [ 0 ]
2018-08-10 12:18:14 +03:00
mem_settings . VirtualQuantity = ram
mem_settings . Reservation = ram
mem_settings . Limit = ram
self . _management . ModifyResourceSettings ( ResourceSettings = [ mem_settings . GetText_ ( 1 ) ] )
cpu_settings . VirtualQuantity = vcpus
cpu_settings . Reservation = vcpus
cpu_settings . Limit = 100000 # use 100% of CPU
cpu_settings . ExposeVirtualizationExtensions = True # allow the VM to use nested virtualization
self . _management . ModifyResourceSettings ( ResourceSettings = [ cpu_settings . GetText_ ( 1 ) ] )
log . info ( " GNS3 VM vCPU count set to {} and RAM amount set to {} " . format ( vcpus , ram ) )
except Exception as e :
raise GNS3VMError ( " Could not set to {} and RAM amount set to {} : {} " . format ( vcpus , ram , e ) )
2018-10-15 13:05:49 +03:00
async def list ( self ) :
2018-08-10 12:18:14 +03:00
"""
List all Hyper - V VMs
"""
2020-05-20 12:49:04 +03:00
if self . _conn is None or self . _management is None :
2018-08-12 11:49:48 +03:00
self . _connect ( )
2018-08-10 12:18:14 +03:00
vms = [ ]
try :
2020-01-21 12:03:07 +02:00
for vm in self . _conn . Msvm_ComputerSystem ( ) :
if vm . ElementName != self . _management . SystemName :
2018-08-12 11:49:48 +03:00
vms . append ( { " vmname " : vm . ElementName } )
2020-01-23 13:04:17 +02:00
except self . _wmi . x_wmi as e :
2018-08-10 12:18:14 +03:00
raise GNS3VMError ( " Could not list Hyper-V VMs: {} " . format ( e ) )
return vms
def _get_wmi_obj ( self , path ) :
"""
Gets the WMI object .
"""
2020-01-23 13:04:17 +02:00
return self . _wmi . WMI ( moniker = path . replace ( ' \\ ' , ' / ' ) )
2018-08-10 12:18:14 +03:00
2018-10-15 13:05:49 +03:00
async def _set_state ( self , state ) :
2018-08-10 12:18:14 +03:00
"""
Set the desired state of the VM
"""
2018-08-12 12:49:24 +03:00
if not self . _vm :
self . _vm = self . _find_vm ( self . vmname )
if not self . _vm :
raise GNS3VMError ( " Could not find Hyper-V VM {} " . format ( self . vmname ) )
2018-08-10 12:18:14 +03:00
job_path , ret = self . _vm . RequestStateChange ( state )
if ret == HyperVGNS3VM . _WMI_JOB_STATUS_STARTED :
job = self . _get_wmi_obj ( job_path )
while job . JobState == HyperVGNS3VM . _WMI_JOB_STATE_RUNNING :
2018-10-15 13:05:49 +03:00
await asyncio . sleep ( 0.1 )
2018-08-10 12:18:14 +03:00
job = self . _get_wmi_obj ( job_path )
if job . JobState != HyperVGNS3VM . _WMI_JOB_STATE_COMPLETED :
raise GNS3VMError ( " Error while changing state: {} " . format ( job . ErrorSummaryDescription ) )
elif ret != 0 or ret != 32775 :
raise GNS3VMError ( " Failed to change state to {} " . format ( state ) )
2020-10-06 09:12:50 +03:00
async def _is_vm_network_active ( self ) :
"""
Check if WMI is updated with VM virtual network adapters
and wait until their count becomes > 0
ProtocolIFType Unknown ( 0 )
Other ( 1 )
IPv4 ( 4096 )
IPv6 ( 4097 )
IPv4 / v6 ( 4098 )
"""
wql = " SELECT * FROM Msvm_GuestNetworkAdapterConfiguration WHERE InstanceID like \
' Microsoft:GuestNetwork \\ " + self._vm.Name + " % ' and ProtocolIFType > 0 "
nic_count = len ( self . _conn . query ( wql ) )
while nic_count == 0 :
await asyncio . sleep ( 0.1 ) # 100ms
nic_count = len ( self . _conn . query ( wql ) )
2018-10-15 13:05:49 +03:00
async def start ( self ) :
2018-08-10 12:18:14 +03:00
"""
Starts the GNS3 VM .
"""
2018-08-12 12:49:24 +03:00
self . _vm = self . _find_vm ( self . vmname )
2018-08-12 11:49:48 +03:00
if not self . _vm :
raise GNS3VMError ( " Could not find Hyper-V VM {} " . format ( self . vmname ) )
2018-08-10 12:18:14 +03:00
if not self . _is_running ( ) :
2019-10-09 09:50:00 +03:00
log . info ( " Update GNS3 VM settings (CPU and RAM) " )
2018-08-10 12:18:14 +03:00
# set the number of vCPUs and amount of RAM
self . _set_vcpus_ram ( self . vcpus , self . ram )
# start the VM
try :
2018-10-15 13:05:49 +03:00
await self . _set_state ( HyperVGNS3VM . _HYPERV_VM_STATE_ENABLED )
2018-08-10 12:18:14 +03:00
except GNS3VMError as e :
raise GNS3VMError ( " Failed to start the GNS3 VM: {} " . format ( e ) )
log . info ( " GNS3 VM has been started " )
2020-10-06 09:12:50 +03:00
# check if VM network is active
await self . _is_vm_network_active ( )
2018-08-12 11:49:48 +03:00
# Get the guest IP address
2019-03-18 10:30:59 +02:00
# LIS (Linux Integration Services) must be installed on the guest
# See https://oitibs.com/hyper-v-lis-on-ubuntu-18-04/ for details.
2018-08-12 11:49:48 +03:00
trial = 120
guest_ip_address = " "
log . info ( " Waiting for GNS3 VM IP " )
ports = self . _get_vm_resources ( self . _vm , ' Msvm_EthernetPortAllocationSettingData ' )
vnics = self . _get_vm_resources ( self . _vm , ' Msvm_SyntheticEthernetPortSettingData ' )
while True :
for port in ports :
2019-07-10 16:56:54 +03:00
try :
vnic = [ v for v in vnics if port . Parent == v . path_ ( ) ] [ 0 ]
except IndexError :
continue
2018-08-12 11:49:48 +03:00
config = vnic . associators ( wmi_result_class = ' Msvm_GuestNetworkAdapterConfiguration ' )
ip_addresses = config [ 0 ] . IPAddresses
for ip_address in ip_addresses :
# take the first valid IPv4 address
try :
ipaddress . IPv4Address ( ip_address )
guest_ip_address = ip_address
except ipaddress . AddressValueError :
continue
if len ( ip_addresses ) :
guest_ip_address = ip_addresses [ 0 ]
break
trial - = 1
if guest_ip_address :
break
elif trial == 0 :
raise GNS3VMError ( " Could not find guest IP address for {} " . format ( self . vmname ) )
2018-10-15 13:05:49 +03:00
await asyncio . sleep ( 1 )
2018-08-12 11:49:48 +03:00
self . ip_address = guest_ip_address
log . info ( " GNS3 VM IP address set to {} " . format ( guest_ip_address ) )
2018-08-10 12:18:14 +03:00
self . running = True
2018-10-15 13:05:49 +03:00
async def suspend ( self ) :
2018-08-10 12:18:14 +03:00
"""
2018-08-12 12:49:24 +03:00
Suspends the GNS3 VM .
2018-08-10 12:18:14 +03:00
"""
try :
2018-10-15 13:05:49 +03:00
await self . _set_state ( HyperVGNS3VM . _HYPERV_VM_STATE_PAUSED )
2018-08-10 12:18:14 +03:00
except GNS3VMError as e :
raise GNS3VMError ( " Failed to suspend the GNS3 VM: {} " . format ( e ) )
log . info ( " GNS3 VM has been suspended " )
self . running = False
2018-10-15 13:05:49 +03:00
async def stop ( self ) :
2018-08-10 12:18:14 +03:00
"""
Stops the GNS3 VM .
"""
try :
2018-10-15 13:05:49 +03:00
await self . _set_state ( HyperVGNS3VM . _HYPERV_VM_STATE_SHUTDOWN )
2018-08-10 12:18:14 +03:00
except GNS3VMError as e :
raise GNS3VMError ( " Failed to stop the GNS3 VM: {} " . format ( e ) )
log . info ( " GNS3 VM has been stopped " )
self . running = False