Server working directory management.

Improvements how to handle status for ports & nodes.
Fix issues with project/topology management.
Push startup-config to servers using base64.
Remote servers allocation.
This commit is contained in:
grossmj
2014-03-02 15:26:24 -07:00
parent dede8be0b5
commit 6230a017bd
23 changed files with 302 additions and 60 deletions

20
gns3/baseconfig.txt Normal file
View File

@@ -0,0 +1,20 @@
!
hostname %h
!
no ip domain lookup
no ip icmp rate-limit unreachable
ip tcp synwait 5
!
line con 0
exec-timeout 0 0
logging synchronous
privilege level 15
no login
line aux 0
exec-timeout 0 0
logging synchronous
privilege level 15
no login
!
!
end

View File

@@ -94,6 +94,14 @@ class GraphicsView(QtGui.QGraphicsView):
Port.reset()
self._topology.reset()
def setLocalBaseWorkingDirtoAllModules(self, path):
try:
dynamips = Dynamips.instance()
dynamips.setLocalBaseWorkingDir(path)
except ModuleError as e:
QtGui.QMessageBox.critical(self, "Local working directory", "{}".format(e))
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.

View File

@@ -21,6 +21,7 @@ Graphical representation of an Ethernet link for QGraphicsScene.
from ..qt import QtCore, QtGui
from .link_item import LinkItem
from ..ports.port import Port
class EthernetLinkItem(LinkItem):
@@ -107,10 +108,10 @@ class EthernetLinkItem(LinkItem):
if self.length < 100:
return
if self._source_port.status() == 1:
if self._source_port.status() == Port.started:
# port is active
color = QtCore.Qt.green
elif self._source_port.status() == 2:
elif self._source_port.status() == Port.suspended:
# port is suspended
color = QtCore.Qt.yellow
else:
@@ -163,10 +164,10 @@ class EthernetLinkItem(LinkItem):
painter.drawPoint(point1)
if self._destination_port.status() == 1:
if self._destination_port.status() == Port.started:
# port is active
color = QtCore.Qt.green
elif self._destination_port.status() == 2:
elif self._destination_port.status() == Port.suspended:
# port is suspended
color = QtCore.Qt.yellow
else:

View File

@@ -134,10 +134,6 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
when a the node has started.
"""
ports = self._node.ports()
for port in ports:
# set ports as started
port.setStatus(1)
for link in self._links:
link.update()
@@ -147,10 +143,6 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
when a the node has stopped.
"""
ports = self._node.ports()
for port in ports:
# set ports as stopped
port.setStatus(0)
for link in self._links:
link.update()
@@ -160,10 +152,6 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
when a the node has suspended.
"""
ports = self._node.ports()
for port in ports:
# set ports as suspended
port.setStatus(2)
for link in self._links:
link.update()

View File

@@ -22,6 +22,7 @@ Graphical representation of a Serial link on the QGraphicsScene.
import math
from ..qt import QtCore, QtGui
from .link_item import LinkItem
from ..ports.port import Port
class SerialLinkItem(LinkItem):
@@ -108,10 +109,10 @@ class SerialLinkItem(LinkItem):
return
# source point color
if self._source_port.status() == 1:
if self._source_port.status() == Port.started:
# port is active
color = QtCore.Qt.green
elif self._source_port.status() == 2:
elif self._source_port.status() == Port.suspended:
# port is suspended
color = QtCore.Qt.yellow
else:
@@ -154,10 +155,10 @@ class SerialLinkItem(LinkItem):
#painter.drawPoint(self.source)
# destination point color
if self._destination_port.status() == 1:
if self._destination_port.status() == Port.started:
# port is active
color = QtCore.Qt.green
elif self._destination_port.status() == 2:
elif self._destination_port.status() == Port.suspended:
# port is suspended
color = QtCore.Qt.yellow
else:

View File

@@ -24,14 +24,16 @@ import tempfile
import socket
import shutil
import json
from .servers import Servers
from .qt import QtGui, QtCore
from .servers import Servers
from .node import Node
from .ui.main_window_ui import Ui_MainWindow
from .preferences_dialog import PreferencesDialog
from .settings import GENERAL_SETTINGS, GENERAL_SETTING_TYPES
from .utils.progress_dialog import ProgressDialog
from .utils.process_files_thread import ProcessFilesThread
from .utils.wait_for_connection_thread import WaitForConnectionThread
from .utils.message_box import MessageBox
from .items.node_item import NodeItem
from .topology import Topology
@@ -727,7 +729,17 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
Saves a project to another location/name.
"""
#TODO: check if any node are running, cancel if any!
# first check if any node that can be started is running
topology = Topology.instance()
running_nodes = []
for node in topology.nodes():
if hasattr(node, "start") and node.status() == Node.started:
running_nodes.append(node.name())
if running_nodes:
nodes = "\n".join(running_nodes)
MessageBox(self, "Save project", "Please stop the following nodes before saving the topology", nodes)
return
if self._temporary_project:
destination_file = os.path.join(self._settings["projects_path"], "untitled.net")
@@ -759,6 +771,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
return False
self._deleteTemporaryProject()
self._project_files_dir = new_project_files_dir
self.uiGraphicsView.setLocalBaseWorkingDirtoAllModules(self._project_files_dir)
return self._saveProject(path)
def _saveProject(self, path):
@@ -790,6 +804,17 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
"""
self.uiGraphicsView.reset()
project_files_dir = path
if path.endswith(".net"):
project_files_dir = path[:-4]
self._project_files_dir = project_files_dir + "-files"
if not os.path.exists(self._project_files_dir):
QtGui.QMessageBox.warning(self, "Load", "Project files directory doesn't exist: {}".format(self._project_files_dir))
self.uiGraphicsView.setLocalBaseWorkingDirtoAllModules(self._project_files_dir)
topology = Topology.instance()
try:
with open(path, "r") as f:
@@ -804,15 +829,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.uiStatusBar.showMessage("Project loaded {}".format(path), 2000)
self._project_path = path
project_files_dir = path
if path.endswith(".net"):
project_files_dir = path[:-4]
self._project_files_dir = project_files_dir + "-files"
if not os.path.exists(self._project_files_dir):
QtGui.QMessageBox.warning(self, "Load", "Project files directory doesn't exist: {}".format(self._project_files_dir))
self._setCurrentFile(path)
def _deleteTemporaryProject(self):
@@ -850,6 +866,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
except EnvironmentError as e:
QtGui.QMessageBox.critical(self, "Save", "Could not create project: {}".format(e))
self.uiGraphicsView.setLocalBaseWorkingDirtoAllModules(self._project_files_dir)
self._setCurrentFile()
def _setCurrentFile(self, path=None):

View File

@@ -20,6 +20,7 @@ Dynamips module implementation.
"""
import socket
import os
from gns3.qt import QtCore
from gns3.servers import Servers
from ..module import Module
@@ -54,6 +55,7 @@ class Dynamips(Module):
self._settings = {}
self._ios_images = {}
self._servers = []
self._working_dir = ""
# load the settings and IOS images.
self._loadSettings()
@@ -98,7 +100,7 @@ class Dynamips(Module):
settings.setArrayIndex(index)
path = settings.value("path", "")
image = settings.value("image", "")
startup_config = settings.value("image", "")
startup_config = settings.value("startup_config", "")
platform = settings.value("platform", "")
chassis = settings.value("chassis", "")
idlepc = settings.value("idlepc", "")
@@ -108,7 +110,7 @@ class Dynamips(Module):
key = "{server}:{image}".format(server=server, image=image)
self._ios_images[key] = {"path": path,
"image": image,
"startup-config": startup_config,
"startup_config": startup_config,
"platform": platform,
"chassis": chassis,
"idlepc": idlepc,
@@ -139,6 +141,26 @@ class Dynamips(Module):
settings.endArray()
settings.endGroup()
def setLocalBaseWorkingDir(self, path):
"""
Sets the local base working directory for this module.
:param path: path to the local working directory
"""
self._working_dir = os.path.join(path, "dynamips")
if not os.path.exists(self._working_dir):
try:
os.makedirs(self._working_dir)
except EnvironmentError as e:
raise ModuleError("{}".format(e))
log.info("local working directory for Dynamips module: {}".format(self._working_dir))
servers = Servers.instance()
server = servers.localServer()
if server.connected():
self._sendSettings(server)
def addServer(self, server):
"""
Adds a server to be used by this module.
@@ -224,20 +246,34 @@ class Dynamips(Module):
"""
log.info("sending Dynamips settings to server {}:{}".format(server.host, server.port))
server.send_notification("dynamips.settings", self._settings)
params = self._settings.copy()
# send the local working directory only if this is a local server
servers = Servers.instance()
if server == servers.localServer():
params.update({"working_dir": self._working_dir})
server.send_notification("dynamips.settings", params)
def createNode(self, node_class):
def createNode(self, node_class, server=None):
"""
Creates a new node.
:param node_class: Node object
:param server: optional WebSocketClient instance
"""
log.info("creating node {}".format(node_class))
# allocate a server for the node
# allocate a server for the node if none is given
servers = Servers.instance()
server = servers.localServer()
if self._settings["use_local_server"] and not server:
# use the local server
server = servers.localServer()
elif not server:
# pick up a remote server (round-robin method)
server = next(iter(servers))
if not server:
raise ModuleError("No remote server is configured")
if not server.connected():
try:
log.info("reconnecting to server {}:{}".format(server.host, server.port))
@@ -283,6 +319,8 @@ class Dynamips(Module):
# set initial settings like an idle-pc value
if ios_image["idlepc"]:
settings["idlepc"] = ios_image["idlepc"]
if ios_image["startup_config"]:
settings["startup_config"] = ios_image["startup_config"]
node.setup(ios_image["path"], ios_image["ram"], initial_settings=settings)
else:
node.setup()
@@ -317,13 +355,11 @@ class Dynamips(Module):
Returns the object with the corresponding name.
:param name: object name
:returns: object or None
"""
if name in globals():
return globals()[name]
return None
raise ModuleError("Dynamips module could not find object {}".format(name))
@staticmethod
def nodes():

View File

@@ -39,6 +39,7 @@ class ATMSwitch(Node):
Node.__init__(self, server)
log.info("ATM switch is being created")
self.setStatus(Node.started) # this is an always-on node
self._atmsw_id = None
self._ports = []
self._module = module
@@ -137,6 +138,7 @@ class ATMSwitch(Node):
for port_name in ports_to_create:
port = SerialPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(SerialPort.started)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_name))
@@ -296,6 +298,7 @@ class ATMSwitch(Node):
for topology_port in ports:
port = SerialPort(topology_port["name"])
port.setPortNumber(topology_port["port_number"])
port.setStatus(SerialPort.started)
self._ports.append(port)
self._settings["ports"].append(port.portNumber())

View File

@@ -39,6 +39,7 @@ class EthernetHub(Node):
Node.__init__(self, server)
log.info("Ethernet hub is being created")
self.setStatus(Node.started) # this is an always-on node
self._ethhub_id = None
self._module = module
self._ports = []
@@ -132,6 +133,7 @@ class EthernetHub(Node):
for port_name in ports_to_create:
port = EthernetPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(EthernetPort.started)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_name))
@@ -278,6 +280,7 @@ class EthernetHub(Node):
for topology_port in ports:
port = EthernetPort(topology_port["name"])
port.setPortNumber(topology_port["port_number"])
port.setStatus(EthernetPort.started)
self._ports.append(port)
self._settings["ports"].append(port.portNumber())

View File

@@ -39,6 +39,7 @@ class EthernetSwitch(Node):
Node.__init__(self, server)
log.info("Ethernet switch is being created")
self.setStatus(Node.started) # this is an always-on node
self._ethsw_id = None
self._module = module
self._ports = []
@@ -126,6 +127,7 @@ class EthernetSwitch(Node):
continue
port = EthernetPort(str(port_number))
port.setPortNumber(port_number)
port.setStatus(EthernetPort.started)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_number))
@@ -320,6 +322,7 @@ class EthernetSwitch(Node):
port = EthernetPort(topology_port["name"])
port.setPortNumber(topology_port["port_number"])
port.setId(topology_port["id"])
port.setStatus(EthernetPort.started)
self._ports.append(port)
self._settings["ports"][port.portNumber()] = {"type": topology_port["type"],
"vlan": topology_port["vlan"]}

View File

@@ -39,6 +39,7 @@ class FrameRelaySwitch(Node):
Node.__init__(self, server)
log.info("Frame-Relay switch is being created")
self.setStatus(Node.started) # this is an always-on node
self._frsw_id = None
self._ports = []
self._module = module
@@ -138,6 +139,7 @@ class FrameRelaySwitch(Node):
for port_name in ports_to_create:
port = SerialPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(SerialPort.started)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_name))
@@ -298,6 +300,7 @@ class FrameRelaySwitch(Node):
port = SerialPort(topology_port["name"])
port.setPortNumber(topology_port["port_number"])
port.setId(topology_port["id"])
port.setStatus(SerialPort.started)
self._ports.append(port)
log.info("Frame-Relay switch {} is loading".format(name))
self.setup(name)

View File

@@ -20,7 +20,11 @@ Base class for Dynamips router implementations on the client side.
Asynchronously sends JSON messages to the GNS3 server and receives responses with callbacks.
"""
import os
import base64
from gns3.node import Node
from gns3.ports.port import Port
from ..settings import PLATFORMS_DEFAULT_RAM
from ..adapters import ADAPTER_MATRIX
from ..wics import WIC_MATRIX
@@ -52,6 +56,8 @@ class Router(Node):
self._settings = {"name": "",
"platform": platform,
"image": "",
"startup_config": "",
"private_config": "",
"ram": 128,
"nvram": 128,
"mmap": True,
@@ -284,6 +290,26 @@ class Router(Node):
log.debug("router {} has been created".format(self.name()))
self.created_signal.emit(self.id())
def _base64Config(self, config_path):
"""
Get the base64 encoded config from a file.
:param config_path: path to the configuration file.
:returns: base64 encoded string
"""
try:
with open(config_path, "r") as f:
log.info("opening configuration file: {}".format(config_path))
config = f.read()
config = '!\n' + config.replace('\r', "")
encoded = ("").join(base64.encodestring(config.encode("utf-8")).decode("utf-8").split())
return encoded
except EnvironmentError as e:
log.warn("could not base64 encode {}: {}".format(config_path, e))
return ""
def update(self, new_settings):
"""
Updates the settings for this router.
@@ -296,6 +322,10 @@ class Router(Node):
if name in self._settings and self._settings[name] != value:
params[name] = value
if "startup_config" in new_settings and self._settings["startup_config"] != new_settings["startup_config"] \
and os.path.exists(new_settings["startup_config"]):
params["startup_config_base64"] = self._base64Config(new_settings["startup_config"])
log.debug("{} is updating settings: {}".format(self.name(), params))
self._server.send_message("dynamips.vm.update", params, self._updateCallback)
@@ -370,6 +400,10 @@ class Router(Node):
self.error_signal.emit(self.name(), result["code"], result["message"])
else:
log.info("{} has started".format(self.name()))
self.setStatus(Node.started)
for port in self._ports:
# set ports as started
port.setStatus(Port.started)
self.started_signal.emit()
def stop(self):
@@ -393,6 +427,10 @@ class Router(Node):
self.error_signal.emit(self.name(), result["code"], result["message"])
else:
log.info("{} has stopped".format(self.name()))
self.setStatus(Node.stopped)
for port in self._ports:
# set ports as stopped
port.setStatus(Port.stopped)
self.stopped_signal.emit()
def suspend(self):
@@ -416,6 +454,10 @@ class Router(Node):
self.error_signal.emit(self.name(), result["code"], result["message"])
else:
log.info("{} has suspended".format(self.name()))
self.setStatus(Node.suspended)
for port in self._ports:
# set ports as suspended
port.setStatus(Port.suspended)
self.suspended_signal.emit()
def reload(self):
@@ -587,6 +629,15 @@ class Router(Node):
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
def _saveConfig(self):
"""
Tells the server to save the router configurations (startup-config and private-config).
"""
params = {"id": self._router_id}
log.debug("{} is saving his configuration: {}".format(self.name(), params))
self._server.send_notification("dynamips.vm.save_config", params)
def _slot_info(self):
"""
Returns information about the slots/ports of this router.
@@ -721,12 +772,15 @@ class Router(Node):
:returns: representation of the node (dictionary)
"""
# tell the server to save the startup-config and
# private-config
self._saveConfig()
router = {"id": self.id(),
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id(),
}
"server_id": self._server.id()}
# add the properties
for name, value in self._settings.items():

View File

@@ -96,6 +96,7 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
self.uiBaseHypervisorPortSpinBox.setValue(settings["base_hypervisor_port"])
self.uiBaseConsolePortSpinBox.setValue(settings["base_console_port"])
self.uiBaseAuxPortSpinBox.setValue(settings["base_aux_port"])
self.uiUseLocalServercheckBox.setChecked(settings["use_local_server"])
self.uiAllocateHypervisorPerDeviceCheckBox.setChecked(settings["allocate_hypervisor_per_device"])
self.uiMemoryUsageLimitPerHypervisorSpinBox.setValue(settings["memory_usage_limit_per_hypervisor"])
self.uiAllocateHypervisorPerIOSCheckBox.setChecked(settings["allocate_hypervisor_per_ios_image"])
@@ -133,6 +134,7 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
new_settings["base_hypervisor_port"] = self.uiBaseHypervisorPortSpinBox.value()
new_settings["base_console_port"] = self.uiBaseConsolePortSpinBox.value()
new_settings["base_aux_port"] = self.uiBaseAuxPortSpinBox.value()
new_settings["use_local_server"] = self.uiUseLocalServercheckBox.isChecked()
new_settings["allocate_hypervisor_per_device"] = self.uiAllocateHypervisorPerDeviceCheckBox.isChecked()
new_settings["memory_usage_limit_per_hypervisor"] = self.uiMemoryUsageLimitPerHypervisorSpinBox.value()
new_settings["allocate_hypervisor_per_ios_image"] = self.uiAllocateHypervisorPerIOSCheckBox.isChecked()

View File

@@ -78,7 +78,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
ios_image = self._ios_images[key]
self.uiIOSPathLineEdit.setText(ios_image["path"])
self.uiStartupConfigLineEdit.setText(ios_image["startup-config"])
self.uiStartupConfigLineEdit.setText(ios_image["startup_config"])
index = self.uiPlatformComboBox.findText(ios_image["platform"])
if index != -1:
self.uiPlatformComboBox.setCurrentIndex(index)
@@ -142,7 +142,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self._ios_images[key] = {"path": path,
"image": image,
"startup-config": startup_config,
"startup_config": startup_config,
"platform": platform,
"chassis": chassis,
"idlepc": idlepc,

View File

@@ -266,6 +266,9 @@ class RouterConfigurationPage(QtGui.QWidget, Ui_routerConfigPageWidget):
# load the IOS image name without the full path
self.uiIOSImageTextLabel.setText(os.path.basename(settings["image"]))
# load the startup-config
self.uiStartupConfigTextLabel.setText(settings["startup_config"])
#TODO: startup-config setting
#self.uiStartupConfigTextLabel.setText("None")

View File

@@ -35,9 +35,10 @@ else:
DYNAMIPS_SETTINGS = {
"path": DEFAULT_DYNAMIPS_PATH,
"base_hypervisor_port": 7200,
"base_hypervisor_port": 7201,
"base_console_port": 2101,
"base_aux_port": 2501,
"use_local_server": True,
"allocate_hypervisor_per_device": True,
"memory_usage_limit_per_hypervisor": 1024,
"allocate_hypervisor_per_ios_image": True,
@@ -54,6 +55,7 @@ DYNAMIPS_SETTING_TYPES = {
"base_hypervisor_port": int,
"base_console_port": int,
"base_aux_port": int,
"use_local_server": bool,
"allocate_hypervisor_per_device": bool,
"memory_usage_limit_per_hypervisor": int,
"allocate_hypervisor_per_ios_image": bool,

View File

@@ -47,6 +47,11 @@ class Node(QtCore.QObject):
_instance_count = 1
# node statuses
stopped = 0
started = 1
suspended = 2
def __init__(self, server=None):
super(Node, self).__init__()
@@ -57,6 +62,7 @@ class Node(QtCore.QObject):
self._server = server
self._initialized = False
self._status = 0
@classmethod
def reset(cls):
@@ -93,6 +99,26 @@ class Node(QtCore.QObject):
self._id = new_id
def status(self):
"""
Returns the status of this node.
0 = stopped, 1 = started, 2 = suspended.
:returns: node status (integer)
"""
return self._status
def setStatus(self, status):
"""
Sets a status for this node.
0 = stopped, 1 = started, 2 = suspended.
:param status: node status (integer)
"""
self._status = status
def initialized(self):
"""
Returns if the node has been initialized

View File

@@ -33,6 +33,11 @@ class Port(object):
_instance_count = 1
# port statuses
stopped = 0
started = 1
suspended = 2
def __init__(self, name, default_nio=None, stub=False):
# create an unique ID
@@ -45,7 +50,7 @@ class Port(object):
self._stub = stub
self._link_id = None
self._description = ""
self._status = 0
self._status = Port.stopped
self._data = {}
if default_nio == None:
self._default_nio = NIOUDP
@@ -100,7 +105,7 @@ class Port(object):
def status(self):
"""
Returns the status of this port.
0 = stopped, 1 = active, 2 = suspended.
0 = stopped, 1 = started, 2 = suspended.
:returns: port status (integer)
"""
@@ -110,7 +115,7 @@ class Port(object):
def setStatus(self, status):
"""
Sets a status for this port.
0 = stopped, 1 = active, 2 = suspended.
0 = stopped, 1 = started, 2 = suspended.
:param status: port status (integer)
"""

View File

@@ -22,7 +22,6 @@ vice-versa. Possibility to add PyQt5 in the future as well. Current default is P
# based on https://gist.github.com/remram44/5985681
from __future__ import unicode_literals
import sys
import sip

View File

@@ -40,6 +40,7 @@ class Servers(object):
self._local_server_path = ""
self._local_server_proccess = QtCore.QProcess()
self._loadSettings()
self._remote_server_iter_pos = 0
def _loadSettings(self):
"""
@@ -215,6 +216,33 @@ class Servers(object):
return self._remote_servers
def __iter__(self):
"""
Creates a round-robin system to pick up a remote server.
"""
return self
def __next__(self):
"""
Returns the next available remote server.
:returns: remote server (WebSocketClient instance)
"""
if not self._remote_servers:
return None
server_ids = list(self._remote_servers.keys())
server_id = server_ids[self._remote_server_iter_pos]
if self._remote_server_iter_pos < len(server_ids) - 1:
self._remote_server_iter_pos += 1
else:
self._remote_server_iter_pos = 0
return self._remote_servers[server_id]
def save(self):
"""
Saves the settings.

View File

@@ -25,6 +25,7 @@ from .qt import QtGui, QtCore
from .items.node_item import NodeItem
from .modules.dynamips import Dynamips
from .modules.module_error import ModuleError
from .utils.message_box import MessageBox
from .version import __version__
import logging
@@ -184,7 +185,7 @@ class Topology(object):
if self._links:
topology_links = topology["topology"]["links"] = []
for link in self._links:
log.info("saving {}".format(link.description().lower()))
log.info("saving {}".format(link.description()))
topology_links.append(link.dump())
# finally the servers
@@ -235,6 +236,7 @@ class Topology(object):
self._node_to_links_mapping[destination_id].append(topology_link)
# then load the nodes
node_errors = []
if "nodes" in topology["topology"]:
nodes = topology["topology"]["nodes"]
for topology_node in nodes:
@@ -244,15 +246,14 @@ class Topology(object):
#TODO: node setup management with other modules
# create the node
node_class = Dynamips.getNodeClass(topology_node["type"])
dynamips = Dynamips.instance()
# TODO: catch exception
try:
node_class = Dynamips.getNodeClass(topology_node["type"])
dynamips = Dynamips.instance()
node = dynamips.createNode(node_class)
except ModuleError as e:
QtGui.QMessageBox.critical(main_window, "Node creation", "{}".format(e))
return
node_errors.append(str(e))
#QtGui.QMessageBox.critical(main_window, "Node creation", "{}".format(e))
continue
node.setId(topology_node["id"])
@@ -268,6 +269,10 @@ class Topology(object):
view.scene().addItem(node_item)
self.addNode(node)
if node_errors:
errors = "\n".join(node_errors)
MessageBox(main_window, "Topology", "Errors detected while importing the topology", errors)
def _nodeCreatedSlot(self, node_id):
"""
Slot to know when a node has been created.

33
gns3/utils/message_box.py Normal file
View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
To show a advanced message box.
"""
from ..qt import QtGui
def MessageBox(parent, title, message, details="", icon=QtGui.QMessageBox.Critical):
msgbox = QtGui.QMessageBox(parent)
msgbox.setWindowTitle(title)
msgbox.setText(message)
msgbox.setIcon(icon)
if details:
msgbox.setDetailedText(details)
msgbox.exec_()

View File

@@ -74,7 +74,8 @@ class ProcessFilesThread(QtCore.QThread):
return
try:
destination_dir = os.path.join(base_dir, directory)
os.makedirs(destination_dir)
if not os.path.exists(destination_dir):
os.makedirs(destination_dir)
except EnvironmentError as e:
self.error.emit("Could not create directory {}: {}".format(destination_dir, str(e)))
return
@@ -118,6 +119,7 @@ class ProcessFilesThread(QtCore.QThread):
:param directory: path to the directory.
"""
if os.path.isdir(directory):
return len(next(os.walk(directory))[2])
return 0
count = 0
for _, _, files in os.walk(directory):
count += len(files)
return count