2015-02-19 10:33:25 +00:00
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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/>.
"""
2015-04-08 17:17:34 +00:00
QEMU VM management ( creates command line , processes , files etc . ) in
order to run a QEMU VM .
2015-02-19 10:33:25 +00:00
"""
import sys
import os
2015-06-10 13:49:24 +00:00
import re
2019-10-09 09:47:22 +00:00
import shlex
2017-02-07 16:04:29 +00:00
import math
2015-02-19 10:33:25 +00:00
import shutil
2020-04-15 18:50:59 +00:00
import struct
2015-02-19 15:46:57 +00:00
import asyncio
2015-03-24 04:52:02 +00:00
import socket
2015-06-03 04:33:38 +00:00
import gns3server
2017-02-07 16:04:29 +00:00
import subprocess
2018-03-22 17:07:32 +00:00
import time
2018-03-30 12:28:22 +00:00
import json
2015-02-19 10:33:25 +00:00
2019-10-09 09:02:30 +00:00
from gns3server . utils import parse_version , shlex_quote
2018-01-29 13:20:48 +00:00
from gns3server . utils . asyncio import subprocess_check_output , cancellable_wait_run_in_executor
2015-02-19 10:33:25 +00:00
from . qemu_error import QemuError
2019-06-04 16:00:44 +00:00
from . utils . qcow2 import Qcow2 , Qcow2Error
2020-04-07 12:11:00 +00:00
from . utils . ziputils import pack_zip , unpack_zip
2015-02-19 10:33:25 +00:00
from . . adapters . ethernet_adapter import EthernetAdapter
2020-04-06 10:56:00 +00:00
from . . error import NodeError , ImageMissingError
2015-02-24 02:00:34 +00:00
from . . nios . nio_udp import NIOUDP
2015-04-27 20:38:15 +00:00
from . . nios . nio_tap import NIOTAP
2016-05-11 17:35:36 +00:00
from . . base_node import BaseNode
2015-03-04 15:01:56 +00:00
from . . . utils . asyncio import monitor_process
2015-06-17 15:11:25 +00:00
from . . . utils . images import md5sum
2016-04-25 14:36:20 +00:00
from . . . utils import macaddress_to_int , int_to_macaddress
2020-10-31 05:32:21 +00:00
from gns3server . schemas . qemu_nodes import Qemu , QemuPlatform
2015-02-19 10:33:25 +00:00
import logging
log = logging . getLogger ( __name__ )
2016-05-11 17:35:36 +00:00
class QemuVM ( BaseNode ) :
2015-02-19 10:33:25 +00:00
module_name = ' qemu '
"""
QEMU VM implementation .
2015-04-08 17:17:34 +00:00
: param name : Qemu VM name
2016-05-11 17:35:36 +00:00
: param node_id : Node identifier
2015-02-19 10:33:25 +00:00
: param project : Project instance
2015-04-08 17:17:34 +00:00
: param manager : Manager instance
2015-02-19 10:33:25 +00:00
: param console : TCP console port
2018-03-24 11:11:21 +00:00
: param console_type : Console type
2015-02-19 10:33:25 +00:00
: param qemu_path : path to the QEMU binary
2015-06-10 13:49:24 +00:00
: param platform : Platform to emulate
2015-02-19 10:33:25 +00:00
"""
2020-07-29 06:53:51 +00:00
def __init__ ( self , name , node_id , project , manager , linked_clone = True , qemu_path = None , console = None , console_type = " telnet " , aux = None , aux_type = " none " , platform = None ) :
2015-02-19 10:33:25 +00:00
2020-07-29 06:53:51 +00:00
super ( ) . __init__ ( name , node_id , project , manager , console = console , console_type = console_type , linked_clone = linked_clone , aux = aux , aux_type = aux_type , wrap_console = True , wrap_aux = True )
2021-04-12 07:32:23 +00:00
self . _host = manager . config . settings . Server . host
self . _monitor_host = manager . config . settings . Qemu . monitor_host
2015-02-19 10:33:25 +00:00
self . _process = None
self . _cpulimit_process = None
2015-03-24 04:52:02 +00:00
self . _monitor = None
2015-02-19 10:33:25 +00:00
self . _stdout_file = " "
2017-11-17 11:13:34 +00:00
self . _qemu_img_stdout_file = " "
2015-08-27 14:06:11 +00:00
self . _execute_lock = asyncio . Lock ( )
2016-06-23 22:56:06 +00:00
self . _local_udp_tunnels = { }
2019-06-05 09:25:35 +00:00
self . _guest_cid = None
2020-09-12 13:37:39 +00:00
self . _command_line_changed = False
2015-02-19 10:33:25 +00:00
2015-04-08 17:17:34 +00:00
# QEMU VM settings
2015-06-10 13:49:24 +00:00
if qemu_path :
try :
self . qemu_path = qemu_path
2020-09-12 13:37:39 +00:00
except QemuError :
2016-05-18 09:23:45 +00:00
# If the binary is not found for topologies 1.4 and later
# search via the platform otherwise use the binary name
2015-06-10 13:49:24 +00:00
if platform :
self . platform = platform
else :
2016-01-28 15:14:26 +00:00
self . qemu_path = os . path . basename ( qemu_path )
2015-06-10 13:49:24 +00:00
else :
self . platform = platform
2015-02-19 10:33:25 +00:00
self . _hda_disk_image = " "
self . _hdb_disk_image = " "
2015-03-10 17:50:30 +00:00
self . _hdc_disk_image = " "
self . _hdd_disk_image = " "
2020-08-15 06:44:16 +00:00
self . _hda_disk_interface = " none "
self . _hdb_disk_interface = " none "
self . _hdc_disk_interface = " none "
self . _hdd_disk_interface = " none "
2015-08-03 05:02:02 +00:00
self . _cdrom_image = " "
2016-12-08 15:18:30 +00:00
self . _bios_image = " "
2015-08-03 05:02:02 +00:00
self . _boot_priority = " c "
2015-06-08 04:18:41 +00:00
self . _mac_address = " "
2015-02-19 10:33:25 +00:00
self . _options = " "
self . _ram = 256
2015-08-05 23:17:55 +00:00
self . _cpus = 1
2020-07-24 05:45:41 +00:00
self . _maxcpus = 1
2015-02-19 10:33:25 +00:00
self . _ethernet_adapters = [ ]
self . _adapter_type = " e1000 "
self . _initrd = " "
self . _kernel_image = " "
self . _kernel_command_line = " "
self . _legacy_networking = False
2020-06-02 09:15:22 +00:00
self . _replicate_network_connection_state = True
2020-08-14 08:27:24 +00:00
self . _create_config_disk = False
2018-03-30 14:18:44 +00:00
self . _on_close = " power_off "
2015-02-19 10:33:25 +00:00
self . _cpu_throttling = 0 # means no CPU throttling
self . _process_priority = " low "
2015-06-08 04:18:41 +00:00
self . mac_address = " " # this will generate a MAC address
2015-02-19 10:33:25 +00:00
self . adapters = 1 # creates 1 adapter by default
2020-04-06 10:56:00 +00:00
# config disk
2020-06-28 14:35:39 +00:00
self . config_disk_name = self . manager . config_disk
2020-04-16 09:07:56 +00:00
self . config_disk_image = " "
2020-08-13 07:40:31 +00:00
if self . config_disk_name :
if not shutil . which ( " mcopy " ) :
log . warning ( " Config disk: ' mtools ' are not installed. " )
2020-06-28 14:35:39 +00:00
self . config_disk_name = " "
2020-08-13 07:40:31 +00:00
else :
try :
self . config_disk_image = self . manager . get_abs_image_path ( self . config_disk_name )
2020-08-14 08:27:24 +00:00
except ( NodeError , ImageMissingError ) :
log . warning ( " Config disk: image ' {} ' missing " . format ( self . config_disk_name ) )
2020-08-13 07:40:31 +00:00
self . config_disk_name = " "
2020-04-06 10:56:00 +00:00
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has been created ' . format ( name = self . _name , id = self . _id ) )
2015-02-19 10:33:25 +00:00
2019-06-04 16:00:44 +00:00
@property
def guest_cid ( self ) :
"""
2019-06-05 09:25:35 +00:00
Returns the CID ( console ID ) which is an unique identifier between 3 and 65535
2019-06-04 16:00:44 +00:00
: returns : integer between 3 and 65535
"""
return self . _guest_cid
2019-06-05 09:25:35 +00:00
@guest_cid.setter
def guest_cid ( self , guest_cid ) :
"""
Set the CID ( console ID ) which is an unique identifier between 3 and 65535
: returns : integer between 3 and 65535
"""
self . _guest_cid = guest_cid
2015-02-19 10:33:25 +00:00
@property
def monitor ( self ) :
"""
Returns the TCP monitor port .
: returns : monitor port ( integer )
"""
return self . _monitor
@property
def qemu_path ( self ) :
"""
Returns the QEMU binary path for this QEMU VM .
: returns : QEMU path
"""
return self . _qemu_path
@qemu_path.setter
def qemu_path ( self , qemu_path ) :
"""
Sets the QEMU binary path this QEMU VM .
: param qemu_path : QEMU path
"""
2015-02-19 15:46:57 +00:00
if qemu_path and os . pathsep not in qemu_path :
2016-09-27 16:01:50 +00:00
if sys . platform . startswith ( " win " ) and " .exe " not in qemu_path . lower ( ) :
qemu_path + = " w.exe "
2015-08-27 16:27:17 +00:00
new_qemu_path = shutil . which ( qemu_path , path = os . pathsep . join ( self . _manager . paths_list ( ) ) )
if new_qemu_path is None :
raise QemuError ( " QEMU binary path {} is not found in the path " . format ( qemu_path ) )
qemu_path = new_qemu_path
2015-02-19 15:46:57 +00:00
2015-06-10 13:49:24 +00:00
self . _check_qemu_path ( qemu_path )
self . _qemu_path = qemu_path
self . _platform = os . path . basename ( qemu_path )
if self . _platform == " qemu-kvm " :
self . _platform = " x86_64 "
else :
2015-08-26 11:47:12 +00:00
qemu_bin = os . path . basename ( qemu_path )
qemu_bin = re . sub ( r ' (w)? \ .(exe|EXE)$ ' , ' ' , qemu_bin )
2016-01-14 16:40:58 +00:00
# Old version of GNS3 provide a binary named qemu.exe
if qemu_bin == " qemu " :
self . _platform = " i386 "
else :
self . _platform = re . sub ( r ' ^qemu-system-(.*)$ ' , r ' \ 1 ' , qemu_bin , re . IGNORECASE )
2020-10-31 05:32:21 +00:00
try :
QemuPlatform ( self . _platform . split ( " . " ) [ 0 ] )
except ValueError :
2015-06-10 13:49:24 +00:00
raise QemuError ( " Platform {} is unknown " . format ( self . _platform ) )
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU path to {qemu_path} ' . format ( name = self . _name ,
id = self . _id ,
qemu_path = qemu_path ) )
def _check_qemu_path ( self , qemu_path ) :
2018-03-15 07:17:39 +00:00
2015-02-19 15:46:57 +00:00
if qemu_path is None :
2015-08-27 16:27:17 +00:00
raise QemuError ( " QEMU binary path is not set " )
2015-02-19 15:46:57 +00:00
if not os . path . exists ( qemu_path ) :
raise QemuError ( " QEMU binary ' {} ' is not accessible " . format ( qemu_path ) )
if not os . access ( qemu_path , os . X_OK ) :
raise QemuError ( " QEMU binary ' {} ' is not executable " . format ( qemu_path ) )
2015-06-10 13:49:24 +00:00
@property
def platform ( self ) :
"""
Return the current platform
"""
return self . _platform
@platform.setter
def platform ( self , platform ) :
2018-03-15 07:17:39 +00:00
2015-06-10 13:49:24 +00:00
self . _platform = platform
if sys . platform . startswith ( " win " ) :
self . qemu_path = " qemu-system- {} w.exe " . format ( platform )
else :
self . qemu_path = " qemu-system- {} " . format ( platform )
2015-02-19 10:33:25 +00:00
2016-10-24 19:39:35 +00:00
def _disk_setter ( self , variable , value ) :
"""
Use by disk image setter for checking and apply modifications
: param variable : Variable name in the class
: param value : New disk value
"""
2018-03-15 07:17:39 +00:00
2018-11-19 08:53:43 +00:00
value = self . manager . get_abs_image_path ( value , self . project . path )
2016-10-24 19:39:35 +00:00
if not self . linked_clone :
for node in self . manager . nodes :
if node != self and getattr ( node , variable ) == value :
raise QemuError ( " Sorry a node without the linked base setting enabled can only be used once on your server. {} is already used by {} " . format ( value , node . name ) )
setattr ( self , " _ " + variable , value )
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU {variable} path to {disk_image} ' . format ( name = self . _name ,
variable = variable ,
id = self . _id ,
disk_image = value ) )
2015-02-19 10:33:25 +00:00
@property
def hda_disk_image ( self ) :
"""
Returns the hda disk image path for this QEMU VM .
: returns : QEMU hda disk image path
"""
return self . _hda_disk_image
@hda_disk_image.setter
def hda_disk_image ( self , hda_disk_image ) :
"""
Sets the hda disk image for this QEMU VM .
: param hda_disk_image : QEMU hda disk image path
"""
2018-03-15 07:17:39 +00:00
2016-10-24 19:39:35 +00:00
self . _disk_setter ( " hda_disk_image " , hda_disk_image )
2015-02-19 10:33:25 +00:00
@property
def hdb_disk_image ( self ) :
"""
Returns the hdb disk image path for this QEMU VM .
: returns : QEMU hdb disk image path
"""
return self . _hdb_disk_image
@hdb_disk_image.setter
def hdb_disk_image ( self , hdb_disk_image ) :
"""
Sets the hdb disk image for this QEMU VM .
: param hdb_disk_image : QEMU hdb disk image path
"""
2016-10-24 19:39:35 +00:00
self . _disk_setter ( " hdb_disk_image " , hdb_disk_image )
2015-02-19 10:33:25 +00:00
2015-03-10 17:50:30 +00:00
@property
def hdc_disk_image ( self ) :
"""
Returns the hdc disk image path for this QEMU VM .
: returns : QEMU hdc disk image path
"""
return self . _hdc_disk_image
@hdc_disk_image.setter
def hdc_disk_image ( self , hdc_disk_image ) :
"""
Sets the hdc disk image for this QEMU VM .
: param hdc_disk_image : QEMU hdc disk image path
"""
2016-10-24 19:39:35 +00:00
self . _disk_setter ( " hdc_disk_image " , hdc_disk_image )
2015-03-10 17:50:30 +00:00
@property
def hdd_disk_image ( self ) :
"""
Returns the hdd disk image path for this QEMU VM .
: returns : QEMU hdd disk image path
"""
return self . _hdd_disk_image
@hdd_disk_image.setter
def hdd_disk_image ( self , hdd_disk_image ) :
"""
Sets the hdd disk image for this QEMU VM .
: param hdd_disk_image : QEMU hdd disk image path
"""
2016-10-24 19:39:35 +00:00
self . _disk_setter ( " hdd_disk_image " , hdd_disk_image )
2015-03-10 17:50:30 +00:00
2015-08-03 05:02:02 +00:00
@property
def hda_disk_interface ( self ) :
"""
Returns the hda disk interface this QEMU VM .
: returns : QEMU hda disk interface
"""
return self . _hda_disk_interface
@hda_disk_interface.setter
def hda_disk_interface ( self , hda_disk_interface ) :
"""
Sets the hda disk interface for this QEMU VM .
: param hda_disk_interface : QEMU hda disk interface
"""
self . _hda_disk_interface = hda_disk_interface
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU hda disk interface to {interface} ' . format ( name = self . _name ,
id = self . _id ,
interface = self . _hda_disk_interface ) )
@property
def hdb_disk_interface ( self ) :
"""
Returns the hdb disk interface this QEMU VM .
: returns : QEMU hdb disk interface
"""
2016-01-01 00:44:23 +00:00
return self . _hdb_disk_interface
2015-08-03 05:02:02 +00:00
@hdb_disk_interface.setter
def hdb_disk_interface ( self , hdb_disk_interface ) :
"""
Sets the hda disk interface for this QEMU VM .
: param hdb_disk_interface : QEMU hdb disk interface
"""
self . _hdb_disk_interface = hdb_disk_interface
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU hdb disk interface to {interface} ' . format ( name = self . _name ,
id = self . _id ,
interface = self . _hdb_disk_interface ) )
@property
def hdc_disk_interface ( self ) :
"""
Returns the hdc disk interface this QEMU VM .
: returns : QEMU hdc disk interface
"""
return self . _hdc_disk_interface
@hdc_disk_interface.setter
def hdc_disk_interface ( self , hdc_disk_interface ) :
"""
Sets the hdc disk interface for this QEMU VM .
: param hdc_disk_interface : QEMU hdc disk interface
"""
self . _hdc_disk_interface = hdc_disk_interface
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU hdc disk interface to {interface} ' . format ( name = self . _name ,
id = self . _id ,
interface = self . _hdc_disk_interface ) )
@property
def hdd_disk_interface ( self ) :
"""
Returns the hda disk interface this QEMU VM .
: returns : QEMU hda disk interface
"""
return self . _hdd_disk_interface
@hdd_disk_interface.setter
def hdd_disk_interface ( self , hdd_disk_interface ) :
"""
Sets the hdd disk interface for this QEMU VM .
: param hdd_disk_interface : QEMU hdd disk interface
"""
self . _hdd_disk_interface = hdd_disk_interface
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU hdd disk interface to {interface} ' . format ( name = self . _name ,
id = self . _id ,
interface = self . _hdd_disk_interface ) )
@property
def cdrom_image ( self ) :
"""
Returns the cdrom image path for this QEMU VM .
: returns : QEMU cdrom image path
"""
return self . _cdrom_image
@cdrom_image.setter
def cdrom_image ( self , cdrom_image ) :
"""
Sets the cdrom image for this QEMU VM .
: param cdrom_image : QEMU cdrom image path
"""
2018-11-19 08:53:43 +00:00
2019-06-12 12:23:03 +00:00
if cdrom_image :
self . _cdrom_image = self . manager . get_abs_image_path ( cdrom_image , self . project . path )
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU cdrom image path to {cdrom_image} ' . format ( name = self . _name ,
id = self . _id ,
cdrom_image = self . _cdrom_image ) )
else :
self . _cdrom_image = " "
2020-10-06 06:38:51 +00:00
async def update_property ( self , name , value ) :
2020-09-12 13:37:39 +00:00
"""
Update Qemu VM properties .
"""
setattr ( self , name , value )
if name == " cdrom_image " :
# let the guest know about the new cdrom image
await self . _update_cdrom_image ( )
self . _command_line_changed = True
async def _update_cdrom_image ( self ) :
2019-06-12 12:23:03 +00:00
"""
Update the cdrom image path for the Qemu guest OS
"""
if self . is_running ( ) :
if self . _cdrom_image :
self . _cdrom_option ( ) # this will check the cdrom image is accessible
2019-06-15 13:20:21 +00:00
await self . _control_vm ( " eject -f ide1-cd0 " )
2019-06-12 12:23:03 +00:00
await self . _control_vm ( " change ide1-cd0 {} " . format ( self . _cdrom_image ) )
2019-06-15 13:20:21 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has changed the cdrom image path to {cdrom_image} ' . format ( name = self . _name ,
id = self . _id ,
cdrom_image = self . _cdrom_image ) )
2019-06-12 12:23:03 +00:00
else :
2019-06-15 13:20:21 +00:00
await self . _control_vm ( " eject -f ide1-cd0 " )
log . info ( ' QEMU VM " {name} " [ {id} ] has ejected the cdrom image ' . format ( name = self . _name , id = self . _id ) )
2015-08-03 05:02:02 +00:00
2016-12-08 15:18:30 +00:00
@property
def bios_image ( self ) :
"""
Returns the bios image path for this QEMU VM .
: returns : QEMU bios image path
"""
return self . _bios_image
@bios_image.setter
def bios_image ( self , bios_image ) :
"""
Sets the bios image for this QEMU VM .
: param bios_image : QEMU bios image path
"""
2018-11-19 08:53:43 +00:00
self . _bios_image = self . manager . get_abs_image_path ( bios_image , self . project . path )
2016-12-08 15:18:30 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU bios image path to {bios_image} ' . format ( name = self . _name ,
id = self . _id ,
bios_image = self . _bios_image ) )
2015-08-03 05:02:02 +00:00
@property
def boot_priority ( self ) :
"""
Returns the boot priority for this QEMU VM .
: returns : QEMU boot priority
"""
return self . _boot_priority
@boot_priority.setter
def boot_priority ( self , boot_priority ) :
"""
Sets the boot priority for this QEMU VM .
: param boot_priority : QEMU boot priority
"""
self . _boot_priority = boot_priority
log . info ( ' QEMU VM " {name} " [ {id} ] has set the boot priority to {boot_priority} ' . format ( name = self . _name ,
id = self . _id ,
boot_priority = self . _boot_priority ) )
2017-07-12 12:50:33 +00:00
@property
def ethernet_adapters ( self ) :
"""
Return the list of ethernet adapters of the node
"""
return self . _ethernet_adapters
2015-02-19 10:33:25 +00:00
@property
def adapters ( self ) :
"""
2015-04-08 17:17:34 +00:00
Returns the number of Ethernet adapters for this QEMU VM .
2015-02-19 10:33:25 +00:00
: returns : number of adapters
"""
return len ( self . _ethernet_adapters )
@adapters.setter
def adapters ( self , adapters ) :
"""
2015-04-08 17:17:34 +00:00
Sets the number of Ethernet adapters for this QEMU VM .
2015-02-19 10:33:25 +00:00
: param adapters : number of adapters
"""
self . _ethernet_adapters . clear ( )
2015-04-08 17:17:34 +00:00
for adapter_number in range ( 0 , adapters ) :
2015-02-19 10:33:25 +00:00
self . _ethernet_adapters . append ( EthernetAdapter ( ) )
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ]: number of Ethernet adapters changed to {adapters} ' . format ( name = self . _name ,
id = self . _id ,
adapters = adapters ) )
2015-02-19 10:33:25 +00:00
@property
def adapter_type ( self ) :
"""
2015-04-08 17:17:34 +00:00
Returns the adapter type for this QEMU VM .
2015-02-19 10:33:25 +00:00
: returns : adapter type ( string )
"""
return self . _adapter_type
@adapter_type.setter
def adapter_type ( self , adapter_type ) :
"""
2015-04-08 17:17:34 +00:00
Sets the adapter type for this QEMU VM .
2015-02-19 10:33:25 +00:00
: param adapter_type : adapter type ( string )
"""
self . _adapter_type = adapter_type
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ]: adapter type changed to {adapter_type} ' . format ( name = self . _name ,
id = self . _id ,
adapter_type = adapter_type ) )
2015-02-19 10:33:25 +00:00
2015-06-03 20:52:49 +00:00
@property
def mac_address ( self ) :
"""
Returns the MAC address for this QEMU VM .
: returns : adapter type ( string )
"""
return self . _mac_address
@mac_address.setter
def mac_address ( self , mac_address ) :
"""
Sets the MAC address for this QEMU VM .
: param mac_address : MAC address
"""
2015-06-08 04:18:41 +00:00
if not mac_address :
2016-05-30 17:52:08 +00:00
# use the node UUID to generate a random MAC address
2018-04-18 08:13:59 +00:00
self . _mac_address = " 0c: %s : %s : %s : %s :00 " % ( self . project . id [ - 4 : - 2 ] , self . project . id [ - 2 : ] , self . id [ - 4 : - 2 ] , self . id [ - 2 : ] )
2015-06-08 04:18:41 +00:00
else :
2016-05-30 17:52:08 +00:00
self . _mac_address = mac_address
2015-06-03 20:52:49 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ]: MAC address changed to {mac_addr} ' . format ( name = self . _name ,
id = self . _id ,
2017-10-14 11:55:16 +00:00
mac_addr = self . _mac_address ) )
2015-06-03 20:52:49 +00:00
2015-02-19 10:33:25 +00:00
@property
def legacy_networking ( self ) :
"""
Returns either QEMU legacy networking commands are used .
: returns : boolean
"""
return self . _legacy_networking
@legacy_networking.setter
def legacy_networking ( self , legacy_networking ) :
"""
Sets either QEMU legacy networking commands are used .
: param legacy_networking : boolean
"""
if legacy_networking :
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has enabled legacy networking ' . format ( name = self . _name , id = self . _id ) )
2015-02-19 10:33:25 +00:00
else :
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has disabled legacy networking ' . format ( name = self . _name , id = self . _id ) )
2015-02-19 10:33:25 +00:00
self . _legacy_networking = legacy_networking
2020-06-02 09:15:22 +00:00
@property
def replicate_network_connection_state ( self ) :
"""
Returns whether the network connection state for links is replicated in QEMU .
: returns : boolean
"""
return self . _replicate_network_connection_state
@replicate_network_connection_state.setter
def replicate_network_connection_state ( self , replicate_network_connection_state ) :
"""
Sets whether the network connection state for links is replicated in QEMU
: param replicate_network_connection_state : boolean
"""
if replicate_network_connection_state :
log . info ( ' QEMU VM " {name} " [ {id} ] has enabled network connection state replication ' . format ( name = self . _name , id = self . _id ) )
else :
log . info ( ' QEMU VM " {name} " [ {id} ] has disabled network connection state replication ' . format ( name = self . _name , id = self . _id ) )
self . _replicate_network_connection_state = replicate_network_connection_state
2020-08-14 08:27:24 +00:00
@property
def create_config_disk ( self ) :
"""
Returns whether a config disk is automatically created on HDD disk interface ( secondary slave )
: returns : boolean
"""
return self . _create_config_disk
@create_config_disk.setter
def create_config_disk ( self , create_config_disk ) :
"""
Sets whether a config disk is automatically created on HDD disk interface ( secondary slave )
: param replicate_network_connection_state : boolean
"""
if create_config_disk :
log . info ( ' QEMU VM " {name} " [ {id} ] has enabled the config disk creation feature ' . format ( name = self . _name , id = self . _id ) )
else :
log . info ( ' QEMU VM " {name} " [ {id} ] has disabled the config disk creation feature ' . format ( name = self . _name , id = self . _id ) )
self . _create_config_disk = create_config_disk
2015-06-03 04:33:38 +00:00
@property
2018-03-30 14:18:44 +00:00
def on_close ( self ) :
2015-06-03 04:33:38 +00:00
"""
2018-03-30 14:18:44 +00:00
Returns the action to execute when the VM is stopped / closed
2015-06-03 04:33:38 +00:00
2018-03-30 14:18:44 +00:00
: returns : string
2018-03-30 12:28:22 +00:00
"""
2018-03-30 14:18:44 +00:00
return self . _on_close
2018-03-30 12:28:22 +00:00
2018-03-30 14:18:44 +00:00
@on_close.setter
def on_close ( self , on_close ) :
2018-03-30 12:28:22 +00:00
"""
2018-03-30 14:18:44 +00:00
Sets the action to execute when the VM is stopped / closed
2018-03-30 12:28:22 +00:00
2018-03-30 14:18:44 +00:00
: param on_close : string
2018-03-30 12:28:22 +00:00
"""
2018-03-30 14:18:44 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] set the close action to " {action} " ' . format ( name = self . _name , id = self . _id , action = on_close ) )
self . _on_close = on_close
2018-03-30 12:28:22 +00:00
2015-02-19 10:33:25 +00:00
@property
def cpu_throttling ( self ) :
"""
Returns the percentage of CPU allowed .
: returns : integer
"""
return self . _cpu_throttling
@cpu_throttling.setter
def cpu_throttling ( self , cpu_throttling ) :
"""
Sets the percentage of CPU allowed .
: param cpu_throttling : integer
"""
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the percentage of CPU allowed to {cpu} ' . format ( name = self . _name ,
id = self . _id ,
cpu = cpu_throttling ) )
2015-02-19 10:33:25 +00:00
self . _cpu_throttling = cpu_throttling
self . _stop_cpulimit ( )
if cpu_throttling :
self . _set_cpu_throttling ( )
@property
def process_priority ( self ) :
"""
Returns the process priority .
: returns : string
"""
return self . _process_priority
@process_priority.setter
def process_priority ( self , process_priority ) :
"""
Sets the process priority .
: param process_priority : string
"""
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the process priority to {priority} ' . format ( name = self . _name ,
id = self . _id ,
priority = process_priority ) )
2015-02-19 10:33:25 +00:00
self . _process_priority = process_priority
@property
def ram ( self ) :
"""
Returns the RAM amount for this QEMU VM .
: returns : RAM amount in MB
"""
return self . _ram
@ram.setter
def ram ( self , ram ) :
"""
Sets the amount of RAM for this QEMU VM .
: param ram : RAM amount in MB
"""
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the RAM to {ram} ' . format ( name = self . _name , id = self . _id , ram = ram ) )
2015-02-19 10:33:25 +00:00
self . _ram = ram
2015-08-05 23:17:55 +00:00
@property
def cpus ( self ) :
"""
Returns the number of vCPUs this QEMU VM .
: returns : number of vCPUs .
"""
return self . _cpus
@cpus.setter
def cpus ( self , cpus ) :
"""
Sets the number of vCPUs this QEMU VM .
: param cpus : number of vCPUs .
"""
log . info ( ' QEMU VM " {name} " [ {id} ] has set the number of vCPUs to {cpus} ' . format ( name = self . _name , id = self . _id , cpus = cpus ) )
self . _cpus = cpus
2020-07-24 05:45:41 +00:00
@property
def maxcpus ( self ) :
"""
Returns the maximum number of hotpluggable vCPUs for this QEMU VM .
: returns : maximum number of hotpluggable vCPUs .
"""
return self . _maxcpus
@maxcpus.setter
def maxcpus ( self , maxcpus ) :
"""
Sets the maximum number of hotpluggable vCPUs for this QEMU VM .
: param maxcpus : maximum number of hotpluggable vCPUs
"""
log . info ( ' QEMU VM " {name} " [ {id} ] has set maximum number of hotpluggable vCPUs to {maxcpus} ' . format ( name = self . _name , id = self . _id , maxcpus = maxcpus ) )
self . _maxcpus = maxcpus
2015-02-19 10:33:25 +00:00
@property
def options ( self ) :
"""
Returns the options for this QEMU VM .
: returns : QEMU options
"""
return self . _options
@options.setter
def options ( self , options ) :
"""
Sets the options for this QEMU VM .
: param options : QEMU options
"""
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU options to {options} ' . format ( name = self . _name ,
id = self . _id ,
options = options ) )
2015-06-12 07:40:38 +00:00
2015-07-22 04:58:28 +00:00
if not sys . platform . startswith ( " linux " ) :
if " -no-kvm " in options :
2015-07-27 21:31:42 +00:00
options = options . replace ( " -no-kvm " , " " )
2015-07-22 04:58:28 +00:00
if " -enable-kvm " in options :
2015-07-27 21:31:42 +00:00
options = options . replace ( " -enable-kvm " , " " )
2018-03-21 09:41:25 +00:00
else :
if " -no-hax " in options :
options = options . replace ( " -no-hax " , " " )
if " -enable-hax " in options :
options = options . replace ( " -enable-hax " , " " )
if " -icount " in options and ( " -no-kvm " not in options ) :
# automatically add the -no-kvm option if -icount is detected
# to help with the migration of ASA VMs created before version 1.4
options = " -no-kvm " + options
2015-06-12 07:40:38 +00:00
self . _options = options . strip ( )
2015-02-19 10:33:25 +00:00
@property
def initrd ( self ) :
"""
Returns the initrd path for this QEMU VM .
: returns : QEMU initrd path
"""
return self . _initrd
@initrd.setter
def initrd ( self , initrd ) :
"""
Sets the initrd path for this QEMU VM .
: param initrd : QEMU initrd path
"""
2018-11-19 08:53:43 +00:00
initrd = self . manager . get_abs_image_path ( initrd , self . project . path )
2015-03-10 03:57:21 +00:00
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU initrd path to {initrd} ' . format ( name = self . _name ,
id = self . _id ,
initrd = initrd ) )
2019-04-15 10:05:20 +00:00
if " asa " in initrd and self . _initrd != initrd :
2018-03-15 07:17:39 +00:00
self . project . emit ( " log.warning " , { " message " : " Warning ASA 8 is not supported by GNS3 and Cisco, please use ASAv instead. Depending of your hardware and OS this could not work or you could be limited to one instance. If ASA 8 is not booting their is no GNS3 solution, you must to upgrade to ASAv. " } )
2015-02-19 10:33:25 +00:00
self . _initrd = initrd
@property
def kernel_image ( self ) :
"""
Returns the kernel image path for this QEMU VM .
: returns : QEMU kernel image path
"""
return self . _kernel_image
@kernel_image.setter
def kernel_image ( self , kernel_image ) :
"""
Sets the kernel image path for this QEMU VM .
: param kernel_image : QEMU kernel image path
"""
2018-11-19 08:53:43 +00:00
kernel_image = self . manager . get_abs_image_path ( kernel_image , self . project . path )
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU kernel image path to {kernel_image} ' . format ( name = self . _name ,
id = self . _id ,
kernel_image = kernel_image ) )
2015-02-19 10:33:25 +00:00
self . _kernel_image = kernel_image
@property
def kernel_command_line ( self ) :
"""
Returns the kernel command line for this QEMU VM .
: returns : QEMU kernel command line
"""
return self . _kernel_command_line
@kernel_command_line.setter
def kernel_command_line ( self , kernel_command_line ) :
"""
Sets the kernel command line for this QEMU VM .
: param kernel_command_line : QEMU kernel command line
"""
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU kernel command line to {kernel_command_line} ' . format ( name = self . _name ,
id = self . _id ,
kernel_command_line = kernel_command_line ) )
2015-02-19 10:33:25 +00:00
self . _kernel_command_line = kernel_command_line
2018-10-15 10:05:49 +00:00
async def _set_process_priority ( self ) :
2015-02-19 10:33:25 +00:00
"""
Changes the process priority
"""
2015-12-22 12:15:28 +00:00
if self . _process_priority == " normal " :
return
2015-02-19 10:33:25 +00:00
if sys . platform . startswith ( " win " ) :
try :
import win32api
import win32con
import win32process
except ImportError :
log . error ( " pywin32 must be installed to change the priority class for QEMU VM {} " . format ( self . _name ) )
else :
2015-12-22 12:15:28 +00:00
log . info ( " Setting QEMU VM {} priority class to {} " . format ( self . _name , self . _process_priority ) )
2015-02-19 10:33:25 +00:00
handle = win32api . OpenProcess ( win32con . PROCESS_ALL_ACCESS , 0 , self . _process . pid )
if self . _process_priority == " realtime " :
priority = win32process . REALTIME_PRIORITY_CLASS
elif self . _process_priority == " very high " :
priority = win32process . HIGH_PRIORITY_CLASS
elif self . _process_priority == " high " :
priority = win32process . ABOVE_NORMAL_PRIORITY_CLASS
elif self . _process_priority == " low " :
priority = win32process . BELOW_NORMAL_PRIORITY_CLASS
elif self . _process_priority == " very low " :
priority = win32process . IDLE_PRIORITY_CLASS
else :
priority = win32process . NORMAL_PRIORITY_CLASS
2016-01-03 20:23:35 +00:00
try :
win32process . SetPriorityClass ( handle , priority )
except win32process . error as e :
log . error ( ' Could not change process priority for QEMU VM " {} " : {} ' . format ( self . _name , e ) )
2015-02-19 10:33:25 +00:00
else :
if self . _process_priority == " realtime " :
priority = - 20
elif self . _process_priority == " very high " :
priority = - 15
elif self . _process_priority == " high " :
priority = - 5
elif self . _process_priority == " low " :
priority = 5
elif self . _process_priority == " very low " :
priority = 19
else :
priority = 0
try :
2018-10-15 10:05:49 +00:00
process = await asyncio . create_subprocess_exec ( ' renice ' , ' -n ' , str ( priority ) , ' -p ' , str ( self . _process . pid ) )
await process . wait ( )
2015-02-19 10:33:25 +00:00
except ( OSError , subprocess . SubprocessError ) as e :
2015-04-08 17:17:34 +00:00
log . error ( ' Could not change process priority for QEMU VM " {} " : {} ' . format ( self . _name , e ) )
2015-02-19 10:33:25 +00:00
def _stop_cpulimit ( self ) :
"""
Stops the cpulimit process .
"""
2015-02-26 09:18:01 +00:00
if self . _cpulimit_process and self . _cpulimit_process . returncode is None :
2015-02-19 10:33:25 +00:00
self . _cpulimit_process . kill ( )
try :
self . _process . wait ( 3 )
except subprocess . TimeoutExpired :
2015-04-08 17:17:34 +00:00
log . error ( " Could not kill cpulimit process {} " . format ( self . _cpulimit_process . pid ) )
2015-02-19 10:33:25 +00:00
def _set_cpu_throttling ( self ) :
"""
Limits the CPU usage for current QEMU process .
"""
if not self . is_running ( ) :
return
try :
if sys . platform . startswith ( " win " ) and hasattr ( sys , " frozen " ) :
cpulimit_exec = os . path . join ( os . path . dirname ( os . path . abspath ( sys . executable ) ) , " cpulimit " , " cpulimit.exe " )
else :
cpulimit_exec = " cpulimit "
2015-02-19 15:46:57 +00:00
subprocess . Popen ( [ cpulimit_exec , " --lazy " , " --pid= {} " . format ( self . _process . pid ) , " --limit= {} " . format ( self . _cpu_throttling ) ] , cwd = self . working_dir )
2015-02-19 10:33:25 +00:00
log . info ( " CPU throttled to {} % " . format ( self . _cpu_throttling ) )
except FileNotFoundError :
raise QemuError ( " cpulimit could not be found, please install it or deactivate CPU throttling " )
except ( OSError , subprocess . SubprocessError ) as e :
raise QemuError ( " Could not throttle CPU: {} " . format ( e ) )
2018-10-15 10:05:49 +00:00
async def create ( self ) :
2018-01-29 13:20:48 +00:00
"""
Creates QEMU VM and sets proper MD5 hashes
"""
# In case user upload image manually we don't have md5 sums.
# We need generate hashes at this point, otherwise they will be generated
# at __json__ but not on separate thread.
2018-10-15 10:05:49 +00:00
await cancellable_wait_run_in_executor ( md5sum , self . _hda_disk_image )
await cancellable_wait_run_in_executor ( md5sum , self . _hdb_disk_image )
await cancellable_wait_run_in_executor ( md5sum , self . _hdc_disk_image )
await cancellable_wait_run_in_executor ( md5sum , self . _hdd_disk_image )
2018-01-29 13:20:48 +00:00
super ( QemuVM , self ) . create ( )
2018-10-15 10:05:49 +00:00
async def start ( self ) :
2015-02-19 10:33:25 +00:00
"""
Starts this QEMU VM .
"""
2018-10-15 10:05:49 +00:00
async with self . _execute_lock :
2015-08-27 14:06:11 +00:00
if self . is_running ( ) :
# resume the VM if it is paused
2018-10-15 10:05:49 +00:00
await self . resume ( )
2015-08-27 14:06:11 +00:00
return
2015-02-19 10:33:25 +00:00
2021-04-12 07:32:23 +00:00
if self . _manager . config . settings . Qemu . enable_monitor :
2015-03-24 04:52:02 +00:00
try :
2016-11-28 12:18:04 +00:00
info = socket . getaddrinfo ( self . _monitor_host , 0 , socket . AF_UNSPEC , socket . SOCK_STREAM , 0 , socket . AI_PASSIVE )
if not info :
raise QemuError ( " getaddrinfo returns an empty list on {} " . format ( self . _monitor_host ) )
for res in info :
af , socktype , proto , _ , sa = res
# let the OS find an unused port for the Qemu monitor
with socket . socket ( af , socktype , proto ) as sock :
2018-04-16 08:36:36 +00:00
sock . setsockopt ( socket . SOL_SOCKET , socket . SO_REUSEADDR , 1 )
2016-11-28 12:18:04 +00:00
sock . bind ( sa )
self . _monitor = sock . getsockname ( ) [ 1 ]
except OSError as e :
raise QemuError ( " Could not find free port for the Qemu monitor: {} " . format ( e ) )
# check if there is enough RAM to run
self . check_available_ram ( self . ram )
2018-10-15 10:05:49 +00:00
command = await self . _build_command ( )
2019-10-09 09:02:30 +00:00
command_string = " " . join ( shlex_quote ( s ) for s in command )
2016-11-28 12:18:04 +00:00
try :
log . info ( " Starting QEMU with: {} " . format ( command_string ) )
self . _stdout_file = os . path . join ( self . working_dir , " qemu.log " )
log . info ( " logging to {} " . format ( self . _stdout_file ) )
with open ( self . _stdout_file , " w " , encoding = " utf-8 " ) as fd :
fd . write ( " Start QEMU with {} \n \n Execution log: \n " . format ( command_string ) )
self . command_line = ' ' . join ( command )
2018-10-15 10:05:49 +00:00
self . _process = await asyncio . create_subprocess_exec ( * command ,
2016-11-28 12:18:04 +00:00
stdout = fd ,
stderr = subprocess . STDOUT ,
cwd = self . working_dir )
log . info ( ' QEMU VM " {} " started PID= {} ' . format ( self . _name , self . _process . pid ) )
2020-09-12 13:37:39 +00:00
self . _command_line_changed = False
2016-11-28 12:18:04 +00:00
self . status = " started "
monitor_process ( self . _process , self . _termination_callback )
except ( OSError , subprocess . SubprocessError , UnicodeEncodeError ) as e :
stdout = self . read_stdout ( )
log . error ( " Could not start QEMU {} : {} \n {} " . format ( self . qemu_path , e , stdout ) )
raise QemuError ( " Could not start QEMU {} : {} \n {} " . format ( self . qemu_path , e , stdout ) )
2018-10-15 10:05:49 +00:00
await self . _set_process_priority ( )
2016-11-28 12:18:04 +00:00
if self . _cpu_throttling :
self . _set_cpu_throttling ( )
2018-03-21 09:41:25 +00:00
if " -enable-kvm " in command_string or " -enable-hax " in command_string :
2016-11-28 12:18:04 +00:00
self . _hw_virtualization = True
2018-03-19 07:22:10 +00:00
2018-10-15 10:05:49 +00:00
await self . _start_ubridge ( )
2018-03-22 17:07:32 +00:00
set_link_commands = [ ]
2018-03-19 07:22:10 +00:00
for adapter_number , adapter in enumerate ( self . _ethernet_adapters ) :
nio = adapter . get_nio ( 0 )
if nio :
2018-10-15 10:05:49 +00:00
await self . add_ubridge_udp_connection ( " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
2018-03-19 07:22:10 +00:00
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
2020-06-02 09:15:22 +00:00
if nio . suspend and self . _replicate_network_connection_state :
2018-03-22 17:07:32 +00:00
set_link_commands . append ( " set_link gns3- {} off " . format ( adapter_number ) )
2020-06-02 09:15:22 +00:00
elif self . _replicate_network_connection_state :
2018-03-22 17:07:32 +00:00
set_link_commands . append ( " set_link gns3- {} off " . format ( adapter_number ) )
2019-05-19 10:59:00 +00:00
2020-06-02 09:15:22 +00:00
if " -loadvm " not in command_string and self . _replicate_network_connection_state :
2019-05-19 10:59:00 +00:00
# only set the link statuses if not restoring a previous VM state
await self . _control_vm_commands ( set_link_commands )
2018-03-19 07:22:10 +00:00
2016-11-28 12:18:04 +00:00
try :
2019-05-25 08:31:35 +00:00
if self . is_running ( ) :
await self . start_wrap_console ( )
2016-11-28 12:18:04 +00:00
except OSError as e :
2018-03-23 08:44:16 +00:00
raise QemuError ( " Could not start Telnet QEMU console {} \n " . format ( e ) )
2015-07-22 04:58:28 +00:00
2018-10-15 10:05:49 +00:00
async def _termination_callback ( self , returncode ) :
2015-03-04 15:01:56 +00:00
"""
2015-05-13 19:53:42 +00:00
Called when the process has stopped .
2015-03-04 15:01:56 +00:00
: param returncode : Process returncode
"""
2015-05-13 19:53:42 +00:00
2015-03-04 15:01:56 +00:00
if self . started :
2015-05-13 19:53:42 +00:00
log . info ( " QEMU process has stopped, return code: %d " , returncode )
2018-10-15 10:05:49 +00:00
await self . stop ( )
2016-11-16 12:06:43 +00:00
# A return code of 1 seem fine on Windows
2019-03-01 16:40:32 +00:00
if returncode != 0 and ( not sys . platform . startswith ( " win " ) or returncode != 1 ) :
2015-06-26 13:10:41 +00:00
self . project . emit ( " log.error " , { " message " : " QEMU process has stopped, return code: {} \n {} " . format ( returncode , self . read_stdout ( ) ) } )
2015-03-04 15:01:56 +00:00
2018-10-15 10:05:49 +00:00
async def stop ( self ) :
2015-02-19 10:33:25 +00:00
"""
Stops this QEMU VM .
"""
2018-10-15 10:05:49 +00:00
await self . _stop_ubridge ( )
async with self . _execute_lock :
2015-08-27 14:06:11 +00:00
# stop the QEMU process
self . _hw_virtualization = False
if self . is_running ( ) :
log . info ( ' Stopping QEMU VM " {} " PID= {} ' . format ( self . _name , self . _process . pid ) )
try :
2018-03-30 12:28:22 +00:00
2018-03-30 14:18:44 +00:00
if self . on_close == " save_vm_state " :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " stop " )
await self . _control_vm ( " savevm GNS3_SAVED_STATE " )
2018-03-30 12:28:22 +00:00
wait_for_savevm = 120
while wait_for_savevm :
2018-10-15 10:05:49 +00:00
await asyncio . sleep ( 1 )
status = await self . _saved_state_option ( )
2018-03-30 12:28:22 +00:00
wait_for_savevm - = 1
if status != [ ] :
break
2018-03-30 14:18:44 +00:00
if self . on_close == " shutdown_signal " :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " system_powerdown " )
2019-03-01 10:23:49 +00:00
await gns3server . utils . asyncio . wait_for_process_termination ( self . _process , timeout = 120 )
2015-08-27 14:06:11 +00:00
else :
self . _process . terminate ( )
2018-10-15 10:05:49 +00:00
await gns3server . utils . asyncio . wait_for_process_termination ( self . _process , timeout = 3 )
2015-12-07 09:48:57 +00:00
except ProcessLookupError :
pass
2015-08-27 14:06:11 +00:00
except asyncio . TimeoutError :
2016-12-12 08:16:29 +00:00
if self . _process :
try :
self . _process . kill ( )
except ProcessLookupError :
pass
if self . _process . returncode is None :
2018-03-15 07:17:39 +00:00
log . warning ( ' QEMU VM " {} " PID= {} is still running ' . format ( self . _name , self . _process . pid ) )
2015-08-27 14:06:11 +00:00
self . _process = None
self . _stop_cpulimit ( )
2018-03-30 14:18:44 +00:00
if self . on_close != " save_vm_state " :
2018-10-15 10:05:49 +00:00
await self . _clear_save_vm_stated ( )
2020-04-06 10:56:00 +00:00
await self . _export_config ( )
2018-10-15 10:05:49 +00:00
await super ( ) . stop ( )
2015-02-19 10:33:25 +00:00
2018-10-15 10:05:49 +00:00
async def _open_qemu_monitor_connection_vm ( self , timeout = 10 ) :
2018-03-22 17:07:32 +00:00
"""
Opens a connection to the QEMU monitor .
: param timeout : timeout to connect to the monitor TCP server
: returns : The reader returned is a StreamReader instance ; the writer is a StreamWriter instance
"""
begin = time . time ( )
connection_success = False
last_exception = None
reader = writer = None
while time . time ( ) - begin < timeout :
2018-10-15 10:05:49 +00:00
await asyncio . sleep ( 0.01 )
2018-03-22 17:07:32 +00:00
try :
log . debug ( " Connecting to Qemu monitor on {} : {} " . format ( self . _monitor_host , self . _monitor ) )
2018-10-15 10:05:49 +00:00
reader , writer = await asyncio . open_connection ( self . _monitor_host , self . _monitor )
2018-03-22 17:07:32 +00:00
except ( asyncio . TimeoutError , OSError ) as e :
last_exception = e
continue
connection_success = True
break
if not connection_success :
log . warning ( " Could not connect to QEMU monitor on {} : {} : {} " . format ( self . _monitor_host , self . _monitor ,
last_exception ) )
else :
log . info ( " Connected to QEMU monitor on {} : {} after {:.4f} seconds " . format ( self . _monitor_host , self . _monitor , time . time ( ) - begin ) )
return reader , writer
2018-10-15 10:05:49 +00:00
async def _control_vm ( self , command , expected = None ) :
2015-02-19 10:33:25 +00:00
"""
Executes a command with QEMU monitor when this VM is running .
: param command : QEMU monitor command ( e . g . info status , stop etc . )
2015-04-08 17:17:34 +00:00
: param expected : An array of expected strings
2015-02-19 10:33:25 +00:00
2015-04-08 17:17:34 +00:00
: returns : result of the command ( matched object or None )
2015-02-19 10:33:25 +00:00
"""
result = None
if self . is_running ( ) and self . _monitor :
2018-03-22 17:07:32 +00:00
log . info ( " Execute QEMU monitor command: {} " . format ( command ) )
2018-10-15 10:05:49 +00:00
reader , writer = await self . _open_qemu_monitor_connection_vm ( )
2018-03-22 17:07:32 +00:00
if reader is None and writer is None :
2015-02-19 10:33:25 +00:00
return result
2018-03-22 17:07:32 +00:00
2015-02-19 10:33:25 +00:00
try :
2019-05-18 12:31:41 +00:00
cmd_byte = command . encode ( ' ascii ' )
writer . write ( cmd_byte + b " \n " )
if not expected :
while True :
line = await asyncio . wait_for ( reader . readline ( ) , timeout = 3 ) # echo of the command
if not line or cmd_byte in line :
break
except asyncio . TimeoutError :
log . warning ( " Missing echo of command ' {} ' " . format ( command ) )
2015-02-19 10:33:25 +00:00
except OSError as e :
2018-03-15 07:17:39 +00:00
log . warning ( " Could not write to QEMU monitor: {} " . format ( e ) )
2015-02-20 23:15:56 +00:00
writer . close ( )
2015-02-19 10:33:25 +00:00
return result
if expected :
try :
2015-02-20 23:15:56 +00:00
while result is None :
2019-05-18 12:31:41 +00:00
line = await asyncio . wait_for ( reader . readline ( ) , timeout = 3 )
2015-02-20 23:15:56 +00:00
if not line :
break
for expect in expected :
if expect in line :
2015-04-27 03:19:39 +00:00
result = line . decode ( " utf-8 " ) . strip ( )
2015-02-20 23:15:56 +00:00
break
2019-05-18 12:31:41 +00:00
except asyncio . TimeoutError :
log . warning ( " Timeout while waiting for result of command ' {} ' " . format ( command ) )
2018-09-06 07:49:12 +00:00
except ( ConnectionError , EOFError ) as e :
2018-03-15 07:17:39 +00:00
log . warning ( " Could not read from QEMU monitor: {} " . format ( e ) )
2015-02-20 23:15:56 +00:00
writer . close ( )
2015-02-19 10:33:25 +00:00
return result
2018-10-15 10:05:49 +00:00
async def _control_vm_commands ( self , commands ) :
2018-03-22 17:07:32 +00:00
"""
Executes commands with QEMU monitor when this VM is running .
: param commands : a list of QEMU monitor commands ( e . g . info status , stop etc . )
"""
if self . is_running ( ) and self . _monitor :
2018-10-15 10:05:49 +00:00
reader , writer = await self . _open_qemu_monitor_connection_vm ( )
2018-03-22 17:07:32 +00:00
if reader is None and writer is None :
return
for command in commands :
log . info ( " Execute QEMU monitor command: {} " . format ( command ) )
try :
2019-10-15 15:42:42 +00:00
cmd_byte = command . encode ( ' ascii ' )
writer . write ( cmd_byte + b " \n " )
while True :
line = await asyncio . wait_for ( reader . readline ( ) , timeout = 3 ) # echo of the command
if not line or cmd_byte in line :
break
2019-05-18 12:31:41 +00:00
except asyncio . TimeoutError :
log . warning ( " Missing echo of command ' {} ' " . format ( command ) )
2018-03-22 17:07:32 +00:00
except OSError as e :
log . warning ( " Could not write to QEMU monitor: {} " . format ( e ) )
writer . close ( )
2018-10-15 10:05:49 +00:00
async def close ( self ) :
2015-04-08 17:17:34 +00:00
"""
Closes this QEMU VM .
"""
2015-02-19 15:46:57 +00:00
2018-10-15 10:05:49 +00:00
if not ( await super ( ) . close ( ) ) :
2016-02-29 09:38:30 +00:00
return False
2019-05-19 10:59:00 +00:00
#FIXME: Don't wait for ACPI shutdown when closing the project, should we?
if self . on_close == " shutdown_signal " :
self . on_close = " power_off "
2018-10-15 10:05:49 +00:00
await self . stop ( )
2015-10-08 02:48:36 +00:00
for adapter in self . _ethernet_adapters :
if adapter is not None :
for nio in adapter . ports . values ( ) :
if nio and isinstance ( nio , NIOUDP ) :
self . manager . port_manager . release_udp_port ( nio . lport , self . _project )
2016-06-23 22:56:06 +00:00
for udp_tunnel in self . _local_udp_tunnels . values ( ) :
self . manager . port_manager . release_udp_port ( udp_tunnel [ 0 ] . lport , self . _project )
self . manager . port_manager . release_udp_port ( udp_tunnel [ 1 ] . lport , self . _project )
self . _local_udp_tunnels = { }
2018-10-15 10:05:49 +00:00
async def _get_vm_status ( self ) :
2015-02-19 10:33:25 +00:00
"""
2015-04-08 17:17:34 +00:00
Returns this VM suspend status .
2015-02-19 10:33:25 +00:00
2015-03-17 10:02:14 +00:00
Status are extracted from :
https : / / github . com / qemu / qemu / blob / master / qapi - schema . json #L152
2015-02-19 10:33:25 +00:00
: returns : status ( string )
"""
2018-10-15 10:05:49 +00:00
result = await self . _control_vm ( " info status " , [
2015-03-17 10:02:14 +00:00
b " debug " , b " inmigrate " , b " internal-error " , b " io-error " ,
b " paused " , b " postmigrate " , b " prelaunch " , b " finish-migrate " ,
b " restore-vm " , b " running " , b " save-vm " , b " shutdown " , b " suspended " ,
b " watchdog " , b " guest-panicked "
] )
2015-03-23 02:40:19 +00:00
if result is None :
return result
2017-01-17 15:03:12 +00:00
status = result . rsplit ( ' ' , 1 ) [ 1 ]
if status == " running " or status == " prelaunch " :
self . status = " started "
elif status == " suspended " :
self . status = " suspended "
elif status == " shutdown " :
self . status = " stopped "
return status
2015-02-19 10:33:25 +00:00
2018-10-15 10:05:49 +00:00
async def suspend ( self ) :
2015-02-19 10:33:25 +00:00
"""
Suspends this QEMU VM .
"""
2015-03-24 04:52:02 +00:00
if self . is_running ( ) :
2018-10-15 10:05:49 +00:00
vm_status = await self . _get_vm_status ( )
2015-03-24 04:52:02 +00:00
if vm_status is None :
raise QemuError ( " Suspending a QEMU VM is not supported " )
2017-01-17 15:03:12 +00:00
elif vm_status == " running " or vm_status == " prelaunch " :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " stop " )
2016-05-14 02:41:58 +00:00
self . status = " suspended "
2015-03-24 04:52:02 +00:00
log . debug ( " QEMU VM has been suspended " )
else :
log . info ( " QEMU VM is not running to be suspended, current status is {} " . format ( vm_status ) )
2015-02-19 10:33:25 +00:00
2018-10-15 10:05:49 +00:00
async def reload ( self ) :
2015-02-19 10:33:25 +00:00
"""
Reloads this QEMU VM .
"""
2020-09-12 13:37:39 +00:00
if self . _command_line_changed :
await self . stop ( )
await self . start ( )
else :
await self . _control_vm ( " system_reset " )
2015-02-19 10:33:25 +00:00
log . debug ( " QEMU VM has been reset " )
2018-10-15 10:05:49 +00:00
async def resume ( self ) :
2015-02-19 10:33:25 +00:00
"""
Resumes this QEMU VM .
"""
2018-10-15 10:05:49 +00:00
vm_status = await self . _get_vm_status ( )
2015-03-23 02:40:19 +00:00
if vm_status is None :
raise QemuError ( " Resuming a QEMU VM is not supported " )
elif vm_status == " paused " :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " cont " )
2020-09-04 08:32:21 +00:00
self . status = " started "
2015-02-19 10:33:25 +00:00
log . debug ( " QEMU VM has been resumed " )
else :
log . info ( " QEMU VM is not paused to be resumed, current status is {} " . format ( vm_status ) )
2018-10-15 10:05:49 +00:00
async def adapter_add_nio_binding ( self , adapter_number , nio ) :
2015-02-19 10:33:25 +00:00
"""
2018-10-27 07:47:17 +00:00
Adds an adapter NIO binding .
2015-02-19 10:33:25 +00:00
2015-04-08 17:17:34 +00:00
: param adapter_number : adapter number
2015-03-07 03:08:00 +00:00
: param nio : NIO instance to add to the adapter
2015-02-19 10:33:25 +00:00
"""
try :
2015-04-08 17:17:34 +00:00
adapter = self . _ethernet_adapters [ adapter_number ]
2015-02-19 10:33:25 +00:00
except IndexError :
2015-04-08 17:17:34 +00:00
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2015-02-19 10:33:25 +00:00
2017-07-12 08:07:21 +00:00
if self . is_running ( ) :
try :
2018-10-15 10:05:49 +00:00
await self . add_ubridge_udp_connection ( " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
2017-07-12 08:07:21 +00:00
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
2020-06-02 09:15:22 +00:00
if self . _replicate_network_connection_state :
await self . _control_vm ( " set_link gns3- {} on " . format ( adapter_number ) )
2017-12-21 07:55:49 +00:00
except ( IndexError , KeyError ) :
2017-07-12 08:07:21 +00:00
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2015-02-19 10:33:25 +00:00
adapter . add_nio ( 0 , nio )
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ]: {nio} added to adapter {adapter_number} ' . format ( name = self . _name ,
2015-04-14 16:46:55 +00:00
id = self . _id ,
nio = nio ,
adapter_number = adapter_number ) )
2017-07-12 12:50:33 +00:00
2018-10-15 10:05:49 +00:00
async def adapter_update_nio_binding ( self , adapter_number , nio ) :
2017-07-12 12:50:33 +00:00
"""
2018-10-27 07:47:17 +00:00
Update an adapter NIO binding .
2017-07-12 12:50:33 +00:00
: param adapter_number : adapter number
2018-10-27 07:47:17 +00:00
: param nio : NIO instance to update the adapter
2017-07-12 12:50:33 +00:00
"""
if self . is_running ( ) :
try :
2018-10-15 10:05:49 +00:00
await self . update_ubridge_udp_connection ( " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
2018-03-19 07:22:10 +00:00
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
2020-06-02 09:15:22 +00:00
if self . _replicate_network_connection_state :
if nio . suspend :
await self . _control_vm ( " set_link gns3- {} off " . format ( adapter_number ) )
else :
await self . _control_vm ( " set_link gns3- {} on " . format ( adapter_number ) )
2017-07-12 12:50:33 +00:00
except IndexError :
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2015-02-19 10:33:25 +00:00
2018-10-15 10:05:49 +00:00
async def adapter_remove_nio_binding ( self , adapter_number ) :
2015-02-19 10:33:25 +00:00
"""
2018-10-27 07:47:17 +00:00
Removes an adapter NIO binding .
2015-02-19 10:33:25 +00:00
2015-04-08 17:17:34 +00:00
: param adapter_number : adapter number
2015-02-19 10:33:25 +00:00
: returns : NIO instance
"""
try :
2015-04-08 17:17:34 +00:00
adapter = self . _ethernet_adapters [ adapter_number ]
2015-02-19 10:33:25 +00:00
except IndexError :
2015-04-08 17:17:34 +00:00
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2015-02-19 10:33:25 +00:00
2019-04-01 12:47:31 +00:00
await self . stop_capture ( adapter_number )
2017-07-12 08:07:21 +00:00
if self . is_running ( ) :
2020-06-02 09:15:22 +00:00
if self . _replicate_network_connection_state :
await self . _control_vm ( " set_link gns3- {} off " . format ( adapter_number ) )
2018-10-15 10:05:49 +00:00
await self . _ubridge_send ( " bridge delete {name} " . format ( name = " QEMU- {} - {} " . format ( self . _id , adapter_number ) ) )
2015-02-19 10:33:25 +00:00
nio = adapter . get_nio ( 0 )
2015-03-07 03:08:00 +00:00
if isinstance ( nio , NIOUDP ) :
2015-03-21 23:19:12 +00:00
self . manager . port_manager . release_udp_port ( nio . lport , self . _project )
2015-02-19 10:33:25 +00:00
adapter . remove_nio ( 0 )
2016-06-23 22:56:06 +00:00
2015-04-08 17:17:34 +00:00
log . info ( ' QEMU VM " {name} " [ {id} ]: {nio} removed from adapter {adapter_number} ' . format ( name = self . _name ,
id = self . _id ,
nio = nio ,
adapter_number = adapter_number ) )
2015-02-19 10:33:25 +00:00
return nio
2018-10-27 07:47:17 +00:00
def get_nio ( self , adapter_number ) :
2016-06-23 22:56:06 +00:00
"""
2018-10-27 07:47:17 +00:00
Gets an adapter NIO binding .
2016-06-23 22:56:06 +00:00
: param adapter_number : adapter number
2018-10-27 07:47:17 +00:00
: returns : NIO instance
2016-06-23 22:56:06 +00:00
"""
try :
adapter = self . _ethernet_adapters [ adapter_number ]
except IndexError :
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
nio = adapter . get_nio ( 0 )
if not nio :
raise QemuError ( " Adapter {} is not connected " . format ( adapter_number ) )
2018-10-27 07:47:17 +00:00
return nio
async def start_capture ( self , adapter_number , output_file ) :
"""
Starts a packet capture .
: param adapter_number : adapter number
: param output_file : PCAP destination file for the capture
"""
nio = self . get_nio ( adapter_number )
2016-06-23 22:56:06 +00:00
if nio . capturing :
raise QemuError ( " Packet capture is already activated on adapter {adapter_number} " . format ( adapter_number = adapter_number ) )
2019-04-01 13:58:18 +00:00
nio . start_packet_capture ( output_file )
2016-12-14 11:01:34 +00:00
if self . ubridge :
2018-10-15 10:05:49 +00:00
await self . _ubridge_send ( ' bridge start_capture {name} " {output_file} " ' . format ( name = " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
2016-06-23 22:56:06 +00:00
output_file = output_file ) )
log . info ( " QEMU VM ' {name} ' [ {id} ]: starting packet capture on adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
adapter_number = adapter_number ) )
2018-10-15 10:05:49 +00:00
async def stop_capture ( self , adapter_number ) :
2016-06-23 22:56:06 +00:00
"""
Stops a packet capture .
: param adapter_number : adapter number
"""
2018-10-27 07:47:17 +00:00
nio = self . get_nio ( adapter_number )
2019-04-01 12:47:31 +00:00
if not nio . capturing :
return
2016-06-23 22:56:06 +00:00
2019-04-01 13:58:18 +00:00
nio . stop_packet_capture ( )
2016-12-14 11:01:34 +00:00
if self . ubridge :
2018-10-15 10:05:49 +00:00
await self . _ubridge_send ( ' bridge stop_capture {name} ' . format ( name = " QEMU- {} - {} " . format ( self . _id , adapter_number ) ) )
2016-06-23 22:56:06 +00:00
log . info ( " QEMU VM ' {name} ' [ {id} ]: stopping packet capture on adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
adapter_number = adapter_number ) )
2016-08-26 08:40:11 +00:00
2015-02-19 10:33:25 +00:00
@property
def started ( self ) :
"""
Returns either this QEMU VM has been started or not .
: returns : boolean
"""
2015-03-04 15:01:56 +00:00
return self . status == " started "
2015-02-19 10:33:25 +00:00
def read_stdout ( self ) :
"""
Reads the standard output of the QEMU process .
Only use when the process has been stopped or has crashed .
"""
output = " "
if self . _stdout_file :
try :
2015-04-25 17:58:34 +00:00
with open ( self . _stdout_file , " rb " ) as file :
output = file . read ( ) . decode ( " utf-8 " , errors = " replace " )
2015-02-19 10:33:25 +00:00
except OSError as e :
2017-11-17 11:13:34 +00:00
log . warning ( " Could not read {} : {} " . format ( self . _stdout_file , e ) )
return output
def read_qemu_img_stdout ( self ) :
"""
Reads the standard output of the QEMU - IMG process .
"""
output = " "
if self . _qemu_img_stdout_file :
try :
with open ( self . _qemu_img_stdout_file , " rb " ) as file :
output = file . read ( ) . decode ( " utf-8 " , errors = " replace " )
except OSError as e :
log . warning ( " Could not read {} : {} " . format ( self . _qemu_img_stdout_file , e ) )
2015-02-19 10:33:25 +00:00
return output
def is_running ( self ) :
"""
Checks if the QEMU process is running
: returns : True or False
"""
2015-02-19 15:46:57 +00:00
if self . _process :
2015-02-25 14:42:01 +00:00
if self . _process . returncode is None :
return True
else :
self . _process = None
2015-02-19 10:33:25 +00:00
return False
def command ( self ) :
"""
Returns the QEMU command line .
: returns : QEMU command line ( string )
"""
return " " . join ( self . _build_command ( ) )
2018-03-24 11:11:21 +00:00
@BaseNode.console_type.setter
def console_type ( self , new_console_type ) :
"""
Sets the console type for this QEMU VM .
: param new_console_type : console type ( string )
"""
if self . is_running ( ) and self . console_type != new_console_type :
raise QemuError ( ' " {name} " must be stopped to change the console type to {new_console_type} ' . format ( name = self . _name , new_console_type = new_console_type ) )
super ( QemuVM , QemuVM ) . console_type . __set__ ( self , new_console_type )
2020-07-29 06:53:51 +00:00
def _serial_options ( self , internal_console_port , external_console_port ) :
2015-02-19 10:33:25 +00:00
2020-07-29 06:53:51 +00:00
if external_console_port :
return [ " -serial " , " telnet:127.0.0.1: {} ,server,nowait " . format ( internal_console_port ) ]
2015-02-19 10:33:25 +00:00
else :
return [ ]
2020-07-29 06:53:51 +00:00
def _vnc_options ( self , port ) :
2015-06-25 01:09:17 +00:00
2020-07-29 06:53:51 +00:00
if port :
vnc_port = port - 5900 # subtract by 5900 to get the display number
2015-06-25 01:09:17 +00:00
return [ " -vnc " , " {} : {} " . format ( self . _manager . port_manager . console_host , vnc_port ) ]
else :
return [ ]
2020-07-29 06:53:51 +00:00
def _spice_options ( self , port ) :
2017-06-16 08:03:33 +00:00
2020-07-29 06:53:51 +00:00
if port :
2017-12-07 18:02:41 +00:00
console_host = self . _manager . port_manager . console_host
2020-06-09 05:36:41 +00:00
if console_host == " 0.0.0.0 " :
if socket . has_ipv6 :
# to fix an issue with Qemu when IPv4 is not enabled
# see https://github.com/GNS3/gns3-gui/issues/2352
# FIXME: consider making this more global (not just for Qemu + SPICE)
console_host = " :: "
else :
raise QemuError ( " IPv6 must be enabled in order to use the SPICE console " )
2017-06-16 08:03:33 +00:00
return [ " -spice " ,
2020-07-29 06:53:51 +00:00
" addr= {} ,port= {} ,disable-ticketing " . format ( console_host , port ) ,
2017-06-22 10:56:28 +00:00
" -vga " , " qxl " ]
2017-06-16 08:03:33 +00:00
else :
return [ ]
2020-07-29 06:53:51 +00:00
def _spice_with_agent_options ( self , port ) :
2018-03-25 07:36:14 +00:00
2020-07-29 06:53:51 +00:00
spice_options = self . _spice_options ( port )
if spice_options :
2018-03-25 07:36:14 +00:00
# agent options (mouse/screen)
agent_options = [ " -device " , " virtio-serial " ,
" -chardev " , " spicevmc,id=vdagent,debug=0,name=vdagent " ,
" -device " , " virtserialport,chardev=vdagent,name=com.redhat.spice.0 " ]
spice_options . extend ( agent_options )
# folder sharing options
folder_sharing_options = [ " -chardev " , " spiceport,name=org.spice-space.webdav.0,id=charchannel0 " ,
" -device " , " virtserialport,chardev=charchannel0,id=channel0,name=org.spice-space.webdav.0 " ]
spice_options . extend ( folder_sharing_options )
return spice_options
2020-07-29 06:53:51 +00:00
def _console_options ( self ) :
if self . _console_type == " telnet " and self . _wrap_console :
return self . _serial_options ( self . _internal_console_port , self . console )
elif self . _console_type == " vnc " :
return self . _vnc_options ( self . console )
elif self . _console_type == " spice " :
return self . _spice_options ( self . console )
elif self . _console_type == " spice+agent " :
return self . _spice_with_agent_options ( self . console )
elif self . _console_type != " none " :
raise QemuError ( " Console type {} is unknown " . format ( self . _console_type ) )
def _aux_options ( self ) :
if self . _aux_type != " none " and self . _aux_type == self . _console_type :
raise QemuError ( " Auxiliary console type {} cannot be the same as console type " . format ( self . _aux_type ) )
if self . _aux_type == " telnet " and self . _wrap_aux :
return self . _serial_options ( self . _internal_aux_port , self . aux )
elif self . _aux_type == " vnc " :
return self . _vnc_options ( self . aux )
elif self . _aux_type == " spice " :
return self . _spice_options ( self . aux )
elif self . _aux_type == " spice+agent " :
return self . _spice_with_agent_options ( self . aux )
elif self . _aux_type != " none " :
raise QemuError ( " Auxiliary console type {} is unknown " . format ( self . _aux_type ) )
return [ ]
2015-02-19 10:33:25 +00:00
def _monitor_options ( self ) :
if self . _monitor :
2015-03-10 06:34:57 +00:00
return [ " -monitor " , " tcp: {} : {} ,server,nowait " . format ( self . _monitor_host , self . _monitor ) ]
2015-02-19 10:33:25 +00:00
else :
return [ ]
2015-07-27 14:19:15 +00:00
def _get_qemu_img ( self ) :
"""
Search the qemu - img binary in the same binary of the qemu binary
2018-03-30 12:28:22 +00:00
for avoiding version incompatibility .
2015-07-27 14:19:15 +00:00
: returns : qemu - img path or raise an error
"""
2015-02-19 10:33:25 +00:00
qemu_img_path = " "
2015-02-19 15:46:57 +00:00
qemu_path_dir = os . path . dirname ( self . qemu_path )
2015-02-19 10:33:25 +00:00
try :
for f in os . listdir ( qemu_path_dir ) :
if f . startswith ( " qemu-img " ) :
qemu_img_path = os . path . join ( qemu_path_dir , f )
except OSError as e :
raise QemuError ( " Error while looking for qemu-img in {} : {} " . format ( qemu_path_dir , e ) )
if not qemu_img_path :
raise QemuError ( " Could not find qemu-img in {} " . format ( qemu_path_dir ) )
2015-07-27 14:19:15 +00:00
return qemu_img_path
2018-10-15 10:05:49 +00:00
async def _qemu_img_exec ( self , command ) :
2017-11-17 11:13:34 +00:00
self . _qemu_img_stdout_file = os . path . join ( self . working_dir , " qemu-img.log " )
log . info ( " logging to {} " . format ( self . _qemu_img_stdout_file ) )
2019-10-09 09:02:30 +00:00
command_string = " " . join ( shlex_quote ( s ) for s in command )
2017-11-16 07:54:37 +00:00
log . info ( " Executing qemu-img with: {} " . format ( command_string ) )
2017-11-17 11:13:34 +00:00
with open ( self . _qemu_img_stdout_file , " w " , encoding = " utf-8 " ) as fd :
2018-10-15 10:05:49 +00:00
process = await asyncio . create_subprocess_exec ( * command , stdout = fd , stderr = subprocess . STDOUT , cwd = self . working_dir )
retcode = await process . wait ( )
2017-11-16 07:54:37 +00:00
log . info ( " {} returned with {} " . format ( self . _get_qemu_img ( ) , retcode ) )
return retcode
2020-12-30 20:36:38 +00:00
async def _create_linked_clone ( self , disk_name , disk_image , disk ) :
try :
qemu_img_path = self . _get_qemu_img ( )
command = [ qemu_img_path , " create " , " -o " , " backing_file= {} " . format ( disk_image ) , " -f " , " qcow2 " , disk ]
retcode = await self . _qemu_img_exec ( command )
if retcode :
stdout = self . read_qemu_img_stdout ( )
raise QemuError ( " Could not create ' {} ' disk image: qemu-img returned with {} \n {} " . format ( disk_name ,
retcode ,
stdout ) )
except ( OSError , subprocess . SubprocessError ) as e :
stdout = self . read_qemu_img_stdout ( )
raise QemuError ( " Could not create ' {} ' disk image: {} \n {} " . format ( disk_name , e , stdout ) )
2020-04-15 18:50:59 +00:00
async def _mcopy ( self , image , * args ) :
2020-04-06 10:56:00 +00:00
try :
2020-04-15 18:50:59 +00:00
# read offset of first partition from MBR
with open ( image , " rb " ) as img_file :
mbr = img_file . read ( 512 )
part_type , offset , signature = struct . unpack ( " <450xB3xL52xH " , mbr )
if signature != 0xAA55 :
2020-06-28 07:21:57 +00:00
raise OSError ( " mcopy failure: {} : invalid MBR " . format ( image ) )
2020-04-15 18:50:59 +00:00
if part_type not in ( 1 , 4 , 6 , 11 , 12 , 14 ) :
2020-06-28 07:21:57 +00:00
raise OSError ( " mcopy failure: {} : invalid partition type {:02X} "
. format ( image , part_type ) )
2020-04-15 18:50:59 +00:00
part_image = image + " @@ {} S " . format ( offset )
process = await asyncio . create_subprocess_exec (
" mcopy " , " -i " , part_image , * args ,
stdin = subprocess . DEVNULL ,
stdout = subprocess . PIPE , stderr = subprocess . STDOUT ,
cwd = self . working_dir )
2020-04-06 10:56:00 +00:00
( stdout , _ ) = await process . communicate ( )
retcode = process . returncode
except ( OSError , subprocess . SubprocessError ) as e :
2020-06-28 07:21:57 +00:00
raise OSError ( " mcopy failure: {} " . format ( e ) )
2020-04-06 10:56:00 +00:00
if retcode != 0 :
stdout = stdout . decode ( " utf-8 " ) . rstrip ( )
if stdout :
2020-06-28 07:21:57 +00:00
raise OSError ( " mcopy failure: {} " . format ( stdout ) )
2020-04-06 10:56:00 +00:00
else :
2020-06-28 07:21:57 +00:00
raise OSError ( " mcopy failure: return code {} " . format ( retcode ) )
2020-04-06 10:56:00 +00:00
async def _export_config ( self ) :
disk_name = getattr ( self , " config_disk_name " )
2020-04-15 18:50:59 +00:00
if not disk_name :
return
disk = os . path . join ( self . working_dir , disk_name )
if not os . path . exists ( disk ) :
2020-04-06 10:56:00 +00:00
return
config_dir = os . path . join ( self . working_dir , " configs " )
zip_file = os . path . join ( self . working_dir , " config.zip " )
try :
os . mkdir ( config_dir )
2020-07-03 09:31:17 +00:00
await self . _mcopy ( disk , " -s " , " -m " , " -n " , " -- " , " ::/ " , config_dir )
2020-04-06 10:56:00 +00:00
if os . path . exists ( zip_file ) :
os . remove ( zip_file )
2020-06-28 07:21:57 +00:00
pack_zip ( zip_file , config_dir )
2020-04-06 10:56:00 +00:00
except OSError as e :
2020-06-28 07:21:57 +00:00
log . warning ( " Can ' t export config: {} " . format ( e ) )
self . project . emit ( " log.warning " , { " message " : " {} : Can ' t export config: {} " . format ( self . _name , e ) } )
2020-07-03 09:31:17 +00:00
shutil . rmtree ( config_dir , ignore_errors = True )
2020-04-06 10:56:00 +00:00
async def _import_config ( self ) :
disk_name = getattr ( self , " config_disk_name " )
zip_file = os . path . join ( self . working_dir , " config.zip " )
if not disk_name or not os . path . exists ( zip_file ) :
return
config_dir = os . path . join ( self . working_dir , " configs " )
disk = os . path . join ( self . working_dir , disk_name )
2020-07-03 09:31:17 +00:00
disk_tmp = disk + " .tmp "
2020-04-06 10:56:00 +00:00
try :
os . mkdir ( config_dir )
2020-07-03 09:31:17 +00:00
shutil . copyfile ( getattr ( self , " config_disk_image " ) , disk_tmp )
2020-04-07 12:11:00 +00:00
unpack_zip ( zip_file , config_dir )
2020-04-06 10:56:00 +00:00
config_files = [ os . path . join ( config_dir , fname )
for fname in os . listdir ( config_dir ) ]
if config_files :
2020-07-03 09:31:17 +00:00
await self . _mcopy ( disk_tmp , " -s " , " -m " , " -o " , " -- " , * config_files , " ::/ " )
os . replace ( disk_tmp , disk )
2020-04-06 10:56:00 +00:00
except OSError as e :
2020-06-28 07:21:57 +00:00
log . warning ( " Can ' t import config: {} " . format ( e ) )
self . project . emit ( " log.warning " , { " message " : " {} : Can ' t import config: {} " . format ( self . _name , e ) } )
2020-07-03 09:31:17 +00:00
if os . path . exists ( disk_tmp ) :
os . remove ( disk_tmp )
os . remove ( zip_file )
shutil . rmtree ( config_dir , ignore_errors = True )
2020-04-06 10:56:00 +00:00
2021-02-18 07:44:35 +00:00
async def _disk_interface_options ( self , disk , disk_index , interface , format = None ) :
2020-04-06 10:56:00 +00:00
options = [ ]
extra_drive_options = " "
if format :
extra_drive_options + = " ,format= {} " . format ( format )
2020-10-12 07:34:49 +00:00
# From Qemu man page: if the filename contains comma, you must double it
# (for instance, "file=my,,file" to use file "my,file").
disk = disk . replace ( " , " , " ,, " )
2020-04-06 10:56:00 +00:00
if interface == " sata " :
# special case, sata controller doesn't exist in Qemu
options . extend ( [ " -device " , ' ahci,id=ahci {} ' . format ( disk_index ) ] )
options . extend ( [ " -drive " , ' file= {} ,if=none,id=drive {} ,index= {} ,media=disk {} ' . format ( disk , disk_index , disk_index , extra_drive_options ) ] )
2021-02-14 04:09:02 +00:00
qemu_version = await self . manager . get_qemu_version ( self . qemu_path )
if qemu_version and parse_version ( qemu_version ) > = parse_version ( " 4.2.0 " ) :
# The ‘ ide-drive’ device is deprecated since version 4.2.0
# https://qemu.readthedocs.io/en/latest/system/deprecated.html#ide-drive-since-4-2
options . extend ( [ " -device " , ' ide-hd,drive=drive {} ,bus=ahci {} .0,id=drive {} ' . format ( disk_index , disk_index , disk_index ) ] )
else :
options . extend ( [ " -device " , ' ide-drive,drive=drive {} ,bus=ahci {} .0,id=drive {} ' . format ( disk_index , disk_index , disk_index ) ] )
2020-04-06 10:56:00 +00:00
elif interface == " nvme " :
options . extend ( [ " -drive " , ' file= {} ,if=none,id=drive {} ,index= {} ,media=disk {} ' . format ( disk , disk_index , disk_index , extra_drive_options ) ] )
options . extend ( [ " -device " , ' nvme,drive=drive {} ,serial= {} ' . format ( disk_index , disk_index ) ] )
elif interface == " scsi " :
options . extend ( [ " -device " , ' virtio-scsi-pci,id=scsi {} ' . format ( disk_index ) ] )
options . extend ( [ " -drive " , ' file= {} ,if=none,id=drive {} ,index= {} ,media=disk {} ' . format ( disk , disk_index , disk_index , extra_drive_options ) ] )
options . extend ( [ " -device " , ' scsi-hd,drive=drive {} ' . format ( disk_index ) ] )
#elif interface == "sd":
# options.extend(["-drive", 'file={},id=drive{},index={}{}'.format(disk, disk_index, disk_index, extra_drive_options)])
# options.extend(["-device", 'sd-card,drive=drive{},id=drive{}'.format(disk_index, disk_index, disk_index)])
else :
options . extend ( [ " -drive " , ' file= {} ,if= {} ,index= {} ,media=disk,id=drive {} {} ' . format ( disk , interface , disk_index , disk_index , extra_drive_options ) ] )
return options
2018-10-15 10:05:49 +00:00
async def _disk_options ( self ) :
2015-07-27 14:19:15 +00:00
options = [ ]
qemu_img_path = self . _get_qemu_img ( )
2016-04-05 10:35:07 +00:00
drives = [ " a " , " b " , " c " , " d " ]
2015-03-10 17:50:30 +00:00
2016-04-05 10:35:07 +00:00
for disk_index , drive in enumerate ( drives ) :
2020-08-18 01:24:11 +00:00
# prioritize config disk over harddisk d
if drive == ' d ' and self . _create_config_disk :
continue
2016-04-05 10:35:07 +00:00
disk_image = getattr ( self , " _hd {} _disk_image " . format ( drive ) )
if not disk_image :
continue
2020-08-15 06:44:16 +00:00
interface = getattr ( self , " hd {} _disk_interface " . format ( drive ) )
# fail-safe: use "ide" if there is a disk image and no interface type has been explicitly configured
if interface == " none " :
2020-08-15 07:05:31 +00:00
interface = " ide "
setattr ( self , " hd {} _disk_interface " . format ( drive ) , interface )
2015-03-10 17:50:30 +00:00
2020-08-15 06:44:16 +00:00
disk_name = " hd " + drive
2016-04-05 10:35:07 +00:00
if not os . path . isfile ( disk_image ) or not os . path . exists ( disk_image ) :
if os . path . islink ( disk_image ) :
raise QemuError ( " {} disk image ' {} ' linked to ' {} ' is not accessible " . format ( disk_name , disk_image , os . path . realpath ( disk_image ) ) )
2015-03-10 17:50:30 +00:00
else :
2016-04-05 10:35:07 +00:00
raise QemuError ( " {} disk image ' {} ' is not accessible " . format ( disk_name , disk_image ) )
2017-11-16 07:54:37 +00:00
else :
try :
# check for corrupt disk image
2018-10-15 10:05:49 +00:00
retcode = await self . _qemu_img_exec ( [ qemu_img_path , " check " , disk_image ] )
2017-11-16 07:54:37 +00:00
if retcode == 3 :
# image has leaked clusters, but is not corrupted, let's try to fix it
log . warning ( " Qemu image {} has leaked clusters " . format ( disk_image ) )
2018-10-15 10:05:49 +00:00
if ( await self . _qemu_img_exec ( [ qemu_img_path , " check " , " -r " , " leaks " , " {} " . format ( disk_image ) ] ) ) == 3 :
2017-11-16 07:54:37 +00:00
self . project . emit ( " log.warning " , { " message " : " Qemu image ' {} ' has leaked clusters and could not be fixed " . format ( disk_image ) } )
elif retcode == 2 :
# image is corrupted, let's try to fix it
log . warning ( " Qemu image {} is corrupted " . format ( disk_image ) )
2018-10-15 10:05:49 +00:00
if ( await self . _qemu_img_exec ( [ qemu_img_path , " check " , " -r " , " all " , " {} " . format ( disk_image ) ] ) ) == 2 :
2017-11-16 07:54:37 +00:00
self . project . emit ( " log.warning " , { " message " : " Qemu image ' {} ' is corrupted and could not be fixed " . format ( disk_image ) } )
except ( OSError , subprocess . SubprocessError ) as e :
2017-11-17 11:13:34 +00:00
stdout = self . read_qemu_img_stdout ( )
raise QemuError ( " Could not check ' {} ' disk image: {} \n {} " . format ( disk_name , e , stdout ) )
2017-11-16 07:54:37 +00:00
2016-10-24 19:39:35 +00:00
if self . linked_clone :
2016-04-05 10:35:07 +00:00
disk = os . path . join ( self . working_dir , " {} _disk.qcow2 " . format ( disk_name ) )
if not os . path . exists ( disk ) :
# create the disk
2020-12-30 20:36:38 +00:00
await self . _create_linked_clone ( disk_name , disk_image , disk )
2016-04-05 10:41:26 +00:00
else :
2017-11-16 07:54:37 +00:00
# The disk exists we check if the clone works
2016-04-05 10:41:26 +00:00
try :
qcow2 = Qcow2 ( disk )
2018-10-15 10:05:49 +00:00
await qcow2 . rebase ( qemu_img_path , disk_image )
2016-04-05 10:41:26 +00:00
except ( Qcow2Error , OSError ) as e :
2017-11-16 07:54:37 +00:00
raise QemuError ( " Could not use qcow2 disk image ' {} ' for {} {} " . format ( disk_image , disk_name , e ) )
2016-04-05 10:41:26 +00:00
2015-10-19 01:19:27 +00:00
else :
2016-04-05 10:35:07 +00:00
disk = disk_image
2017-01-10 13:11:40 +00:00
2021-02-14 04:09:02 +00:00
options . extend ( await self . _disk_interface_options ( disk , disk_index , interface ) )
2020-04-06 10:56:00 +00:00
# config disk
disk_image = getattr ( self , " config_disk_image " )
2020-08-14 08:27:24 +00:00
if disk_image and self . _create_config_disk :
2020-08-18 01:24:11 +00:00
disk_name = getattr ( self , " config_disk_name " )
disk = os . path . join ( self . working_dir , disk_name )
if self . hdd_disk_interface == " none " :
# use the HDA interface type if none has been configured for HDD
self . hdd_disk_interface = getattr ( self , " hda_disk_interface " , " none " )
await self . _import_config ( )
disk_exists = os . path . exists ( disk )
if not disk_exists :
try :
shutil . copyfile ( disk_image , disk )
disk_exists = True
except OSError as e :
log . warning ( " Could not create ' {} ' disk image: {} " . format ( disk_name , e ) )
if disk_exists :
2021-02-14 04:09:02 +00:00
options . extend ( await self . _disk_interface_options ( disk , 3 , self . hdd_disk_interface , " raw " ) )
2015-03-10 17:50:30 +00:00
2015-02-19 10:33:25 +00:00
return options
2018-10-15 10:05:49 +00:00
async def resize_disk ( self , drive_name , extend ) :
2018-03-26 11:05:49 +00:00
if self . is_running ( ) :
raise QemuError ( " Cannot resize {} while the VM is running " . format ( drive_name ) )
if self . linked_clone :
disk_image_path = os . path . join ( self . working_dir , " {} _disk.qcow2 " . format ( drive_name ) )
2020-12-30 20:36:38 +00:00
if not os . path . exists ( disk_image_path ) :
disk_image = getattr ( self , " _ {} _disk_image " . format ( drive_name ) )
await self . _create_linked_clone ( drive_name , disk_image , disk_image_path )
2018-03-26 11:05:49 +00:00
else :
disk_image_path = getattr ( self , " {} _disk_image " . format ( drive_name ) )
if not os . path . exists ( disk_image_path ) :
raise QemuError ( " Disk path ' {} ' does not exist " . format ( disk_image_path ) )
qemu_img_path = self . _get_qemu_img ( )
2018-10-15 10:05:49 +00:00
await self . manager . resize_disk ( qemu_img_path , disk_image_path , extend )
2018-03-26 11:05:49 +00:00
2015-08-03 05:02:02 +00:00
def _cdrom_option ( self ) :
options = [ ]
if self . _cdrom_image :
if not os . path . isfile ( self . _cdrom_image ) or not os . path . exists ( self . _cdrom_image ) :
if os . path . islink ( self . _cdrom_image ) :
raise QemuError ( " cdrom image ' {} ' linked to ' {} ' is not accessible " . format ( self . _cdrom_image , os . path . realpath ( self . _cdrom_image ) ) )
else :
raise QemuError ( " cdrom image ' {} ' is not accessible " . format ( self . _cdrom_image ) )
if self . _hdc_disk_image :
raise QemuError ( " You cannot use a disk image on hdc disk and a CDROM image at the same time " )
2020-10-11 03:32:22 +00:00
options . extend ( [ " -cdrom " , self . _cdrom_image . replace ( " , " , " ,, " ) ] )
2015-08-03 05:02:02 +00:00
return options
2016-12-08 15:18:30 +00:00
def _bios_option ( self ) :
options = [ ]
if self . _bios_image :
if not os . path . isfile ( self . _bios_image ) or not os . path . exists ( self . _bios_image ) :
if os . path . islink ( self . _bios_image ) :
raise QemuError ( " bios image ' {} ' linked to ' {} ' is not accessible " . format ( self . _bios_image , os . path . realpath ( self . _bios_image ) ) )
else :
raise QemuError ( " bios image ' {} ' is not accessible " . format ( self . _bios_image ) )
2020-10-11 03:32:22 +00:00
options . extend ( [ " -bios " , self . _bios_image . replace ( " , " , " ,, " ) ] )
2016-12-08 15:18:30 +00:00
return options
2015-02-19 10:33:25 +00:00
def _linux_boot_options ( self ) :
options = [ ]
if self . _initrd :
if not os . path . isfile ( self . _initrd ) or not os . path . exists ( self . _initrd ) :
if os . path . islink ( self . _initrd ) :
raise QemuError ( " initrd file ' {} ' linked to ' {} ' is not accessible " . format ( self . _initrd , os . path . realpath ( self . _initrd ) ) )
else :
raise QemuError ( " initrd file ' {} ' is not accessible " . format ( self . _initrd ) )
2020-10-11 03:32:22 +00:00
options . extend ( [ " -initrd " , self . _initrd . replace ( " , " , " ,, " ) ] )
2015-02-19 10:33:25 +00:00
if self . _kernel_image :
if not os . path . isfile ( self . _kernel_image ) or not os . path . exists ( self . _kernel_image ) :
if os . path . islink ( self . _kernel_image ) :
raise QemuError ( " kernel image ' {} ' linked to ' {} ' is not accessible " . format ( self . _kernel_image , os . path . realpath ( self . _kernel_image ) ) )
else :
raise QemuError ( " kernel image ' {} ' is not accessible " . format ( self . _kernel_image ) )
2020-10-11 03:32:22 +00:00
options . extend ( [ " -kernel " , self . _kernel_image . replace ( " , " , " ,, " ) ] )
2015-02-19 10:33:25 +00:00
if self . _kernel_command_line :
options . extend ( [ " -append " , self . _kernel_command_line ] )
return options
2018-10-15 10:05:49 +00:00
async def _network_options ( self ) :
2015-02-19 10:33:25 +00:00
network_options = [ ]
2015-05-06 20:59:01 +00:00
network_options . extend ( [ " -net " , " none " ] ) # we do not want any user networking back-end if no adapter is connected.
2015-10-04 12:41:39 +00:00
patched_qemu = False
if self . _legacy_networking :
2020-01-24 11:06:24 +00:00
qemu_version = await self . manager . get_qemu_version ( self . qemu_path )
if qemu_version :
if parse_version ( qemu_version ) > = parse_version ( " 2.9.0 " ) :
raise QemuError ( " Qemu version 2.9.0 and later doesn ' t support legacy networking mode " )
if parse_version ( qemu_version ) < parse_version ( " 1.1.0 " ) :
# this is a patched Qemu if version is below 1.1.0
patched_qemu = True
2015-10-04 12:41:39 +00:00
2017-02-07 16:04:29 +00:00
# Each 32 PCI device we need to add a PCI bridge with max 9 bridges
pci_devices = 4 + len ( self . _ethernet_adapters ) # 4 PCI devices are use by default by qemu
2019-11-03 09:56:52 +00:00
pci_bridges = math . floor ( pci_devices / 32 )
pci_bridges_created = 0
if pci_bridges > = 1 :
2018-10-15 10:05:49 +00:00
qemu_version = await self . manager . get_qemu_version ( self . qemu_path )
2017-02-17 08:55:50 +00:00
if qemu_version and parse_version ( qemu_version ) < parse_version ( " 2.4.0 " ) :
2017-02-17 09:37:06 +00:00
raise QemuError ( " Qemu version 2.4 or later is required to run this VM with a large number of network adapters " )
2017-02-17 08:55:50 +00:00
2019-11-03 09:56:52 +00:00
pci_device_id = 4 + pci_bridges # Bridge consume PCI ports
2015-05-06 20:59:01 +00:00
for adapter_number , adapter in enumerate ( self . _ethernet_adapters ) :
2016-04-25 14:36:20 +00:00
mac = int_to_macaddress ( macaddress_to_int ( self . _mac_address ) + adapter_number )
2016-06-23 22:56:06 +00:00
2017-07-11 11:42:47 +00:00
# use a local UDP tunnel to connect to uBridge instead
if adapter_number not in self . _local_udp_tunnels :
self . _local_udp_tunnels [ adapter_number ] = self . _create_local_udp_tunnel ( )
nio = self . _local_udp_tunnels [ adapter_number ] [ 0 ]
2018-04-02 15:27:12 +00:00
custom_adapter = self . _get_custom_adapter_settings ( adapter_number )
adapter_type = custom_adapter . get ( " adapter_type " , self . _adapter_type )
custom_mac_address = custom_adapter . get ( " mac_address " )
if custom_mac_address :
mac = int_to_macaddress ( macaddress_to_int ( custom_mac_address ) )
2015-05-06 20:59:01 +00:00
if self . _legacy_networking :
# legacy QEMU networking syntax (-net)
if nio :
2018-04-02 15:27:12 +00:00
network_options . extend ( [ " -net " , " nic,vlan= {} ,macaddr= {} ,model= {} " . format ( adapter_number , mac , adapter_type ) ] )
2015-05-06 20:59:01 +00:00
if isinstance ( nio , NIOUDP ) :
2015-10-04 12:41:39 +00:00
if patched_qemu :
# use patched Qemu syntax
network_options . extend ( [ " -net " , " udp,vlan= {} ,name=gns3- {} ,sport= {} ,dport= {} ,daddr= {} " . format ( adapter_number ,
adapter_number ,
nio . lport ,
nio . rport ,
nio . rhost ) ] )
else :
# use UDP tunnel support added in Qemu 1.1.0
network_options . extend ( [ " -net " , " socket,vlan= {} ,name=gns3- {} ,udp= {} : {} ,localaddr= {} : {} " . format ( adapter_number ,
adapter_number ,
nio . rhost ,
nio . rport ,
2017-05-16 18:51:14 +00:00
" 127.0.0.1 " ,
2015-10-04 12:41:39 +00:00
nio . lport ) ] )
2018-03-19 09:26:12 +00:00
elif isinstance ( nio , NIOTAP ) :
network_options . extend ( [ " -net " , " tap,name=gns3- {} ,ifname= {} " . format ( adapter_number , nio . tap_device ) ] )
2015-05-06 20:59:01 +00:00
else :
2018-04-02 15:27:12 +00:00
network_options . extend ( [ " -net " , " nic,vlan= {} ,macaddr= {} ,model= {} " . format ( adapter_number , mac , adapter_type ) ] )
2015-05-06 20:59:01 +00:00
else :
# newer QEMU networking syntax
2018-04-02 15:27:12 +00:00
device_string = " {} ,mac= {} " . format ( adapter_type , mac )
2017-02-07 16:04:29 +00:00
bridge_id = math . floor ( pci_device_id / 32 )
if bridge_id > 0 :
2019-11-03 09:56:52 +00:00
if pci_bridges_created < bridge_id :
network_options . extend ( [ " -device " , " i82801b11-bridge,id=dmi_pci_bridge {bridge_id} " . format ( bridge_id = bridge_id ) ] )
network_options . extend ( [ " -device " , " pci-bridge,id=pci-bridge {bridge_id} ,bus=dmi_pci_bridge {bridge_id} ,chassis_nr=0x1,addr=0x {bridge_id} ,shpc=off " . format ( bridge_id = bridge_id ) ] )
pci_bridges_created + = 1
2017-02-07 16:04:29 +00:00
addr = pci_device_id % 32
device_string = " {} ,bus=pci-bridge {bridge_id} ,addr=0x {addr:02x} " . format ( device_string , bridge_id = bridge_id , addr = addr )
pci_device_id + = 1
2015-05-06 20:59:01 +00:00
if nio :
2017-02-07 16:04:29 +00:00
network_options . extend ( [ " -device " , " {} ,netdev=gns3- {} " . format ( device_string , adapter_number ) ] )
2015-05-06 20:59:01 +00:00
if isinstance ( nio , NIOUDP ) :
2015-04-27 20:38:15 +00:00
network_options . extend ( [ " -netdev " , " socket,id=gns3- {} ,udp= {} : {} ,localaddr= {} : {} " . format ( adapter_number ,
nio . rhost ,
nio . rport ,
2017-05-16 18:51:14 +00:00
" 127.0.0.1 " ,
2015-04-27 20:38:15 +00:00
nio . lport ) ] )
2018-03-19 09:26:12 +00:00
elif isinstance ( nio , NIOTAP ) :
network_options . extend ( [ " -netdev " , " tap,id=gns3- {} ,ifname= {} ,script=no,downscript=no " . format ( adapter_number , nio . tap_device ) ] )
2015-03-03 02:59:44 +00:00
else :
2017-02-07 16:04:29 +00:00
network_options . extend ( [ " -device " , device_string ] )
2015-02-19 10:33:25 +00:00
return network_options
2019-02-22 11:04:49 +00:00
async def _disable_graphics ( self ) :
2015-02-23 19:21:00 +00:00
"""
2019-02-17 11:03:36 +00:00
Disable graphics depending of the QEMU version
2015-02-23 19:21:00 +00:00
"""
2015-04-08 17:17:34 +00:00
2019-02-17 11:03:36 +00:00
if any ( opt in self . _options for opt in [ " -display " , " -nographic " , " -curses " , " -sdl " " -spice " , " -vnc " ] ) :
2015-02-23 19:21:00 +00:00
return [ ]
2019-02-22 11:04:49 +00:00
version = await self . manager . get_qemu_version ( self . qemu_path )
2019-02-17 11:03:36 +00:00
if version and parse_version ( version ) > = parse_version ( " 3.0 " ) :
return [ " -display " , " none " ]
else :
2017-11-18 09:22:29 +00:00
return [ " -nographic " ]
2015-02-23 19:21:00 +00:00
2018-10-15 10:05:49 +00:00
async def _run_with_hardware_acceleration ( self , qemu_path , options ) :
2015-08-07 14:49:45 +00:00
"""
2018-03-21 09:41:25 +00:00
Check if we can run Qemu with hardware acceleration
2015-08-07 14:49:45 +00:00
: param qemu_path : Path to qemu
: param options : String of qemu user options
2018-03-21 09:41:25 +00:00
: returns : Boolean True if we need to enable hardware acceleration
"""
2021-04-12 07:32:23 +00:00
enable_hardware_accel = self . manager . config . settings . Qemu . enable_hardware_acceleration
require_hardware_accel = self . manager . config . settings . Qemu . require_hardware_acceleration
2018-03-21 09:41:25 +00:00
if enable_hardware_accel and " -no-kvm " not in options and " -no-hax " not in options :
# Turn OFF hardware acceleration for non x86 architectures
2018-03-22 08:45:41 +00:00
if sys . platform . startswith ( " win " ) :
supported_binaries = [ " qemu-system-x86_64.exe " , " qemu-system-x86_64w.exe " , " qemu-system-i386.exe " , " qemu-system-i386w.exe " ]
else :
supported_binaries = [ " qemu-system-x86_64 " , " qemu-system-i386 " , " qemu-kvm " ]
if os . path . basename ( qemu_path ) not in supported_binaries :
2018-03-21 09:41:25 +00:00
if require_hardware_accel :
2018-03-22 08:45:41 +00:00
raise QemuError ( " Hardware acceleration can only be used with the following Qemu executables: {} " . format ( " , " . join ( supported_binaries ) ) )
2018-03-21 09:41:25 +00:00
else :
return False
2015-08-07 14:49:45 +00:00
2018-03-21 09:41:25 +00:00
if sys . platform . startswith ( " linux " ) and not os . path . exists ( " /dev/kvm " ) :
if require_hardware_accel :
2021-04-12 07:32:23 +00:00
raise QemuError ( " KVM acceleration cannot be used (/dev/kvm doesn ' t exist). It is possible to turn off KVM support in the gns3_server.conf by adding enable_hardware_acceleration = false to the [Qemu] section. " )
2018-03-21 09:41:25 +00:00
else :
return False
elif sys . platform . startswith ( " win " ) :
if require_hardware_accel :
# HAXM is only available starting with Qemu version 2.9.0
2018-10-15 10:05:49 +00:00
version = await self . manager . get_qemu_version ( self . qemu_path )
2018-03-21 09:41:25 +00:00
if version and parse_version ( version ) < parse_version ( " 2.9.0 " ) :
raise QemuError ( " HAXM acceleration can only be enable for Qemu version 2.9.0 and above (current version: {} ) " . format ( version ) )
# check if HAXM is installed
version = self . manager . get_haxm_windows_version ( )
2018-03-22 08:45:41 +00:00
if version is None :
2018-03-21 09:41:25 +00:00
raise QemuError ( " HAXM acceleration support is not installed on this host " )
log . info ( " HAXM support version {} detected " . format ( version ) )
2018-03-24 15:15:20 +00:00
# check if the HAXM service is running
from gns3server . utils . windows_service import check_windows_service_is_running
if not check_windows_service_is_running ( " intelhaxm " ) :
raise QemuError ( " Intel HAXM service is not running on this host " )
2017-11-19 05:39:37 +00:00
else :
return False
2018-03-21 09:41:25 +00:00
elif sys . platform . startswith ( " darwin " ) :
2018-10-15 10:05:49 +00:00
process = await asyncio . create_subprocess_shell ( " kextstat | grep com.intel.kext.intelhaxm " )
await process . wait ( )
2018-03-22 15:37:09 +00:00
if process . returncode != 0 :
if require_hardware_accel :
raise QemuError ( " HAXM acceleration support is not installed on this host (com.intel.kext.intelhaxm extension not loaded) " )
else :
return False
2015-08-07 14:49:45 +00:00
return True
return False
2018-10-15 10:05:49 +00:00
async def _clear_save_vm_stated ( self , snapshot_name = " GNS3_SAVED_STATE " ) :
2018-03-30 12:28:22 +00:00
drives = [ " a " , " b " , " c " , " d " ]
qemu_img_path = self . _get_qemu_img ( )
for disk_index , drive in enumerate ( drives ) :
disk_image = getattr ( self , " _hd {} _disk_image " . format ( drive ) )
if not disk_image :
continue
try :
if self . linked_clone :
disk = os . path . join ( self . working_dir , " hd {} _disk.qcow2 " . format ( drive ) )
else :
disk = disk_image
2018-04-06 06:19:54 +00:00
if not os . path . exists ( disk ) :
continue
2019-01-12 10:04:37 +00:00
output = await subprocess_check_output ( qemu_img_path , " info " , " --output=json " , disk )
2019-05-19 10:59:00 +00:00
if output :
try :
json_data = json . loads ( output )
except ValueError as e :
raise QemuError ( " Invalid JSON data returned by qemu-img while looking for the Qemu VM saved state snapshot: {} " . format ( e ) )
if " snapshots " in json_data :
for snapshot in json_data [ " snapshots " ] :
if snapshot [ " name " ] == snapshot_name :
# delete the snapshot
command = [ qemu_img_path , " snapshot " , " -d " , snapshot_name , disk ]
retcode = await self . _qemu_img_exec ( command )
if retcode :
stdout = self . read_qemu_img_stdout ( )
log . warning ( " Could not delete saved VM state from disk {} : {} " . format ( disk , stdout ) )
else :
log . info ( " Deleted saved VM state from disk {} " . format ( disk ) )
2018-03-30 12:28:22 +00:00
except subprocess . SubprocessError as e :
raise QemuError ( " Error while looking for the Qemu VM saved state snapshot: {} " . format ( e ) )
2018-10-15 10:05:49 +00:00
async def _saved_state_option ( self , snapshot_name = " GNS3_SAVED_STATE " ) :
2018-03-30 12:28:22 +00:00
drives = [ " a " , " b " , " c " , " d " ]
qemu_img_path = self . _get_qemu_img ( )
for disk_index , drive in enumerate ( drives ) :
disk_image = getattr ( self , " _hd {} _disk_image " . format ( drive ) )
if not disk_image :
continue
try :
if self . linked_clone :
disk = os . path . join ( self . working_dir , " hd {} _disk.qcow2 " . format ( drive ) )
else :
disk = disk_image
2018-04-06 06:19:54 +00:00
if not os . path . exists ( disk ) :
continue
2018-10-15 10:05:49 +00:00
output = await subprocess_check_output ( qemu_img_path , " info " , " --output=json " , disk )
2019-05-19 10:59:00 +00:00
if output :
try :
json_data = json . loads ( output )
except ValueError as e :
raise QemuError ( " Invalid JSON data returned by qemu-img while looking for the Qemu VM saved state snapshot: {} " . format ( e ) )
if " snapshots " in json_data :
for snapshot in json_data [ " snapshots " ] :
if snapshot [ " name " ] == snapshot_name :
log . info ( ' QEMU VM " {name} " [ {id} ] VM saved state detected (snapshot name: {snapshot} ) ' . format ( name = self . _name ,
id = self . id ,
snapshot = snapshot_name ) )
2020-10-11 03:32:22 +00:00
return [ " -loadvm " , snapshot_name . replace ( " , " , " ,, " ) ]
2019-05-19 10:59:00 +00:00
2018-03-30 12:28:22 +00:00
except subprocess . SubprocessError as e :
raise QemuError ( " Error while looking for the Qemu VM saved state snapshot: {} " . format ( e ) )
return [ ]
2018-10-15 10:05:49 +00:00
async def _build_command ( self ) :
2015-02-19 10:33:25 +00:00
"""
Command to start the QEMU process .
( to be passed to subprocess . Popen ( ) )
"""
2020-10-11 03:32:22 +00:00
vm_name = self . _name . replace ( " , " , " ,, " )
project_path = self . project . path . replace ( " , " , " ,, " )
2016-10-03 13:54:20 +00:00
additional_options = self . _options . strip ( )
2020-10-11 03:32:22 +00:00
additional_options = additional_options . replace ( " % vm-name % " , ' " ' + vm_name . replace ( ' " ' , ' \\ " ' ) + ' " ' )
2017-11-18 10:36:11 +00:00
additional_options = additional_options . replace ( " % vm-id % " , self . _id )
additional_options = additional_options . replace ( " % project-id % " , self . project . id )
2020-10-11 03:32:22 +00:00
additional_options = additional_options . replace ( " % project-path % " , ' " ' + project_path . replace ( ' " ' , ' \\ " ' ) + ' " ' )
2019-06-04 16:00:44 +00:00
additional_options = additional_options . replace ( " %g uest-cid % " , str ( self . _guest_cid ) )
if self . _console_type != " none " and self . _console :
2019-05-18 07:31:21 +00:00
additional_options = additional_options . replace ( " %c onsole-port % " , str ( self . _console ) )
2015-02-19 15:46:57 +00:00
command = [ self . qemu_path ]
2020-10-11 03:32:22 +00:00
command . extend ( [ " -name " , vm_name ] )
2015-08-07 15:08:10 +00:00
command . extend ( [ " -m " , " {} M " . format ( self . _ram ) ] )
2020-07-24 05:45:41 +00:00
# set the maximum number of the hotpluggable CPUs to match the number of CPUs to avoid issues.
maxcpus = self . _maxcpus
if self . _cpus > maxcpus :
maxcpus = self . _cpus
2020-10-12 07:34:49 +00:00
command . extend ( [ " -smp " , " cpus= {} ,maxcpus= {} ,sockets=1 " . format ( self . _cpus , maxcpus ) ] )
2018-10-15 10:05:49 +00:00
if ( await self . _run_with_hardware_acceleration ( self . qemu_path , self . _options ) ) :
2018-03-21 09:41:25 +00:00
if sys . platform . startswith ( " linux " ) :
command . extend ( [ " -enable-kvm " ] )
2018-10-15 10:05:49 +00:00
version = await self . manager . get_qemu_version ( self . qemu_path )
2018-03-21 09:41:25 +00:00
# Issue on some combo Intel CPU + KVM + Qemu 2.4.0
# https://github.com/GNS3/gns3-server/issues/685
if version and parse_version ( version ) > = parse_version ( " 2.4.0 " ) and self . platform == " x86_64 " :
command . extend ( [ " -machine " , " smm=off " ] )
elif sys . platform . startswith ( " win " ) or sys . platform . startswith ( " darwin " ) :
command . extend ( [ " -enable-hax " ] )
2015-08-03 05:02:02 +00:00
command . extend ( [ " -boot " , " order= {} " . format ( self . _boot_priority ) ] )
2016-12-08 15:18:30 +00:00
command . extend ( self . _bios_option ( ) )
command . extend ( self . _cdrom_option ( ) )
2018-10-15 10:05:49 +00:00
command . extend ( ( await self . _disk_options ( ) ) )
2015-02-19 10:33:25 +00:00
command . extend ( self . _linux_boot_options ( ) )
2016-10-03 13:54:20 +00:00
if " -uuid " not in additional_options :
command . extend ( [ " -uuid " , self . _id ] )
2020-07-29 06:53:51 +00:00
command . extend ( self . _console_options ( ) )
command . extend ( self . _aux_options ( ) )
2015-02-19 10:33:25 +00:00
command . extend ( self . _monitor_options ( ) )
2018-10-15 10:05:49 +00:00
command . extend ( ( await self . _network_options ( ) ) )
2018-03-30 14:18:44 +00:00
if self . on_close != " save_vm_state " :
2018-10-15 10:05:49 +00:00
await self . _clear_save_vm_stated ( )
2018-03-30 12:28:22 +00:00
else :
2018-10-15 10:05:49 +00:00
command . extend ( ( await self . _saved_state_option ( ) ) )
2019-02-17 11:03:36 +00:00
if self . _console_type == " telnet " :
2019-02-22 11:04:49 +00:00
command . extend ( ( await self . _disable_graphics ( ) ) )
2015-02-19 10:33:25 +00:00
if additional_options :
2015-03-31 20:14:08 +00:00
try :
command . extend ( shlex . split ( additional_options ) )
except ValueError as e :
2015-06-25 01:09:17 +00:00
raise QemuError ( " Invalid additional options: {} error {} " . format ( additional_options , e ) )
2015-02-19 10:33:25 +00:00
return command
2015-02-19 18:43:45 +00:00
def __json__ ( self ) :
2015-02-19 19:22:30 +00:00
answer = {
2015-02-19 18:43:45 +00:00
" project_id " : self . project . id ,
2016-05-11 17:35:36 +00:00
" node_id " : self . id ,
2017-10-02 08:41:57 +00:00
" node_directory " : self . working_path
2015-02-19 18:43:45 +00:00
}
2015-03-10 17:50:30 +00:00
# Qemu has a long list of options. The JSON schema is the single source of information
2020-10-31 05:32:21 +00:00
for field in Qemu . schema ( ) [ " properties " ] :
2015-02-19 19:22:30 +00:00
if field not in answer :
2015-06-17 15:11:25 +00:00
try :
answer [ field ] = getattr ( self , field )
except AttributeError :
pass
2018-11-19 08:53:43 +00:00
answer [ " hda_disk_image " ] = self . manager . get_relative_image_path ( self . _hda_disk_image , self . project . path )
2015-06-17 15:11:25 +00:00
answer [ " hda_disk_image_md5sum " ] = md5sum ( self . _hda_disk_image )
2018-11-19 08:53:43 +00:00
answer [ " hdb_disk_image " ] = self . manager . get_relative_image_path ( self . _hdb_disk_image , self . project . path )
2015-06-17 15:11:25 +00:00
answer [ " hdb_disk_image_md5sum " ] = md5sum ( self . _hdb_disk_image )
2018-11-19 08:53:43 +00:00
answer [ " hdc_disk_image " ] = self . manager . get_relative_image_path ( self . _hdc_disk_image , self . project . path )
2015-06-17 15:11:25 +00:00
answer [ " hdc_disk_image_md5sum " ] = md5sum ( self . _hdc_disk_image )
2018-11-19 08:53:43 +00:00
answer [ " hdd_disk_image " ] = self . manager . get_relative_image_path ( self . _hdd_disk_image , self . project . path )
2015-06-17 15:11:25 +00:00
answer [ " hdd_disk_image_md5sum " ] = md5sum ( self . _hdd_disk_image )
2018-11-19 08:53:43 +00:00
answer [ " cdrom_image " ] = self . manager . get_relative_image_path ( self . _cdrom_image , self . project . path )
2015-08-03 05:02:02 +00:00
answer [ " cdrom_image_md5sum " ] = md5sum ( self . _cdrom_image )
2018-11-19 08:53:43 +00:00
answer [ " bios_image " ] = self . manager . get_relative_image_path ( self . _bios_image , self . project . path )
2016-12-08 15:18:30 +00:00
answer [ " bios_image_md5sum " ] = md5sum ( self . _bios_image )
2018-11-19 08:53:43 +00:00
answer [ " initrd " ] = self . manager . get_relative_image_path ( self . _initrd , self . project . path )
2015-06-17 15:11:25 +00:00
answer [ " initrd_md5sum " ] = md5sum ( self . _initrd )
2018-11-19 08:53:43 +00:00
answer [ " kernel_image " ] = self . manager . get_relative_image_path ( self . _kernel_image , self . project . path )
2015-06-17 15:11:25 +00:00
answer [ " kernel_image_md5sum " ] = md5sum ( self . _kernel_image )
2015-02-19 19:22:30 +00:00
return answer