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
2021-04-13 12:16:50 +03:00
2018-08-10 12:18:14 +03:00
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 :
2021-04-13 12:16:50 +03:00
raise GNS3VMError (
f " Windows 10/Windows Server 2016 or a later version is required to run Hyper-V with nested virtualization enabled (version { sys . getwindowsversion ( ) . platform_version [ 0 ] } detected) "
)
2018-08-10 12:18:14 +03:00
2021-04-13 12:16:50 +03:00
is_windows_10 = (
sys . getwindowsversion ( ) . platform_version [ 0 ] == 10 and sys . getwindowsversion ( ) . platform_version [ 1 ] == 0
)
2020-06-10 08:51:58 +03:00
if is_windows_10 and sys . getwindowsversion ( ) . platform_version [ 2 ] < 14393 :
2021-04-13 12:16:50 +03:00
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
2021-04-13 12:16:50 +03:00
2020-01-23 13:04:17 +02:00
pythoncom . CoInitialize ( )
import wmi
2021-04-13 12:16:50 +03:00
2020-01-23 13:04:17 +02:00
self . _wmi = wmi
conn = self . _wmi . WMI ( )
except self . _wmi . x_wmi as e :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Could not connect to WMI: { 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 :
2021-04-13 12:16:50 +03:00
raise GNS3VMError (
" Windows 10 (build 10.0.19640) or later is required by Hyper-V to support nested virtualization with AMD processors "
)
2020-06-10 08:51:58 +03:00
else :
2021-04-13 12:16:50 +03:00
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
2021-04-13 12:16:50 +03:00
# if not conn.Win32_Processor()[0].VirtualizationFirmwareEnabled:
2019-03-13 18:13:54 +02:00
# 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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Could not connect to WMI: { 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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Duplicate VM name found for { vm_name } " )
2018-08-10 12:18:14 +03:00
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
"""
2021-04-13 12:16:50 +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 ]
2018-08-12 11:49:48 +03:00
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 :
2021-04-13 12:16:50 +03:00
raise GNS3VMError (
f " You have allocated too many vCPUs for the GNS3 VM! (max available is { available_vcpus } vCPUs) "
)
2018-08-10 12:18:14 +03:00
try :
2021-04-13 12:16:50 +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 ) ] )
2021-04-13 12:07:58 +03:00
log . info ( f " GNS3 VM vCPU count set to { vcpus } and RAM amount set to { ram } " )
2018-08-10 12:18:14 +03:00
except Exception as e :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Could not set to { vcpus } and RAM amount set to { ram } : { e } " )
2018-08-10 12:18:14 +03:00
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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Could not list Hyper-V VMs: { e } " )
2018-08-10 12:18:14 +03:00
return vms
def _get_wmi_obj ( self , path ) :
"""
Gets the WMI object .
"""
2021-04-13 12:16:50 +03: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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Could not find Hyper-V VM { 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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Error while changing state: { job . ErrorSummaryDescription } " )
2018-08-10 12:18:14 +03:00
elif ret != 0 or ret != 32775 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Failed to change state to { state } " )
2018-08-10 12:18:14 +03:00
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 )
"""
2021-04-13 12:16:50 +03:00
wql = (
" SELECT * FROM Msvm_GuestNetworkAdapterConfiguration WHERE InstanceID like \
' Microsoft:GuestNetwork \\ "
+ self . _vm . Name
+ " % ' and ProtocolIFType > 0 "
)
2020-10-06 09:12:50 +03:00
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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Could not find Hyper-V VM { self . vmname } " )
2018-08-12 11:49:48 +03:00
2018-08-10 12:18:14 +03:00
if not self . _is_running ( ) :
2020-11-05 02:43:57 +02:00
if self . allocate_vcpus_ram :
log . info ( " Update GNS3 VM settings (CPU and RAM) " )
# set the number of vCPUs and amount of RAM
self . _set_vcpus_ram ( self . vcpus , self . ram )
2018-08-10 12:18:14 +03:00
# 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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Failed to start the GNS3 VM: { e } " )
2018-08-10 12:18:14 +03:00
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 " )
2021-04-13 12:16:50 +03:00
ports = self . _get_vm_resources ( self . _vm , " Msvm_EthernetPortAllocationSettingData " )
vnics = self . _get_vm_resources ( self . _vm , " Msvm_SyntheticEthernetPortSettingData " )
2018-08-12 11:49:48 +03:00
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
2021-04-13 12:16:50 +03:00
config = vnic . associators ( wmi_result_class = " Msvm_GuestNetworkAdapterConfiguration " )
2018-08-12 11:49:48 +03:00
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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Could not find guest IP address for { 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
2021-04-13 12:07:58 +03:00
log . info ( f " GNS3 VM IP address set to { 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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Failed to suspend the GNS3 VM: { e } " )
2018-08-10 12:18:14 +03:00
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 :
2021-04-13 12:07:58 +03:00
raise GNS3VMError ( f " Failed to stop the GNS3 VM: { e } " )
2018-08-10 12:18:14 +03:00
log . info ( " GNS3 VM has been stopped " )
self . running = False