2015-02-19 12:33:25 +02: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 20:17:34 +03:00
QEMU VM management ( creates command line , processes , files etc . ) in
order to run a QEMU VM .
2015-02-19 12:33:25 +02:00
"""
import sys
import os
2015-06-10 16:49:24 +03:00
import re
2017-02-07 18:04:29 +02:00
import math
2015-02-19 12:33:25 +02:00
import shutil
import shlex
2015-02-19 17:46:57 +02:00
import asyncio
2015-03-24 06:52:02 +02:00
import socket
2015-06-03 07:33:38 +03:00
import gns3server
2017-02-07 18:04:29 +02:00
import subprocess
2018-03-22 19:07:32 +02:00
import time
2018-03-30 15:28:22 +03:00
import json
2015-02-19 12:33:25 +02:00
2016-05-02 18:13:23 +03:00
from gns3server . utils import parse_version
2018-01-29 15:20:48 +02:00
from gns3server . utils . asyncio import subprocess_check_output , cancellable_wait_run_in_executor
2015-02-19 12:33:25 +02:00
from . qemu_error import QemuError
from . . adapters . ethernet_adapter import EthernetAdapter
2015-02-24 04:00:34 +02:00
from . . nios . nio_udp import NIOUDP
2015-04-27 23:38:15 +03:00
from . . nios . nio_tap import NIOTAP
2016-05-11 20:35:36 +03:00
from . . base_node import BaseNode
2015-06-10 16:49:24 +03:00
from . . . schemas . qemu import QEMU_OBJECT_SCHEMA , QEMU_PLATFORMS
2015-03-04 17:01:56 +02:00
from . . . utils . asyncio import monitor_process
2015-06-17 18:11:25 +03:00
from . . . utils . images import md5sum
2016-04-05 13:41:26 +03:00
from . qcow2 import Qcow2 , Qcow2Error
2016-04-25 17:36:20 +03:00
from . . . utils import macaddress_to_int , int_to_macaddress
2015-02-19 12:33:25 +02:00
import logging
log = logging . getLogger ( __name__ )
2016-05-11 20:35:36 +03:00
class QemuVM ( BaseNode ) :
2015-02-19 12:33:25 +02:00
module_name = ' qemu '
"""
QEMU VM implementation .
2015-04-08 20:17:34 +03:00
: param name : Qemu VM name
2016-05-11 20:35:36 +03:00
: param node_id : Node identifier
2015-02-19 12:33:25 +02:00
: param project : Project instance
2015-04-08 20:17:34 +03:00
: param manager : Manager instance
2015-02-19 12:33:25 +02:00
: param console : TCP console port
2018-03-24 14:11:21 +03:00
: param console_type : Console type
2015-02-19 12:33:25 +02:00
: param qemu_path : path to the QEMU binary
2015-06-10 16:49:24 +03:00
: param platform : Platform to emulate
2015-02-19 12:33:25 +02:00
"""
2016-05-11 20:35:36 +03:00
def __init__ ( self , name , node_id , project , manager , linked_clone = True , qemu_path = None , console = None , console_type = " telnet " , platform = None ) :
2015-02-19 12:33:25 +02:00
2018-03-15 09:17:39 +02:00
super ( ) . __init__ ( name , node_id , project , manager , console = console , console_type = console_type , linked_clone = linked_clone , wrap_console = True )
2015-03-16 20:45:21 +02:00
server_config = manager . config . get_section_config ( " Server " )
self . _host = server_config . get ( " host " , " 127.0.0.1 " )
self . _monitor_host = server_config . get ( " monitor_host " , " 127.0.0.1 " )
2015-02-19 12:33:25 +02:00
self . _process = None
self . _cpulimit_process = None
2015-03-24 06:52:02 +02:00
self . _monitor = None
2015-02-19 12:33:25 +02:00
self . _stdout_file = " "
2017-11-17 13:13:34 +02:00
self . _qemu_img_stdout_file = " "
2015-08-27 17:06:11 +03:00
self . _execute_lock = asyncio . Lock ( )
2016-06-24 01:56:06 +03:00
self . _local_udp_tunnels = { }
2015-02-19 12:33:25 +02:00
2015-04-08 20:17:34 +03:00
# QEMU VM settings
2015-06-10 16:49:24 +03:00
if qemu_path :
try :
self . qemu_path = qemu_path
except QemuError as e :
2016-05-18 12:23:45 +03: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 16:49:24 +03:00
if platform :
self . platform = platform
else :
2016-01-28 17:14:26 +02:00
self . qemu_path = os . path . basename ( qemu_path )
2015-06-10 16:49:24 +03:00
else :
self . platform = platform
2015-02-19 12:33:25 +02:00
self . _hda_disk_image = " "
self . _hdb_disk_image = " "
2015-03-10 19:50:30 +02:00
self . _hdc_disk_image = " "
self . _hdd_disk_image = " "
2015-08-03 08:02:02 +03:00
self . _hda_disk_interface = " ide "
self . _hdb_disk_interface = " ide "
self . _hdc_disk_interface = " ide "
self . _hdd_disk_interface = " ide "
self . _cdrom_image = " "
2016-12-08 17:18:30 +02:00
self . _bios_image = " "
2015-08-03 08:02:02 +03:00
self . _boot_priority = " c "
2015-06-08 07:18:41 +03:00
self . _mac_address = " "
2015-02-19 12:33:25 +02:00
self . _options = " "
self . _ram = 256
2015-08-06 02:17:55 +03:00
self . _cpus = 1
2015-02-19 12:33:25 +02:00
self . _ethernet_adapters = [ ]
self . _adapter_type = " e1000 "
self . _initrd = " "
self . _kernel_image = " "
self . _kernel_command_line = " "
self . _legacy_networking = False
2018-03-30 17:18:44 +03:00
self . _on_close = " power_off "
2015-02-19 12:33:25 +02:00
self . _cpu_throttling = 0 # means no CPU throttling
self . _process_priority = " low "
2015-06-08 07:18:41 +03:00
self . mac_address = " " # this will generate a MAC address
2015-02-19 12:33:25 +02:00
self . adapters = 1 # creates 1 adapter by default
2015-04-08 20:17:34 +03:00
log . info ( ' QEMU VM " {name} " [ {id} ] has been created ' . format ( name = self . _name , id = self . _id ) )
2015-02-19 12:33:25 +02: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 17:46:57 +02:00
if qemu_path and os . pathsep not in qemu_path :
2016-09-27 19:01:50 +03:00
if sys . platform . startswith ( " win " ) and " .exe " not in qemu_path . lower ( ) :
qemu_path + = " w.exe "
2015-08-27 19:27:17 +03: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 17:46:57 +02:00
2015-06-10 16:49:24 +03: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 14:47:12 +03:00
qemu_bin = os . path . basename ( qemu_path )
qemu_bin = re . sub ( r ' (w)? \ .(exe|EXE)$ ' , ' ' , qemu_bin )
2016-01-14 18:40:58 +02: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 )
2015-08-07 17:49:45 +03:00
if self . _platform . split ( " . " ) [ 0 ] not in QEMU_PLATFORMS :
2015-06-10 16:49:24 +03: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 09:17:39 +02:00
2015-02-19 17:46:57 +02:00
if qemu_path is None :
2015-08-27 19:27:17 +03:00
raise QemuError ( " QEMU binary path is not set " )
2015-02-19 17:46:57 +02: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 16:49:24 +03:00
@property
def platform ( self ) :
"""
Return the current platform
"""
return self . _platform
@platform.setter
def platform ( self , platform ) :
2018-03-15 09:17:39 +02:00
2015-06-10 16:49:24 +03: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 12:33:25 +02:00
2016-10-24 22:39:35 +03: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 09:17:39 +02:00
2016-10-24 22:39:35 +03:00
value = self . manager . get_abs_image_path ( value )
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 12:33:25 +02: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 09:17:39 +02:00
2016-10-24 22:39:35 +03:00
self . _disk_setter ( " hda_disk_image " , hda_disk_image )
2015-02-19 12:33:25 +02: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 22:39:35 +03:00
self . _disk_setter ( " hdb_disk_image " , hdb_disk_image )
2015-02-19 12:33:25 +02:00
2015-03-10 19:50:30 +02: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 22:39:35 +03:00
self . _disk_setter ( " hdc_disk_image " , hdc_disk_image )
2015-03-10 19:50:30 +02: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 22:39:35 +03:00
self . _disk_setter ( " hdd_disk_image " , hdd_disk_image )
2015-03-10 19:50:30 +02:00
2015-08-03 08:02:02 +03: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 02:44:23 +02:00
return self . _hdb_disk_interface
2015-08-03 08:02:02 +03: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
"""
self . _cdrom_image = self . manager . get_abs_image_path ( cdrom_image )
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 ) )
2016-12-08 17:18:30 +02: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
"""
self . _bios_image = self . manager . get_abs_image_path ( bios_image )
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 08:02:02 +03: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 15:50:33 +03:00
@property
def ethernet_adapters ( self ) :
"""
Return the list of ethernet adapters of the node
"""
return self . _ethernet_adapters
2015-02-19 12:33:25 +02:00
@property
def adapters ( self ) :
"""
2015-04-08 20:17:34 +03:00
Returns the number of Ethernet adapters for this QEMU VM .
2015-02-19 12:33:25 +02:00
: returns : number of adapters
"""
return len ( self . _ethernet_adapters )
@adapters.setter
def adapters ( self , adapters ) :
"""
2015-04-08 20:17:34 +03:00
Sets the number of Ethernet adapters for this QEMU VM .
2015-02-19 12:33:25 +02:00
: param adapters : number of adapters
"""
self . _ethernet_adapters . clear ( )
2015-04-08 20:17:34 +03:00
for adapter_number in range ( 0 , adapters ) :
2015-02-19 12:33:25 +02:00
self . _ethernet_adapters . append ( EthernetAdapter ( ) )
2015-04-08 20:17:34 +03: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 12:33:25 +02:00
@property
def adapter_type ( self ) :
"""
2015-04-08 20:17:34 +03:00
Returns the adapter type for this QEMU VM .
2015-02-19 12:33:25 +02:00
: returns : adapter type ( string )
"""
return self . _adapter_type
@adapter_type.setter
def adapter_type ( self , adapter_type ) :
"""
2015-04-08 20:17:34 +03:00
Sets the adapter type for this QEMU VM .
2015-02-19 12:33:25 +02:00
: param adapter_type : adapter type ( string )
"""
self . _adapter_type = adapter_type
2015-04-08 20:17:34 +03: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 12:33:25 +02:00
2015-06-03 23:52:49 +03: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 07:18:41 +03:00
if not mac_address :
2016-05-30 20:52:08 +03:00
# use the node UUID to generate a random MAC address
2018-04-18 11:13:59 +03: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 07:18:41 +03:00
else :
2016-05-30 20:52:08 +03:00
self . _mac_address = mac_address
2015-06-03 23:52:49 +03:00
log . info ( ' QEMU VM " {name} " [ {id} ]: MAC address changed to {mac_addr} ' . format ( name = self . _name ,
id = self . _id ,
2017-10-14 14:55:16 +03:00
mac_addr = self . _mac_address ) )
2015-06-03 23:52:49 +03:00
2015-02-19 12:33:25 +02: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 20:17:34 +03:00
log . info ( ' QEMU VM " {name} " [ {id} ] has enabled legacy networking ' . format ( name = self . _name , id = self . _id ) )
2015-02-19 12:33:25 +02:00
else :
2015-04-08 20:17:34 +03:00
log . info ( ' QEMU VM " {name} " [ {id} ] has disabled legacy networking ' . format ( name = self . _name , id = self . _id ) )
2015-02-19 12:33:25 +02:00
self . _legacy_networking = legacy_networking
2015-06-03 07:33:38 +03:00
@property
2018-03-30 17:18:44 +03:00
def on_close ( self ) :
2015-06-03 07:33:38 +03:00
"""
2018-03-30 17:18:44 +03:00
Returns the action to execute when the VM is stopped / closed
2015-06-03 07:33:38 +03:00
2018-03-30 17:18:44 +03:00
: returns : string
2018-03-30 15:28:22 +03:00
"""
2018-03-30 17:18:44 +03:00
return self . _on_close
2018-03-30 15:28:22 +03:00
2018-03-30 17:18:44 +03:00
@on_close.setter
def on_close ( self , on_close ) :
2018-03-30 15:28:22 +03:00
"""
2018-03-30 17:18:44 +03:00
Sets the action to execute when the VM is stopped / closed
2018-03-30 15:28:22 +03:00
2018-03-30 17:18:44 +03:00
: param on_close : string
2018-03-30 15:28:22 +03:00
"""
2018-03-30 17:18:44 +03: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 15:28:22 +03:00
2015-02-19 12:33:25 +02: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 20:17:34 +03: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 12:33:25 +02: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 20:17:34 +03: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 12:33:25 +02: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 20:17:34 +03: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 12:33:25 +02:00
self . _ram = ram
2015-08-06 02:17:55 +03: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
2015-02-19 12:33:25 +02: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 20:17:34 +03: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 10:40:38 +03:00
2015-07-22 07:58:28 +03:00
if not sys . platform . startswith ( " linux " ) :
if " -no-kvm " in options :
2015-07-28 00:31:42 +03:00
options = options . replace ( " -no-kvm " , " " )
2015-07-22 07:58:28 +03:00
if " -enable-kvm " in options :
2015-07-28 00:31:42 +03:00
options = options . replace ( " -enable-kvm " , " " )
2018-03-21 11:41:25 +02: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 10:40:38 +03:00
self . _options = options . strip ( )
2015-02-19 12:33:25 +02: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
"""
2016-10-03 16:54:20 +03:00
initrd = self . manager . get_abs_image_path ( initrd )
2015-03-10 05:57:21 +02:00
2015-04-08 20:17:34 +03:00
log . info ( ' QEMU VM " {name} " [ {id} ] has set the QEMU initrd path to {initrd} ' . format ( name = self . _name ,
id = self . _id ,
initrd = initrd ) )
2016-01-22 20:46:05 +02:00
if " asa " in initrd :
2018-03-15 09:17:39 +02: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 12:33:25 +02: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
"""
2016-09-27 19:01:50 +03:00
kernel_image = self . manager . get_abs_image_path ( kernel_image )
2015-04-08 20:17:34 +03: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 12:33:25 +02: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 20:17:34 +03: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 12:33:25 +02:00
self . _kernel_command_line = kernel_command_line
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def _set_process_priority ( self ) :
"""
Changes the process priority
"""
2015-12-22 14:15:28 +02:00
if self . _process_priority == " normal " :
return
2015-02-19 12:33:25 +02: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 14:15:28 +02:00
log . info ( " Setting QEMU VM {} priority class to {} " . format ( self . _name , self . _process_priority ) )
2015-02-19 12:33:25 +02: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 22:23:35 +02: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 12:33:25 +02: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 :
2015-02-19 17:46:57 +02:00
process = yield from asyncio . create_subprocess_exec ( ' renice ' , ' -n ' , str ( priority ) , ' -p ' , str ( self . _process . pid ) )
yield from process . wait ( )
2015-02-19 12:33:25 +02:00
except ( OSError , subprocess . SubprocessError ) as e :
2015-04-08 20:17:34 +03:00
log . error ( ' Could not change process priority for QEMU VM " {} " : {} ' . format ( self . _name , e ) )
2015-02-19 12:33:25 +02:00
def _stop_cpulimit ( self ) :
"""
Stops the cpulimit process .
"""
2015-02-26 11:18:01 +02:00
if self . _cpulimit_process and self . _cpulimit_process . returncode is None :
2015-02-19 12:33:25 +02:00
self . _cpulimit_process . kill ( )
try :
self . _process . wait ( 3 )
except subprocess . TimeoutExpired :
2015-04-08 20:17:34 +03:00
log . error ( " Could not kill cpulimit process {} " . format ( self . _cpulimit_process . pid ) )
2015-02-19 12:33:25 +02: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 17:46:57 +02:00
subprocess . Popen ( [ cpulimit_exec , " --lazy " , " --pid= {} " . format ( self . _process . pid ) , " --limit= {} " . format ( self . _cpu_throttling ) ] , cwd = self . working_dir )
2015-02-19 12:33:25 +02: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-01-29 15:20:48 +02:00
@asyncio.coroutine
def create ( self ) :
"""
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.
yield from cancellable_wait_run_in_executor ( md5sum , self . _hda_disk_image )
yield from cancellable_wait_run_in_executor ( md5sum , self . _hdb_disk_image )
yield from cancellable_wait_run_in_executor ( md5sum , self . _hdc_disk_image )
yield from cancellable_wait_run_in_executor ( md5sum , self . _hdd_disk_image )
super ( QemuVM , self ) . create ( )
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def start ( self ) :
"""
Starts this QEMU VM .
"""
2015-08-27 17:06:11 +03:00
with ( yield from self . _execute_lock ) :
if self . is_running ( ) :
# resume the VM if it is paused
yield from self . resume ( )
return
2015-02-19 12:33:25 +02:00
2016-11-28 14:18:04 +02:00
if self . _manager . config . get_section_config ( " Qemu " ) . getboolean ( " monitor " , True ) :
2015-03-24 06:52:02 +02:00
try :
2016-11-28 14:18:04 +02: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 11:36:36 +03:00
sock . setsockopt ( socket . SOL_SOCKET , socket . SO_REUSEADDR , 1 )
2016-11-28 14:18:04 +02: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 )
command = yield from self . _build_command ( )
command_string = " " . join ( shlex . quote ( s ) for s in command )
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 )
self . _process = yield from asyncio . create_subprocess_exec ( * command ,
stdout = fd ,
stderr = subprocess . STDOUT ,
cwd = self . working_dir )
log . info ( ' QEMU VM " {} " started PID= {} ' . format ( self . _name , self . _process . pid ) )
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 ) )
yield from self . _set_process_priority ( )
if self . _cpu_throttling :
self . _set_cpu_throttling ( )
2018-03-21 11:41:25 +02:00
if " -enable-kvm " in command_string or " -enable-hax " in command_string :
2016-11-28 14:18:04 +02:00
self . _hw_virtualization = True
2018-03-19 09:22:10 +02:00
yield from self . _start_ubridge ( )
2018-03-22 19:07:32 +02:00
set_link_commands = [ ]
2018-03-19 09:22:10 +02:00
for adapter_number , adapter in enumerate ( self . _ethernet_adapters ) :
nio = adapter . get_nio ( 0 )
if nio :
yield from self . add_ubridge_udp_connection ( " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
2018-03-19 11:26:12 +02:00
if nio . suspend :
2018-03-22 19:07:32 +02:00
set_link_commands . append ( " set_link gns3- {} off " . format ( adapter_number ) )
2018-03-19 09:22:10 +02:00
else :
2018-03-22 19:07:32 +02:00
set_link_commands . append ( " set_link gns3- {} off " . format ( adapter_number ) )
yield from self . _control_vm_commands ( set_link_commands )
2018-03-19 09:22:10 +02:00
2016-11-28 14:18:04 +02:00
try :
yield from self . start_wrap_console ( )
except OSError as e :
2018-03-23 11:44:16 +03:00
raise QemuError ( " Could not start Telnet QEMU console {} \n " . format ( e ) )
2015-07-22 07:58:28 +03:00
2017-03-03 19:40:26 +02:00
@asyncio.coroutine
2015-03-04 17:01:56 +02:00
def _termination_callback ( self , returncode ) :
"""
2015-05-13 22:53:42 +03:00
Called when the process has stopped .
2015-03-04 17:01:56 +02:00
: param returncode : Process returncode
"""
2015-05-13 22:53:42 +03:00
2015-03-04 17:01:56 +02:00
if self . started :
2015-05-13 22:53:42 +03:00
log . info ( " QEMU process has stopped, return code: %d " , returncode )
2017-03-03 19:40:26 +02:00
yield from self . stop ( )
2016-11-16 14:06:43 +02:00
# A return code of 1 seem fine on Windows
if returncode != 0 and ( returncode != 1 or not sys . platform . startswith ( " win " ) ) :
2015-06-26 16:10:41 +03:00
self . project . emit ( " log.error " , { " message " : " QEMU process has stopped, return code: {} \n {} " . format ( returncode , self . read_stdout ( ) ) } )
2015-03-04 17:01:56 +02:00
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def stop ( self ) :
"""
Stops this QEMU VM .
"""
2016-06-24 01:56:06 +03:00
yield from self . _stop_ubridge ( )
2015-08-27 17:06:11 +03:00
with ( yield from self . _execute_lock ) :
# 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 15:28:22 +03:00
2018-03-30 17:18:44 +03:00
if self . on_close == " save_vm_state " :
2018-03-30 15:28:22 +03:00
yield from self . _control_vm ( " stop " )
yield from self . _control_vm ( " savevm GNS3_SAVED_STATE " )
wait_for_savevm = 120
while wait_for_savevm :
yield from asyncio . sleep ( 1 )
status = yield from self . _saved_state_option ( )
wait_for_savevm - = 1
if status != [ ] :
break
2018-03-30 17:18:44 +03:00
if self . on_close == " shutdown_signal " :
2015-08-27 17:06:11 +03:00
yield from self . _control_vm ( " system_powerdown " )
yield from gns3server . utils . asyncio . wait_for_process_termination ( self . _process , timeout = 30 )
else :
self . _process . terminate ( )
yield from gns3server . utils . asyncio . wait_for_process_termination ( self . _process , timeout = 3 )
2015-12-07 11:48:57 +02:00
except ProcessLookupError :
pass
2015-08-27 17:06:11 +03:00
except asyncio . TimeoutError :
2016-12-12 10:16:29 +02:00
if self . _process :
try :
self . _process . kill ( )
except ProcessLookupError :
pass
if self . _process . returncode is None :
2018-03-15 09:17:39 +02:00
log . warning ( ' QEMU VM " {} " PID= {} is still running ' . format ( self . _name , self . _process . pid ) )
2015-08-27 17:06:11 +03:00
self . _process = None
self . _stop_cpulimit ( )
2018-03-30 17:18:44 +03:00
if self . on_close != " save_vm_state " :
2018-03-30 15:28:22 +03:00
yield from self . _clear_save_vm_stated ( )
2016-11-09 10:47:48 +02:00
yield from super ( ) . stop ( )
2015-02-19 12:33:25 +02:00
2018-03-22 19:07:32 +02:00
@asyncio.coroutine
def _open_qemu_monitor_connection_vm ( self , timeout = 10 ) :
"""
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 :
yield from asyncio . sleep ( 0.01 )
try :
log . debug ( " Connecting to Qemu monitor on {} : {} " . format ( self . _monitor_host , self . _monitor ) )
reader , writer = yield from asyncio . open_connection ( self . _monitor_host , self . _monitor )
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
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-21 01:15:56 +02:00
def _control_vm ( self , command , expected = None ) :
2015-02-19 12:33:25 +02: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 20:17:34 +03:00
: param expected : An array of expected strings
2015-02-19 12:33:25 +02:00
2015-04-08 20:17:34 +03:00
: returns : result of the command ( matched object or None )
2015-02-19 12:33:25 +02:00
"""
result = None
if self . is_running ( ) and self . _monitor :
2018-03-22 19:07:32 +02:00
log . info ( " Execute QEMU monitor command: {} " . format ( command ) )
reader , writer = yield from self . _open_qemu_monitor_connection_vm ( )
if reader is None and writer is None :
2015-02-19 12:33:25 +02:00
return result
2018-03-22 19:07:32 +02:00
2015-02-19 12:33:25 +02:00
try :
2015-02-21 01:15:56 +02:00
writer . write ( command . encode ( ' ascii ' ) + b " \n " )
2015-02-19 12:33:25 +02:00
except OSError as e :
2018-03-15 09:17:39 +02:00
log . warning ( " Could not write to QEMU monitor: {} " . format ( e ) )
2015-02-21 01:15:56 +02:00
writer . close ( )
2015-02-19 12:33:25 +02:00
return result
if expected :
try :
2015-02-21 01:15:56 +02:00
while result is None :
line = yield from reader . readline ( )
if not line :
break
for expect in expected :
if expect in line :
2015-04-27 06:19:39 +03:00
result = line . decode ( " utf-8 " ) . strip ( )
2015-02-21 01:15:56 +02:00
break
2015-02-19 12:33:25 +02:00
except EOFError as e :
2018-03-15 09:17:39 +02:00
log . warning ( " Could not read from QEMU monitor: {} " . format ( e ) )
2015-02-21 01:15:56 +02:00
writer . close ( )
2015-02-19 12:33:25 +02:00
return result
2018-03-22 19:07:32 +02:00
@asyncio.coroutine
def _control_vm_commands ( self , commands ) :
"""
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 :
reader , writer = yield from self . _open_qemu_monitor_connection_vm ( )
if reader is None and writer is None :
return
for command in commands :
log . info ( " Execute QEMU monitor command: {} " . format ( command ) )
try :
writer . write ( command . encode ( ' ascii ' ) + b " \n " )
except OSError as e :
log . warning ( " Could not write to QEMU monitor: {} " . format ( e ) )
writer . close ( )
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
def close ( self ) :
2015-04-08 20:17:34 +03:00
"""
Closes this QEMU VM .
"""
2015-02-19 17:46:57 +02:00
2016-02-29 11:38:30 +02:00
if not ( yield from super ( ) . close ( ) ) :
return False
2018-03-30 17:18:44 +03:00
self . on_close = " power_off "
2015-02-19 17:46:57 +02:00
yield from self . stop ( )
2015-10-08 05:48:36 +03: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-24 01:56:06 +03: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 = { }
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def _get_vm_status ( self ) :
"""
2015-04-08 20:17:34 +03:00
Returns this VM suspend status .
2015-02-19 12:33:25 +02:00
2015-03-17 12:02:14 +02:00
Status are extracted from :
https : / / github . com / qemu / qemu / blob / master / qapi - schema . json #L152
2015-02-19 12:33:25 +02:00
: returns : status ( string )
"""
2015-03-17 12:02:14 +02:00
result = yield from self . _control_vm ( " info status " , [
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 04:40:19 +02:00
if result is None :
return result
2017-01-17 17:03:12 +02: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 12:33:25 +02:00
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def suspend ( self ) :
"""
Suspends this QEMU VM .
"""
2015-03-24 06:52:02 +02:00
if self . is_running ( ) :
vm_status = yield from self . _get_vm_status ( )
if vm_status is None :
raise QemuError ( " Suspending a QEMU VM is not supported " )
2017-01-17 17:03:12 +02:00
elif vm_status == " running " or vm_status == " prelaunch " :
2015-03-24 06:52:02 +02:00
yield from self . _control_vm ( " stop " )
2016-05-14 05:41:58 +03:00
self . status = " suspended "
2015-03-24 06:52:02 +02: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 12:33:25 +02:00
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def reload ( self ) :
"""
Reloads this QEMU VM .
"""
2015-02-19 17:46:57 +02:00
yield from self . _control_vm ( " system_reset " )
2015-02-19 12:33:25 +02:00
log . debug ( " QEMU VM has been reset " )
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def resume ( self ) :
"""
Resumes this QEMU VM .
"""
2015-02-19 17:46:57 +02:00
vm_status = yield from self . _get_vm_status ( )
2015-03-23 04:40:19 +02:00
if vm_status is None :
raise QemuError ( " Resuming a QEMU VM is not supported " )
elif vm_status == " paused " :
2015-02-19 17:46:57 +02:00
yield from self . _control_vm ( " cont " )
2015-02-19 12:33:25 +02: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 ) )
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-04-08 20:17:34 +03:00
def adapter_add_nio_binding ( self , adapter_number , nio ) :
2015-02-19 12:33:25 +02:00
"""
Adds a port NIO binding .
2015-04-08 20:17:34 +03:00
: param adapter_number : adapter number
2015-03-07 05:08:00 +02:00
: param nio : NIO instance to add to the adapter
2015-02-19 12:33:25 +02:00
"""
try :
2015-04-08 20:17:34 +03:00
adapter = self . _ethernet_adapters [ adapter_number ]
2015-02-19 12:33:25 +02:00
except IndexError :
2015-04-08 20:17:34 +03:00
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2015-02-19 12:33:25 +02:00
2017-07-12 11:07:21 +03:00
if self . is_running ( ) :
try :
yield from self . add_ubridge_udp_connection ( " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
2018-03-19 09:22:10 +02:00
yield from self . _control_vm ( " set_link gns3- {} on " . format ( adapter_number ) )
2017-12-21 09:55:49 +02:00
except ( IndexError , KeyError ) :
2017-07-12 11:07:21 +03:00
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2015-02-19 12:33:25 +02:00
adapter . add_nio ( 0 , nio )
2015-04-08 20:17:34 +03:00
log . info ( ' QEMU VM " {name} " [ {id} ]: {nio} added to adapter {adapter_number} ' . format ( name = self . _name ,
2015-04-14 19:46:55 +03:00
id = self . _id ,
nio = nio ,
adapter_number = adapter_number ) )
2017-07-12 15:50:33 +03:00
@asyncio.coroutine
def adapter_update_nio_binding ( self , adapter_number , nio ) :
"""
Update a port NIO binding .
: param adapter_number : adapter number
: param nio : NIO instance to add to the adapter
"""
if self . is_running ( ) :
try :
2018-03-19 09:22:10 +02:00
yield from self . update_ubridge_udp_connection ( " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
2018-03-19 11:26:12 +02:00
if nio . suspend :
yield from self . _control_vm ( " set_link gns3- {} off " . format ( adapter_number ) )
else :
yield from self . _control_vm ( " set_link gns3- {} on " . format ( adapter_number ) )
2017-07-12 15:50:33 +03: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 12:33:25 +02:00
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-04-08 20:17:34 +03:00
def adapter_remove_nio_binding ( self , adapter_number ) :
2015-02-19 12:33:25 +02:00
"""
Removes a port NIO binding .
2015-04-08 20:17:34 +03:00
: param adapter_number : adapter number
2015-02-19 12:33:25 +02:00
: returns : NIO instance
"""
try :
2015-04-08 20:17:34 +03:00
adapter = self . _ethernet_adapters [ adapter_number ]
2015-02-19 12:33:25 +02:00
except IndexError :
2015-04-08 20:17:34 +03:00
raise QemuError ( ' Adapter {adapter_number} does not exist on QEMU VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2015-02-19 12:33:25 +02:00
2017-07-12 11:07:21 +03:00
if self . is_running ( ) :
2018-03-19 09:22:10 +02:00
yield from self . _control_vm ( " set_link gns3- {} off " . format ( adapter_number ) )
2017-07-12 11:07:21 +03:00
yield from self . _ubridge_send ( " bridge delete {name} " . format ( name = " QEMU- {} - {} " . format ( self . _id , adapter_number ) ) )
2015-02-19 12:33:25 +02:00
nio = adapter . get_nio ( 0 )
2015-03-07 05:08:00 +02:00
if isinstance ( nio , NIOUDP ) :
2015-03-22 01:19:12 +02:00
self . manager . port_manager . release_udp_port ( nio . lport , self . _project )
2015-02-19 12:33:25 +02:00
adapter . remove_nio ( 0 )
2016-06-24 01:56:06 +03:00
2015-04-08 20:17:34 +03: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 12:33:25 +02:00
return nio
2016-06-24 01:56:06 +03:00
@asyncio.coroutine
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
"""
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 ) )
if nio . capturing :
raise QemuError ( " Packet capture is already activated on adapter {adapter_number} " . format ( adapter_number = adapter_number ) )
nio . startPacketCapture ( output_file )
2016-12-14 13:01:34 +02:00
if self . ubridge :
2016-06-24 01:56:06 +03:00
yield from self . _ubridge_send ( ' bridge start_capture {name} " {output_file} " ' . format ( name = " QEMU- {} - {} " . format ( self . _id , adapter_number ) ,
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 ) )
def stop_capture ( self , adapter_number ) :
"""
Stops a packet capture .
: param adapter_number : adapter number
"""
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 ) )
nio . stopPacketCapture ( )
2016-12-14 13:01:34 +02:00
if self . ubridge :
2016-06-24 01:56:06 +03:00
yield from self . _ubridge_send ( ' bridge stop_capture {name} ' . format ( name = " QEMU- {} - {} " . format ( self . _id , adapter_number ) ) )
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 11:40:11 +03:00
2015-02-19 12:33:25 +02:00
@property
def started ( self ) :
"""
Returns either this QEMU VM has been started or not .
: returns : boolean
"""
2015-03-04 17:01:56 +02:00
return self . status == " started "
2015-02-19 12:33:25 +02: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 20:58:34 +03:00
with open ( self . _stdout_file , " rb " ) as file :
output = file . read ( ) . decode ( " utf-8 " , errors = " replace " )
2015-02-19 12:33:25 +02:00
except OSError as e :
2017-11-17 13:13:34 +02: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 12:33:25 +02:00
return output
def is_running ( self ) :
"""
Checks if the QEMU process is running
: returns : True or False
"""
2015-02-19 17:46:57 +02:00
if self . _process :
2015-02-25 16:42:01 +02:00
if self . _process . returncode is None :
return True
else :
self . _process = None
2015-02-19 12:33:25 +02:00
return False
def command ( self ) :
"""
Returns the QEMU command line .
: returns : QEMU command line ( string )
"""
return " " . join ( self . _build_command ( ) )
2018-03-24 14:11:21 +03: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 )
2015-02-19 12:33:25 +02:00
def _serial_options ( self ) :
if self . _console :
2016-11-09 10:47:48 +02:00
return [ " -serial " , " telnet:127.0.0.1: {} ,server,nowait " . format ( self . _internal_console_port ) ]
2015-02-19 12:33:25 +02:00
else :
return [ ]
2015-06-25 04:09:17 +03:00
def _vnc_options ( self ) :
if self . _console :
2015-07-04 01:06:25 +03:00
vnc_port = self . _console - 5900 # subtract by 5900 to get the display number
2015-06-25 04:09:17 +03:00
return [ " -vnc " , " {} : {} " . format ( self . _manager . port_manager . console_host , vnc_port ) ]
else :
return [ ]
2017-06-16 11:03:33 +03:00
def _spice_options ( self ) :
if self . _console :
2017-12-07 20:02:41 +02:00
console_host = self . _manager . port_manager . console_host
if console_host == " 0.0.0.0 " and 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 = " :: "
2017-06-16 11:03:33 +03:00
return [ " -spice " ,
2017-12-07 20:02:41 +02:00
" addr= {} ,port= {} ,disable-ticketing " . format ( console_host , self . _console ) ,
2017-06-22 13:56:28 +03:00
" -vga " , " qxl " ]
2017-06-16 11:03:33 +03:00
else :
return [ ]
2018-03-25 10:36:14 +03:00
def _spice_with_agent_options ( self ) :
spice_options = self . _spice_options ( )
if self . _console :
# 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
2015-02-19 12:33:25 +02:00
def _monitor_options ( self ) :
if self . _monitor :
2015-03-10 08:34:57 +02:00
return [ " -monitor " , " tcp: {} : {} ,server,nowait " . format ( self . _monitor_host , self . _monitor ) ]
2015-02-19 12:33:25 +02:00
else :
return [ ]
2015-07-27 17:19:15 +03:00
def _get_qemu_img ( self ) :
"""
Search the qemu - img binary in the same binary of the qemu binary
2018-03-30 15:28:22 +03:00
for avoiding version incompatibility .
2015-07-27 17:19:15 +03:00
: returns : qemu - img path or raise an error
"""
2015-02-19 12:33:25 +02:00
qemu_img_path = " "
2015-02-19 17:46:57 +02:00
qemu_path_dir = os . path . dirname ( self . qemu_path )
2015-02-19 12:33:25 +02: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 17:19:15 +03:00
return qemu_img_path
2017-11-16 09:54:37 +02:00
@asyncio.coroutine
def _qemu_img_exec ( self , command ) :
2017-11-17 13:13:34 +02: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 ) )
2017-11-16 09:54:37 +02:00
command_string = " " . join ( shlex . quote ( s ) for s in command )
log . info ( " Executing qemu-img with: {} " . format ( command_string ) )
2017-11-17 13:13:34 +02:00
with open ( self . _qemu_img_stdout_file , " w " , encoding = " utf-8 " ) as fd :
process = yield from asyncio . create_subprocess_exec ( * command , stdout = fd , stderr = subprocess . STDOUT , cwd = self . working_dir )
2017-11-16 09:54:37 +02:00
retcode = yield from process . wait ( )
log . info ( " {} returned with {} " . format ( self . _get_qemu_img ( ) , retcode ) )
return retcode
2015-07-27 17:19:15 +03:00
@asyncio.coroutine
def _disk_options ( self ) :
options = [ ]
qemu_img_path = self . _get_qemu_img ( )
2016-04-05 13:35:07 +03:00
drives = [ " a " , " b " , " c " , " d " ]
2015-03-10 19:50:30 +02:00
2016-04-05 13:35:07 +03:00
for disk_index , drive in enumerate ( drives ) :
disk_image = getattr ( self , " _hd {} _disk_image " . format ( drive ) )
interface = getattr ( self , " hd {} _disk_interface " . format ( drive ) )
2015-02-19 12:33:25 +02:00
2016-04-05 13:35:07 +03:00
if not disk_image :
continue
disk_name = " hd " + drive
2015-03-10 19:50:30 +02:00
2016-04-05 13:35:07 +03: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 19:50:30 +02:00
else :
2016-04-05 13:35:07 +03:00
raise QemuError ( " {} disk image ' {} ' is not accessible " . format ( disk_name , disk_image ) )
2017-11-16 09:54:37 +02:00
else :
try :
# check for corrupt disk image
retcode = yield from self . _qemu_img_exec ( [ qemu_img_path , " check " , disk_image ] )
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 ) )
if ( yield from self . _qemu_img_exec ( [ qemu_img_path , " check " , " -r " , " leaks " , " {} " . format ( disk_image ) ] ) ) == 3 :
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 ) )
if ( yield from self . _qemu_img_exec ( [ qemu_img_path , " check " , " -r " , " all " , " {} " . format ( disk_image ) ] ) ) == 2 :
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 13:13:34 +02:00
stdout = self . read_qemu_img_stdout ( )
raise QemuError ( " Could not check ' {} ' disk image: {} \n {} " . format ( disk_name , e , stdout ) )
2017-11-16 09:54:37 +02:00
2016-10-24 22:39:35 +03:00
if self . linked_clone :
2016-04-05 13:35:07 +03:00
disk = os . path . join ( self . working_dir , " {} _disk.qcow2 " . format ( disk_name ) )
if not os . path . exists ( disk ) :
# create the disk
2015-10-19 04:19:27 +03:00
try :
2017-10-01 19:47:16 +03:00
command = [ qemu_img_path , " create " , " -o " , " backing_file= {} " . format ( disk_image ) , " -f " , " qcow2 " , disk ]
2017-11-16 09:54:37 +02:00
retcode = yield from self . _qemu_img_exec ( command )
if retcode :
2017-11-17 13:13:34 +02:00
stdout = self . read_qemu_img_stdout ( )
raise QemuError ( " Could not create ' {} ' disk image: qemu-img returned with {} \n {} " . format ( disk_name ,
retcode ,
stdout ) )
2015-10-19 04:19:27 +03:00
except ( OSError , subprocess . SubprocessError ) as e :
2017-11-17 13:13:34 +02:00
stdout = self . read_qemu_img_stdout ( )
raise QemuError ( " Could not create ' {} ' disk image: {} \n {} " . format ( disk_name , e , stdout ) )
2016-04-05 13:41:26 +03:00
else :
2017-11-16 09:54:37 +02:00
# The disk exists we check if the clone works
2016-04-05 13:41:26 +03:00
try :
qcow2 = Qcow2 ( disk )
yield from qcow2 . rebase ( qemu_img_path , disk_image )
except ( Qcow2Error , OSError ) as e :
2017-11-16 09:54:37 +02:00
raise QemuError ( " Could not use qcow2 disk image ' {} ' for {} {} " . format ( disk_image , disk_name , e ) )
2016-04-05 13:41:26 +03:00
2015-10-19 04:19:27 +03:00
else :
2016-04-05 13:35:07 +03:00
disk = disk_image
2017-01-10 15:11:40 +02:00
if interface == " sata " :
# special case, sata controller doesn't exist in Qemu
options . extend ( [ " -device " , ' ahci,id=ahci {} ,bus=pci. {} ' . format ( disk_index , disk_index ) ] )
options . extend ( [ " -drive " , ' file= {} ,if=none,id=drive-sata-disk {} ,index= {} ,media=disk ' . format ( disk , disk_index , disk_index ) ] )
2017-04-07 12:25:14 +03:00
options . extend ( [ " -device " , ' ide-drive,drive=drive-sata-disk {} ,bus=ahci {} .0,id=drive-sata-disk {} ' . format ( disk_index , disk_index , disk_index ) ] )
2017-01-10 15:11:40 +02:00
else :
options . extend ( [ " -drive " , ' file= {} ,if= {} ,index= {} ,media=disk ' . format ( disk , interface , disk_index ) ] )
2015-03-10 19:50:30 +02:00
2015-02-19 12:33:25 +02:00
return options
2018-03-26 14:05:49 +03:00
@asyncio.coroutine
def resize_disk ( self , drive_name , extend ) :
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 ) )
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 ( )
yield from self . manager . resize_disk ( qemu_img_path , disk_image_path , extend )
2015-08-03 08:02:02 +03: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 " )
options . extend ( [ " -cdrom " , self . _cdrom_image ] )
return options
2016-12-08 17:18:30 +02: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 ) )
options . extend ( [ " -bios " , self . _bios_image ] )
return options
2015-02-19 12:33:25 +02: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 ) )
options . extend ( [ " -initrd " , self . _initrd ] )
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 ) )
options . extend ( [ " -kernel " , self . _kernel_image ] )
if self . _kernel_command_line :
options . extend ( [ " -append " , self . _kernel_command_line ] )
return options
2015-10-04 16:00:47 +03:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def _network_options ( self ) :
network_options = [ ]
2015-05-06 23:59:01 +03:00
network_options . extend ( [ " -net " , " none " ] ) # we do not want any user networking back-end if no adapter is connected.
2015-10-04 15:41:39 +03:00
patched_qemu = False
if self . _legacy_networking :
version = yield from self . manager . get_qemu_version ( self . qemu_path )
if version and parse_version ( version ) < parse_version ( " 1.1.0 " ) :
# this is a patched Qemu if version is below 1.1.0
patched_qemu = True
2017-02-07 18:04:29 +02: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
bridge_id = 0
for bridge_id in range ( 1 , math . floor ( pci_devices / 32 ) + 1 ) :
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 ) ] )
2017-02-17 10:55:50 +02:00
if bridge_id > 1 :
qemu_version = yield from self . manager . get_qemu_version ( self . qemu_path )
if qemu_version and parse_version ( qemu_version ) < parse_version ( " 2.4.0 " ) :
2017-02-17 11:37:06 +02: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 10:55:50 +02:00
2017-02-07 18:04:29 +02:00
pci_device_id = 4 + bridge_id # Bridge consume PCI ports
2017-02-17 10:55:50 +02:00
2015-05-06 23:59:01 +03:00
for adapter_number , adapter in enumerate ( self . _ethernet_adapters ) :
2016-04-25 17:36:20 +03:00
mac = int_to_macaddress ( macaddress_to_int ( self . _mac_address ) + adapter_number )
2016-06-24 01:56:06 +03:00
2017-07-11 14:42:47 +03: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 18:27:12 +03: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 23:59:01 +03:00
if self . _legacy_networking :
# legacy QEMU networking syntax (-net)
if nio :
2018-04-02 18:27:12 +03:00
network_options . extend ( [ " -net " , " nic,vlan= {} ,macaddr= {} ,model= {} " . format ( adapter_number , mac , adapter_type ) ] )
2015-05-06 23:59:01 +03:00
if isinstance ( nio , NIOUDP ) :
2015-10-04 15:41:39 +03: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 21:51:14 +03:00
" 127.0.0.1 " ,
2015-10-04 15:41:39 +03:00
nio . lport ) ] )
2018-03-19 11:26:12 +02:00
elif isinstance ( nio , NIOTAP ) :
network_options . extend ( [ " -net " , " tap,name=gns3- {} ,ifname= {} " . format ( adapter_number , nio . tap_device ) ] )
2015-05-06 23:59:01 +03:00
else :
2018-04-02 18:27:12 +03:00
network_options . extend ( [ " -net " , " nic,vlan= {} ,macaddr= {} ,model= {} " . format ( adapter_number , mac , adapter_type ) ] )
2015-05-06 23:59:01 +03:00
else :
# newer QEMU networking syntax
2018-04-02 18:27:12 +03:00
device_string = " {} ,mac= {} " . format ( adapter_type , mac )
2017-02-07 18:04:29 +02:00
bridge_id = math . floor ( pci_device_id / 32 )
if bridge_id > 0 :
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 23:59:01 +03:00
if nio :
2017-02-07 18:04:29 +02:00
network_options . extend ( [ " -device " , " {} ,netdev=gns3- {} " . format ( device_string , adapter_number ) ] )
2015-05-06 23:59:01 +03:00
if isinstance ( nio , NIOUDP ) :
2015-04-27 23:38:15 +03:00
network_options . extend ( [ " -netdev " , " socket,id=gns3- {} ,udp= {} : {} ,localaddr= {} : {} " . format ( adapter_number ,
nio . rhost ,
nio . rport ,
2017-05-16 21:51:14 +03:00
" 127.0.0.1 " ,
2015-04-27 23:38:15 +03:00
nio . lport ) ] )
2018-03-19 11:26:12 +02: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 04:59:44 +02:00
else :
2017-02-07 18:04:29 +02:00
network_options . extend ( [ " -device " , device_string ] )
2015-02-19 12:33:25 +02:00
return network_options
2015-02-23 21:21:00 +02:00
def _graphic ( self ) :
"""
2015-04-08 20:17:34 +03:00
Adds the correct graphic options depending of the OS
2015-02-23 21:21:00 +02:00
"""
2015-04-08 20:17:34 +03:00
2015-02-23 21:21:00 +02:00
if sys . platform . startswith ( " win " ) :
return [ ]
if len ( os . environ . get ( " DISPLAY " , " " ) ) > 0 :
return [ ]
2017-11-18 11:22:29 +02:00
if " -nographic " not in self . _options :
return [ " -nographic " ]
return [ ]
2015-02-23 21:21:00 +02:00
2018-03-22 09:05:31 +02:00
@asyncio.coroutine
2018-03-21 11:41:25 +02:00
def _run_with_hardware_acceleration ( self , qemu_path , options ) :
2015-08-07 17:49:45 +03:00
"""
2018-03-21 11:41:25 +02:00
Check if we can run Qemu with hardware acceleration
2015-08-07 17:49:45 +03:00
: param qemu_path : Path to qemu
: param options : String of qemu user options
2018-03-21 11:41:25 +02:00
: returns : Boolean True if we need to enable hardware acceleration
"""
enable_hardware_accel = self . manager . config . get_section_config ( " Qemu " ) . getboolean ( " enable_hardware_acceleration " , True )
require_hardware_accel = self . manager . config . get_section_config ( " Qemu " ) . getboolean ( " require_hardware_acceleration " , True )
if sys . platform . startswith ( " linux " ) :
# compatibility: these options were used before version 2.0 and have priority
enable_kvm = self . manager . config . get_section_config ( " Qemu " ) . getboolean ( " enable_kvm " )
if enable_kvm is not None :
enable_hardware_accel = enable_kvm
require_kvm = self . manager . config . get_section_config ( " Qemu " ) . getboolean ( " require_kvm " )
if require_kvm is not None :
require_hardware_accel = require_kvm
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 10:45:41 +02: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 11:41:25 +02:00
if require_hardware_accel :
2018-03-22 10:45:41 +02:00
raise QemuError ( " Hardware acceleration can only be used with the following Qemu executables: {} " . format ( " , " . join ( supported_binaries ) ) )
2018-03-21 11:41:25 +02:00
else :
return False
2015-08-07 17:49:45 +03:00
2018-03-21 11:41:25 +02:00
if sys . platform . startswith ( " linux " ) and not os . path . exists ( " /dev/kvm " ) :
if require_hardware_accel :
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_kvm = false to the [Qemu] section. " )
else :
return False
elif sys . platform . startswith ( " win " ) :
if require_hardware_accel :
# HAXM is only available starting with Qemu version 2.9.0
version = yield from self . manager . get_qemu_version ( self . qemu_path )
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 10:45:41 +02:00
if version is None :
2018-03-21 11:41:25 +02:00
raise QemuError ( " HAXM acceleration support is not installed on this host " )
log . info ( " HAXM support version {} detected " . format ( version ) )
2018-03-24 18:15:20 +03: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 07:39:37 +02:00
else :
return False
2018-03-21 11:41:25 +02:00
elif sys . platform . startswith ( " darwin " ) :
2018-03-22 17:37:09 +02:00
process = yield from asyncio . create_subprocess_shell ( " kextstat | grep com.intel.kext.intelhaxm " )
yield from process . wait ( )
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 17:49:45 +03:00
return True
return False
2018-03-30 15:28:22 +03:00
@asyncio.coroutine
def _clear_save_vm_stated ( self , snapshot_name = " GNS3_SAVED_STATE " ) :
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 09:19:54 +03:00
if not os . path . exists ( disk ) :
continue
2018-03-30 15:28:22 +03:00
command = [ qemu_img_path , " snapshot " , " -c " , snapshot_name , disk ]
retcode = yield from 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 ) )
except subprocess . SubprocessError as e :
raise QemuError ( " Error while looking for the Qemu VM saved state snapshot: {} " . format ( e ) )
@asyncio.coroutine
def _saved_state_option ( self , snapshot_name = " GNS3_SAVED_STATE " ) :
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 09:19:54 +03:00
if not os . path . exists ( disk ) :
continue
2018-03-30 15:28:22 +03:00
output = yield from subprocess_check_output ( qemu_img_path , " info " , " --output=json " , disk )
json_data = json . loads ( output )
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 ) )
return [ " -loadvm " , snapshot_name ]
except subprocess . SubprocessError as e :
raise QemuError ( " Error while looking for the Qemu VM saved state snapshot: {} " . format ( e ) )
return [ ]
2015-02-19 17:46:57 +02:00
@asyncio.coroutine
2015-02-19 12:33:25 +02:00
def _build_command ( self ) :
"""
Command to start the QEMU process .
( to be passed to subprocess . Popen ( ) )
"""
2016-10-03 16:54:20 +03:00
additional_options = self . _options . strip ( )
2017-12-21 10:38:18 +02:00
additional_options = additional_options . replace ( " % vm-name % " , ' " ' + self . _name . replace ( ' " ' , ' \\ " ' ) + ' " ' )
2017-11-18 12:36:11 +02:00
additional_options = additional_options . replace ( " % vm-id % " , self . _id )
additional_options = additional_options . replace ( " % project-id % " , self . project . id )
2017-12-21 10:38:18 +02:00
additional_options = additional_options . replace ( " % project-path % " , ' " ' + self . project . path . replace ( ' " ' , ' \\ " ' ) + ' " ' )
2015-02-19 17:46:57 +02:00
command = [ self . qemu_path ]
2015-02-19 12:33:25 +02:00
command . extend ( [ " -name " , self . _name ] )
2015-08-07 18:08:10 +03:00
command . extend ( [ " -m " , " {} M " . format ( self . _ram ) ] )
2015-08-06 02:17:55 +03:00
command . extend ( [ " -smp " , " cpus= {} " . format ( self . _cpus ) ] )
2018-03-22 09:05:31 +02:00
if ( yield from self . _run_with_hardware_acceleration ( self . qemu_path , self . _options ) ) :
2018-03-21 11:41:25 +02:00
if sys . platform . startswith ( " linux " ) :
command . extend ( [ " -enable-kvm " ] )
version = yield from self . manager . get_qemu_version ( self . qemu_path )
# 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 08:02:02 +03:00
command . extend ( [ " -boot " , " order= {} " . format ( self . _boot_priority ) ] )
2016-12-08 17:18:30 +02:00
command . extend ( self . _bios_option ( ) )
command . extend ( self . _cdrom_option ( ) )
2015-10-04 16:00:47 +03:00
command . extend ( ( yield from self . _disk_options ( ) ) )
2015-02-19 12:33:25 +02:00
command . extend ( self . _linux_boot_options ( ) )
2016-10-03 16:54:20 +03:00
if " -uuid " not in additional_options :
command . extend ( [ " -uuid " , self . _id ] )
2015-06-25 04:09:17 +03:00
if self . _console_type == " telnet " :
command . extend ( self . _serial_options ( ) )
elif self . _console_type == " vnc " :
command . extend ( self . _vnc_options ( ) )
2017-06-16 11:03:33 +03:00
elif self . _console_type == " spice " :
command . extend ( self . _spice_options ( ) )
2018-03-25 10:36:14 +03:00
elif self . _console_type == " spice+agent " :
command . extend ( self . _spice_with_agent_options ( ) )
2018-03-23 11:44:16 +03:00
elif self . _console_type != " none " :
2015-06-25 04:09:17 +03:00
raise QemuError ( " Console type {} is unknown " . format ( self . _console_type ) )
2015-02-19 12:33:25 +02:00
command . extend ( self . _monitor_options ( ) )
2015-10-07 10:01:17 +03:00
command . extend ( ( yield from self . _network_options ( ) ) )
2015-08-27 19:46:02 +03:00
command . extend ( self . _graphic ( ) )
2018-03-30 17:18:44 +03:00
if self . on_close != " save_vm_state " :
2018-03-30 15:28:22 +03:00
yield from self . _clear_save_vm_stated ( )
else :
command . extend ( ( yield from self . _saved_state_option ( ) ) )
2015-02-19 12:33:25 +02:00
if additional_options :
2015-03-31 23:14:08 +03:00
try :
command . extend ( shlex . split ( additional_options ) )
except ValueError as e :
2015-06-25 04:09:17 +03:00
raise QemuError ( " Invalid additional options: {} error {} " . format ( additional_options , e ) )
2015-02-19 12:33:25 +02:00
return command
2015-02-19 20:43:45 +02:00
def __json__ ( self ) :
2015-02-19 21:22:30 +02:00
answer = {
2015-02-19 20:43:45 +02:00
" project_id " : self . project . id ,
2016-05-11 20:35:36 +03:00
" node_id " : self . id ,
2017-10-02 11:41:57 +03:00
" node_directory " : self . working_path
2015-02-19 20:43:45 +02:00
}
2015-03-10 19:50:30 +02:00
# Qemu has a long list of options. The JSON schema is the single source of information
2015-02-19 21:22:30 +02:00
for field in QEMU_OBJECT_SCHEMA [ " required " ] :
if field not in answer :
2015-06-17 18:11:25 +03:00
try :
answer [ field ] = getattr ( self , field )
except AttributeError :
pass
2015-04-14 19:46:55 +03:00
answer [ " hda_disk_image " ] = self . manager . get_relative_image_path ( self . _hda_disk_image )
2015-06-17 18:11:25 +03:00
answer [ " hda_disk_image_md5sum " ] = md5sum ( self . _hda_disk_image )
2015-04-14 19:46:55 +03:00
answer [ " hdb_disk_image " ] = self . manager . get_relative_image_path ( self . _hdb_disk_image )
2015-06-17 18:11:25 +03:00
answer [ " hdb_disk_image_md5sum " ] = md5sum ( self . _hdb_disk_image )
2015-04-14 19:46:55 +03:00
answer [ " hdc_disk_image " ] = self . manager . get_relative_image_path ( self . _hdc_disk_image )
2015-06-17 18:11:25 +03:00
answer [ " hdc_disk_image_md5sum " ] = md5sum ( self . _hdc_disk_image )
2015-04-14 19:46:55 +03:00
answer [ " hdd_disk_image " ] = self . manager . get_relative_image_path ( self . _hdd_disk_image )
2015-06-17 18:11:25 +03:00
answer [ " hdd_disk_image_md5sum " ] = md5sum ( self . _hdd_disk_image )
2015-08-03 08:02:02 +03:00
answer [ " cdrom_image " ] = self . manager . get_relative_image_path ( self . _cdrom_image )
answer [ " cdrom_image_md5sum " ] = md5sum ( self . _cdrom_image )
2016-12-08 17:18:30 +02:00
answer [ " bios_image " ] = self . manager . get_relative_image_path ( self . _bios_image )
answer [ " bios_image_md5sum " ] = md5sum ( self . _bios_image )
2015-04-14 19:46:55 +03:00
answer [ " initrd " ] = self . manager . get_relative_image_path ( self . _initrd )
2015-06-17 18:11:25 +03:00
answer [ " initrd_md5sum " ] = md5sum ( self . _initrd )
2015-04-14 19:46:55 +03:00
answer [ " kernel_image " ] = self . manager . get_relative_image_path ( self . _kernel_image )
2015-06-17 18:11:25 +03:00
answer [ " kernel_image_md5sum " ] = md5sum ( self . _kernel_image )
2015-03-11 23:04:11 +02:00
2015-02-19 21:22:30 +02:00
return answer