# -*- 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 . """ 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