2015-05-01 04:05:37 +03:00
# -*- coding: utf-8 -*-
#
2015-09-05 23:38:11 +03:00
# Copyright (C) 2015 GNS3 Technologies Inc.
2015-05-01 04:05:37 +03:00
#
# 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/>.
"""
VMware VM instance .
"""
import sys
import os
import asyncio
2015-05-28 06:06:18 +03:00
import tempfile
2021-06-08 05:26:33 +03:00
import platform
2015-05-01 04:05:37 +03:00
2016-11-07 12:16:51 +02:00
from gns3server . utils . asyncio . telnet_server import AsyncioTelnetServer
from gns3server . utils . asyncio . serial import asyncio_open_serial
2021-06-08 05:26:33 +03:00
from gns3server . utils import parse_version
2018-08-25 10:10:47 +03:00
from gns3server . utils . asyncio import locking
2015-05-22 06:48:59 +03:00
from collections import OrderedDict
2015-05-01 04:05:37 +03:00
from . vmware_error import VMwareError
from . . nios . nio_udp import NIOUDP
2015-05-15 05:11:57 +03:00
from . . adapters . ethernet_adapter import EthernetAdapter
2016-05-11 20:35:36 +03:00
from . . base_node import BaseNode
2015-05-01 04:05:37 +03:00
2015-05-21 04:05:26 +03:00
2015-05-01 04:05:37 +03:00
import logging
log = logging . getLogger ( __name__ )
2015-05-21 04:05:26 +03:00
2016-05-11 20:35:36 +03:00
class VMwareVM ( BaseNode ) :
2015-05-01 04:05:37 +03:00
"""
VMware VM implementation .
"""
2018-03-24 14:11:21 +03:00
def __init__ ( self , name , node_id , project , manager , vmx_path , linked_clone = False , console = None , console_type = " telnet " ) :
2015-05-01 04:05:37 +03:00
2018-03-24 14:11:21 +03:00
super ( ) . __init__ ( name , node_id , project , manager , console = console , console_type = console_type , linked_clone = linked_clone )
2015-05-01 04:05:37 +03:00
2015-05-22 06:48:59 +03:00
self . _vmx_pairs = OrderedDict ( )
2016-11-07 12:16:51 +02:00
self . _telnet_server = None
2015-05-22 06:48:59 +03:00
self . _vmnets = [ ]
self . _maximum_adapters = 10
2015-05-28 06:06:18 +03:00
self . _started = False
2015-05-01 04:05:37 +03:00
self . _closed = False
# VMware VM settings
self . _headless = False
self . _vmx_path = vmx_path
2018-03-30 17:18:44 +03:00
self . _on_close = " power_off "
2015-05-15 05:11:57 +03:00
self . _adapters = 0
self . _ethernet_adapters = { }
self . _adapter_type = " e1000 "
2015-05-22 06:48:59 +03:00
self . _use_any_adapter = False
2015-05-15 05:11:57 +03:00
if not os . path . exists ( vmx_path ) :
2016-05-11 20:35:36 +03:00
raise VMwareError ( ' VMware VM " {name} " [ {id} ]: could not find VMX file " {vmx_path} " ' . format ( name = name , id = node_id , vmx_path = vmx_path ) )
2015-05-01 04:05:37 +03:00
2017-07-18 19:04:03 +03:00
@property
def ethernet_adapters ( self ) :
return self . _ethernet_adapters
2015-05-01 04:05:37 +03:00
def __json__ ( self ) :
2015-06-26 18:09:19 +03:00
json = { " name " : self . name ,
2018-12-30 14:35:24 +02:00
" usage " : self . usage ,
2016-05-11 20:35:36 +03:00
" node_id " : self . id ,
2015-05-01 04:05:37 +03:00
" console " : self . console ,
2016-11-07 12:16:51 +02:00
" console_type " : self . console_type ,
2015-05-01 04:05:37 +03:00
" project_id " : self . project . id ,
" vmx_path " : self . vmx_path ,
2015-05-15 05:11:57 +03:00
" headless " : self . headless ,
2018-03-30 17:18:44 +03:00
" on_close " : self . on_close ,
2015-05-15 05:11:57 +03:00
" adapters " : self . _adapters ,
2015-05-22 06:48:59 +03:00
" adapter_type " : self . adapter_type ,
2015-07-09 02:48:34 +03:00
" use_any_adapter " : self . use_any_adapter ,
2016-05-17 20:51:06 +03:00
" status " : self . status ,
2017-10-02 11:41:57 +03:00
" node_directory " : self . working_path ,
2016-10-24 22:39:35 +03:00
" linked_clone " : self . linked_clone }
2015-06-26 18:09:19 +03:00
return json
2015-05-22 06:48:59 +03:00
@property
def vmnets ( self ) :
return self . _vmnets
2015-05-01 04:05:37 +03:00
2018-08-25 10:10:47 +03:00
@locking
2018-10-15 13:05:49 +03:00
async def _control_vm ( self , subcommand , * additional_args ) :
2015-05-01 04:05:37 +03:00
args = [ self . _vmx_path ]
args . extend ( additional_args )
2018-10-15 13:05:49 +03:00
result = await self . manager . execute ( subcommand , args )
2015-05-01 04:05:37 +03:00
log . debug ( " Control VM ' {} ' result: {} " . format ( subcommand , result ) )
return result
2015-09-05 23:38:11 +03:00
def _read_vmx_file ( self ) :
"""
Reads from the VMware VMX file corresponding to this VM .
"""
try :
self . _vmx_pairs = self . manager . parse_vmware_file ( self . _vmx_path )
except OSError as e :
raise VMwareError ( ' Could not read VMware VMX file " {} " : {} ' . format ( self . _vmx_path , e ) )
def _write_vmx_file ( self ) :
"""
Writes pairs to the VMware VMX file corresponding to this VM .
"""
try :
self . manager . write_vmx_file ( self . _vmx_path , self . _vmx_pairs )
except OSError as e :
raise VMwareError ( ' Could not write VMware VMX file " {} " : {} ' . format ( self . _vmx_path , e ) )
2018-10-15 13:05:49 +03:00
async def is_running ( self ) :
2015-10-12 01:41:55 +03:00
2018-10-15 13:05:49 +03:00
result = await self . manager . execute ( " list " , [ ] )
2015-10-12 01:41:55 +03:00
if self . _vmx_path in result :
return True
return False
2018-10-15 13:05:49 +03:00
async def _check_duplicate_linked_clone ( self ) :
2016-11-17 13:21:38 +02: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 . vmx_path == self . _vmx_path :
found = True
if node . project != self . project :
if trial > = 30 :
raise VMwareError ( " 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 . vmx_path , node . name , self . project . name ) )
else :
if trial > = 5 :
raise VMwareError ( " 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 . vmx_path , node . name ) )
if not found :
return
trial + = 1
2018-10-15 13:05:49 +03:00
await asyncio . sleep ( 1 )
2016-11-17 13:21:38 +02:00
2018-10-15 13:05:49 +03:00
async def create ( self ) :
2015-06-18 02:36:52 +03:00
"""
Creates this VM and handle linked clones .
"""
2016-11-17 13:21:38 +02:00
if not self . linked_clone :
2018-10-15 13:05:49 +03:00
await self . _check_duplicate_linked_clone ( )
2015-05-31 05:26:38 +03:00
2018-10-15 13:05:49 +03:00
await self . manager . check_vmrun_version ( )
2016-10-24 22:39:35 +03:00
if self . linked_clone and not os . path . exists ( os . path . join ( self . working_dir , os . path . basename ( self . _vmx_path ) ) ) :
2016-04-04 21:10:48 +03:00
if self . manager . host_type == " player " :
raise VMwareError ( " Linked clones are not supported by VMware Player " )
2015-05-31 05:26:38 +03:00
# create the base snapshot for linked clones
base_snapshot_name = " GNS3 Linked Base for clones "
vmsd_path = os . path . splitext ( self . _vmx_path ) [ 0 ] + " .vmsd "
if not os . path . exists ( vmsd_path ) :
raise VMwareError ( " {} doesn ' t not exist " . format ( vmsd_path ) )
try :
vmsd_pairs = self . manager . parse_vmware_file ( vmsd_path )
except OSError as e :
raise VMwareError ( ' Could not read VMware VMSD file " {} " : {} ' . format ( vmsd_path , e ) )
gns3_snapshot_exists = False
for value in vmsd_pairs . values ( ) :
if value == base_snapshot_name :
gns3_snapshot_exists = True
break
if not gns3_snapshot_exists :
log . info ( " Creating snapshot ' {} ' " . format ( base_snapshot_name ) )
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " snapshot " , base_snapshot_name )
2015-05-31 05:26:38 +03:00
# create the linked clone based on the base snapshot
new_vmx_path = os . path . join ( self . working_dir , self . name + " .vmx " )
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " clone " ,
2018-11-11 14:13:58 +02:00
new_vmx_path ,
" linked " ,
" -snapshot= {} " . format ( base_snapshot_name ) ,
" -cloneName= {} " . format ( self . name ) )
2015-05-31 05:26:38 +03:00
try :
vmsd_pairs = self . manager . parse_vmware_file ( vmsd_path )
except OSError as e :
raise VMwareError ( ' Could not read VMware VMSD file " {} " : {} ' . format ( vmsd_path , e ) )
snapshot_name = None
for name , value in vmsd_pairs . items ( ) :
if value == base_snapshot_name :
snapshot_name = name . split ( " . " , 1 ) [ 0 ]
break
if snapshot_name is None :
raise VMwareError ( " Could not find the linked base snapshot in {} " . format ( vmsd_path ) )
num_clones_entry = " {} .numClones " . format ( snapshot_name )
if num_clones_entry in vmsd_pairs :
try :
nb_of_clones = int ( vmsd_pairs [ num_clones_entry ] )
except ValueError :
raise VMwareError ( " Value of {} in {} is not a number " . format ( num_clones_entry , vmsd_path ) )
vmsd_pairs [ num_clones_entry ] = str ( nb_of_clones - 1 )
for clone_nb in range ( 0 , nb_of_clones ) :
clone_entry = " {} .clone {} " . format ( snapshot_name , clone_nb )
if clone_entry in vmsd_pairs :
del vmsd_pairs [ clone_entry ]
try :
self . manager . write_vmware_file ( vmsd_path , vmsd_pairs )
except OSError as e :
raise VMwareError ( ' Could not write VMware VMSD file " {} " : {} ' . format ( vmsd_path , e ) )
# update the VMX file path
self . _vmx_path = new_vmx_path
2015-05-22 06:48:59 +03:00
def _get_vmx_setting ( self , name , value = None ) :
2015-05-21 04:05:26 +03:00
2015-05-22 06:48:59 +03:00
if name in self . _vmx_pairs :
if value is not None :
if self . _vmx_pairs [ name ] == value :
return value
else :
return self . _vmx_pairs [ name ]
return None
2015-05-21 04:05:26 +03:00
2015-05-15 05:11:57 +03:00
def _set_network_options ( self ) :
2015-06-18 02:36:52 +03:00
"""
Set up VMware networking .
"""
2015-05-15 05:11:57 +03:00
2015-09-05 23:38:11 +03:00
# first some sanity checks
2015-05-21 04:05:26 +03:00
for adapter_number in range ( 0 , self . _adapters ) :
2015-09-05 23:38:11 +03:00
# we want the vmnet interface to be connected when starting the VM
2015-05-22 06:48:59 +03:00
connected = " ethernet {} .startConnected " . format ( adapter_number )
if self . _get_vmx_setting ( connected ) :
del self . _vmx_pairs [ connected ]
2021-06-08 05:26:33 +03:00
use_ubridge = True
# use alternative method to find vmnet interfaces on macOS >= 11.0 (BigSur)
# because "bridge" interfaces are used instead and they are only created on the VM starts
if sys . platform . startswith ( " darwin " ) and parse_version ( platform . mac_ver ( ) [ 0 ] ) > = parse_version ( " 11.0.0 " ) :
use_ubridge = False
self . manager . refresh_vmnet_list ( ubridge = use_ubridge )
2015-09-05 23:38:11 +03:00
# then configure VMware network adapters
2015-05-22 06:48:59 +03:00
for adapter_number in range ( 0 , self . _adapters ) :
2015-09-05 23:38:11 +03:00
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 )
2015-09-05 23:38:11 +03:00
# add/update the interface
2018-04-02 18:27:12 +03:00
if adapter_type == " default " :
2016-05-23 23:14:42 +03:00
# force default to e1000 because some guest OS don't detect the adapter (i.e. Windows 2012 server)
# when 'virtualdev' is not set in the VMX file.
2018-04-02 18:27:12 +03:00
vmware_adapter_type = " e1000 "
2016-05-23 23:14:42 +03:00
else :
2018-04-02 18:27:12 +03:00
vmware_adapter_type = adapter_type
2015-05-22 06:48:59 +03:00
ethernet_adapter = { " ethernet {} .present " . format ( adapter_number ) : " TRUE " ,
2015-06-19 02:53:08 +03:00
" ethernet {} .addresstype " . format ( adapter_number ) : " generated " ,
2016-05-23 23:14:42 +03:00
" ethernet {} .generatedaddressoffset " . format ( adapter_number ) : " 0 " ,
2018-04-02 18:27:12 +03:00
" ethernet {} .virtualdev " . format ( adapter_number ) : vmware_adapter_type }
2015-05-22 06:48:59 +03:00
self . _vmx_pairs . update ( ethernet_adapter )
2015-06-19 02:53:08 +03:00
connection_type = " ethernet {} .connectiontype " . format ( adapter_number )
2015-05-22 06:48:59 +03:00
if not self . _use_any_adapter and connection_type in self . _vmx_pairs and self . _vmx_pairs [ connection_type ] in ( " nat " , " bridged " , " hostonly " ) :
continue
2015-09-05 23:38:11 +03:00
2016-05-24 00:53:03 +03:00
self . _vmx_pairs [ " ethernet {} .connectiontype " . format ( adapter_number ) ] = " custom "
2018-04-17 06:47:25 +03:00
2016-06-01 04:16:55 +03:00
# make sure we have a vmnet per adapter if we use uBridge
allocate_vmnet = False
2015-09-05 23:38:11 +03:00
2016-06-01 04:16:55 +03:00
# first check if a vmnet is already assigned to the adapter
vnet = " ethernet {} .vnet " . format ( adapter_number )
if vnet in self . _vmx_pairs :
vmnet = os . path . basename ( self . _vmx_pairs [ vnet ] )
if self . manager . is_managed_vmnet ( vmnet ) or vmnet in ( " vmnet0 " , " vmnet1 " , " vmnet8 " ) :
2018-04-17 06:47:25 +03:00
# vmnet already managed or a special vmnet, try to allocate a new one
2015-09-05 23:38:11 +03:00
allocate_vmnet = True
2015-09-13 23:52:25 +03:00
else :
2016-06-01 04:16:55 +03:00
# otherwise allocate a new one
allocate_vmnet = True
if allocate_vmnet :
try :
vmnet = self . manager . allocate_vmnet ( )
2017-07-18 19:04:03 +03:00
except BaseException :
2016-06-01 04:16:55 +03:00
# clear everything up in case of error (e.g. no enough vmnets)
self . _vmnets . clear ( )
raise
2018-04-17 06:47:25 +03:00
# mark the vmnet as managed by us
2016-06-01 04:16:55 +03:00
if vmnet not in self . _vmnets :
self . _vmnets . append ( vmnet )
self . _vmx_pairs [ " ethernet {} .vnet " . format ( adapter_number ) ] = vmnet
2015-05-22 06:48:59 +03:00
# disable remaining network adapters
for adapter_number in range ( self . _adapters , self . _maximum_adapters ) :
if self . _get_vmx_setting ( " ethernet {} .present " . format ( adapter_number ) , " TRUE " ) :
log . debug ( " disabling remaining adapter {} " . format ( adapter_number ) )
2015-06-19 02:53:08 +03:00
self . _vmx_pairs [ " ethernet {} .startconnected " . format ( adapter_number ) ] = " FALSE "
2015-05-21 04:05:26 +03:00
2017-07-18 19:04:03 +03:00
def _get_vnet ( self , adapter_number ) :
"""
Return the vnet will use in ubridge
"""
vnet = " ethernet {} .vnet " . format ( adapter_number )
if vnet not in self . _vmx_pairs :
raise VMwareError ( " vnet {} not in VMX file " . format ( vnet ) )
return vnet
2018-10-15 13:05:49 +03:00
async def _add_ubridge_connection ( self , nio , adapter_number ) :
2015-07-20 07:55:10 +03:00
"""
Creates a connection in uBridge .
2015-05-21 04:05:26 +03:00
2015-07-20 07:55:10 +03:00
: param nio : NIO instance
: param adapter_number : adapter number
2015-05-21 04:05:26 +03:00
"""
2015-07-20 07:55:10 +03:00
2017-07-18 19:04:03 +03:00
vnet = self . _get_vnet ( adapter_number )
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( " bridge create {name} " . format ( name = vnet ) )
2015-07-20 07:55:10 +03:00
vmnet_interface = os . path . basename ( self . _vmx_pairs [ vnet ] )
2016-03-15 02:27:51 +02:00
2016-11-13 11:28:14 +02:00
if sys . platform . startswith ( " darwin " ) :
2021-06-08 05:26:33 +03:00
if parse_version ( platform . mac_ver ( ) [ 0 ] ) > = parse_version ( " 11.0.0 " ) :
# a bridge interface (bridge100, bridge101 etc.) is used instead of a vmnet interface
# on macOS >= 11.0 (Big Sur)
vmnet_interface = self . manager . find_bridge_interface ( vmnet_interface )
if not vmnet_interface :
2021-06-12 08:50:55 +03:00
raise VMwareError ( f " Could not find bridge interface linked with { vmnet_interface } " )
2021-06-08 05:26:33 +03:00
block_host_traffic = self . manager . config . get_section_config ( " VMware " ) . getboolean ( " block_host_traffic " , False )
await self . _add_ubridge_ethernet_connection ( vnet , vmnet_interface , block_host_traffic )
else :
# special case on macOS, we cannot bind VMnet interfaces using the libpcap
await self . _ubridge_send ( ' bridge add_nio_fusion_vmnet {name} " {interface} " ' . format ( name = vnet , interface = vmnet_interface ) )
2015-07-20 07:55:10 +03:00
else :
2017-07-19 12:56:24 +03:00
block_host_traffic = self . manager . config . get_section_config ( " VMware " ) . getboolean ( " block_host_traffic " , False )
2018-10-15 13:05:49 +03:00
await self . _add_ubridge_ethernet_connection ( vnet , vmnet_interface , block_host_traffic )
2015-07-20 07:55:10 +03:00
if isinstance ( nio , NIOUDP ) :
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( ' bridge add_nio_udp {name} {lport} {rhost} {rport} ' . format ( name = vnet ,
2016-06-24 01:56:06 +03:00
lport = nio . lport ,
rhost = nio . rhost ,
rport = nio . rport ) )
2015-07-20 07:55:10 +03:00
if nio . capturing :
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( ' bridge start_capture {name} " {pcap_file} " ' . format ( name = vnet , pcap_file = nio . pcap_output_file ) )
2015-07-20 07:55:10 +03:00
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( ' bridge start {name} ' . format ( name = vnet ) )
await self . _ubridge_apply_filters ( vnet , nio . filters )
2017-07-18 19:04:03 +03:00
2018-10-15 13:05:49 +03:00
async def _update_ubridge_connection ( self , adapter_number , nio ) :
2017-07-18 19:04:03 +03:00
"""
Update a connection in uBridge .
: param nio : NIO instance
: param adapter_number : adapter number
"""
try :
bridge_name = self . _get_vnet ( adapter_number )
except VMwareError :
return # vnet not yet available
2018-10-15 13:05:49 +03:00
await self . _ubridge_apply_filters ( bridge_name , nio . filters )
2016-03-15 02:27:51 +02:00
2018-10-15 13:05:49 +03:00
async def _delete_ubridge_connection ( self , adapter_number ) :
2015-05-21 04:05:26 +03:00
"""
2015-07-20 07:55:10 +03:00
Deletes a connection in uBridge .
2015-05-15 05:11:57 +03:00
2015-07-20 07:55:10 +03:00
: param adapter_number : adapter number
"""
2015-05-21 04:05:26 +03:00
2015-07-20 07:55:10 +03:00
vnet = " ethernet {} .vnet " . format ( adapter_number )
if vnet not in self . _vmx_pairs :
raise VMwareError ( " vnet {} not in VMX file " . format ( vnet ) )
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( " bridge delete {name} " . format ( name = vnet ) )
2015-05-21 04:05:26 +03:00
2018-10-15 13:05:49 +03:00
async def _start_ubridge_capture ( self , adapter_number , output_file ) :
2015-09-13 18:40:09 +03:00
"""
Start a packet capture in uBridge .
: param adapter_number : adapter number
: param output_file : PCAP destination file for the capture
"""
vnet = " ethernet {} .vnet " . format ( adapter_number )
if vnet not in self . _vmx_pairs :
raise VMwareError ( " vnet {} not in VMX file " . format ( vnet ) )
2016-01-26 23:45:55 +02:00
if not self . _ubridge_hypervisor :
raise VMwareError ( " Cannot start the packet capture: uBridge is not running " )
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( ' bridge start_capture {name} " {output_file} " ' . format ( name = vnet ,
2016-07-13 18:47:00 +03:00
output_file = output_file ) )
2015-09-13 18:40:09 +03:00
2018-10-15 13:05:49 +03:00
async def _stop_ubridge_capture ( self , adapter_number ) :
2015-09-13 18:40:09 +03:00
"""
Stop a packet capture in uBridge .
: param adapter_number : adapter number
"""
vnet = " ethernet {} .vnet " . format ( adapter_number )
if vnet not in self . _vmx_pairs :
raise VMwareError ( " vnet {} not in VMX file " . format ( vnet ) )
2016-01-26 23:45:55 +02:00
if not self . _ubridge_hypervisor :
raise VMwareError ( " Cannot stop the packet capture: uBridge is not running " )
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( " bridge stop_capture {name} " . format ( name = vnet ) )
2015-09-13 18:40:09 +03:00
2015-07-27 04:21:30 +03:00
def check_hw_virtualization ( self ) :
"""
Returns either hardware virtualization is activated or not .
: returns : boolean
"""
2015-09-05 23:38:11 +03:00
self . _read_vmx_file ( )
2015-07-27 04:21:30 +03:00
if self . _get_vmx_setting ( " vhv.enable " , " TRUE " ) :
return True
return False
2018-10-15 13:05:49 +03:00
async def start ( self ) :
2015-05-01 04:05:37 +03:00
"""
Starts this VMware VM .
"""
2016-11-17 11:38:29 +02:00
if self . status == " started " :
return
2021-06-08 05:26:33 +03:00
if await self . is_running ( ) :
2015-10-12 01:41:55 +03:00
raise VMwareError ( " The VM is already running in VMware " )
2015-05-26 00:49:28 +03:00
2015-05-21 04:05:26 +03:00
ubridge_path = self . ubridge_path
if not ubridge_path or not os . path . isfile ( ubridge_path ) :
raise VMwareError ( " ubridge is necessary to start a VMware VM " )
2018-11-19 19:22:16 +02:00
await self . _start_ubridge ( require_privileged_access = True )
2015-09-05 23:38:11 +03:00
self . _read_vmx_file ( )
2015-10-13 00:57:37 +03:00
# check if there is enough RAM to run
if " memsize " in self . _vmx_pairs :
self . check_available_ram ( int ( self . _vmx_pairs [ " memsize " ] ) )
2015-05-15 05:11:57 +03:00
self . _set_network_options ( )
2015-05-28 06:06:18 +03:00
self . _set_serial_console ( )
2015-09-05 23:38:11 +03:00
self . _write_vmx_file ( )
2015-05-21 04:05:26 +03:00
2015-05-01 04:05:37 +03:00
if self . _headless :
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " start " , " nogui " )
2015-05-01 04:05:37 +03:00
else :
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " start " )
2015-05-28 06:06:18 +03:00
2016-03-23 18:54:45 +02:00
try :
2016-06-01 04:16:55 +03:00
if self . _ubridge_hypervisor :
2016-03-23 18:54:45 +02:00
for adapter_number in range ( 0 , self . _adapters ) :
nio = self . _ethernet_adapters [ adapter_number ] . get_nio ( 0 )
if nio :
2018-10-15 13:05:49 +03:00
await self . _add_ubridge_connection ( nio , adapter_number )
2015-07-20 07:55:10 +03:00
2018-10-15 13:05:49 +03:00
await self . _start_console ( )
2016-03-23 18:54:45 +02:00
except VMwareError :
2018-10-15 13:05:49 +03:00
await self . stop ( )
2016-03-23 18:54:45 +02:00
raise
2015-05-28 06:06:18 +03:00
2015-07-22 07:58:28 +03:00
if self . _get_vmx_setting ( " vhv.enable " , " TRUE " ) :
self . _hw_virtualization = True
2015-05-28 06:06:18 +03:00
self . _started = True
2016-05-14 05:41:58 +03:00
self . status = " started "
2015-05-01 04:05:37 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ] started " . format ( name = self . name , id = self . id ) )
2018-10-15 13:05:49 +03:00
async def stop ( self ) :
2015-05-01 04:05:37 +03:00
"""
Stops this VMware VM .
"""
2015-07-22 07:58:28 +03:00
self . _hw_virtualization = False
2018-10-15 13:05:49 +03:00
await self . _stop_remote_console ( )
await self . _stop_ubridge ( )
2015-05-21 04:05:26 +03:00
2015-05-22 06:48:59 +03:00
try :
2021-06-08 05:26:33 +03:00
if await self . is_running ( ) :
2018-03-30 17:18:44 +03:00
if self . on_close == " save_vm_state " :
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " suspend " )
2018-03-30 17:18:44 +03:00
elif self . on_close == " shutdown_signal " :
2016-03-19 20:08:44 +02:00
# use ACPI to shutdown the VM
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " stop " , " soft " )
2016-03-19 20:08:44 +02:00
else :
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " stop " )
2015-05-22 06:48:59 +03:00
finally :
2015-05-28 06:06:18 +03:00
self . _started = False
2016-05-14 05:41:58 +03:00
self . status = " stopped "
2015-05-22 06:48:59 +03:00
2015-09-05 23:38:11 +03:00
self . _read_vmx_file ( )
2016-06-01 04:16:55 +03:00
self . _vmnets . clear ( )
# remove the adapters managed by GNS3
for adapter_number in range ( 0 , self . _adapters ) :
vnet = " ethernet {} .vnet " . format ( adapter_number )
if self . _get_vmx_setting ( vnet ) or self . _get_vmx_setting ( " ethernet {} .connectiontype " . format ( adapter_number ) ) is None :
if vnet in self . _vmx_pairs :
vmnet = os . path . basename ( self . _vmx_pairs [ vnet ] )
if not self . manager . is_managed_vmnet ( vmnet ) :
continue
log . debug ( " removing adapter {} " . format ( adapter_number ) )
self . _vmx_pairs [ vnet ] = " vmnet1 "
self . _vmx_pairs [ " ethernet {} .connectiontype " . format ( adapter_number ) ] = " custom "
2015-05-22 06:48:59 +03:00
# re-enable any remaining network adapters
for adapter_number in range ( self . _adapters , self . _maximum_adapters ) :
if self . _get_vmx_setting ( " ethernet {} .present " . format ( adapter_number ) , " TRUE " ) :
log . debug ( " enabling remaining adapter {} " . format ( adapter_number ) )
2015-06-19 02:53:08 +03:00
self . _vmx_pairs [ " ethernet {} .startconnected " . format ( adapter_number ) ] = " TRUE "
2015-09-05 23:38:11 +03:00
self . _write_vmx_file ( )
2015-05-22 06:48:59 +03:00
2018-10-15 13:05:49 +03:00
await super ( ) . stop ( )
2015-05-01 04:05:37 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ] stopped " . format ( name = self . name , id = self . id ) )
2018-10-15 13:05:49 +03:00
async def suspend ( self ) :
2015-05-01 04:05:37 +03:00
"""
Suspends this VMware VM .
"""
2015-05-16 04:09:48 +03:00
if self . manager . host_type != " ws " :
raise VMwareError ( " Pausing a VM is only supported by VMware Workstation " )
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " pause " )
2016-05-14 05:41:58 +03:00
self . status = " suspended "
2015-05-02 03:47:46 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ] paused " . format ( name = self . name , id = self . id ) )
2015-05-01 04:05:37 +03:00
2018-10-15 13:05:49 +03:00
async def resume ( self ) :
2015-05-01 04:05:37 +03:00
"""
Resumes this VMware VM .
"""
2015-05-16 04:09:48 +03:00
if self . manager . host_type != " ws " :
raise VMwareError ( " Unpausing a VM is only supported by VMware Workstation " )
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " unpause " )
2016-05-14 05:41:58 +03:00
self . status = " started "
2015-05-01 04:05:37 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ] resumed " . format ( name = self . name , id = self . id ) )
2018-10-15 13:05:49 +03:00
async def reload ( self ) :
2015-05-01 04:05:37 +03:00
"""
Reloads this VMware VM .
"""
2018-10-15 13:05:49 +03:00
await self . _control_vm ( " reset " )
2015-05-01 04:05:37 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ] reloaded " . format ( name = self . name , id = self . id ) )
2018-10-15 13:05:49 +03:00
async def close ( self ) :
2015-05-01 04:05:37 +03:00
"""
2015-09-05 23:38:11 +03:00
Closes this VMware VM .
2015-05-01 04:05:37 +03:00
"""
2018-10-15 13:05:49 +03:00
if not ( await super ( ) . close ( ) ) :
2016-02-29 11:38:30 +02:00
return False
2015-05-01 04:05:37 +03:00
2015-05-21 04:05:26 +03:00
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 )
2015-05-02 03:47:46 +03:00
try :
2018-03-30 17:18:44 +03:00
self . on_close = " power_off "
2018-10-15 13:05:49 +03:00
await self . stop ( )
2015-05-02 03:47:46 +03:00
except VMwareError :
pass
2015-05-01 04:05:37 +03:00
2016-10-24 22:39:35 +03:00
if self . linked_clone :
2018-10-15 13:05:49 +03:00
await self . manager . remove_from_vmware_inventory ( self . _vmx_path )
2015-05-31 05:26:38 +03:00
2015-05-01 04:05:37 +03: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 :
log . info ( " VMware VM ' {name} ' [ {id} ] has enabled the headless mode " . format ( name = self . name , id = self . id ) )
else :
log . info ( " VMware VM ' {name} ' [ {id} ] has disabled the headless mode " . format ( name = self . name , id = self . id ) )
self . _headless = headless
2015-06-19 00:02:31 +03:00
@property
2018-03-30 17:18:44 +03:00
def on_close ( self ) :
2015-06-19 00:02:31 +03:00
"""
2018-03-30 17:18:44 +03:00
Returns the action to execute when the VM is stopped / closed
2015-06-19 00:02:31 +03:00
2018-03-30 17:18:44 +03:00
: returns : string
2015-06-19 00:02:31 +03:00
"""
2018-03-30 17:18:44 +03:00
return self . _on_close
2015-06-19 00:02:31 +03:00
2018-03-30 17:18:44 +03:00
@on_close.setter
def on_close ( self , on_close ) :
2015-06-19 00:02:31 +03:00
"""
2018-03-30 17:18:44 +03:00
Sets the action to execute when the VM is stopped / closed
2015-06-19 00:02:31 +03:00
2018-03-30 17:18:44 +03:00
: param on_close : string
2015-06-19 00:02:31 +03:00
"""
2018-03-30 17:18:44 +03:00
log . info ( ' VMware 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-19 00:02:31 +03:00
2015-05-01 04:05:37 +03:00
@property
def vmx_path ( self ) :
"""
Returns the path to the vmx file .
: returns : VMware vmx file
"""
return self . _vmx_path
@vmx_path.setter
def vmx_path ( self , vmx_path ) :
"""
Sets the path to the vmx file .
: param vmx_path : VMware vmx file
"""
log . info ( " VMware VM ' {name} ' [ {id} ] has set the vmx file path to ' {vmx} ' " . format ( name = self . name , id = self . id , vmx = vmx_path ) )
self . _vmx_path = vmx_path
2015-05-15 05:11:57 +03:00
@property
def adapters ( self ) :
"""
Returns the number of adapters configured for this VMware VM .
: returns : number of adapters
"""
return self . _adapters
@adapters.setter
def adapters ( self , adapters ) :
"""
Sets the number of Ethernet adapters for this VMware VM instance .
: param adapters : number of adapters
"""
2015-07-20 07:55:10 +03:00
# VMware VMs are limited to 10 adapters
2015-05-15 05:11:57 +03:00
if adapters > 10 :
raise VMwareError ( " Number of adapters above the maximum supported of 10 " )
self . _ethernet_adapters . clear ( )
for adapter_number in range ( 0 , adapters ) :
self . _ethernet_adapters [ adapter_number ] = EthernetAdapter ( )
self . _adapters = len ( self . _ethernet_adapters )
log . info ( " VMware VM ' {name} ' [ {id} ] has changed the number of Ethernet adapters to {adapters} " . format ( name = self . name ,
id = self . id ,
adapters = adapters ) )
@property
def adapter_type ( self ) :
"""
Returns the adapter type for this VMware 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 VMware VM instance .
: param adapter_type : adapter type ( string )
"""
self . _adapter_type = adapter_type
log . info ( " VMware VM ' {name} ' [ {id} ]: adapter type changed to {adapter_type} " . format ( name = self . name ,
id = self . id ,
adapter_type = adapter_type ) )
2015-05-16 04:09:48 +03:00
2015-05-22 06:48:59 +03:00
@property
def use_any_adapter ( self ) :
"""
Returns either GNS3 can use any VMware adapter on this instance .
: returns : boolean
"""
return self . _use_any_adapter
@use_any_adapter.setter
def use_any_adapter ( self , use_any_adapter ) :
"""
Allows GNS3 to use any VMware adapter on this instance .
: param use_any_adapter : boolean
"""
if use_any_adapter :
log . info ( " VMware VM ' {name} ' [ {id} ] is allowed to use any adapter " . format ( name = self . name , id = self . id ) )
else :
log . info ( " VMware VM ' {name} ' [ {id} ] is not allowed to use any adapter " . format ( name = self . name , id = self . id ) )
self . _use_any_adapter = use_any_adapter
2018-10-15 13:05:49 +03:00
async def adapter_add_nio_binding ( self , adapter_number , nio ) :
2015-05-16 04:09:48 +03:00
"""
Adds an adapter NIO binding .
: param adapter_number : adapter number
: param nio : NIO instance to add to the slot / port
"""
try :
adapter = self . _ethernet_adapters [ adapter_number ]
except IndexError :
raise VMwareError ( " Adapter {adapter_number} doesn ' t exist on VMware VM ' {name} ' " . format ( name = self . name ,
adapter_number = adapter_number ) )
2015-09-05 23:38:11 +03:00
self . _read_vmx_file ( )
2016-06-01 04:06:48 +03:00
# check if trying to connect to a nat, bridged or host-only adapter
2017-11-15 11:55:19 +02:00
if self . _get_vmx_setting ( " ethernet {} .present " . format ( adapter_number ) , " TRUE " ) :
2016-06-01 04:06:48 +03:00
# check for the connection type
connection_type = " ethernet {} .connectiontype " . format ( adapter_number )
2018-04-17 06:47:25 +03:00
if not self . _use_any_adapter and connection_type in self . _vmx_pairs and self . _vmx_pairs [ connection_type ] in ( " nat " , " bridged " , " hostonly " ) :
2021-06-08 05:26:33 +03:00
if await self . is_running ( ) :
2017-11-15 11:55:19 +02:00
raise VMwareError ( " Attachment ' {attachment} ' is configured on network adapter {adapter_number} . "
" Please stop VMware VM ' {name} ' to link to this adapter and allow GNS3 to change the attachment type. " . format ( attachment = self . _vmx_pairs [ connection_type ] ,
adapter_number = adapter_number ,
name = self . name ) )
2018-04-17 06:47:25 +03:00
else :
raise VMwareError ( " Attachment ' {attachment} ' is already configured on network adapter {adapter_number} . "
" Please remove it or allow VMware VM ' {name} ' to use any adapter. " . format ( attachment = self . _vmx_pairs [ connection_type ] ,
adapter_number = adapter_number ,
name = self . name ) )
2016-06-01 04:06:48 +03:00
adapter . add_nio ( 0 , nio )
2016-06-01 04:16:55 +03:00
if self . _started and self . _ubridge_hypervisor :
2018-10-15 13:05:49 +03:00
await self . _add_ubridge_connection ( nio , adapter_number )
2015-07-20 07:55:10 +03:00
2015-05-16 04:09:48 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ]: {nio} added to adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
nio = nio ,
adapter_number = adapter_number ) )
2018-10-15 13:05:49 +03:00
async def adapter_update_nio_binding ( self , adapter_number , nio ) :
2017-07-18 19:04:03 +03:00
"""
2018-10-27 10:47:17 +03:00
Updates an adapter NIO binding .
2017-07-18 19:04:03 +03:00
: param adapter_number : adapter number
2018-10-27 10:47:17 +03:00
: param nio : NIO instance to update on the adapter
2017-07-18 19:04:03 +03:00
"""
if self . _ubridge_hypervisor :
try :
2018-10-15 13:05:49 +03:00
await self . _update_ubridge_connection ( adapter_number , nio )
2017-07-18 19:04:03 +03:00
except IndexError :
2018-10-27 10:47:17 +03:00
raise VMwareError ( ' Adapter {adapter_number} does not exist on VMware VM " {name} " ' . format ( name = self . _name ,
adapter_number = adapter_number ) )
2017-07-18 19:04:03 +03:00
2018-10-15 13:05:49 +03:00
async def adapter_remove_nio_binding ( self , adapter_number ) :
2015-05-16 04:09:48 +03:00
"""
Removes an adapter NIO binding .
: param adapter_number : adapter number
: returns : NIO instance
"""
try :
adapter = self . _ethernet_adapters [ adapter_number ]
except IndexError :
raise VMwareError ( " Adapter {adapter_number} doesn ' t exist on VMware VM ' {name} ' " . format ( name = self . name ,
adapter_number = adapter_number ) )
2019-04-01 15:47:31 +03:00
await self . stop_capture ( adapter_number )
2015-05-16 04:09:48 +03:00
nio = adapter . get_nio ( 0 )
if isinstance ( nio , NIOUDP ) :
self . manager . port_manager . release_udp_port ( nio . lport , self . _project )
adapter . remove_nio ( 0 )
2016-06-01 04:16:55 +03:00
if self . _started and self . _ubridge_hypervisor :
2018-10-15 13:05:49 +03:00
await self . _delete_ubridge_connection ( adapter_number )
2015-05-16 04:09:48 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ]: {nio} removed from adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
nio = nio ,
adapter_number = adapter_number ) )
2015-06-18 02:36:52 +03:00
2015-05-16 04:09:48 +03:00
return nio
2015-05-28 06:06:18 +03:00
2018-10-27 10:47:17 +03:00
def get_nio ( self , adapter_number ) :
"""
Gets an adapter NIO binding .
: param adapter_number : adapter number
: returns : NIO instance
"""
try :
adapter = self . ethernet_adapters [ adapter_number ]
except KeyError :
raise VMwareError ( " Adapter {adapter_number} doesn ' t exist on VMware VM ' {name} ' " . format ( name = self . name ,
adapter_number = adapter_number ) )
nio = adapter . get_nio ( 0 )
if not nio :
raise VMwareError ( " Adapter {} is not connected " . format ( adapter_number ) )
return nio
2015-05-28 06:06:18 +03:00
def _get_pipe_name ( self ) :
"""
Returns the pipe name to create a serial connection .
: returns : pipe path ( string )
"""
if sys . platform . startswith ( " win " ) :
pipe_name = r " \\ . \ pipe \ gns3_vmware \ {} " . format ( self . id )
else :
pipe_name = os . path . join ( tempfile . gettempdir ( ) , " gns3_vmware " , " {} " . format ( self . id ) )
try :
os . makedirs ( os . path . dirname ( pipe_name ) , exist_ok = True )
except OSError as e :
raise VMwareError ( " Could not create the VMware pipe directory: {} " . format ( e ) )
return pipe_name
def _set_serial_console ( self ) :
"""
Configures the first serial port to allow a serial console connection .
"""
pipe_name = self . _get_pipe_name ( )
serial_port = { " serial0.present " : " TRUE " ,
2015-06-19 02:53:08 +03:00
" serial0.filetype " : " pipe " ,
" serial0.filename " : pipe_name ,
2016-11-07 12:16:51 +02:00
" serial0.pipe.endpoint " : " server " ,
" serial0.startconnected " : " TRUE " }
2015-05-28 06:06:18 +03:00
self . _vmx_pairs . update ( serial_port )
2018-10-15 13:05:49 +03:00
async def _start_console ( self ) :
2015-05-28 06:06:18 +03:00
"""
Starts remote console support for this VM .
"""
2018-03-24 14:11:21 +03:00
if self . console and self . console_type == " telnet " :
2018-10-04 16:44:13 +03:00
pipe_name = self . _get_pipe_name ( )
try :
2018-10-15 13:05:49 +03:00
self . _remote_pipe = await asyncio_open_serial ( self . _get_pipe_name ( ) )
2018-10-04 16:44:13 +03:00
except OSError as e :
raise VMwareError ( " Could not open serial pipe ' {} ' : {} " . format ( pipe_name , e ) )
2018-04-18 12:08:42 +03:00
server = AsyncioTelnetServer ( reader = self . _remote_pipe ,
writer = self . _remote_pipe ,
binary = True ,
echo = True )
try :
2018-10-15 13:05:49 +03:00
self . _telnet_server = await asyncio . start_server ( server . run , self . _manager . port_manager . console_host , self . console )
2018-04-18 12:08:42 +03: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-05-28 06:06:18 +03:00
2018-10-15 13:05:49 +03:00
async def _stop_remote_console ( self ) :
2015-05-28 06:06:18 +03:00
"""
Stops remote console support for this VM .
"""
2016-11-07 12:16:51 +02:00
if self . _telnet_server :
self . _telnet_server . close ( )
2018-10-15 13:05:49 +03:00
await self . _telnet_server . wait_closed ( )
2016-11-18 12:27:50 +02:00
self . _remote_pipe . close ( )
self . _telnet_server = None
2015-09-13 18:40:09 +03:00
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 VMware VM .
: param new_console_type : console type ( string )
"""
if self . _started and self . console_type != new_console_type :
raise VMwareError ( ' " {name} " must be stopped to change the console type to {new_console_type} ' . format ( name = self . _name , new_console_type = new_console_type ) )
super ( VMwareVM , VMwareVM ) . console_type . __set__ ( self , new_console_type )
2018-10-15 13:05:49 +03:00
async def start_capture ( self , adapter_number , output_file ) :
2015-09-13 18:40:09 +03:00
"""
Starts a packet capture .
: param adapter_number : adapter number
: param output_file : PCAP destination file for the capture
"""
2018-10-27 10:47:17 +03:00
nio = self . get_nio ( adapter_number )
2015-09-13 18:40:09 +03:00
if nio . capturing :
raise VMwareError ( " Packet capture is already activated on adapter {adapter_number} " . format ( adapter_number = adapter_number ) )
2019-04-01 16:58:18 +03:00
nio . start_packet_capture ( output_file )
2015-09-13 18:40:09 +03:00
if self . _started :
2018-10-15 13:05:49 +03:00
await self . _start_ubridge_capture ( adapter_number , output_file )
2015-09-13 18:40:09 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ]: starting packet capture on adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
adapter_number = adapter_number ) )
2018-10-15 13:05:49 +03:00
async def stop_capture ( self , adapter_number ) :
2015-09-13 18:40:09 +03:00
"""
Stops a packet capture .
: param adapter_number : adapter number
"""
2018-10-27 10:47:17 +03:00
nio = self . get_nio ( adapter_number )
2019-04-01 15:47:31 +03:00
if not nio . capturing :
return
2015-09-13 18:40:09 +03:00
2019-04-01 16:58:18 +03:00
nio . stop_packet_capture ( )
2015-09-13 18:40:09 +03:00
if self . _started :
2018-10-15 13:05:49 +03:00
await self . _stop_ubridge_capture ( adapter_number )
2015-09-13 18:40:09 +03:00
log . info ( " VMware VM ' {name} ' [ {id} ]: stopping packet capture on adapter {adapter_number} " . format ( name = self . name ,
id = self . id ,
adapter_number = adapter_number ) )