2014-07-12 19:18: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/>.
"""
VirtualBox VM instance .
"""
2014-10-31 00:53:17 +00:00
import re
2014-07-12 19:18:25 +00:00
import os
2017-02-13 18:11:29 +00:00
import sys
2014-11-09 18:50:47 +00:00
import json
2017-02-13 18:11:29 +00:00
import uuid
import shlex
import shutil
2015-01-20 01:30:57 +00:00
import asyncio
2017-02-13 18:11:29 +00:00
import tempfile
2016-10-28 14:00:26 +00:00
import xml . etree . ElementTree as ET
2014-07-12 19:18:25 +00:00
2016-05-02 15:13:23 +00:00
from gns3server . utils import parse_version
2016-11-07 10:16:51 +00:00
from gns3server . utils . asyncio . telnet_server import AsyncioTelnetServer
from gns3server . utils . asyncio . serial import asyncio_open_serial
2018-08-25 07:10:47 +00:00
from gns3server . utils . asyncio import locking
2016-11-07 10:16:51 +00:00
from gns3server . compute . virtualbox . virtualbox_error import VirtualBoxError
from gns3server . compute . nios . nio_udp import NIOUDP
from gns3server . compute . adapters . ethernet_adapter import EthernetAdapter
from gns3server . compute . base_node import BaseNode
2014-10-31 00:53:17 +00:00
if sys . platform . startswith ( ' win ' ) :
import msvcrt
import win32file
2014-07-12 19:18:25 +00:00
import logging
log = logging . getLogger ( __name__ )
2014-07-18 02:02:18 +00:00
2016-05-11 17:35:36 +00:00
class VirtualBoxVM ( BaseNode ) :
2015-01-31 21:34:49 +00:00
2014-07-12 19:18:25 +00:00
"""
VirtualBox VM implementation .
"""
2018-03-24 11:11:21 +00:00
def __init__ ( self , name , node_id , project , manager , vmname , linked_clone = False , console = None , console_type = " telnet " , adapters = 0 ) :
2015-01-20 01:30:57 +00:00
2018-03-24 11:11:21 +00:00
super ( ) . __init__ ( name , node_id , project , manager , console = console , linked_clone = linked_clone , console_type = console_type )
2015-01-20 01:30:57 +00:00
2019-03-18 16:29:18 +00:00
self . _uuid = None # UUID in VirtualBox
2015-01-22 02:26:39 +00:00
self . _maximum_adapters = 8
2015-01-21 02:02:22 +00:00
self . _system_properties = { }
2016-11-07 10:16:51 +00:00
self . _telnet_server = None
2016-06-25 00:35:39 +00:00
self . _local_udp_tunnels = { }
2015-01-20 01:30:57 +00:00
2014-07-12 19:18:25 +00:00
# VirtualBox settings
2015-01-31 02:36:05 +00:00
self . _adapters = adapters
2015-04-03 10:08:18 +00:00
self . _ethernet_adapters = { }
2014-07-17 21:28:02 +00:00
self . _headless = False
2018-03-30 14:18:44 +00:00
self . _on_close = " power_off "
2014-07-17 21:28:02 +00:00
self . _vmname = vmname
2015-02-07 00:31:13 +00:00
self . _use_any_adapter = False
2015-03-13 23:13:36 +00:00
self . _ram = 0
2014-11-05 02:00:01 +00:00
self . _adapter_type = " Intel PRO/1000 MT Desktop (82540EM) "
2014-07-12 19:18:25 +00:00
2015-01-21 22:21:15 +00:00
def __json__ ( self ) :
2014-11-09 18:50:47 +00:00
2015-06-26 15:09:19 +00:00
json = { " name " : self . name ,
2018-12-30 12:35:24 +00:00
" usage " : self . usage ,
2016-05-11 17:35:36 +00:00
" node_id " : self . id ,
2015-01-23 23:38:46 +00:00
" console " : self . console ,
2016-11-07 10:16:51 +00:00
" console_type " : self . console_type ,
2015-02-04 20:48:29 +00:00
" project_id " : self . project . id ,
2015-01-23 02:07:09 +00:00
" vmname " : self . vmname ,
" headless " : self . headless ,
2018-03-30 14:18:44 +00:00
" on_close " : self . on_close ,
2015-01-31 02:36:05 +00:00
" adapters " : self . _adapters ,
2015-01-23 04:31:26 +00:00
" adapter_type " : self . adapter_type ,
2015-03-13 23:13:36 +00:00
" ram " : self . ram ,
2016-05-17 17:51:06 +00:00
" status " : self . status ,
2016-07-12 14:22:55 +00:00
" use_any_adapter " : self . use_any_adapter ,
2016-10-24 19:39:35 +00:00
" linked_clone " : self . linked_clone }
if self . linked_clone :
2017-10-02 08:41:57 +00:00
json [ " node_directory " ] = self . working_path
2015-06-26 15:09:19 +00:00
else :
2016-05-12 08:39:50 +00:00
json [ " node_directory " ] = None
2015-06-26 15:09:19 +00:00
return json
2014-07-12 19:18:25 +00:00
2017-07-18 12:59:47 +00:00
@property
def ethernet_adapters ( self ) :
return self . _ethernet_adapters
2018-10-15 10:05:49 +00:00
async def _get_system_properties ( self ) :
2015-01-20 01:30:57 +00:00
2018-10-15 10:05:49 +00:00
properties = await self . manager . execute ( " list " , [ " systemproperties " ] )
2015-01-20 01:30:57 +00:00
for prop in properties :
try :
name , value = prop . split ( ' : ' , 1 )
except ValueError :
continue
self . _system_properties [ name . strip ( ) ] = value . strip ( )
2014-07-12 19:18:25 +00:00
2018-10-15 10:05:49 +00:00
async def _get_vm_state ( self ) :
2015-01-22 02:26:39 +00:00
"""
Returns the VM state ( e . g . running , paused etc . )
2014-07-12 19:18:25 +00:00
2015-01-22 02:26:39 +00:00
: returns : state ( string )
"""
2015-01-20 01:30:57 +00:00
2019-03-18 16:29:18 +00:00
results = await self . manager . execute ( " showvminfo " , [ self . _uuid , " --machinereadable " ] )
2015-01-22 02:26:39 +00:00
for info in results :
2015-04-14 12:35:48 +00:00
if ' = ' in info :
name , value = info . split ( ' = ' , 1 )
if name == " VMState " :
return value . strip ( ' " ' )
2015-01-22 02:26:39 +00:00
raise VirtualBoxError ( " Could not get VM state for {} " . format ( self . _vmname ) )
2018-10-15 10:05:49 +00:00
async def _control_vm ( self , params ) :
2015-01-22 02:26:39 +00:00
"""
Change setting in this VM when running .
: param params : params to use with sub - command controlvm
: returns : result of the command .
"""
args = shlex . split ( params )
2019-03-18 16:29:18 +00:00
result = await self . manager . execute ( " controlvm " , [ self . _uuid ] + args )
2015-01-22 02:26:39 +00:00
return result
2018-10-15 10:05:49 +00:00
async def _modify_vm ( self , params ) :
2015-01-22 02:26:39 +00:00
"""
Change setting in this VM when not running .
: param params : params to use with sub - command modifyvm
"""
2014-07-12 19:18:25 +00:00
2015-01-22 02:26:39 +00:00
args = shlex . split ( params )
2019-03-18 16:29:18 +00:00
await self . manager . execute ( " modifyvm " , [ self . _uuid ] + args )
2014-07-12 19:18:25 +00:00
2018-10-15 10:05:49 +00:00
async def _check_duplicate_linked_clone ( self ) :
2016-11-17 11:21:38 +00:00
"""
Without linked clone two VM using the same image can ' t run
at the same time .
To avoid issue like false detection when a project close
and another open we try multiple times .
"""
trial = 0
while True :
found = False
for node in self . manager . nodes :
if node != self and node . vmname == self . vmname :
found = True
if node . project != self . project :
if trial > = 30 :
raise VirtualBoxError ( " Sorry a node without the linked clone setting enabled can only be used once on your server. \n {} is already used by {} in project {} " . format ( self . vmname , node . name , self . project . name ) )
else :
if trial > = 5 :
raise VirtualBoxError ( " Sorry a node without the linked clone setting enabled can only be used once on your server. \n {} is already used by {} in this project " . format ( self . vmname , node . name ) )
if not found :
return
trial + = 1
2018-10-15 10:05:49 +00:00
await asyncio . sleep ( 1 )
2016-11-17 11:21:38 +00:00
2018-10-15 10:05:49 +00:00
async def create ( self ) :
2019-03-18 16:29:18 +00:00
2016-11-17 11:21:38 +00:00
if not self . linked_clone :
2018-10-15 10:05:49 +00:00
await self . _check_duplicate_linked_clone ( )
2015-01-23 02:07:09 +00:00
2018-10-15 10:05:49 +00:00
await self . _get_system_properties ( )
2015-04-14 12:32:44 +00:00
if " API version " not in self . _system_properties :
2015-07-09 03:38:58 +00:00
raise VirtualBoxError ( " Can ' t access to VirtualBox API version: \n {} " . format ( self . _system_properties ) )
2015-01-22 02:26:39 +00:00
if parse_version ( self . _system_properties [ " API version " ] ) < parse_version ( " 4_3 " ) :
raise VirtualBoxError ( " The VirtualBox API version is lower than 4.3 " )
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] created " . format ( name = self . name , id = self . id ) )
2015-01-22 02:26:39 +00:00
2019-03-18 16:29:18 +00:00
vm_info = await self . _get_vm_info ( )
if " memory " in vm_info :
self . _ram = int ( vm_info [ " memory " ] )
if " UUID " in vm_info :
self . _uuid = vm_info [ " UUID " ]
if not self . _uuid :
raise VirtualBoxError ( " Could not find any UUID for VM ' {} ' " . format ( self . _vmname ) )
2016-10-24 19:39:35 +00:00
if self . linked_clone :
2015-02-04 20:48:29 +00:00
if self . id and os . path . isdir ( os . path . join ( self . working_dir , self . _vmname ) ) :
2016-10-28 14:00:26 +00:00
self . _patch_vm_uuid ( )
2018-10-15 10:05:49 +00:00
await self . manager . execute ( " registervm " , [ self . _linked_vbox_file ( ) ] )
await self . _reattach_linked_hdds ( )
2019-03-18 16:29:18 +00:00
vm_info = await self . _get_vm_info ( )
self . _uuid = vm_info . get ( " UUID " )
if not self . _uuid :
raise VirtualBoxError ( " Could not find any UUID for VM ' {} ' " . format ( self . _vmname ) )
2015-01-22 02:26:39 +00:00
else :
2018-10-15 10:05:49 +00:00
await self . _create_linked_clone ( )
2014-07-12 19:18:25 +00:00
2015-01-31 19:01:23 +00:00
if self . _adapters :
2018-10-15 10:05:49 +00:00
await self . set_adapters ( self . _adapters )
2015-01-23 04:31:26 +00:00
2016-10-28 14:00:26 +00:00
def _linked_vbox_file ( self ) :
return os . path . join ( self . working_dir , self . _vmname , self . _vmname + " .vbox " )
def _patch_vm_uuid ( self ) :
"""
Fix the VM uuid in the case of linked clone
"""
2017-01-10 13:22:04 +00:00
if os . path . exists ( self . _linked_vbox_file ( ) ) :
2017-06-09 07:57:47 +00:00
try :
tree = ET . parse ( self . _linked_vbox_file ( ) )
except ET . ParseError :
raise VirtualBoxError ( " Cannot modify VirtualBox linked nodes file. "
" File {} is corrupted. " . format ( self . _linked_vbox_file ( ) ) )
2018-09-11 13:06:01 +00:00
except OSError as e :
raise VirtualBoxError ( " Cannot modify VirtualBox linked nodes file ' {} ' : {} " . format ( self . _linked_vbox_file ( ) , e ) )
2017-06-09 07:57:47 +00:00
2017-01-10 13:22:04 +00:00
machine = tree . getroot ( ) . find ( " { http://www.virtualbox.org/}Machine " )
2017-02-13 18:11:29 +00:00
if machine is not None and machine . get ( " uuid " ) != " { " + self . id + " } " :
for image in tree . getroot ( ) . findall ( " { http://www.virtualbox.org/}Image " ) :
currentSnapshot = machine . get ( " currentSnapshot " )
if currentSnapshot :
2019-01-17 11:01:58 +00:00
newSnapshot = re . sub ( r " \ { .* \ } " , " { " + str ( uuid . uuid4 ( ) ) + " } " , currentSnapshot )
2017-02-13 18:11:29 +00:00
shutil . move ( os . path . join ( self . working_dir , self . _vmname , " Snapshots " , currentSnapshot ) + " .vdi " ,
os . path . join ( self . working_dir , self . _vmname , " Snapshots " , newSnapshot ) + " .vdi " )
image . set ( " uuid " , newSnapshot )
2017-01-13 16:20:02 +00:00
machine . set ( " uuid " , " { " + self . id + " } " )
tree . write ( self . _linked_vbox_file ( ) )
2016-10-28 14:00:26 +00:00
2018-10-15 10:05:49 +00:00
async def check_hw_virtualization ( self ) :
2015-07-22 04:58:28 +00:00
"""
Returns either hardware virtualization is activated or not .
: returns : boolean
"""
2018-10-15 10:05:49 +00:00
vm_info = await self . _get_vm_info ( )
2015-07-22 04:58:28 +00:00
if " hwvirtex " in vm_info and vm_info [ " hwvirtex " ] == " on " :
return True
return False
2018-09-11 13:06:01 +00:00
@locking
2018-10-15 10:05:49 +00:00
async def start ( self ) :
2015-01-22 02:26:39 +00:00
"""
Starts this VirtualBox VM .
"""
2015-01-20 01:30:57 +00:00
2016-11-17 09:38:29 +00:00
if self . status == " started " :
return
2015-01-22 02:26:39 +00:00
# resume the VM if it is paused
2018-10-15 10:05:49 +00:00
vm_state = await self . _get_vm_state ( )
2015-01-22 02:26:39 +00:00
if vm_state == " paused " :
2018-10-15 10:05:49 +00:00
await self . resume ( )
2015-01-22 02:26:39 +00:00
return
2015-01-20 01:30:57 +00:00
2015-09-08 07:20:46 +00:00
# VM must be powered off to start it
if vm_state != " poweroff " :
raise VirtualBoxError ( " VirtualBox VM not powered off " )
2015-01-20 01:30:57 +00:00
2018-10-15 10:05:49 +00:00
await self . _set_network_options ( )
await self . _set_serial_console ( )
2015-01-20 01:30:57 +00:00
2015-10-12 21:57:37 +00:00
# check if there is enough RAM to run
self . check_available_ram ( self . ram )
2019-03-18 16:29:18 +00:00
args = [ self . _uuid ]
2015-01-22 02:26:39 +00:00
if self . _headless :
args . extend ( [ " --type " , " headless " ] )
2018-10-15 10:05:49 +00:00
result = await self . manager . execute ( " startvm " , args )
2016-05-14 02:41:58 +00:00
self . status = " started "
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] started " . format ( name = self . name , id = self . id ) )
2015-01-22 02:26:39 +00:00
log . debug ( " Start result: {} " . format ( result ) )
# add a guest property to let the VM know about the GNS3 name
2019-03-18 16:29:18 +00:00
await self . manager . execute ( " guestproperty " , [ " set " , self . _uuid , " NameInGNS3 " , self . name ] )
2015-01-22 02:26:39 +00:00
# add a guest property to let the VM know about the GNS3 project directory
2019-03-18 16:29:18 +00:00
await self . manager . execute ( " guestproperty " , [ " set " , self . _uuid , " ProjectDirInGNS3 " , self . working_dir ] )
2015-01-22 02:26:39 +00:00
2018-10-15 10:05:49 +00:00
await self . _start_ubridge ( )
2017-07-11 11:42:47 +00:00
for adapter_number in range ( 0 , self . _adapters ) :
nio = self . _ethernet_adapters [ adapter_number ] . get_nio ( 0 )
if nio :
2018-10-15 10:05:49 +00:00
await self . add_ubridge_udp_connection ( " VBOX- {} - {} " . format ( self . _id , adapter_number ) ,
2017-07-12 08:32:02 +00:00
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
2016-06-25 00:35:39 +00:00
2018-10-15 10:05:49 +00:00
await self . _start_console ( )
2014-07-12 19:18:25 +00:00
2018-10-15 10:05:49 +00:00
if ( await self . check_hw_virtualization ( ) ) :
2015-07-22 04:58:28 +00:00
self . _hw_virtualization = True
2018-08-25 07:10:47 +00:00
@locking
2018-10-15 10:05:49 +00:00
async def stop ( self ) :
2015-01-22 02:26:39 +00:00
"""
Stops this VirtualBox VM .
2014-07-12 19:18:25 +00:00
"""
2015-07-22 04:58:28 +00:00
self . _hw_virtualization = False
2018-10-15 10:05:49 +00:00
await self . _stop_ubridge ( )
await self . _stop_remote_console ( )
vm_state = await self . _get_vm_state ( )
2015-01-22 02:26:39 +00:00
if vm_state == " running " or vm_state == " paused " or vm_state == " stuck " :
2018-03-30 14:18:44 +00:00
if self . on_close == " save_vm_state " :
2018-10-15 10:05:49 +00:00
result = await self . _control_vm ( " savestate " )
2018-03-30 14:18:44 +00:00
self . status = " stopped "
log . debug ( " Stop result: {} " . format ( result ) )
elif self . on_close == " shutdown_signal " :
2015-06-02 22:30:35 +00:00
# use ACPI to shutdown the VM
2018-10-15 10:05:49 +00:00
result = await self . _control_vm ( " acpipowerbutton " )
2017-02-24 12:58:03 +00:00
trial = 0
while True :
2018-10-15 10:05:49 +00:00
vm_state = await self . _get_vm_state ( )
2017-02-24 12:58:03 +00:00
if vm_state == " poweroff " :
break
2018-10-15 10:05:49 +00:00
await asyncio . sleep ( 1 )
2017-02-24 12:58:03 +00:00
trial + = 1
if trial > = 120 :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " poweroff " )
2017-02-24 12:58:03 +00:00
break
2016-05-14 02:41:58 +00:00
self . status = " stopped "
2015-06-02 22:30:35 +00:00
log . debug ( " ACPI shutdown result: {} " . format ( result ) )
else :
# power off the VM
2018-10-15 10:05:49 +00:00
result = await self . _control_vm ( " poweroff " )
2016-05-14 02:41:58 +00:00
self . status = " stopped "
2015-06-02 22:30:35 +00:00
log . debug ( " Stop result: {} " . format ( result ) )
2014-07-17 21:28:02 +00:00
2015-06-02 22:30:35 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] stopped " . format ( name = self . name , id = self . id ) )
2018-10-15 10:05:49 +00:00
await asyncio . sleep ( 0.5 ) # give some time for VirtualBox to unlock the VM
2015-01-22 02:26:39 +00:00
try :
# deactivate the first serial port
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --uart1 off " )
2015-01-22 02:26:39 +00:00
except VirtualBoxError as e :
2018-03-15 07:17:39 +00:00
log . warning ( " Could not deactivate the first serial port: {} " . format ( e ) )
2014-07-12 19:18:25 +00:00
2015-04-03 10:08:18 +00:00
for adapter_number in range ( 0 , self . _adapters ) :
2015-02-13 22:41:56 +00:00
nio = self . _ethernet_adapters [ adapter_number ] . get_nio ( 0 )
2015-02-07 00:31:13 +00:00
if nio :
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --nictrace {} off " . format ( adapter_number + 1 ) )
await self . _modify_vm ( " --cableconnected {} off " . format ( adapter_number + 1 ) )
await self . _modify_vm ( " --nic {} null " . format ( adapter_number + 1 ) )
await super ( ) . stop ( )
2014-07-12 19:18:25 +00:00
2018-10-15 10:05:49 +00:00
async def suspend ( self ) :
2015-01-22 02:26:39 +00:00
"""
Suspends this VirtualBox VM .
2014-07-12 19:18:25 +00:00
"""
2018-10-15 10:05:49 +00:00
vm_state = await self . _get_vm_state ( )
2015-01-22 02:26:39 +00:00
if vm_state == " running " :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " pause " )
2016-05-14 02:41:58 +00:00
self . status = " suspended "
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] suspended " . format ( name = self . name , id = self . id ) )
2015-01-22 02:26:39 +00:00
else :
2018-03-15 07:17:39 +00:00
log . warning ( " VirtualBox VM ' {name} ' [ {id} ] cannot be suspended, current state: {state} " . format ( name = self . name ,
2015-02-04 20:48:29 +00:00
id = self . id ,
state = vm_state ) )
2014-07-12 19:18:25 +00:00
2018-10-15 10:05:49 +00:00
async def resume ( self ) :
2014-07-12 19:18:25 +00:00
"""
2015-01-22 02:26:39 +00:00
Resumes this VirtualBox VM .
2014-07-12 19:18:25 +00:00
"""
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " resume " )
2016-05-14 02:41:58 +00:00
self . status = " started "
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] resumed " . format ( name = self . name , id = self . id ) )
2015-01-22 02:26:39 +00:00
2018-10-15 10:05:49 +00:00
async def reload ( self ) :
2015-01-22 02:26:39 +00:00
"""
Reloads this VirtualBox VM .
"""
2014-07-12 19:18:25 +00:00
2018-10-15 10:05:49 +00:00
result = await self . _control_vm ( " reset " )
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] reloaded " . format ( name = self . name , id = self . id ) )
2015-01-22 02:26:39 +00:00
log . debug ( " Reload result: {} " . format ( result ) )
2014-07-12 19:18:25 +00:00
2018-10-15 10:05:49 +00:00
async def _get_all_hdd_files ( self ) :
2014-11-09 18:50:47 +00:00
hdds = [ ]
2018-10-15 10:05:49 +00:00
properties = await self . manager . execute ( " list " , [ " hdds " ] )
2014-11-09 18:50:47 +00:00
for prop in properties :
try :
name , value = prop . split ( ' : ' , 1 )
except ValueError :
continue
if name . strip ( ) == " Location " :
hdds . append ( value . strip ( ) )
return hdds
2018-10-15 10:05:49 +00:00
async def _reattach_linked_hdds ( self ) :
2016-04-26 21:44:11 +00:00
"""
Reattach linked cloned hard disks .
"""
2014-11-09 18:50:47 +00:00
2015-01-23 02:07:09 +00:00
hdd_info_file = os . path . join ( self . working_dir , self . _vmname , " hdd_info.json " )
2014-11-09 18:50:47 +00:00
try :
2015-04-25 17:58:34 +00:00
with open ( hdd_info_file , " r " , encoding = " utf-8 " ) as f :
2014-11-09 18:50:47 +00:00
hdd_table = json . load ( f )
2015-09-29 20:15:01 +00:00
except ( ValueError , OSError ) as e :
2016-11-02 11:50:10 +00:00
# The VM has never be started
return
2014-11-09 18:50:47 +00:00
for hdd_info in hdd_table :
2015-01-23 02:07:09 +00:00
hdd_file = os . path . join ( self . working_dir , self . _vmname , " Snapshots " , hdd_info [ " hdd " ] )
2014-11-09 18:50:47 +00:00
if os . path . exists ( hdd_file ) :
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] attaching HDD {controller} {port} {device} {medium} " . format ( name = self . name ,
id = self . id ,
controller = hdd_info [ " controller " ] ,
port = hdd_info [ " port " ] ,
device = hdd_info [ " device " ] ,
medium = hdd_file ) )
2015-02-07 00:31:13 +00:00
2016-04-26 21:44:11 +00:00
try :
2018-10-15 10:05:49 +00:00
await self . _storage_attach ( ' --storagectl " {} " --port {} --device {} --type hdd --medium " {} " ' . format ( hdd_info [ " controller " ] ,
2016-04-26 21:44:11 +00:00
hdd_info [ " port " ] ,
hdd_info [ " device " ] ,
hdd_file ) )
except VirtualBoxError as e :
2018-03-15 07:17:39 +00:00
log . warning ( " VirtualBox VM ' {name} ' [ {id} ] error reattaching HDD {controller} {port} {device} {medium} : {error} " . format ( name = self . name ,
2016-04-26 21:44:11 +00:00
id = self . id ,
controller = hdd_info [ " controller " ] ,
port = hdd_info [ " port " ] ,
device = hdd_info [ " device " ] ,
medium = hdd_file ,
error = e ) )
continue
2014-11-09 18:50:47 +00:00
2018-10-15 10:05:49 +00:00
async def save_linked_hdds_info ( self ) :
2014-07-12 19:18:25 +00:00
"""
2016-04-26 21:44:11 +00:00
Save linked cloned hard disks information .
2014-07-12 19:18:25 +00:00
2016-04-26 21:44:11 +00:00
: returns : disk table information
"""
2015-02-24 02:00:34 +00:00
2016-04-26 21:44:11 +00:00
hdd_table = [ ]
2016-10-24 19:39:35 +00:00
if self . linked_clone :
2015-01-23 23:38:46 +00:00
if os . path . exists ( self . working_dir ) :
2018-10-15 10:05:49 +00:00
hdd_files = await self . _get_all_hdd_files ( )
vm_info = await self . _get_vm_info ( )
2014-11-15 23:05:55 +00:00
for entry , value in vm_info . items ( ) :
2019-01-17 11:01:58 +00:00
match = re . search ( r " ^([ \ s \ w]+) \ -( \ d) \ -( \ d)$ " , entry ) # match Controller-PortNumber-DeviceNumber entry
2014-11-15 23:05:55 +00:00
if match :
controller = match . group ( 1 )
port = match . group ( 2 )
device = match . group ( 3 )
2016-04-26 21:06:22 +00:00
if value in hdd_files and os . path . exists ( os . path . join ( self . working_dir , self . _vmname , " Snapshots " , os . path . basename ( value ) ) ) :
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] detaching HDD {controller} {port} {device} " . format ( name = self . name ,
id = self . id ,
controller = controller ,
port = port ,
device = device ) )
2014-11-15 23:05:55 +00:00
hdd_table . append (
{
" hdd " : os . path . basename ( value ) ,
" controller " : controller ,
" port " : port ,
" device " : device ,
}
)
2014-11-09 18:50:47 +00:00
2014-11-15 23:05:55 +00:00
if hdd_table :
try :
2015-01-23 23:38:46 +00:00
hdd_info_file = os . path . join ( self . working_dir , self . _vmname , " hdd_info.json " )
2015-04-25 17:58:34 +00:00
with open ( hdd_info_file , " w " , encoding = " utf-8 " ) as f :
2014-11-15 23:05:55 +00:00
json . dump ( hdd_table , f , indent = 4 )
except OSError as e :
2015-02-04 20:48:29 +00:00
log . warning ( " VirtualBox VM ' {name} ' [ {id} ] could not write HHD info file: {error} " . format ( name = self . name ,
id = self . id ,
error = e . strerror ) )
2014-11-09 18:50:47 +00:00
2016-04-26 21:44:11 +00:00
return hdd_table
2018-10-15 10:05:49 +00:00
async def close ( self ) :
2016-04-26 21:44:11 +00:00
"""
Closes this VirtualBox VM .
"""
if self . _closed :
# VM is already closed
return
2018-10-15 10:05:49 +00:00
if not ( await super ( ) . close ( ) ) :
2016-04-28 14:52:29 +00:00
return False
2016-04-26 21:44:11 +00:00
log . debug ( " VirtualBox VM ' {name} ' [ {id} ] is closing " . format ( name = self . name , id = self . id ) )
if self . _console :
self . _manager . port_manager . release_tcp_port ( self . _console , self . _project )
self . _console = None
for adapter in self . _ethernet_adapters . values ( ) :
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-25 00:35:39 +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-03-30 14:18:44 +00:00
self . on_close = " power_off "
2018-10-15 10:05:49 +00:00
await self . stop ( )
2016-04-26 21:44:11 +00:00
2016-10-24 19:39:35 +00:00
if self . linked_clone :
2018-10-15 10:05:49 +00:00
hdd_table = await self . save_linked_hdds_info ( )
2016-04-26 21:44:11 +00:00
for hdd in hdd_table . copy ( ) :
log . info ( " VirtualBox VM ' {name} ' [ {id} ] detaching HDD {controller} {port} {device} " . format ( name = self . name ,
id = self . id ,
controller = hdd [ " controller " ] ,
port = hdd [ " port " ] ,
device = hdd [ " device " ] ) )
try :
2018-10-15 10:05:49 +00:00
await self . _storage_attach ( ' --storagectl " {} " --port {} --device {} --type hdd --medium none ' . format ( hdd [ " controller " ] ,
2016-04-26 21:44:11 +00:00
hdd [ " port " ] ,
hdd [ " device " ] ) )
except VirtualBoxError as e :
2018-03-15 07:17:39 +00:00
log . warning ( " VirtualBox VM ' {name} ' [ {id} ] error detaching HDD {controller} {port} {device} : {error} " . format ( name = self . name ,
2016-04-26 21:44:11 +00:00
id = self . id ,
controller = hdd [ " controller " ] ,
port = hdd [ " port " ] ,
device = hdd [ " device " ] ,
error = e ) )
continue
log . info ( " VirtualBox VM ' {name} ' [ {id} ] unregistering " . format ( name = self . name , id = self . id ) )
2018-10-15 10:05:49 +00:00
await self . manager . execute ( " unregistervm " , [ self . _name ] )
2016-04-26 21:44:11 +00:00
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] closed " . format ( name = self . name , id = self . id ) )
2015-02-03 01:56:13 +00:00
self . _closed = True
2014-07-12 19:18:25 +00:00
2014-07-17 21:28:02 +00:00
@property
def headless ( self ) :
"""
Returns either the VM will start in headless mode
: returns : boolean
"""
return self . _headless
@headless.setter
def headless ( self , headless ) :
"""
Sets either the VM will start in headless mode
: param headless : boolean
"""
if headless :
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] has enabled the headless mode " . format ( name = self . name , id = self . id ) )
2014-07-17 21:28:02 +00:00
else :
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] has disabled the headless mode " . format ( name = self . name , id = self . id ) )
2014-07-17 21:28:02 +00:00
self . _headless = headless
2015-06-02 22:30:35 +00:00
@property
2018-03-30 14:18:44 +00:00
def on_close ( self ) :
2015-06-02 22:30:35 +00:00
"""
2018-03-30 14:18:44 +00:00
Returns the action to execute when the VM is stopped / closed
2015-06-02 22:30:35 +00:00
2018-03-30 14:18:44 +00:00
: returns : string
2015-06-02 22:30:35 +00:00
"""
2018-03-30 14:18:44 +00:00
return self . _on_close
2015-06-02 22:30:35 +00:00
2018-03-30 14:18:44 +00:00
@on_close.setter
def on_close ( self , on_close ) :
2015-06-02 22:30:35 +00:00
"""
2018-03-30 14:18:44 +00:00
Sets the action to execute when the VM is stopped / closed
2015-06-02 22:30:35 +00:00
2018-03-30 14:18:44 +00:00
: param on_close : string
2015-06-02 22:30:35 +00:00
"""
2018-03-30 14:18:44 +00:00
log . info ( ' VirtualBox VM " {name} " [ {id} ] set the close action to " {action} " ' . format ( name = self . _name , id = self . _id , action = on_close ) )
self . _on_close = on_close
2015-06-02 22:30:35 +00:00
2015-03-13 23:13:36 +00:00
@property
def ram ( self ) :
"""
Returns the amount of RAM allocated to this VirtualBox VM .
: returns : amount RAM in MB ( integer )
"""
return self . _ram
2018-10-15 10:05:49 +00:00
async def set_ram ( self , ram ) :
2015-03-13 23:13:36 +00:00
"""
Set the amount of RAM allocated to this VirtualBox VM .
: param ram : amount RAM in MB ( integer )
"""
if ram == 0 :
return
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( ' --memory {} ' . format ( ram ) )
2015-03-13 23:13:36 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] has set amount of RAM to {ram} " . format ( name = self . name , id = self . id , ram = ram ) )
self . _ram = ram
2014-07-17 21:28:02 +00:00
@property
def vmname ( self ) :
"""
2015-03-08 18:32:36 +00:00
Returns the VirtualBox VM name .
2014-07-17 21:28:02 +00:00
: returns : VirtualBox VM name
"""
return self . _vmname
2018-10-15 10:05:49 +00:00
async def set_vmname ( self , vmname ) :
2014-07-17 21:28:02 +00:00
"""
2015-03-08 18:32:36 +00:00
Renames the VirtualBox VM .
2014-07-17 21:28:02 +00:00
: param vmname : VirtualBox VM name
"""
2017-01-31 17:58:43 +00:00
if vmname == self . _vmname :
return
2016-10-24 19:39:35 +00:00
if self . linked_clone :
2017-01-30 14:19:46 +00:00
if self . status == " started " :
raise VirtualBoxError ( " You can ' t change the name of running VM {} " . format ( self . _name ) )
2017-01-31 17:58:43 +00:00
# We can't rename a VM to name that already exists
2018-10-15 10:05:49 +00:00
vms = await self . manager . list_vms ( allow_clone = True )
2017-01-31 17:58:43 +00:00
if vmname in [ vm [ " vmname " ] for vm in vms ] :
raise VirtualBoxError ( " You can ' t change the name to {} it ' s already use in VirtualBox " . format ( vmname ) )
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( ' --name " {} " ' . format ( vmname ) )
2015-03-08 18:32:36 +00:00
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] has set the VM name to ' {vmname} ' " . format ( name = self . name , id = self . id , vmname = vmname ) )
2014-07-17 21:28:02 +00:00
self . _vmname = vmname
2015-02-07 00:31:13 +00:00
@property
def adapters ( self ) :
"""
Returns the number of adapters configured for this VirtualBox VM .
: returns : number of adapters
"""
2015-02-05 18:58:10 +00:00
2015-02-07 00:31:13 +00:00
return self . _adapters
2015-02-05 18:58:10 +00:00
2018-10-15 10:05:49 +00:00
async def set_adapters ( self , adapters ) :
2014-07-17 21:28:02 +00:00
"""
Sets the number of Ethernet adapters for this VirtualBox VM instance .
: param adapters : number of adapters
"""
2014-10-31 23:41:12 +00:00
# check for the maximum adapters supported by the VM
2018-10-15 10:05:49 +00:00
vm_info = await self . _get_vm_info ( )
2017-10-22 08:23:43 +00:00
chipset = " piix3 " # default chipset for VirtualBox VMs
self . _maximum_adapters = 8 # default maximum network adapter count for PIIX3 chipset
if " chipset " in vm_info :
chipset = vm_info [ " chipset " ]
max_adapter_string = " Maximum {} Network Adapter count " . format ( chipset . upper ( ) )
if max_adapter_string in self . _system_properties :
try :
self . _maximum_adapters = int ( self . _system_properties [ max_adapter_string ] )
except ValueError :
log . error ( " Could not convert system property to integer: {} = {} " . format ( max_adapter_string , self . _system_properties [ max_adapter_string ] ) )
else :
log . warning ( " Could not find system property ' {} ' for chipset {} " . format ( max_adapter_string , chipset ) )
log . info ( " VirtualBox VM ' {name} ' [ {id} ] can have a maximum of {max} network adapters for chipset {chipset} " . format ( name = self . name ,
id = self . id ,
max = self . _maximum_adapters ,
chipset = chipset . upper ( ) ) )
2015-04-03 10:08:18 +00:00
if adapters > self . _maximum_adapters :
2017-10-22 08:23:43 +00:00
raise VirtualBoxError ( " The configured {} chipset limits the VM to {} network adapters. The chipset can be changed outside GNS3 in the VirtualBox VM settings. " . format ( chipset . upper ( ) ,
self . _maximum_adapters ) )
2014-10-31 23:41:12 +00:00
2014-07-17 21:28:02 +00:00
self . _ethernet_adapters . clear ( )
2015-02-13 22:41:56 +00:00
for adapter_number in range ( 0 , adapters ) :
2015-04-03 10:08:18 +00:00
self . _ethernet_adapters [ adapter_number ] = EthernetAdapter ( )
2014-07-17 21:28:02 +00:00
2015-01-31 02:36:05 +00:00
self . _adapters = len ( self . _ethernet_adapters )
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] has changed the number of Ethernet adapters to {adapters} " . format ( name = self . name ,
id = self . id ,
adapters = adapters ) )
2014-08-26 21:27:43 +00:00
@property
2015-02-07 00:31:13 +00:00
def use_any_adapter ( self ) :
2014-08-26 21:27:43 +00:00
"""
2015-02-07 00:31:13 +00:00
Returns either GNS3 can use any VirtualBox adapter on this instance .
2014-08-26 21:27:43 +00:00
2015-05-22 03:48:59 +00:00
: returns : boolean
2014-08-26 21:27:43 +00:00
"""
2015-02-07 00:31:13 +00:00
return self . _use_any_adapter
2014-08-26 21:27:43 +00:00
2015-02-07 00:31:13 +00:00
@use_any_adapter.setter
def use_any_adapter ( self , use_any_adapter ) :
2014-08-26 21:27:43 +00:00
"""
2015-02-07 00:31:13 +00:00
Allows GNS3 to use any VirtualBox adapter on this instance .
2014-08-26 21:27:43 +00:00
2015-02-07 00:31:13 +00:00
: param use_any_adapter : boolean
2014-08-26 21:27:43 +00:00
"""
2015-02-07 00:31:13 +00:00
if use_any_adapter :
log . info ( " VirtualBox VM ' {name} ' [ {id} ] is allowed to use any adapter " . format ( name = self . name , id = self . id ) )
else :
2015-05-22 03:48:59 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ] is not allowed to use any adapter " . format ( name = self . name , id = self . id ) )
2015-02-07 00:31:13 +00:00
self . _use_any_adapter = use_any_adapter
2014-07-17 21:28:02 +00:00
@property
def adapter_type ( self ) :
"""
Returns the adapter type for this VirtualBox VM instance .
: returns : adapter type ( string )
"""
return self . _adapter_type
@adapter_type.setter
def adapter_type ( self , adapter_type ) :
"""
Sets the adapter type for this VirtualBox VM instance .
: param adapter_type : adapter type ( string )
"""
self . _adapter_type = adapter_type
2015-02-04 20:48:29 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ]: adapter type changed to {adapter_type} " . format ( name = self . name ,
id = self . id ,
adapter_type = adapter_type ) )
2014-10-31 00:53:17 +00:00
2018-10-15 10:05:49 +00:00
async def _get_vm_info ( self ) :
2014-10-31 00:53:17 +00:00
"""
Returns this VM info .
: returns : dict of info
"""
vm_info = { }
2019-03-18 16:29:18 +00:00
results = await self . manager . execute ( " showvminfo " , [ " --machinereadable " , " -- " , self . _vmname ] ) # "--" is to protect against vm names containing the "-" character
2014-10-31 00:53:17 +00:00
for info in results :
try :
2014-11-09 18:50:47 +00:00
name , value = info . split ( ' = ' , 1 )
2014-10-31 00:53:17 +00:00
except ValueError :
continue
2014-11-09 18:50:47 +00:00
vm_info [ name . strip ( ' " ' ) ] = value . strip ( ' " ' )
2014-10-31 00:53:17 +00:00
return vm_info
def _get_pipe_name ( self ) :
"""
Returns the pipe name to create a serial connection .
: returns : pipe path ( string )
"""
2015-01-22 02:26:39 +00:00
if sys . platform . startswith ( " win " ) :
2015-04-23 02:29:52 +00:00
pipe_name = r " \\ . \ pipe \ gns3_vbox \ {} " . format ( self . id )
2014-10-31 00:53:17 +00:00
else :
2015-04-23 02:29:52 +00:00
pipe_name = os . path . join ( tempfile . gettempdir ( ) , " gns3_vbox " , " {} " . format ( self . id ) )
try :
os . makedirs ( os . path . dirname ( pipe_name ) , exist_ok = True )
except OSError as e :
raise VirtualBoxError ( " Could not create the VirtualBox pipe directory: {} " . format ( e ) )
2014-10-31 00:53:17 +00:00
return pipe_name
2018-10-15 10:05:49 +00:00
async def _set_serial_console ( self ) :
2014-10-31 00:53:17 +00:00
"""
Configures the first serial port to allow a serial console connection .
"""
# activate the first serial port
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --uart1 0x3F8 4 " )
2014-10-31 00:53:17 +00:00
# set server mode with a pipe on the first serial port
pipe_name = self . _get_pipe_name ( )
2019-03-18 16:29:18 +00:00
args = [ self . _uuid , " --uartmode1 " , " server " , pipe_name ]
2018-10-15 10:05:49 +00:00
await self . manager . execute ( " modifyvm " , args )
2014-10-31 00:53:17 +00:00
2018-10-15 10:05:49 +00:00
async def _storage_attach ( self , params ) :
2014-11-09 18:50:47 +00:00
"""
Change storage medium in this VM .
: param params : params to use with sub - command storageattach
"""
args = shlex . split ( params )
2019-03-18 16:29:18 +00:00
await self . manager . execute ( " storageattach " , [ self . _uuid ] + args )
2014-11-09 18:50:47 +00:00
2018-10-15 10:05:49 +00:00
async def _get_nic_attachements ( self , maximum_adapters ) :
2014-10-31 00:53:17 +00:00
"""
Returns NIC attachements .
: param maximum_adapters : maximum number of supported adapters
: returns : list of adapters with their Attachment setting ( NAT , bridged etc . )
"""
nics = [ ]
2018-10-15 10:05:49 +00:00
vm_info = await self . _get_vm_info ( )
2015-02-13 22:41:56 +00:00
for adapter_number in range ( 0 , maximum_adapters ) :
entry = " nic {} " . format ( adapter_number + 1 )
2014-10-31 00:53:17 +00:00
if entry in vm_info :
value = vm_info [ entry ]
2015-03-11 18:05:22 +00:00
nics . append ( value . lower ( ) )
2014-10-31 00:53:17 +00:00
else :
nics . append ( None )
return nics
2018-10-15 10:05:49 +00:00
async def _set_network_options ( self ) :
2014-10-31 00:53:17 +00:00
"""
Configures network options .
"""
2018-10-15 10:05:49 +00:00
nic_attachments = await self . _get_nic_attachements ( self . _maximum_adapters )
2015-04-03 10:08:18 +00:00
for adapter_number in range ( 0 , self . _adapters ) :
2015-03-06 02:11:33 +00:00
attachment = nic_attachments [ adapter_number ]
if attachment == " null " :
# disconnect the cable if no backend is attached.
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --cableconnected {} off " . format ( adapter_number + 1 ) )
2015-04-04 01:08:29 +00:00
if attachment == " none " :
# set the backend to null to avoid a difference in the number of interfaces in the Guest.
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --nic {} null " . format ( adapter_number + 1 ) )
await self . _modify_vm ( " --cableconnected {} off " . format ( adapter_number + 1 ) )
2016-06-25 00:35:39 +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 ]
2016-06-25 00:35:39 +00:00
2015-02-07 00:31:13 +00:00
if nio :
2017-11-15 09:41:33 +00:00
if not self . _use_any_adapter and attachment in ( " nat " , " bridged " , " intnet " , " hostonly " , " natnetwork " ) :
continue
2014-10-31 00:53:17 +00:00
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --nictrace {} off " . format ( adapter_number + 1 ) )
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 )
2014-10-31 00:53:17 +00:00
vbox_adapter_type = " 82540EM "
2018-04-02 15:27:12 +00:00
if adapter_type == " PCnet-PCI II (Am79C970A) " :
2015-02-07 00:31:13 +00:00
vbox_adapter_type = " Am79C970A "
2018-04-02 15:27:12 +00:00
if adapter_type == " PCNet-FAST III (Am79C973) " :
2015-02-07 00:31:13 +00:00
vbox_adapter_type = " Am79C973 "
2018-04-02 15:27:12 +00:00
if adapter_type == " Intel PRO/1000 MT Desktop (82540EM) " :
2015-02-07 00:31:13 +00:00
vbox_adapter_type = " 82540EM "
2018-04-02 15:27:12 +00:00
if adapter_type == " Intel PRO/1000 T Server (82543GC) " :
2015-02-07 00:31:13 +00:00
vbox_adapter_type = " 82543GC "
2018-04-02 15:27:12 +00:00
if adapter_type == " Intel PRO/1000 MT Server (82545EM) " :
2015-02-07 00:31:13 +00:00
vbox_adapter_type = " 82545EM "
2018-04-02 15:27:12 +00:00
if adapter_type == " Paravirtualized Network (virtio-net) " :
2015-02-07 00:31:13 +00:00
vbox_adapter_type = " virtio "
2019-03-18 16:29:18 +00:00
args = [ self . _uuid , " --nictype {} " . format ( adapter_number + 1 ) , vbox_adapter_type ]
2018-10-15 10:05:49 +00:00
await self . manager . execute ( " modifyvm " , args )
2014-10-31 00:53:17 +00:00
2015-05-06 21:21:39 +00:00
if isinstance ( nio , NIOUDP ) :
log . debug ( " setting UDP params on adapter {} " . format ( adapter_number ) )
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --nic {} generic " . format ( adapter_number + 1 ) )
await self . _modify_vm ( " --nicgenericdrv {} UDPTunnel " . format ( adapter_number + 1 ) )
await self . _modify_vm ( " --nicproperty {} sport= {} " . format ( adapter_number + 1 , nio . lport ) )
await self . _modify_vm ( " --nicproperty {} dest= {} " . format ( adapter_number + 1 , nio . rhost ) )
await self . _modify_vm ( " --nicproperty {} dport= {} " . format ( adapter_number + 1 , nio . rport ) )
2018-03-19 09:26:12 +00:00
if nio . suspend :
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --cableconnected {} off " . format ( adapter_number + 1 ) )
2018-03-19 09:26:12 +00:00
else :
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --cableconnected {} on " . format ( adapter_number + 1 ) )
2014-10-31 00:53:17 +00:00
if nio . capturing :
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --nictrace {} on " . format ( adapter_number + 1 ) )
await self . _modify_vm ( ' --nictracefile {} " {} " ' . format ( adapter_number + 1 , nio . pcap_output_file ) )
2014-10-31 00:53:17 +00:00
2017-07-11 11:42:47 +00:00
if not self . _ethernet_adapters [ adapter_number ] . get_nio ( 0 ) :
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --cableconnected {} off " . format ( adapter_number + 1 ) )
2017-01-05 06:30:23 +00:00
2015-04-03 10:08:18 +00:00
for adapter_number in range ( self . _adapters , self . _maximum_adapters ) :
2015-02-13 22:41:56 +00:00
log . debug ( " disabling remaining adapter {} " . format ( adapter_number ) )
2018-10-15 10:05:49 +00:00
await self . _modify_vm ( " --nic {} none " . format ( adapter_number + 1 ) )
2014-10-31 00:53:17 +00:00
2018-10-15 10:05:49 +00:00
async def _create_linked_clone ( self ) :
2014-11-15 23:05:55 +00:00
"""
Creates a new linked clone .
"""
2014-11-09 18:50:47 +00:00
gns3_snapshot_exists = False
2018-10-15 10:05:49 +00:00
vm_info = await self . _get_vm_info ( )
2014-11-09 18:50:47 +00:00
for entry , value in vm_info . items ( ) :
if entry . startswith ( " SnapshotName " ) and value == " GNS3 Linked Base for clones " :
gns3_snapshot_exists = True
if not gns3_snapshot_exists :
2019-03-18 16:29:18 +00:00
result = await self . manager . execute ( " snapshot " , [ self . _uuid , " take " , " GNS3 Linked Base for clones " ] )
2014-11-15 23:05:55 +00:00
log . debug ( " GNS3 snapshot created: {} " . format ( result ) )
2014-11-09 18:50:47 +00:00
2019-03-18 16:29:18 +00:00
args = [ self . _uuid ,
2014-11-09 18:50:47 +00:00
" --snapshot " ,
" GNS3 Linked Base for clones " ,
" --options " ,
" link " ,
" --name " ,
2015-01-23 02:07:09 +00:00
self . name ,
2014-11-09 18:50:47 +00:00
" --basefolder " ,
2015-01-23 02:07:09 +00:00
self . working_dir ,
2014-11-09 18:50:47 +00:00
" --register " ]
2018-10-15 10:05:49 +00:00
result = await self . manager . execute ( " clonevm " , args )
2015-02-03 01:56:13 +00:00
log . debug ( " VirtualBox VM: {} cloned " . format ( result ) )
2015-01-11 12:22:59 +00:00
2014-11-09 18:50:47 +00:00
self . _vmname = self . _name
2019-03-18 16:29:18 +00:00
await self . manager . execute ( " setextradata " , [ self . _uuid , " GNS3/Clone " , " yes " ] )
2015-01-12 01:24:13 +00:00
2016-11-04 10:07:43 +00:00
# We create a reset snapshot in order to simplify life of user who want to rollback their VM
# Warning: Do not document this it's seem buggy we keep it because Raizo students use it.
try :
2019-03-18 16:29:18 +00:00
args = [ self . _uuid , " take " , " reset " ]
2018-10-15 10:05:49 +00:00
result = await self . manager . execute ( " snapshot " , args )
2016-11-04 10:07:43 +00:00
log . debug ( " Snapshot ' reset ' created: {} " . format ( result ) )
# It seem sometimes this failed due to internal race condition of Vbox
# we have no real explanation of this.
except VirtualBoxError :
2018-03-15 07:17:39 +00:00
log . warning ( " Snapshot ' reset ' not created " )
2014-11-09 18:50:47 +00:00
2016-11-07 13:10:44 +00:00
os . makedirs ( os . path . join ( self . working_dir , self . _vmname ) , exist_ok = True )
2018-10-15 10:05:49 +00:00
async def _start_console ( self ) :
2015-01-04 21:56:17 +00:00
"""
Starts remote console support for this VM .
"""
2018-03-24 11:11:21 +00:00
if self . console and self . console_type == " telnet " :
2018-10-04 13:44:13 +00:00
pipe_name = self . _get_pipe_name ( )
try :
2018-10-15 10:05:49 +00:00
self . _remote_pipe = await asyncio_open_serial ( pipe_name )
2018-10-04 13:44:13 +00:00
except OSError as e :
raise VirtualBoxError ( " Could not open serial pipe ' {} ' : {} " . format ( pipe_name , e ) )
2018-03-24 11:11:21 +00:00
server = AsyncioTelnetServer ( reader = self . _remote_pipe ,
writer = self . _remote_pipe ,
binary = True ,
echo = True )
2018-04-18 09:08:42 +00:00
try :
2018-10-15 10:05:49 +00:00
self . _telnet_server = await asyncio . start_server ( server . run , self . _manager . port_manager . console_host , self . console )
2018-04-18 09:08:42 +00:00
except OSError as e :
self . project . emit ( " log.warning " , { " message " : " Could not start Telnet server on socket {} : {} : {} " . format ( self . _manager . port_manager . console_host , self . console , e ) } )
2015-01-04 21:56:17 +00:00
2018-10-15 10:05:49 +00:00
async def _stop_remote_console ( self ) :
2015-01-04 21:56:17 +00:00
"""
Stops remote console support for this VM .
"""
2016-11-07 10:16:51 +00:00
if self . _telnet_server :
self . _telnet_server . close ( )
2018-10-15 10:05:49 +00:00
await self . _telnet_server . wait_closed ( )
2016-11-18 10:27:50 +00:00
self . _remote_pipe . close ( )
self . _telnet_server = None
2015-01-04 21:56:17 +00:00
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 VirtualBox VM .
: param new_console_type : console type ( string )
"""
if self . is_running ( ) and self . console_type != new_console_type :
raise VirtualBoxError ( ' " {name} " must be stopped to change the console type to {new_console_type} ' . format ( name = self . _name , new_console_type = new_console_type ) )
super ( VirtualBoxVM , VirtualBoxVM ) . console_type . __set__ ( self , new_console_type )
2018-10-15 10:05:49 +00:00
async def adapter_add_nio_binding ( self , adapter_number , nio ) :
2014-07-17 21:28:02 +00:00
"""
2015-02-07 00:31:13 +00:00
Adds an adapter NIO binding .
2014-07-17 21:28:02 +00:00
2015-02-13 22:41:56 +00:00
: param adapter_number : adapter number
2014-07-17 21:28:02 +00:00
: param nio : NIO instance to add to the slot / port
"""
try :
2015-02-13 22:41:56 +00:00
adapter = self . _ethernet_adapters [ adapter_number ]
2015-05-27 14:38:57 +00:00
except KeyError :
2015-02-13 22:41:56 +00:00
raise VirtualBoxError ( " Adapter {adapter_number} doesn ' t exist on VirtualBox VM ' {name} ' " . format ( name = self . name ,
adapter_number = adapter_number ) )
2014-07-17 21:28:02 +00:00
2017-11-15 09:41:33 +00:00
# check if trying to connect to a nat, bridged, host-only or any other special adapter
2018-10-15 10:05:49 +00:00
nic_attachments = await self . _get_nic_attachements ( self . _maximum_adapters )
2017-11-15 09:41:33 +00:00
attachment = nic_attachments [ adapter_number ]
if attachment in ( " nat " , " bridged " , " intnet " , " hostonly " , " natnetwork " ) :
if not self . _use_any_adapter :
raise VirtualBoxError ( " Attachment ' {attachment} ' is already configured on adapter {adapter_number} . "
" Please remove it or allow VirtualBox VM ' {name} ' to use any adapter. " . format ( attachment = attachment ,
adapter_number = adapter_number ,
name = self . name ) )
elif self . is_running ( ) :
# dynamically configure an UDP tunnel attachment if the VM is already running
local_nio = self . _local_udp_tunnels [ adapter_number ] [ 0 ]
if local_nio and isinstance ( local_nio , NIOUDP ) :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " nic {} generic UDPTunnel " . format ( adapter_number + 1 ) )
await self . _control_vm ( " nicproperty {} sport= {} " . format ( adapter_number + 1 , local_nio . lport ) )
await self . _control_vm ( " nicproperty {} dest= {} " . format ( adapter_number + 1 , local_nio . rhost ) )
await self . _control_vm ( " nicproperty {} dport= {} " . format ( adapter_number + 1 , local_nio . rport ) )
await self . _control_vm ( " setlinkstate {} on " . format ( adapter_number + 1 ) )
2017-11-15 09:41:33 +00:00
2017-07-12 08:32:02 +00:00
if self . is_running ( ) :
try :
2018-10-15 10:05:49 +00:00
await self . add_ubridge_udp_connection ( " VBOX- {} - {} " . format ( self . _id , adapter_number ) ,
2017-07-12 08:32:02 +00:00
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
except KeyError :
2017-11-15 09:41:33 +00:00
raise VirtualBoxError ( " Adapter {adapter_number} doesn ' t exist on VirtualBox VM ' {name} ' " . format ( name = self . name ,
adapter_number = adapter_number ) )
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " setlinkstate {} on " . format ( adapter_number + 1 ) )
2016-02-11 02:08:34 +00:00
2014-07-17 21:28:02 +00:00
adapter . add_nio ( 0 , nio )
2017-11-15 09:41:33 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ]: {nio} added to adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
nio = nio ,
adapter_number = adapter_number ) )
2017-07-18 12:59:47 +00:00
2018-10-15 10:05:49 +00:00
async def adapter_update_nio_binding ( self , adapter_number , nio ) :
2017-07-18 12:59:47 +00:00
"""
2018-10-27 07:47:17 +00:00
Update an adapter NIO binding .
2017-07-18 12:59:47 +00:00
: param adapter_number : adapter number
2018-10-27 07:47:17 +00:00
: param nio : NIO instance to update on the adapter
2017-07-18 12:59:47 +00:00
"""
if self . is_running ( ) :
try :
2018-10-15 10:05:49 +00:00
await self . update_ubridge_udp_connection ( " VBOX- {} - {} " . format ( self . _id , adapter_number ) ,
2018-03-19 09:26:12 +00:00
self . _local_udp_tunnels [ adapter_number ] [ 1 ] ,
nio )
if nio . suspend :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " setlinkstate {} off " . format ( adapter_number + 1 ) )
2018-03-19 09:26:12 +00:00
else :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " setlinkstate {} on " . format ( adapter_number + 1 ) )
2017-07-18 12:59:47 +00:00
except IndexError :
2018-10-27 07:47:17 +00:00
raise VirtualBoxError ( ' Adapter {adapter_number} does not exist on VirtualBox VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2014-07-17 21:28:02 +00:00
2018-10-15 10:05:49 +00:00
async def adapter_remove_nio_binding ( self , adapter_number ) :
2014-07-17 21:28:02 +00:00
"""
2015-02-07 00:31:13 +00:00
Removes an adapter NIO binding .
2014-07-17 21:28:02 +00:00
2015-02-13 22:41:56 +00:00
: param adapter_number : adapter number
2014-07-17 21:28:02 +00:00
: returns : NIO instance
"""
try :
2015-02-13 22:41:56 +00:00
adapter = self . _ethernet_adapters [ adapter_number ]
2015-05-27 14:38:57 +00:00
except KeyError :
2015-02-13 22:41:56 +00:00
raise VirtualBoxError ( " Adapter {adapter_number} doesn ' t exist on VirtualBox VM ' {name} ' " . format ( name = self . name ,
adapter_number = adapter_number ) )
2014-07-17 21:28:02 +00:00
2019-04-01 12:47:31 +00:00
await self . stop_capture ( adapter_number )
2017-07-12 08:32:02 +00:00
if self . is_running ( ) :
2018-10-15 10:05:49 +00:00
await self . _ubridge_send ( " bridge delete {name} " . format ( name = " VBOX- {} - {} " . format ( self . _id , adapter_number ) ) )
vm_state = await self . _get_vm_state ( )
2017-07-11 11:42:47 +00:00
if vm_state == " running " :
2018-10-15 10:05:49 +00:00
await self . _control_vm ( " setlinkstate {} off " . format ( adapter_number + 1 ) )
2014-07-17 21:28:02 +00:00
nio = adapter . get_nio ( 0 )
2015-02-24 02:00:34 +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 )
2014-07-17 21:28:02 +00:00
adapter . remove_nio ( 0 )
2015-01-24 22:32:58 +00:00
2015-02-13 22:41:56 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ]: {nio} removed from adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
nio = nio ,
adapter_number = adapter_number ) )
2014-07-17 21:28:02 +00:00
return nio
2018-10-27 07:47:17 +00:00
def get_nio ( self , adapter_number ) :
2014-07-17 21:28:02 +00:00
"""
2018-10-27 07:47:17 +00:00
Gets an adapter NIO binding .
2014-07-17 21:28:02 +00:00
2015-02-13 22:41:56 +00:00
: param adapter_number : adapter number
2018-10-27 07:47:17 +00:00
: returns : NIO instance
2014-07-17 21:28:02 +00:00
"""
try :
2018-10-27 07:47:17 +00:00
adapter = self . ethernet_adapters [ adapter_number ]
2015-05-27 14:38:57 +00:00
except KeyError :
2015-02-13 22:41:56 +00:00
raise VirtualBoxError ( " Adapter {adapter_number} doesn ' t exist on VirtualBox VM ' {name} ' " . format ( name = self . name ,
adapter_number = adapter_number ) )
2014-07-17 21:28:02 +00:00
nio = adapter . get_nio ( 0 )
2015-06-01 21:42:17 +00:00
if not nio :
raise VirtualBoxError ( " Adapter {} is not connected " . format ( adapter_number ) )
2018-10-27 07:47:17 +00:00
return nio
def is_running ( self ) :
"""
: returns : True if the vm is not stopped
"""
return self . ubridge is not None
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 )
2014-07-17 21:28:02 +00:00
if nio . capturing :
2015-02-13 22:41:56 +00:00
raise VirtualBoxError ( " Packet capture is already activated on adapter {adapter_number} " . format ( adapter_number = adapter_number ) )
2014-07-17 21:28:02 +00:00
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 = " VBOX- {} - {} " . format ( self . _id , adapter_number ) ,
2016-06-25 00:35:39 +00:00
output_file = output_file ) )
2015-02-13 22:41:56 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ]: starting packet capture on adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
adapter_number = adapter_number ) )
2014-07-17 21:28:02 +00:00
2018-10-15 10:05:49 +00:00
async def stop_capture ( self , adapter_number ) :
2014-07-17 21:28:02 +00:00
"""
Stops a packet capture .
2015-02-13 22:41:56 +00:00
: param adapter_number : adapter number
2014-07-17 21:28:02 +00:00
"""
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
2014-07-17 21:28:02 +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 = " VBOX- {} - {} " . format ( self . _id , adapter_number ) ) )
2016-06-25 00:35:39 +00:00
2015-02-13 22:41:56 +00:00
log . info ( " VirtualBox VM ' {name} ' [ {id} ]: stopping packet capture on adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
adapter_number = adapter_number ) )