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
2016-09-01 16:36:41 +03:00
from . . . utils . asyncio import locked_coroutine
2016-08-24 12:36:32 +03:00
from . vmware_gns3_vm import VMwareGNS3VM
from . virtualbox_gns3_vm import VirtualBoxGNS3VM
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
"""
def __init__ ( self , controller , settings = { } ) :
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 ,
" vcpus " : 1
2016-08-24 12:36:32 +03:00
}
2016-08-26 15:09:18 +03:00
self . settings = settings
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__ )
2016-08-24 12:36:32 +03:00
vmware_informations = {
" 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 " ) :
vmware_informations [ " name " ] = " VMware Fusion "
else :
vmware_informations [ " name " ] = " VMware Workstation / Player "
2016-08-30 11:19:01 +03:00
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__ )
2016-08-30 11:19:01 +03:00
virtualbox_informations = {
" engine_id " : " virtualbox " ,
" name " : " VirtualBox " ,
2016-09-08 17:00:32 +03:00
" description " : ' VirtualBox doesn \' t support nested virtualization, this means running Qemu based VM could be very slow.<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
}
remote_informations = {
" 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
}
2016-08-24 12:36:32 +03:00
return [
vmware_informations ,
2016-08-30 11:19:01 +03:00
virtualbox_informations ,
remote_informations
2016-08-24 12:36:32 +03:00
]
2016-10-25 12:56:24 +03:00
def current_engine ( self ) :
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 ) :
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
"""
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
"""
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
"""
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
"""
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
"""
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
"""
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
"""
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
"""
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 ) :
return self . _settings
@settings.setter
def settings ( self , val ) :
self . _settings . update ( val )
2016-08-26 15:09:18 +03:00
@asyncio.coroutine
def update_settings ( self , settings ) :
"""
Update settings and will restart the VM if require
"""
new_settings = copy . copy ( self . _settings )
new_settings . update ( settings )
if self . settings != new_settings :
yield from self . _stop ( )
self . _settings = settings
self . _controller . save ( )
2016-09-21 16:46:56 +03:00
if self . enable :
yield from self . start ( )
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 :
2016-09-21 18:01:50 +03:00
yield from self . start ( )
2016-08-24 12:36:32 +03:00
def _get_engine ( self , engine ) :
"""
Load an engine
"""
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 " ]
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
@asyncio.coroutine
def list ( self , engine ) :
"""
List VMS for an engine
"""
engine = self . _get_engine ( engine )
vms = [ ]
2017-02-28 12:42:07 +02:00
try :
for vm in ( yield from engine . list ( ) ) :
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
@asyncio.coroutine
2016-08-26 15:09:18 +03:00
def auto_start_vm ( self ) :
"""
Auto start the GNS3 VM if require
"""
if self . enable :
2016-09-01 16:36:41 +03:00
try :
yield from self . start ( )
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 :
yield from self . _controller . add_compute ( compute_id = " vm " ,
name = " GNS3 VM ( {} ) " . format ( self . current_engine ( ) . vmname ) ,
host = None ,
force = True )
except aiohttp . web . HTTPConflict :
pass
2017-05-23 14:39:41 +03:00
log . error ( " Can ' t start the GNS3 VM: %s " , str ( e ) )
2016-09-06 14:06:20 +03:00
2016-08-26 15:09:18 +03:00
@asyncio.coroutine
2016-09-08 16:32:35 +03:00
def exit_vm ( self ) :
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 " :
yield from self . _stop ( )
elif self . _settings [ " when_exit " ] == " suspend " :
yield from self . _suspend ( )
2016-08-30 17:38:19 +03:00
except GNS3VMError as e :
log . warn ( str ( e ) )
2016-08-26 15:09:18 +03:00
2016-09-01 16:36:41 +03:00
@locked_coroutine
def start ( self ) :
2016-08-25 12:49:06 +03:00
"""
Start the GNS3 VM
"""
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 " ]
2016-10-26 19:32:01 +03:00
compute = yield from self . _controller . add_compute ( compute_id = " vm " ,
name = " GNS3 VM is starting ( {} ) " . format ( engine . vmname ) ,
host = None ,
2018-01-10 11:22:55 +02:00
force = True ,
connect = False )
2016-10-26 19:32:01 +03:00
try :
yield from engine . start ( )
except Exception as e :
yield from self . _controller . delete_compute ( " vm " )
2017-05-23 13:15:04 +03:00
log . error ( " Can ' t start the GNS3 VM: {} " . format ( str ( e ) ) )
2017-02-27 13:03:26 +02:00
yield from compute . update ( name = " GNS3 VM ( {} ) " . format ( engine . vmname ) )
2016-10-26 19:32:01 +03:00
raise e
2018-01-10 11:22:55 +02:00
yield from compute . connect ( ) # we can connect now that the VM has started
2016-10-26 19:32:01 +03:00
yield from compute . update ( name = " GNS3 VM ( {} ) " . format ( engine . vmname ) ,
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-01-24 12:11:53 +02:00
asyncio . get_event_loop ( ) . call_later ( 10 , lambda : asyncio . ensure_future ( self . _check_network ( compute ) ) )
2017-12-06 00:43:44 +02:00
@asyncio.coroutine
def _check_network ( self , compute ) :
"""
Check that the VM is in the same subnet as the local server
"""
2018-01-10 11:22:55 +02:00
try :
vm_interfaces = yield from compute . interfaces ( )
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 )
interfaces = yield from compute . interfaces ( )
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 :
msg = " The GNS3 VM ( {} ) is not on the same network as the {} server ( {} ), please make sure the local server binding is in the same network as the GNS3 VM " . format (
vm_network , compute_id , compute_network )
self . _controller . notification . emit ( " log.warning " , { " message " : msg } )
except ComputeError as e :
log . warning ( " Could not check the VM is in the same subnet as the local server: {} " . format ( e ) )
2017-12-06 00:43:44 +02:00
2016-09-08 16:32:35 +03:00
@locked_coroutine
def _suspend ( self ) :
"""
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 :
yield from self . _controller . delete_compute ( " vm " )
if engine . running :
log . info ( " Suspend the GNS3 VM " )
yield from engine . suspend ( )
2016-09-01 16:36:41 +03:00
@locked_coroutine
2016-08-26 15:09:18 +03:00
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 :
yield from self . _controller . delete_compute ( " vm " )
if engine . running :
2016-08-25 15:26:01 +03:00
log . info ( " Stop the GNS3 VM " )
yield from engine . stop ( )