Files
gns3-gui/gns3/modules/dynamips/__init__.py

477 lines
16 KiB
Python

# -*- 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/>.
"""
Dynamips module implementation.
"""
import os
import shutil
import hashlib
from gns3.qt import QtWidgets
from gns3.servers import Servers
from gns3.local_config import LocalConfig
from gns3.image_manager import ImageManager
from gns3.local_server_config import LocalServerConfig
from gns3.gns3_vm import GNS3VM
from ..module import Module
from ..module_error import ModuleError
from .nodes.router import Router
from .nodes.c1700 import C1700
from .nodes.c2600 import C2600
from .nodes.c2691 import C2691
from .nodes.c3600 import C3600
from .nodes.c3725 import C3725
from .nodes.c3745 import C3745
from .nodes.c7200 import C7200
from .nodes.etherswitch_router import EtherSwitchRouter
from .nodes.ethernet_switch import EthernetSwitch
from .nodes.ethernet_hub import EthernetHub
from .nodes.frame_relay_switch import FrameRelaySwitch
from .nodes.atm_switch import ATMSwitch
from .settings import DYNAMIPS_SETTINGS
from .settings import IOS_ROUTER_SETTINGS
from .settings import PLATFORMS_DEFAULT_RAM
from .settings import DEFAULT_IDLEPC
PLATFORM_TO_CLASS = {
"c1700": C1700,
"c2600": C2600,
"c2691": C2691,
"c3600": C3600,
"c3725": C3725,
"c3745": C3745,
"c7200": C7200
}
import logging
log = logging.getLogger(__name__)
class Dynamips(Module):
"""
Dynamips module.
"""
def __init__(self):
super().__init__()
self._settings = {}
self._ios_routers = {}
self._nodes = []
self._ios_images_cache = {}
self.configChangedSlot()
def configChangedSlot(self):
# load the settings and IOS images.
self._loadSettings()
@staticmethod
def getDefaultIdlePC(path):
"""
Return the default IDLE PC for an image if the image
exists or None otherwise
"""
if not os.path.isfile(path):
path = os.path.join(ImageManager.instance().getDirectoryForType("DYNAMIPS"), path)
if not os.path.isfile(path):
return None
try:
md5sum = Dynamips._md5sum(path)
log.debug("Get idlePC for %s. md5sum %s", path, md5sum)
if md5sum in DEFAULT_IDLEPC:
log.debug("IDLEPC found for %s", path)
return DEFAULT_IDLEPC[md5sum]
except OSError:
return None
@staticmethod
def _md5sum(path):
with open(path, "rb") as fd:
m = hashlib.md5()
while True:
data = fd.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
"""
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, DYNAMIPS_SETTINGS)
if not os.path.exists(self._settings["dynamips_path"]):
dynamips_path = shutil.which("dynamips")
if dynamips_path:
self._settings["dynamips_path"] = os.path.abspath(dynamips_path)
else:
self._settings["dynamips_path"] = ""
self._loadIOSRouters()
def _saveSettings(self):
"""
Saves the settings to the persistent settings file.
"""
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
# save some settings to the local server config file
server_settings = {
"allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
"ghost_ios_support": self._settings["ghost_ios_support"],
"sparse_memory_support": self._settings["sparse_memory_support"],
"mmap_support": self._settings["mmap_support"],
}
if self._settings["dynamips_path"]:
server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
def _loadIOSRouters(self):
"""
Load the IOS routers from the persistent settings file.
"""
settings = LocalConfig.instance().settings()
if "routers" in settings.get(self.__class__.__name__, {}):
for router in settings[self.__class__.__name__]["routers"]:
name = router.get("name")
server = router.get("server")
router["image"] = router.get("path", router["image"]) # for backward compatibility before version 1.3
key = "{server}:{name}".format(server=server, name=name)
if key in self._ios_routers or not name or not server:
continue
router_settings = IOS_ROUTER_SETTINGS.copy()
router_settings.update(router)
# for backward compatibility before version 1.4
if "symbol" not in router_settings:
router_settings["symbol"] = router_settings["default_symbol"]
router_settings["symbol"] = router_settings["symbol"][:-11] + ".svg" if router_settings["symbol"].endswith("normal.svg") else router_settings["symbol"]
self._ios_routers[key] = router_settings
def _saveIOSRouters(self):
"""
Saves the IOS routers to the persistent settings file.
"""
self._settings["routers"] = list(self._ios_routers.values())
self._saveSettings()
def addNode(self, node):
"""
Adds a node to this module.
:param node: Node instance
"""
self._nodes.append(node)
def removeNode(self, node):
"""
Removes a node from this module.
:param node: Node instance
"""
if node in self._nodes:
if "ram" in node.settings():
node.server().decreaseAllocatedRAM(node.settings()["ram"])
self._nodes.remove(node)
def iosRouters(self):
"""
Returns IOS routers settings.
:returns: IOS routers settings (dictionary)
"""
return self._ios_routers
def setIOSRouters(self, new_ios_routers):
"""
Sets IOS images settings.
:param new_ios_routers: IOS images settings (dictionary)
"""
self._ios_routers = new_ios_routers.copy()
self._saveIOSRouters()
def settings(self):
"""
Returns the module settings
:returns: module settings (dictionary)
"""
return self._settings
def setSettings(self, settings):
"""
Sets the module settings
:param settings: module settings (dictionary)
"""
self._settings.update(settings)
self._saveSettings()
def createNode(self, node_class, server, project):
"""
Creates a new node.
:param node_class: Node object
:param server: HTTPClient instance
:param project: Project instance
"""
log.info("creating node {}".format(node_class))
# create an instance of the node class
return node_class(self, server, project)
def setupNode(self, node, node_name):
"""
Setups a node.
:param node: Node instance
:param node_name: Node name
"""
log.info("configuring node {}".format(node))
if isinstance(node, Router):
ios_router = None
if node_name:
for ios_key, info in self._ios_routers.items():
if node_name == info["name"]:
ios_router = self._ios_routers[ios_key]
break
if not ios_router:
raise ModuleError("No IOS router for platform {}".format(node.settings()["platform"]))
vm_settings = {}
for setting_name, value in ios_router.items():
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
vm_settings[setting_name] = value
base_name = "R"
if "slot1" in vm_settings and vm_settings["slot1"] == "NM-16ESW":
# must be an EtherSwitch router
base_name = "ESW"
# Older GNS3 versions may have the following invalid settings in the VM template
if "console" in vm_settings:
del vm_settings["console"]
if "sensors" in vm_settings:
del vm_settings["sensors"]
if "power_supplies" in vm_settings:
del vm_settings["power_supplies"]
ram = vm_settings.pop("ram")
image = vm_settings.pop("image", None)
if image is None:
raise ModuleError("No IOS image has been associated with this IOS router")
node.setup(image, ram, additional_settings=vm_settings, base_name=base_name)
else:
node.setup()
def updateImageIdlepc(self, image_path, idlepc):
"""
Updates the Idle-PC for an IOS image.
:param image_path: path to the IOS image
:param idlepc: Idle-PC value
"""
for ios_router in self._ios_routers.values():
if os.path.basename(ios_router["image"]) == image_path:
if ios_router["idlepc"] != idlepc:
ios_router["idlepc"] = idlepc
self._saveIOSRouters()
break
def reset(self):
"""
Resets the module.
"""
log.info("Dynamips module reset")
self._nodes.clear()
def exportConfigs(self, directory):
"""
Exports all configs for all nodes to a directory.
:param directory: destination directory path
"""
for node in self._nodes:
if isinstance(node, Router) and node.initialized():
node.exportConfigToDirectory(directory)
def importConfigs(self, directory):
"""
Imports configs to all nodes from a directory.
:param directory: source directory path
"""
for node in self._nodes:
if isinstance(node, Router) and node.initialized():
node.importConfigFromDirectory(directory)
def findAlternativeIOSImage(self, image, node):
"""
Tries to find an alternative IOS image.
:param image: image name
:param node: requesting Node instance
:return: IOS image (dictionary)
"""
if image in self._ios_images_cache:
return self._ios_images_cache[image]
from gns3.main_window import MainWindow
mainwindow = MainWindow.instance()
ios_routers = self.iosRouters()
candidate_ios_images = {}
alternative_image = {"image": image,
"ram": None,
"idlepc": None}
# find all images with the same platform and local server
for ios_router in ios_routers.values():
if ios_router["platform"] == node.settings()["platform"] and ios_router["server"] == "local":
candidate_ios_images[ios_router["image"]] = ios_router
if candidate_ios_images:
selection, ok = QtWidgets.QInputDialog.getItem(mainwindow,
"IOS image", "IOS image {} could not be found\nPlease select an alternative from your existing images:".format(image),
list(candidate_ios_images.keys()), 0, False)
if ok:
ios_image = candidate_ios_images[selection] # FIXME
alternative_image["image"] = ios_router["image"]
alternative_image["ram"] = ios_router["ram"]
alternative_image["idlepc"] = ios_router["idlepc"]
self._ios_images_cache[image] = alternative_image
return alternative_image
# no registered IOS image is used, let's just ask for an IOS image path
QtWidgets.QMessageBox.critical(mainwindow, "IOS image", "Could not find the {} IOS image \nPlease select a similar IOS image!".format(image))
from .pages.ios_router_preferences_page import IOSRouterPreferencesPage
image_path = IOSRouterPreferencesPage.getIOSImage(mainwindow, None)
if image_path:
alternative_image["image"] = image_path
self._ios_images_cache[image] = alternative_image
return alternative_image
@staticmethod
def getNodeClass(name):
"""
Returns the object with the corresponding name.
:param name: object name
"""
if name in globals():
return globals()[name]
return None
@staticmethod
def classes():
"""
Returns all the node classes supported by this module.
:returns: list of classes
"""
return [C1700, C2600, C2691, C3600, C3725, C3745, C7200, EtherSwitchRouter, EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]
def nodes(self):
"""
Returns all the node data necessary to represent a node
in the nodes view and create a node on the scene.
"""
server = "local"
if not self._settings["use_local_server"]:
if GNS3VM.instance().isRunning():
server = "vm"
else:
# pick up a remote server (round-robin method) #FIXME: review this
remote_server = next(iter(Servers.instance()))
if remote_server:
server = remote_server.url()
nodes = []
for node_class in [EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
nodes.append(
{"class": node_class.__name__,
"name": node_class.symbolName(),
"server": server,
"categories": node_class.categories(),
"symbol": node_class.defaultSymbol()}
)
for ios_router in self._ios_routers.values():
node_class = PLATFORM_TO_CLASS[ios_router["platform"]]
nodes.append(
{"class": node_class.__name__,
"name": ios_router["name"],
"ram": ios_router["ram"],
"server": ios_router["server"],
"symbol": ios_router["symbol"],
"categories": [ios_router["category"]]}
)
return nodes
@staticmethod
def preferencePages():
"""
:returns: QWidget object list
"""
from .pages.dynamips_preferences_page import DynamipsPreferencesPage
from .pages.ios_router_preferences_page import IOSRouterPreferencesPage
return [DynamipsPreferencesPage, IOSRouterPreferencesPage]
@staticmethod
def instance():
"""
Singleton to return only on instance of Dynamips.
:returns: instance of Dynamips
"""
if not hasattr(Dynamips, "_instance"):
Dynamips._instance = Dynamips()
return Dynamips._instance