2016-03-02 10:49:52 +02: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/>.
2016-04-19 16:35:50 +03:00
import os
2017-03-16 11:50:08 +02:00
import sys
2016-04-19 16:35:50 +03:00
import json
2017-03-21 14:35:02 +02:00
import uuid
2016-08-29 16:53:10 +03:00
import socket
2017-02-02 20:13:47 +02:00
import shutil
2016-03-10 11:32:07 +02:00
import asyncio
import aiohttp
2016-03-02 10:49:52 +02:00
from . . config import Config
2016-03-10 22:51:29 +02:00
from . project import Project
2017-02-01 12:30:14 +02:00
from . appliance import Appliance
2017-02-08 16:40:56 +02:00
from . appliance_template import ApplianceTemplate
2016-11-11 11:38:59 +02:00
from . compute import Compute , ComputeError
2016-05-18 15:56:23 +03:00
from . notification import Notification
2016-06-27 19:33:42 +03:00
from . symbols import Symbols
2016-04-19 16:35:50 +03:00
from . . version import __version__
2016-06-14 17:07:37 +03:00
from . topology import load_topology
2016-08-24 12:36:32 +03:00
from . gns3vm import GNS3VM
2017-02-01 12:30:14 +02:00
from . . utils . get_resource import get_resource
2017-04-04 15:23:43 +03:00
from . gns3vm . gns3_vm_error import GNS3VMError
2016-04-19 16:35:50 +03:00
import logging
log = logging . getLogger ( __name__ )
2016-03-02 10:49:52 +02:00
class Controller :
2016-05-14 03:48:10 +03:00
""" The controller is responsible to manage one or more compute servers """
2016-03-02 10:49:52 +02:00
2016-03-03 17:02:27 +02:00
def __init__ ( self ) :
2016-04-15 18:57:06 +03:00
self . _computes = { }
2016-03-10 11:32:07 +02:00
self . _projects = { }
2017-02-08 16:40:56 +02:00
# Store settings shared by the different GUI will be replaced
# by dedicated API later
self . _settings = { }
2017-02-01 12:30:14 +02:00
2016-05-18 15:56:23 +03:00
self . _notification = Notification ( self )
2016-08-24 12:36:32 +03:00
self . gns3vm = GNS3VM ( self )
2016-06-27 19:33:42 +03:00
self . symbols = Symbols ( )
2016-03-03 17:02:27 +02:00
2016-06-29 15:16:29 +03:00
# Store settings shared by the different GUI will be replace by dedicated API later
2017-03-07 16:36:35 +02:00
self . _settings = None
2017-04-12 15:35:49 +03:00
self . _appliances = { }
self . _appliance_templates = { }
2016-09-30 18:34:28 +03:00
self . _config_file = os . path . join ( Config . instance ( ) . config_dir , " gns3_controller.conf " )
2016-08-22 18:21:03 +03:00
log . info ( " Load controller configuration file {} " . format ( self . _config_file ) )
2017-02-01 12:30:14 +02:00
def load_appliances ( self ) :
2017-02-08 16:40:56 +02:00
self . _appliance_templates = { }
2017-07-19 12:44:05 +03:00
for directory , builtin in (
( get_resource ( ' appliances ' ) , True , ) , ( self . appliances_path ( ) , False , )
) :
if os . path . isdir ( directory ) :
for file in os . listdir ( directory ) :
2017-07-20 17:10:56 +03:00
if not file . endswith ( ' .gns3a ' ) and not file . endswith ( ' .gns3appliance ' ) :
continue
2017-07-19 12:44:05 +03:00
path = os . path . join ( directory , file )
appliance_id = uuid . uuid3 ( uuid . NAMESPACE_URL , path ) # Generate the UUID from path to avoid change between reboots
2017-07-20 17:10:56 +03:00
try :
with open ( path , ' r ' , encoding = ' utf-8 ' ) as f :
appliance = ApplianceTemplate ( appliance_id , json . load ( f ) , builtin = builtin )
2017-07-28 16:21:35 +03:00
appliance . __json__ ( ) # Check if loaded without error
if appliance . status != ' broken ' :
self . _appliance_templates [ appliance . id ] = appliance
except ( ValueError , OSError , KeyError ) as e :
2018-02-06 10:07:23 +02:00
log . warning ( " Cannot load appliance template file ' %s ' : %s " , path , str ( e ) )
continue
2017-02-01 12:30:14 +02:00
2017-04-12 15:35:49 +03:00
self . _appliances = { }
2017-06-19 09:47:50 +03:00
vms = [ ]
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " Qemu " , { } ) . get ( " vms " , [ ] ) :
vm [ " node_type " ] = " qemu "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " IOU " , { } ) . get ( " devices " , [ ] ) :
vm [ " node_type " ] = " iou "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " Docker " , { } ) . get ( " containers " , [ ] ) :
vm [ " node_type " ] = " docker "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " Builtin " , { } ) . get ( " cloud_nodes " , [ ] ) :
vm [ " node_type " ] = " cloud "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " Builtin " , { } ) . get ( " ethernet_switches " , [ ] ) :
vm [ " node_type " ] = " ethernet_switch "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " Builtin " , { } ) . get ( " ethernet_hubs " , [ ] ) :
vm [ " node_type " ] = " ethernet_hub "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " Dynamips " , { } ) . get ( " routers " , [ ] ) :
vm [ " node_type " ] = " dynamips "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " VMware " , { } ) . get ( " vms " , [ ] ) :
vm [ " node_type " ] = " vmware "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " VirtualBox " , { } ) . get ( " vms " , [ ] ) :
vm [ " node_type " ] = " virtualbox "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2017-04-12 15:35:49 +03:00
for vm in self . _settings . get ( " VPCS " , { } ) . get ( " nodes " , [ ] ) :
vm [ " node_type " ] = " vpcs "
2017-06-19 09:47:50 +03:00
vms . append ( vm )
2018-03-12 12:57:13 +02:00
for vm in self . _settings . get ( " TraceNG " , { } ) . get ( " nodes " , [ ] ) :
vm [ " node_type " ] = " traceng "
vms . append ( vm )
2017-06-19 09:47:50 +03:00
for vm in vms :
2017-10-13 12:50:23 +03:00
# remove deprecated properties
2017-10-13 12:51:19 +03:00
for prop in vm . copy ( ) :
2018-03-05 12:16:03 +02:00
if prop in [ " enable_remote_console " , " use_ubridge " ] :
2017-10-13 12:51:19 +03:00
del vm [ prop ]
2018-03-02 14:48:27 +02:00
# remove deprecated default_symbol and hover_symbol
# and set symbol if not present
deprecated = [ " default_symbol " , " hover_symbol " ]
if len ( [ prop for prop in vm . keys ( ) if prop in deprecated ] ) > 0 :
if " default_symbol " in vm . keys ( ) :
del vm [ " default_symbol " ]
if " hover_symbol " in vm . keys ( ) :
del vm [ " hover_symbol " ]
if " symbol " not in vm . keys ( ) :
vm [ " symbol " ] = " :/symbols/computer.svg "
2017-06-19 09:47:50 +03:00
vm . setdefault ( " appliance_id " , str ( uuid . uuid4 ( ) ) )
2018-01-14 15:29:17 +02:00
try :
appliance = Appliance ( vm [ " appliance_id " ] , vm )
2018-02-06 10:07:23 +02:00
appliance . __json__ ( ) # Check if loaded without error
2018-01-14 15:29:17 +02:00
self . _appliances [ appliance . id ] = appliance
except KeyError as e :
# appliance data is not complete (missing name or type)
2018-02-06 10:07:23 +02:00
log . warning ( " Cannot load appliance template {} ( ' {} ' ): missing key {} " . format ( vm [ " appliance_id " ] , vm . get ( " name " , " unknown " ) , e ) )
2018-01-14 15:29:17 +02:00
continue
2017-06-19 09:47:50 +03:00
2017-04-12 15:35:49 +03:00
# Add builtins
builtins = [ ]
2017-06-19 09:47:50 +03:00
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " cloud " ) , { " node_type " : " cloud " , " name " : " Cloud " , " category " : 2 , " symbol " : " :/symbols/cloud.svg " } , builtin = True ) )
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " nat " ) , { " node_type " : " nat " , " name " : " NAT " , " category " : 2 , " symbol " : " :/symbols/cloud.svg " } , builtin = True ) )
2018-01-09 18:40:35 +02:00
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " vpcs " ) , { " node_type " : " vpcs " , " name " : " VPCS " , " default_name_format " : " PC- {0} " , " category " : 2 , " symbol " : " :/symbols/vpcs_guest.svg " , " properties " : { " base_script_file " : " vpcs_base_config.txt " } } , builtin = True ) )
2018-03-12 12:57:13 +02:00
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " traceng " ) , { " node_type " : " traceng " , " name " : " TraceNG " , " default_name_format " : " TraceNG- {0} " , " category " : 2 , " symbol " : " :/symbols/vpcs_guest.svg " , " properties " : { } } , builtin = True ) ) # TODO: change default symbol
2017-06-19 09:47:50 +03:00
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " ethernet_switch " ) , { " node_type " : " ethernet_switch " , " name " : " Ethernet switch " , " category " : 1 , " symbol " : " :/symbols/ethernet_switch.svg " } , builtin = True ) )
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " ethernet_hub " ) , { " node_type " : " ethernet_hub " , " name " : " Ethernet hub " , " category " : 1 , " symbol " : " :/symbols/hub.svg " } , builtin = True ) )
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " frame_relay_switch " ) , { " node_type " : " frame_relay_switch " , " name " : " Frame Relay switch " , " category " : 1 , " symbol " : " :/symbols/frame_relay_switch.svg " } , builtin = True ) )
builtins . append ( Appliance ( uuid . uuid3 ( uuid . NAMESPACE_DNS , " atm_switch " ) , { " node_type " : " atm_switch " , " name " : " ATM switch " , " category " : 1 , " symbol " : " :/symbols/atm_switch.svg " } , builtin = True ) )
2017-04-12 15:35:49 +03:00
for b in builtins :
self . _appliances [ b . id ] = b
2016-08-24 12:36:32 +03:00
@asyncio.coroutine
def start ( self ) :
log . info ( " Start controller " )
2017-02-02 20:13:47 +02:00
self . load_base_files ( )
2016-08-24 12:36:32 +03:00
server_config = Config . instance ( ) . get_section_config ( " Server " )
2017-03-22 19:29:08 +02:00
Config . instance ( ) . listen_for_config_changes ( self . _update_config )
2016-09-27 12:54:23 +03:00
host = server_config . get ( " host " , " localhost " )
2017-02-28 13:08:47 +02:00
2016-10-26 15:43:47 +03:00
# If console_host is 0.0.0.0 client will use the ip they use
# to connect to the controller
console_host = host
2016-09-27 12:54:23 +03:00
if host == " 0.0.0.0 " :
host = " 127.0.0.1 "
2017-02-28 14:11:03 +02:00
name = socket . gethostname ( )
if name == " gns3vm " :
name = " Main server "
2017-03-07 16:36:35 +02:00
computes = yield from self . _load_controller_settings ( )
2017-03-16 11:50:08 +02:00
try :
2017-03-22 19:29:08 +02:00
self . _local_server = yield from self . add_compute ( compute_id = " local " ,
name = name ,
protocol = server_config . get ( " protocol " , " http " ) ,
host = host ,
console_host = console_host ,
port = server_config . getint ( " port " , 3080 ) ,
user = server_config . get ( " user " , " " ) ,
password = server_config . get ( " password " , " " ) ,
force = True )
2017-03-16 11:50:08 +02:00
except aiohttp . web_exceptions . HTTPConflict as e :
2017-06-07 11:50:40 +03:00
log . fatal ( " Can ' t access to the local server, make sure anything else is not running on the same port " )
2017-03-16 11:50:08 +02:00
sys . exit ( 1 )
2017-03-07 16:36:35 +02:00
for c in computes :
try :
yield from self . add_compute ( * * c )
2017-05-03 18:40:58 +03:00
except ( aiohttp . web_exceptions . HTTPConflict , KeyError ) :
2017-03-07 16:36:35 +02:00
pass # Skip not available servers at loading
2016-08-31 11:42:45 +03:00
yield from self . load_projects ( )
2017-04-04 15:23:43 +03:00
try :
yield from self . gns3vm . auto_start_vm ( )
except GNS3VMError as e :
log . warn ( str ( e ) )
2016-09-06 12:30:08 +03:00
yield from self . _project_auto_open ( )
2016-08-24 12:36:32 +03:00
2017-03-22 19:29:08 +02:00
def _update_config ( self ) :
"""
Call this when the server configuration file
change
"""
if self . _local_server :
server_config = Config . instance ( ) . get_section_config ( " Server " )
self . _local_server . user = server_config . get ( " user " )
self . _local_server . password = server_config . get ( " password " )
2016-08-24 12:36:32 +03:00
@asyncio.coroutine
def stop ( self ) :
log . info ( " Stop controller " )
2016-08-26 12:22:09 +03:00
for project in self . _projects . values ( ) :
yield from project . close ( )
2016-08-24 12:36:32 +03:00
for compute in self . _computes . values ( ) :
2016-08-26 15:09:18 +03:00
try :
yield from compute . close ( )
# We don't care if a compute is down at this step
2016-11-11 11:38:59 +02:00
except ( ComputeError , aiohttp . web_exceptions . HTTPError , OSError ) :
2016-08-26 15:09:18 +03:00
pass
2016-09-08 16:32:35 +03:00
yield from self . gns3vm . exit_vm ( )
2016-08-24 12:36:32 +03:00
self . _computes = { }
self . _projects = { }
2016-05-24 16:45:06 +03:00
2016-04-19 16:35:50 +03:00
def save ( self ) :
"""
Save the controller configuration on disk
"""
2017-03-07 16:36:35 +02:00
# We don't save during the loading otherwise we could lost stuff
if self . _settings is None :
return
2016-08-23 23:40:26 +03:00
data = {
" computes " : [ ] ,
" settings " : self . _settings ,
2016-08-24 12:36:32 +03:00
" gns3vm " : self . gns3vm . __json__ ( ) ,
2016-08-23 23:40:26 +03:00
" version " : __version__
}
for c in self . _computes . values ( ) :
2016-09-01 12:28:35 +03:00
if c . id != " local " and c . id != " vm " :
2016-08-23 23:40:26 +03:00
data [ " computes " ] . append ( {
" host " : c . host ,
" name " : c . name ,
" port " : c . port ,
" protocol " : c . protocol ,
" user " : c . user ,
" password " : c . password ,
" compute_id " : c . id
} )
2017-04-10 18:44:09 +03:00
try :
os . makedirs ( os . path . dirname ( self . _config_file ) , exist_ok = True )
with open ( self . _config_file , ' w+ ' ) as f :
json . dump ( data , f , indent = 4 )
except OSError as e :
log . error ( " Can ' t write the configuration {} : {} " . format ( self . _config_file , str ( e ) ) )
2016-04-19 16:35:50 +03:00
@asyncio.coroutine
2017-02-28 13:08:47 +02:00
def _load_controller_settings ( self ) :
2016-04-19 16:35:50 +03:00
"""
Reload the controller configuration from disk
"""
try :
2016-12-08 11:52:21 +02:00
if not os . path . exists ( self . _config_file ) :
yield from self . _import_gns3_gui_conf ( )
self . save ( )
2016-04-19 16:35:50 +03:00
with open ( self . _config_file ) as f :
data = json . load ( f )
2016-12-21 10:45:24 +02:00
except ( OSError , ValueError ) as e :
2016-05-14 03:48:10 +03:00
log . critical ( " Cannot load %s : %s " , self . _config_file , str ( e ) )
2017-03-07 16:36:35 +02:00
self . _settings = { }
return [ ]
2016-12-08 11:52:21 +02:00
2017-03-07 16:36:35 +02:00
if " settings " in data and data [ " settings " ] is not None :
2016-06-29 15:16:29 +03:00
self . _settings = data [ " settings " ]
2017-03-07 16:36:35 +02:00
else :
self . _settings = { }
2016-08-24 12:36:32 +03:00
if " gns3vm " in data :
self . gns3vm . settings = data [ " gns3vm " ]
2016-06-29 15:16:29 +03:00
2017-04-12 15:35:49 +03:00
self . load_appliances ( )
2017-06-07 11:50:40 +03:00
return data . get ( " computes " , [ ] )
2016-04-19 16:35:50 +03:00
2016-08-16 17:04:20 +03:00
@asyncio.coroutine
def load_projects ( self ) :
"""
Preload the list of projects from disk
"""
2016-06-15 00:08:30 +03:00
server_config = Config . instance ( ) . get_section_config ( " Server " )
projects_path = os . path . expanduser ( server_config . get ( " projects_path " , " ~/GNS3/projects " ) )
2016-06-15 16:12:38 +03:00
os . makedirs ( projects_path , exist_ok = True )
2016-06-15 00:08:30 +03:00
try :
for project_path in os . listdir ( projects_path ) :
project_dir = os . path . join ( projects_path , project_path )
if os . path . isdir ( project_dir ) :
for file in os . listdir ( project_dir ) :
if file . endswith ( " .gns3 " ) :
try :
yield from self . load_project ( os . path . join ( project_dir , file ) , load = False )
2016-12-12 12:18:17 +02:00
except ( aiohttp . web_exceptions . HTTPConflict , NotImplementedError ) :
2016-06-15 16:12:38 +03:00
pass # Skip not compatible projects
2016-06-15 00:08:30 +03:00
except OSError as e :
log . error ( str ( e ) )
2017-02-02 20:13:47 +02:00
def load_base_files ( self ) :
"""
At startup we copy base file to the user location to allow
them to customize it
"""
dst_path = self . configs_path ( )
src_path = get_resource ( ' configs ' )
try :
for file in os . listdir ( src_path ) :
if not os . path . exists ( os . path . join ( dst_path , file ) ) :
shutil . copy ( os . path . join ( src_path , file ) , os . path . join ( dst_path , file ) )
except OSError :
pass
2016-07-21 18:55:15 +03:00
def images_path ( self ) :
"""
Get the image storage directory
"""
server_config = Config . instance ( ) . get_section_config ( " Server " )
images_path = os . path . expanduser ( server_config . get ( " images_path " , " ~/GNS3/projects " ) )
os . makedirs ( images_path , exist_ok = True )
return images_path
2017-02-02 20:13:47 +02:00
def configs_path ( self ) :
"""
Get the configs storage directory
"""
server_config = Config . instance ( ) . get_section_config ( " Server " )
images_path = os . path . expanduser ( server_config . get ( " configs_path " , " ~/GNS3/projects " ) )
os . makedirs ( images_path , exist_ok = True )
return images_path
2017-07-19 12:44:05 +03:00
def appliances_path ( self ) :
"""
Get the image storage directory
"""
server_config = Config . instance ( ) . get_section_config ( " Server " )
appliances_path = os . path . expanduser ( server_config . get ( " appliances_path " , " ~/GNS3/projects " ) )
os . makedirs ( appliances_path , exist_ok = True )
return appliances_path
2016-07-14 16:14:56 +03:00
@asyncio.coroutine
def _import_gns3_gui_conf ( self ) :
"""
Import old config from GNS3 GUI
"""
config_file = os . path . join ( os . path . dirname ( self . _config_file ) , " gns3_gui.conf " )
if os . path . exists ( config_file ) :
with open ( config_file ) as f :
data = json . load ( f )
2016-09-06 14:06:20 +03:00
server_settings = data . get ( " Servers " , { } )
for remote in server_settings . get ( " remote_servers " , [ ] ) :
2017-03-13 19:17:17 +02:00
try :
yield from self . add_compute (
host = remote . get ( " host " , " localhost " ) ,
port = remote . get ( " port " , 3080 ) ,
protocol = remote . get ( " protocol " , " http " ) ,
name = remote . get ( " url " ) ,
user = remote . get ( " user " ) ,
password = remote . get ( " password " )
)
except aiohttp . web . HTTPConflict :
pass # if the server is broken we skip it
2016-09-06 14:06:20 +03:00
if " vm " in server_settings :
2017-01-18 10:39:27 +02:00
vmname = None
2016-09-06 14:06:20 +03:00
vm_settings = server_settings [ " vm " ]
if vm_settings [ " virtualization " ] == " VMware " :
engine = " vmware "
vmname = vm_settings . get ( " vmname " , " " )
elif vm_settings [ " virtualization " ] == " VirtualBox " :
engine = " virtualbox "
vmname = vm_settings . get ( " vmname " , " " )
else :
engine = " remote "
# In case of remote server we match the compute with url parameter
for compute in self . _computes . values ( ) :
if compute . host == vm_settings . get ( " remote_vm_host " ) and compute . port == vm_settings . get ( " remote_vm_port " ) :
vmname = compute . name
2016-09-08 16:32:35 +03:00
if vm_settings . get ( " auto_stop " , True ) :
when_exit = " stop "
else :
when_exit = " keep "
2016-09-06 14:06:20 +03:00
self . gns3vm . settings = {
" engine " : engine ,
" enable " : vm_settings . get ( " auto_start " , False ) ,
2016-09-08 16:32:35 +03:00
" when_exit " : when_exit ,
2016-09-06 14:06:20 +03:00
" headless " : vm_settings . get ( " headless " , False ) ,
" vmname " : vmname
}
2017-03-07 16:36:35 +02:00
self . _settings = { }
2016-07-14 16:14:56 +03:00
2016-06-29 15:16:29 +03:00
@property
def settings ( self ) :
"""
Store settings shared by the different GUI will be replace by dedicated API later . Dictionnary
"""
return self . _settings
@settings.setter
def settings ( self , val ) :
self . _settings = val
2017-03-21 14:35:02 +02:00
self . _settings [ " modification_uuid " ] = str ( uuid . uuid4 ( ) ) # We add a modification id to the settings it's help the gui to detect changes
2017-03-07 12:12:51 +02:00
self . save ( )
2017-04-12 15:35:49 +03:00
self . load_appliances ( )
2016-06-29 15:16:29 +03:00
self . notification . emit ( " settings.updated " , val )
2016-03-10 22:51:29 +02:00
@asyncio.coroutine
2016-08-30 10:58:37 +03:00
def add_compute ( self , compute_id = None , name = None , force = False , connect = True , * * kwargs ) :
2016-03-03 17:02:27 +02:00
"""
2016-05-14 03:48:10 +03:00
Add a server to the dictionary of compute servers controlled by this controller
2016-03-10 22:51:29 +02:00
2016-05-14 03:48:10 +03:00
: param compute_id : Compute server identifier
2016-06-29 18:39:41 +03:00
: param name : Compute name
2016-08-26 15:09:18 +03:00
: param force : True skip security check
2016-08-30 10:58:37 +03:00
: param connect : True connect to the compute immediately
2016-04-15 18:57:06 +03:00
: param kwargs : See the documentation of Compute
2016-03-03 17:02:27 +02:00
"""
2018-01-10 11:22:55 +02:00
2016-05-23 19:44:20 +03:00
if compute_id not in self . _computes :
2016-05-11 16:01:57 +03:00
2016-07-19 14:26:27 +03:00
# We disallow to create from the outside the local and VM server
2016-08-26 15:09:18 +03:00
if ( compute_id == ' local ' or compute_id == ' vm ' ) and not force :
2016-05-24 16:45:06 +03:00
return None
2016-05-23 19:44:20 +03:00
2016-10-24 16:24:45 +03:00
# It seem we have error with a gns3vm imported as a remote server and conflict
# with GNS3 VM settings. That's why we ignore server name gns3vm
if name == ' gns3vm ' :
return None
2016-06-29 18:39:41 +03:00
for compute in self . _computes . values ( ) :
2017-02-23 15:34:21 +02:00
if name and compute . name == name and not force :
2016-08-20 20:10:34 +03:00
raise aiohttp . web . HTTPConflict ( text = ' Compute name " {} " already exists ' . format ( name ) )
2016-06-29 18:39:41 +03:00
compute = Compute ( compute_id = compute_id , controller = self , name = name , * * kwargs )
2016-05-25 15:10:03 +03:00
self . _computes [ compute . id ] = compute
2016-04-19 16:35:50 +03:00
self . save ( )
2016-08-30 10:58:37 +03:00
if connect :
yield from compute . connect ( )
2016-05-25 15:10:03 +03:00
self . notification . emit ( " compute.created " , compute . __json__ ( ) )
return compute
2016-05-23 19:44:20 +03:00
else :
2016-08-30 10:58:37 +03:00
if connect :
yield from self . _computes [ compute_id ] . connect ( )
2016-05-23 19:44:20 +03:00
self . notification . emit ( " compute.updated " , self . _computes [ compute_id ] . __json__ ( ) )
2016-05-25 15:10:03 +03:00
return self . _computes [ compute_id ]
2016-03-03 17:02:27 +02:00
2016-12-15 22:57:59 +02:00
@asyncio.coroutine
def close_compute_projects ( self , compute ) :
"""
Close projects running on a compute
"""
for project in self . _projects . values ( ) :
if compute in project . computes :
yield from project . close ( )
2016-05-25 12:27:41 +03:00
@asyncio.coroutine
def delete_compute ( self , compute_id ) :
"""
2016-08-26 12:22:09 +03:00
Delete a compute node . Project using this compute will be close
2016-05-25 12:27:41 +03:00
: param compute_id : Compute server identifier
"""
2016-12-12 12:09:07 +02:00
try :
compute = self . get_compute ( compute_id )
except aiohttp . web . HTTPNotFound :
return
2016-12-15 22:57:59 +02:00
yield from self . close_compute_projects ( compute )
2016-06-02 14:44:12 +03:00
yield from compute . close ( )
2016-05-25 12:27:41 +03:00
del self . _computes [ compute_id ]
2016-05-25 15:10:03 +03:00
self . save ( )
2016-05-25 12:27:41 +03:00
self . notification . emit ( " compute.deleted " , compute . __json__ ( ) )
2016-05-18 15:56:23 +03:00
@property
def notification ( self ) :
"""
The notification system
"""
return self . _notification
2016-03-03 17:02:27 +02:00
@property
2016-04-15 18:57:06 +03:00
def computes ( self ) :
2016-03-03 17:02:27 +02:00
"""
2016-05-14 03:48:10 +03:00
: returns : The dictionary of compute server managed by this controller
2016-03-03 17:02:27 +02:00
"""
2016-04-15 18:57:06 +03:00
return self . _computes
2016-03-03 17:02:27 +02:00
2016-05-12 00:19:00 +03:00
def get_compute ( self , compute_id ) :
2016-03-10 22:51:29 +02:00
"""
2016-05-14 03:48:10 +03:00
Returns a compute server or raise a 404 error .
2016-03-10 22:51:29 +02:00
"""
try :
2016-04-15 18:57:06 +03:00
return self . _computes [ compute_id ]
2016-03-10 22:51:29 +02:00
except KeyError :
2016-08-22 19:37:32 +03:00
if compute_id == " vm " :
2016-10-24 16:16:53 +03:00
raise aiohttp . web . HTTPNotFound ( text = " You try to use a node on the GNS3 VM server but the GNS3 VM is not configured " )
2016-04-15 18:57:06 +03:00
raise aiohttp . web . HTTPNotFound ( text = " Compute ID {} doesn ' t exist " . format ( compute_id ) )
2016-03-10 22:51:29 +02:00
2016-07-25 19:02:22 +03:00
def has_compute ( self , compute_id ) :
"""
Return True if the compute exist in the controller
"""
return compute_id in self . _computes
2016-03-10 11:32:07 +02:00
@asyncio.coroutine
2016-06-29 18:39:41 +03:00
def add_project ( self , project_id = None , name = None , * * kwargs ) :
2016-03-10 11:32:07 +02:00
"""
2016-05-14 03:48:10 +03:00
Creates a project or returns an existing project
2016-03-10 22:51:29 +02:00
2016-06-29 18:39:41 +03:00
: param project_id : Project ID
: param name : Project name
2016-03-10 22:51:29 +02:00
: param kwargs : See the documentation of Project
2016-03-10 11:32:07 +02:00
"""
2016-03-10 22:51:29 +02:00
if project_id not in self . _projects :
2016-06-29 18:39:41 +03:00
for project in self . _projects . values ( ) :
if name and project . name == name :
2016-08-20 20:10:34 +03:00
raise aiohttp . web . HTTPConflict ( text = ' Project name " {} " already exists ' . format ( name ) )
2016-06-29 18:39:41 +03:00
project = Project ( project_id = project_id , controller = self , name = name , * * kwargs )
2016-03-10 11:32:07 +02:00
self . _projects [ project . id ] = project
2016-03-10 22:51:29 +02:00
return self . _projects [ project . id ]
return self . _projects [ project_id ]
2016-03-10 11:32:07 +02:00
2016-05-11 20:35:36 +03:00
def get_project ( self , project_id ) :
2016-03-10 11:32:07 +02:00
"""
2016-11-18 18:35:28 +02:00
Returns a project or raise a 404 error .
2016-03-10 11:32:07 +02:00
"""
try :
return self . _projects [ project_id ]
except KeyError :
raise aiohttp . web . HTTPNotFound ( text = " Project ID {} doesn ' t exist " . format ( project_id ) )
2016-11-18 18:35:28 +02:00
@asyncio.coroutine
def get_loaded_project ( self , project_id ) :
"""
Returns a project or raise a 404 error .
If project is not finished to load wait for it
"""
project = self . get_project ( project_id )
yield from project . wait_loaded ( )
return project
2016-05-12 00:19:00 +03:00
def remove_project ( self , project ) :
2017-02-13 16:24:22 +02:00
if project . id in self . _projects :
del self . _projects [ project . id ]
2016-03-10 11:32:07 +02:00
2016-06-14 17:07:37 +03:00
@asyncio.coroutine
2016-06-15 00:08:30 +03:00
def load_project ( self , path , load = True ) :
2016-06-14 17:07:37 +03:00
"""
Load a project from a . gns3
: param path : Path of the . gns3
2016-06-15 00:08:30 +03:00
: param load : Load the topology
2016-06-14 17:07:37 +03:00
"""
topo_data = load_topology ( path )
2017-02-06 12:07:35 +02:00
topo_data . pop ( " topology " )
2016-06-14 17:07:37 +03:00
topo_data . pop ( " version " )
topo_data . pop ( " revision " )
topo_data . pop ( " type " )
2016-06-17 18:50:06 +03:00
if topo_data [ " project_id " ] in self . _projects :
2016-11-14 19:45:44 +02:00
project = self . _projects [ topo_data [ " project_id " ] ]
else :
project = yield from self . add_project ( path = os . path . dirname ( path ) , status = " closed " , filename = os . path . basename ( path ) , * * topo_data )
2016-08-15 21:51:59 +03:00
if load or project . auto_open :
2016-06-15 16:12:38 +03:00
yield from project . open ( )
2016-06-16 17:57:54 +03:00
return project
2016-06-14 17:07:37 +03:00
2016-09-06 12:30:08 +03:00
@asyncio.coroutine
def _project_auto_open ( self ) :
"""
Auto open the project with auto open enable
"""
for project in self . _projects . values ( ) :
if project . auto_open :
yield from project . open ( )
2016-07-21 15:48:13 +03:00
def get_free_project_name ( self , base_name ) :
"""
Generate a free project name base on the base name
"""
2016-07-21 18:55:15 +03:00
names = [ p . name for p in self . _projects . values ( ) ]
2016-07-21 15:48:13 +03:00
if base_name not in names :
return base_name
i = 1
2016-07-22 19:02:11 +03:00
projects_path = self . projects_directory ( )
while True :
new_name = " {} - {} " . format ( base_name , i )
if new_name not in names and not os . path . exists ( os . path . join ( projects_path , new_name ) ) :
break
2016-07-21 15:48:13 +03:00
i + = 1
if i > 1000000 :
raise aiohttp . web . HTTPConflict ( text = " A project name could not be allocated (node limit reached?) " )
2016-07-22 19:02:11 +03:00
return new_name
2016-07-21 15:48:13 +03:00
2016-03-10 11:32:07 +02:00
@property
def projects ( self ) :
"""
2017-11-17 13:13:34 +02:00
: returns : The dictionary of projects managed by GNS3
2016-03-10 11:32:07 +02:00
"""
return self . _projects
2017-02-01 12:30:14 +02:00
@property
2017-02-08 16:40:56 +02:00
def appliance_templates ( self ) :
2017-02-01 12:30:14 +02:00
"""
2017-02-08 16:40:56 +02:00
: returns : The dictionary of appliances templates managed by GNS3
2017-02-01 12:30:14 +02:00
"""
2017-02-08 16:40:56 +02:00
return self . _appliance_templates
2017-02-01 12:30:14 +02:00
2017-04-12 15:35:49 +03:00
@property
def appliances ( self ) :
"""
: returns : The dictionary of appliances managed by GNS3
"""
return self . _appliances
2016-07-22 19:02:11 +03:00
def projects_directory ( self ) :
server_config = Config . instance ( ) . get_section_config ( " Server " )
return os . path . expanduser ( server_config . get ( " projects_path " , " ~/GNS3/projects " ) )
2016-03-02 10:49:52 +02:00
@staticmethod
def instance ( ) :
"""
Singleton to return only on instance of Controller .
2016-05-14 03:48:10 +03:00
2016-03-02 10:49:52 +02:00
: returns : instance of Controller
"""
if not hasattr ( Controller , ' _instance ' ) or Controller . _instance is None :
Controller . _instance = Controller ( )
return Controller . _instance
2017-07-07 18:50:40 +03:00
@asyncio.coroutine
2017-11-23 18:00:31 +02:00
def autoidlepc ( self , compute_id , platform , image , ram ) :
2017-07-07 18:50:40 +03:00
"""
Compute and IDLE PC value for an image
: param compute_id : ID of the compute where the idlepc operation need to run
: param platform : Platform type
: param image : Image to use
2017-11-23 18:00:31 +02:00
: param ram : amount of RAM to use
2017-07-07 18:50:40 +03:00
"""
compute = self . get_compute ( compute_id )
for project in list ( self . _projects . values ( ) ) :
if project . name == " AUTOIDLEPC " :
yield from project . delete ( )
self . remove_project ( project )
project = yield from self . add_project ( name = " AUTOIDLEPC " )
2017-11-23 18:00:31 +02:00
node = yield from project . add_node ( compute , " AUTOIDLEPC " , str ( uuid . uuid4 ( ) ) , node_type = " dynamips " , platform = platform , image = image , ram = ram )
2017-07-07 18:50:40 +03:00
res = yield from node . dynamips_auto_idlepc ( )
yield from project . delete ( )
self . remove_project ( project )
return res