2016-08-24 12:36:32 +03:00
#!/usr/bin/env python
#
# Copyright (C) 2016 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
2016-08-26 15:09:18 +03:00
import copy
2016-08-24 12:36:32 +03:00
import asyncio
2017-04-07 16:33:22 +03:00
import aiohttp
2017-12-06 00:43:44 +02:00
import ipaddress
2016-08-24 12:36:32 +03:00
2018-10-15 13:05:49 +03:00
from . . . utils . asyncio import locking
2016-08-24 12:36:32 +03:00
from . vmware_gns3_vm import VMwareGNS3VM
from . virtualbox_gns3_vm import VirtualBoxGNS3VM
2018-08-10 12:18:14 +03:00
from . hyperv_gns3_vm import HyperVGNS3VM
2016-08-30 11:19:01 +03:00
from . remote_gns3_vm import RemoteGNS3VM
2016-09-01 16:36:41 +03:00
from . gns3_vm_error import GNS3VMError
2016-09-08 17:00:32 +03:00
from . . . version import __version__
2018-01-10 11:22:55 +02:00
from . . compute import ComputeError
2016-08-24 12:36:32 +03:00
2016-08-25 12:49:06 +03:00
import logging
log = logging . getLogger ( __name__ )
2016-08-24 12:36:32 +03:00
class GNS3VM :
"""
Proxy between the controller and the GNS3 VM engine
"""
2020-05-05 06:10:50 +03:00
def __init__ ( self , controller ) :
2016-08-24 12:36:32 +03:00
self . _controller = controller
# Keep instance of the loaded engines
self . _engines = { }
self . _settings = {
" vmname " : None ,
2016-09-08 16:32:35 +03:00
" when_exit " : " stop " ,
2016-08-24 12:36:32 +03:00
" headless " : False ,
" enable " : False ,
2016-09-21 16:46:56 +03:00
" engine " : " vmware " ,
" ram " : 2048 ,
2020-05-05 06:10:50 +03:00
" vcpus " : 1 ,
" port " : 80 ,
2016-08-24 12:36:32 +03:00
}
def engine_list ( self ) :
"""
: returns : Return list of engines supported by GNS3 for the GNS3VM
"""
2016-09-08 17:00:32 +03:00
download_url = " https://github.com/GNS3/gns3-gui/releases/download/v {version} /GNS3.VM.VMware.Workstation. {version} .zip " . format ( version = __version__ )
2018-03-15 09:17:39 +02:00
vmware_info = {
2016-08-24 12:36:32 +03:00
" engine_id " : " vmware " ,
2016-09-08 17:00:32 +03:00
" description " : ' VMware is the recommended choice for best performances.<br>The GNS3 VM can be <a href= " {} " >downloaded here</a>. ' . format ( download_url ) ,
2016-09-08 16:32:35 +03:00
" support_when_exit " : True ,
2016-09-21 18:01:50 +03:00
" support_headless " : True ,
" support_ram " : True
2016-08-24 12:36:32 +03:00
}
if sys . platform . startswith ( " darwin " ) :
2018-08-08 11:02:31 +03:00
vmware_info [ " name " ] = " VMware Fusion (recommended) "
2016-08-24 12:36:32 +03:00
else :
2018-08-08 11:02:31 +03:00
vmware_info [ " name " ] = " VMware Workstation / Player (recommended) "
2016-08-30 11:19:01 +03:00
2020-05-05 06:10:50 +03:00
download_url = " https://github.com/GNS3/gns3-gui/releases/download/v {version} /GNS3.VM.Hyper-V. {version} .zip " . format ( version = __version__ )
2018-08-10 12:18:14 +03:00
hyperv_info = {
" engine_id " : " hyper-v " ,
" name " : " Hyper-V " ,
2020-05-05 06:10:50 +03:00
" description " : ' Hyper-V support (Windows 10/Server 2016 and above). Nested virtualization must be supported and enabled (Intel processor only)<br>The GNS3 VM can be <a href= " {} " >downloaded here</a> ' . format ( download_url ) ,
2018-08-10 12:18:14 +03:00
" support_when_exit " : True ,
2018-08-12 13:11:32 +03:00
" support_headless " : False ,
2018-08-10 12:18:14 +03:00
" support_ram " : True
}
2016-09-08 17:00:32 +03:00
download_url = " https://github.com/GNS3/gns3-gui/releases/download/v {version} /GNS3.VM.VirtualBox. {version} .zip " . format ( version = __version__ )
2018-03-15 09:17:39 +02:00
virtualbox_info = {
2016-08-30 11:19:01 +03:00
" engine_id " : " virtualbox " ,
2019-01-16 14:22:16 +02:00
" name " : " VirtualBox " ,
2020-05-05 06:10:50 +03:00
" description " : ' VirtualBox support. Nested virtualization for both Intel and AMD processors is supported since version 6.1<br>The GNS3 VM can be <a href= " {} " >downloaded here</a> ' . format ( download_url ) ,
2016-09-08 16:32:35 +03:00
" support_when_exit " : True ,
2016-09-21 18:01:50 +03:00
" support_headless " : True ,
" support_ram " : True
2016-08-30 11:19:01 +03:00
}
2018-03-15 09:17:39 +02:00
remote_info = {
2016-08-30 11:19:01 +03:00
" engine_id " : " remote " ,
" name " : " Remote " ,
" description " : " Use a remote GNS3 server as the GNS3 VM. " ,
2016-09-08 16:32:35 +03:00
" support_when_exit " : False ,
2016-09-21 18:01:50 +03:00
" support_headless " : False ,
" support_ram " : False
2016-08-30 11:19:01 +03:00
}
2018-08-10 12:18:14 +03:00
engines = [ vmware_info ,
virtualbox_info ,
remote_info ]
if sys . platform . startswith ( " win " ) :
engines . append ( hyperv_info )
return engines
2016-08-24 12:36:32 +03:00
2016-10-25 12:56:24 +03:00
def current_engine ( self ) :
2018-03-15 09:17:39 +02:00
2016-08-25 12:49:06 +03:00
return self . _get_engine ( self . _settings [ " engine " ] )
2016-10-25 12:56:24 +03:00
@property
def engine ( self ) :
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
return self . _settings [ " engine " ]
2016-08-25 12:49:06 +03:00
@property
def ip_address ( self ) :
"""
Returns the GNS3 VM IP address .
: returns : VM IP address
"""
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
return self . current_engine ( ) . ip_address
2016-08-25 12:49:06 +03:00
2016-08-26 15:09:18 +03:00
@property
def running ( self ) :
"""
Returns if the GNS3 VM is running .
: returns : Boolean
"""
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
return self . current_engine ( ) . running
2016-08-26 15:09:18 +03:00
2016-08-25 12:49:06 +03:00
@property
def user ( self ) :
"""
Returns the GNS3 VM user .
: returns : VM user
"""
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
return self . current_engine ( ) . user
2016-08-25 12:49:06 +03:00
@property
def password ( self ) :
"""
Returns the GNS3 VM password .
: returns : VM password
"""
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
return self . current_engine ( ) . password
2016-08-25 12:49:06 +03:00
@property
def port ( self ) :
"""
Returns the GNS3 VM port .
: returns : VM port
"""
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
return self . current_engine ( ) . port
2016-08-25 12:49:06 +03:00
@property
def protocol ( self ) :
"""
Returns the GNS3 VM protocol .
: returns : VM protocol
"""
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
return self . current_engine ( ) . protocol
2016-08-25 12:49:06 +03:00
@property
def enable ( self ) :
"""
The GNSVM is activated
"""
2018-03-15 09:17:39 +02:00
2016-08-29 15:07:52 +03:00
return self . _settings . get ( " enable " , False )
2016-08-25 12:49:06 +03:00
2016-08-25 15:26:01 +03:00
@property
2016-09-08 16:32:35 +03:00
def when_exit ( self ) :
2016-08-25 15:26:01 +03:00
"""
2016-09-08 16:32:35 +03:00
What should be done when exit
2016-08-25 15:26:01 +03:00
"""
2018-03-15 09:17:39 +02:00
2016-09-08 16:32:35 +03:00
return self . _settings [ " when_exit " ]
2016-08-25 15:26:01 +03:00
2016-08-24 12:36:32 +03:00
@property
def settings ( self ) :
2018-03-15 09:17:39 +02:00
2016-08-24 12:36:32 +03:00
return self . _settings
@settings.setter
def settings ( self , val ) :
2018-03-15 09:17:39 +02:00
2016-08-24 12:36:32 +03:00
self . _settings . update ( val )
2016-08-26 15:09:18 +03:00
2018-10-15 13:05:49 +03:00
async def update_settings ( self , settings ) :
2016-08-26 15:09:18 +03:00
"""
Update settings and will restart the VM if require
"""
2018-03-15 09:17:39 +02:00
2016-08-26 15:09:18 +03:00
new_settings = copy . copy ( self . _settings )
new_settings . update ( settings )
if self . settings != new_settings :
2019-03-18 10:30:59 +02:00
try :
await self . _stop ( )
finally :
self . _settings = settings
self . _controller . save ( )
2016-09-21 16:46:56 +03:00
if self . enable :
2018-10-15 13:05:49 +03:00
await self . start ( )
2016-09-21 16:46:56 +03:00
else :
# When user fix something on his system and try again
2016-11-28 21:28:19 +02:00
if self . enable and not self . current_engine ( ) . running :
2018-10-15 13:05:49 +03:00
await self . start ( )
2016-08-24 12:36:32 +03:00
def _get_engine ( self , engine ) :
"""
Load an engine
"""
2018-03-15 09:17:39 +02:00
2016-08-24 12:36:32 +03:00
if engine in self . _engines :
return self . _engines [ engine ]
if engine == " vmware " :
2016-08-30 11:19:01 +03:00
self . _engines [ " vmware " ] = VMwareGNS3VM ( self . _controller )
2016-08-24 12:36:32 +03:00
return self . _engines [ " vmware " ]
2018-08-10 12:18:14 +03:00
elif engine == " hyper-v " :
self . _engines [ " hyper-v " ] = HyperVGNS3VM ( self . _controller )
return self . _engines [ " hyper-v " ]
2016-08-24 12:36:32 +03:00
elif engine == " virtualbox " :
2016-08-30 11:19:01 +03:00
self . _engines [ " virtualbox " ] = VirtualBoxGNS3VM ( self . _controller )
2016-08-24 12:36:32 +03:00
return self . _engines [ " virtualbox " ]
2016-08-30 11:19:01 +03:00
elif engine == " remote " :
self . _engines [ " remote " ] = RemoteGNS3VM ( self . _controller )
return self . _engines [ " remote " ]
2016-08-24 12:36:32 +03:00
raise NotImplementedError ( " The engine {} for the GNS3 VM is not supported " . format ( engine ) )
def __json__ ( self ) :
return self . _settings
2020-05-20 13:03:56 +03:00
@locking
2018-10-15 13:05:49 +03:00
async def list ( self , engine ) :
2016-08-24 12:36:32 +03:00
"""
List VMS for an engine
"""
2018-03-15 09:17:39 +02:00
2016-08-24 12:36:32 +03:00
engine = self . _get_engine ( engine )
vms = [ ]
2017-02-28 12:42:07 +02:00
try :
2018-10-15 13:05:49 +03:00
for vm in ( await engine . list ( ) ) :
2017-02-28 12:42:07 +02:00
vms . append ( { " vmname " : vm [ " vmname " ] } )
except GNS3VMError as e :
# We raise error only if user activated the GNS3 VM
# otherwise you have noise when VMware is not installed
if self . enable :
raise e
2016-08-24 12:36:32 +03:00
return vms
2016-08-25 12:49:06 +03:00
2018-10-15 13:05:49 +03:00
async def auto_start_vm ( self ) :
2016-08-26 15:09:18 +03:00
"""
Auto start the GNS3 VM if require
"""
2018-03-15 09:17:39 +02:00
2016-08-26 15:09:18 +03:00
if self . enable :
2016-09-01 16:36:41 +03:00
try :
2018-10-15 13:05:49 +03:00
await self . start ( )
2016-09-01 16:36:41 +03:00
except GNS3VMError as e :
# User will receive the error later when they will try to use the node
2017-04-07 16:33:22 +03:00
try :
2018-10-15 13:05:49 +03:00
compute = await self . _controller . add_compute ( compute_id = " vm " ,
2020-05-05 06:10:50 +03:00
name = " GNS3 VM ( {} ) " . format ( self . current_engine ( ) . vmname ) ,
host = None ,
force = True )
2018-08-28 11:42:06 +03:00
compute . set_last_error ( str ( e ) )
2017-04-07 16:33:22 +03:00
except aiohttp . web . HTTPConflict :
pass
2018-08-28 11:42:06 +03:00
log . error ( " Cannot start the GNS3 VM: {} " . format ( e ) )
2016-09-06 14:06:20 +03:00
2018-10-15 13:05:49 +03:00
async def exit_vm ( self ) :
2018-03-15 09:17:39 +02:00
2016-09-08 16:32:35 +03:00
if self . enable :
2016-08-30 17:38:19 +03:00
try :
2016-09-08 16:32:35 +03:00
if self . _settings [ " when_exit " ] == " stop " :
2018-10-15 13:05:49 +03:00
await self . _stop ( )
2016-09-08 16:32:35 +03:00
elif self . _settings [ " when_exit " ] == " suspend " :
2018-10-15 13:05:49 +03:00
await self . _suspend ( )
2016-08-30 17:38:19 +03:00
except GNS3VMError as e :
2018-03-15 09:17:39 +02:00
log . warning ( str ( e ) )
2016-08-26 15:09:18 +03:00
2018-08-25 10:10:47 +03:00
@locking
2018-10-15 13:05:49 +03:00
async def start ( self ) :
2016-08-25 12:49:06 +03:00
"""
Start the GNS3 VM
"""
2018-03-15 09:17:39 +02:00
2016-10-25 12:56:24 +03:00
engine = self . current_engine ( )
2016-08-25 12:49:06 +03:00
if not engine . running :
2017-01-27 11:41:39 +02:00
if self . _settings [ " vmname " ] is None :
return
2016-08-25 12:49:06 +03:00
log . info ( " Start the GNS3 VM " )
engine . vmname = self . _settings [ " vmname " ]
2016-09-21 16:46:56 +03:00
engine . ram = self . _settings [ " ram " ]
2017-11-06 16:15:37 +02:00
engine . vcpus = self . _settings [ " vcpus " ]
2017-02-23 16:35:30 +02:00
engine . headless = self . _settings [ " headless " ]
2020-05-05 06:10:50 +03:00
engine . port = self . _settings [ " port " ]
2018-10-15 13:05:49 +03:00
compute = await self . _controller . add_compute ( compute_id = " vm " ,
2018-11-23 12:27:10 +02:00
name = " GNS3 VM is starting ( {} ) " . format ( engine . vmname ) ,
host = None ,
force = True ,
connect = False )
2016-10-26 19:32:01 +03:00
try :
2018-10-15 13:05:49 +03:00
await engine . start ( )
2016-10-26 19:32:01 +03:00
except Exception as e :
2018-10-15 13:05:49 +03:00
await self . _controller . delete_compute ( " vm " )
2018-08-28 11:42:06 +03:00
log . error ( " Cannot start the GNS3 VM: {} " . format ( str ( e ) ) )
2018-10-15 13:05:49 +03:00
await compute . update ( name = " GNS3 VM ( {} ) " . format ( engine . vmname ) )
2018-08-28 11:42:06 +03:00
compute . set_last_error ( str ( e ) )
2016-10-26 19:32:01 +03:00
raise e
2018-10-15 13:05:49 +03:00
await compute . connect ( ) # we can connect now that the VM has started
await compute . update ( name = " GNS3 VM ( {} ) " . format ( engine . vmname ) ,
2020-05-05 06:10:50 +03:00
protocol = self . protocol ,
host = self . ip_address ,
port = self . port ,
user = self . user ,
password = self . password )
2016-08-25 12:49:06 +03:00
2018-01-10 11:22:55 +02:00
# check if the VM is in the same subnet as the local server, start 10 seconds later to give
# some time for the compute in the VM to be ready for requests
2018-10-15 13:05:49 +03:00
asyncio . get_event_loop ( ) . call_later ( 10 , lambda : asyncio . ensure_future ( self . _check_network ( compute ) ) )
2017-12-06 00:43:44 +02:00
2018-10-15 13:05:49 +03:00
async def _check_network ( self , compute ) :
2017-12-06 00:43:44 +02:00
"""
Check that the VM is in the same subnet as the local server
"""
2018-01-10 11:22:55 +02:00
try :
2018-10-15 13:05:49 +03:00
vm_interfaces = await compute . interfaces ( )
2018-01-10 11:22:55 +02:00
vm_interface_netmask = None
for interface in vm_interfaces :
if interface [ " ip_address " ] == self . ip_address :
vm_interface_netmask = interface [ " netmask " ]
break
if vm_interface_netmask :
vm_network = ipaddress . ip_interface ( " {} / {} " . format ( compute . host_ip , vm_interface_netmask ) ) . network
for compute_id in self . _controller . computes :
if compute_id == " local " :
compute = self . _controller . get_compute ( compute_id )
2018-10-15 13:05:49 +03:00
interfaces = await compute . interfaces ( )
2018-01-10 11:22:55 +02:00
netmask = None
for interface in interfaces :
if interface [ " ip_address " ] == compute . host_ip :
netmask = interface [ " netmask " ]
break
if netmask :
compute_network = ipaddress . ip_interface ( " {} / {} " . format ( compute . host_ip , netmask ) ) . network
if vm_network . compare_networks ( compute_network ) != 0 :
2018-11-23 12:27:10 +02:00
msg = " The GNS3 VM (IP= {} , NETWORK= {} ) is not on the same network as the {} server (IP= {} , NETWORK= {} ), please make sure the local server binding is in the same network as the GNS3 VM " . format ( self . ip_address ,
vm_network ,
compute_id ,
compute . host_ip ,
compute_network )
2018-08-16 17:31:56 +03:00
self . _controller . notification . controller_emit ( " log.warning " , { " message " : msg } )
2018-01-10 11:22:55 +02:00
except ComputeError as e :
log . warning ( " Could not check the VM is in the same subnet as the local server: {} " . format ( e ) )
2018-08-22 12:54:43 +03:00
except aiohttp . web . HTTPConflict as e :
log . warning ( " Could not check the VM is in the same subnet as the local server: {} " . format ( e . text ) )
2017-12-06 00:43:44 +02:00
2018-08-25 10:10:47 +03:00
@locking
2018-10-15 13:05:49 +03:00
async def _suspend ( self ) :
2016-09-08 16:32:35 +03:00
"""
Suspend the GNS3 VM
"""
2016-10-25 12:56:24 +03:00
engine = self . current_engine ( )
2016-09-08 16:32:35 +03:00
if " vm " in self . _controller . computes :
2018-10-15 13:05:49 +03:00
await self . _controller . delete_compute ( " vm " )
2016-09-08 16:32:35 +03:00
if engine . running :
log . info ( " Suspend the GNS3 VM " )
2018-10-15 13:05:49 +03:00
await engine . suspend ( )
2016-09-08 16:32:35 +03:00
2018-08-25 10:10:47 +03:00
@locking
2018-10-15 13:05:49 +03:00
async def _stop ( self ) :
2016-08-25 15:26:01 +03:00
"""
Stop the GNS3 VM
"""
2016-10-25 12:56:24 +03:00
engine = self . current_engine ( )
2016-08-26 15:09:18 +03:00
if " vm " in self . _controller . computes :
2018-10-15 13:05:49 +03:00
await self . _controller . delete_compute ( " vm " )
2016-08-26 15:09:18 +03:00
if engine . running :
2016-08-25 15:26:01 +03:00
log . info ( " Stop the GNS3 VM " )
2018-10-15 13:05:49 +03:00
await engine . stop ( )