diff --git a/gns3/appliance_manager.py b/gns3/appliance_manager.py index a6b42f93..26179bf3 100644 --- a/gns3/appliance_manager.py +++ b/gns3/appliance_manager.py @@ -15,37 +15,100 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from .qt import QtCore from .controller import Controller - +from .utils.server_select import server_select import logging log = logging.getLogger(__name__) -class ApplianceManager: +class ApplianceManager(QtCore.QObject): + + appliances_changed_signal = QtCore.Signal() def __init__(self): + super().__init__() self._appliance_templates = [] + self._appliances = [] self._controller = Controller.instance() - self._controller.connected_signal.connect(self._controllerConnectedSlot) + self._controller.connected_signal.connect(self.refresh) self._controller.disconnected_signal.connect(self._controllerDisconnectedSlot) - self._controllerConnectedSlot() + self.refresh() - def _controllerConnectedSlot(self): + def refresh(self): if self._controller.connected(): self._controller.get("/appliances/templates", self._listApplianceTemplateCallback) + self._controller.get("/appliances", self._listAppliancesCallback) def _controllerDisconnectedSlot(self): self._appliance_templates = [] + self._appliances = [] + self.appliances_changed_signal.emit() def appliance_templates(self): return self._appliance_templates + def appliances(self): + return self._appliances + + def getAppliance(self, appliance_id): + """ + Look for an appliance by appliance ID + """ + for appliance in self._appliances: + if appliance["appliance_id"] == appliance_id: + return appliance + return None + + def _listAppliancesCallback(self, result, error=False, **kwargs): + if error is True: + log.error("Error while getting appliances list: {}".format(result["message"])) + return + self._appliances = result + self.appliances_changed_signal.emit() + def _listApplianceTemplateCallback(self, result, error=False, **kwargs): + if error is True: + log.error("Error while getting appliance templates list: {}".format(result["message"])) + return + self._appliance_templates = result + self.appliances_changed_signal.emit() + + def createNodeFromApplianceId(self, project, appliance_id, x, y): + for appliance in self._appliances: + if appliance["appliance_id"] == appliance_id: + break + if appliance.get("compute_id") is None: + server = server_select(None, node_type=appliance["node_type"]) + self._controller.post("/projects/" + project.id() + "/appliances/" + appliance_id, self._createNodeFromApplianceCallback, { + "compute_id": server.id(), + "x": int(x), + "y": int(y) + }) + else: + self._controller.post("/projects/" + project.id() + "/appliances/" + appliance_id, self._createNodeFromApplianceCallback, { + "x": int(x), + "y": int(y) + }) + + def _createNodeFromApplianceCallback(self, result, error=False, **kwargs): + if error: + if "message" in result: + log.error("Error while creating node: {}".format(result["message"])) + return + + def _controllerDisconnectedSlot(self): + self._appliances = [] + + def appliances(self): + return self._appliances + + def _listAppliancesCallback(self, result, error=False, **kwargs): if error is True: log.error("Error while getting appliance list: {}".format(result["message"])) return - self._appliance_templates = result + self._appliances = result @staticmethod def instance(): diff --git a/gns3/graphics_view.py b/gns3/graphics_view.py index ab185298..46f37d0e 100644 --- a/gns3/graphics_view.py +++ b/gns3/graphics_view.py @@ -24,17 +24,16 @@ import os import sip import pickle -from .qt import QtCore, QtGui, QtSvg, QtNetwork, QtWidgets, qpartial, qslot +from .qt import QtCore, QtGui, QtNetwork, QtWidgets, qpartial, qslot from .items.node_item import NodeItem from .dialogs.node_properties_dialog import NodePropertiesDialog from .link import Link from .node import Node from .modules import MODULES -from .modules.builtin.cloud import Cloud from .modules.module_error import ModuleError from .settings import GRAPHICS_VIEW_SETTINGS from .topology import Topology -from .ports.port import Port +from .appliance_manager import ApplianceManager from .dialogs.style_editor_dialog import StyleEditorDialog from .dialogs.text_editor_dialog import TextEditorDialog from .dialogs.symbol_selection_dialog import SymbolSelectionDialog @@ -44,7 +43,6 @@ from .dialogs.file_editor_dialog import FileEditorDialog from .local_config import LocalConfig from .progress import Progress from .utils.server_select import server_select -from .utils.normalize_filename import normalize_filename from .compute_manager import ComputeManager # link items @@ -613,7 +611,8 @@ class GraphicsView(QtWidgets.QGraphicsView): """ # check if what is dragged is handled by this view - if event.mimeData().hasFormat("application/x-gns3-node") or event.mimeData().hasFormat("text/uri-list"): + if event.mimeData().hasFormat("text/uri-list") \ + or event.mimeData().hasFormat("application/x-gns3-appliance"): event.acceptProposedAction() event.accept() else: @@ -627,10 +626,8 @@ class GraphicsView(QtWidgets.QGraphicsView): """ # check if what has been dropped is handled by this view - if event.mimeData().hasFormat("application/x-gns3-node"): - data = event.mimeData().data("application/x-gns3-node") - # load the pickled node data - node_data = pickle.loads(data) + if event.mimeData().hasFormat("application/x-gns3-appliance"): + appliance_id = event.mimeData().data("application/x-gns3-appliance").data().decode() event.setDropAction(QtCore.Qt.CopyAction) event.accept() if event.keyboardModifiers() == QtCore.Qt.ShiftModifier: @@ -641,12 +638,9 @@ class GraphicsView(QtWidgets.QGraphicsView): for node_number in range(integer): x = event.pos().x() - (150 / 2) + (node_number % max_nodes_per_line) * offset y = event.pos().y() - (70 / 2) + (node_number // max_nodes_per_line) * offset - node_item = self.createNode(node_data, QtCore.QPoint(x, y)) - if node_item is None: - # stop if there is any error - break + self.createNodeFromApplianceId(appliance_id, QtCore.QPoint(x, y)) else: - self.createNode(node_data, event.pos()) + self.createNodeFromApplianceId(appliance_id, event.pos()) elif event.mimeData().hasFormat("text/uri-list") and event.mimeData().hasUrls(): # This should not arrive but we received bug report with it... if len(event.mimeData().urls()) == 0: @@ -1434,38 +1428,12 @@ class GraphicsView(QtWidgets.QGraphicsView): raise ModuleError("Please select a server") return server - def createNode(self, node_data, pos): + def createNodeFromApplianceId(self, appliance_id, pos): """ - Creates a new node on the scene. - - :param node_data: node data to create a new node - :param pos: position of the drop event - - :returns: NodeItem instance + Ask the server to create a node using this appliance """ - try: - node_module = None - for module in MODULES: - instance = module.instance() - node_class = module.getNodeClass(node_data["class"]) - if node_class in instance.classes(): - node_module = instance - break - - if not node_module: - raise ModuleError("Could not find any module for {}".format(node_class)) - - node = node_module.instantiateNode(node_class, self.allocateCompute(node_data, instance), self._topology.project()) - # If no server is available a ValueError is raised - except (ModuleError, ValueError) as e: - QtWidgets.QMessageBox.critical(self, "Node creation", "{}".format(e)) - return - pos = self.mapToScene(pos) - node_item = self.createNodeItem(node, node_data["symbol"], pos.x(), pos.y()) - node.setGraphics(node_item) - node_module.createNode(node, node_data["name"]) - return node_item + ApplianceManager().instance().createNodeFromApplianceId(self._topology.project(), appliance_id, pos.x(), pos.y()) def createNodeItem(self, node, symbol, x, y): node.setSymbol(symbol) diff --git a/gns3/modules/builtin/__init__.py b/gns3/modules/builtin/__init__.py index 2f690dea..4e87acb8 100644 --- a/gns3/modules/builtin/__init__.py +++ b/gns3/modules/builtin/__init__.py @@ -33,7 +33,6 @@ from .atm_switch import ATMSwitch from .settings import ( BUILTIN_SETTINGS, CLOUD_SETTINGS, - NAT_SETTINGS, ETHERNET_HUB_SETTINGS, ETHERNET_SWITCH_SETTINGS ) @@ -227,40 +226,6 @@ class Builtin(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - if isinstance(node, Cloud): - for key, info in self._cloud_nodes.items(): - if node_name == info["name"]: - default_name_format = info["default_name_format"].replace('{name}', node_name) - node.create(ports=info["ports_mapping"], default_name_format=default_name_format) - return - elif isinstance(node, Nat): - for key, info in self._nat_nodes.items(): - if node_name == info["name"]: - default_name_format = info["default_name_format"].replace('{name}', node_name) - node.create(default_name_format=default_name_format) - return - elif isinstance(node, EthernetHub): - for key, info in self._ethernet_hubs.items(): - if node_name == info["name"]: - default_name_format = info["default_name_format"].replace('{name}', node_name) - node.create(ports=info["ports_mapping"], default_name_format=default_name_format) - return - elif isinstance(node, EthernetSwitch): - for key, info in self._ethernet_switches.items(): - if node_name == info["name"]: - default_name_format = info["default_name_format"].replace('{name}', node_name) - node.create(ports=info["ports_mapping"], default_name_format=default_name_format) - return - node.create() - @staticmethod def findAlternativeInterface(node, missing_interface): @@ -328,17 +293,6 @@ class Builtin(Module): """ nodes = [] - for node_class in Builtin.classes(): - nodes.append( - {"class": node_class.__name__, - "name": node_class.symbolName(), - "categories": node_class.categories(), - "symbol": node_class.defaultSymbol(), - "builtin": True, - "node_type": node_class.URL_PREFIX - } - ) - # add custom cloud node templates for cloud_node in self._cloud_nodes.values(): nodes.append( @@ -369,9 +323,8 @@ class Builtin(Module): "server": switch["server"], "symbol": switch["symbol"], "categories": [switch["category"]] - } + } ) - return nodes @staticmethod diff --git a/gns3/modules/docker/__init__.py b/gns3/modules/docker/__init__.py index 28f92702..ddba2ff8 100644 --- a/gns3/modules/docker/__init__.py +++ b/gns3/modules/docker/__init__.py @@ -140,55 +140,6 @@ class Docker(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - image = None - if node_name: - for image_key, info in self._docker_containers.items(): - if node_name == info["name"]: - image = image_key - if not image: - selected_images = [] - for image, info in self._docker_containers.items(): - if info["server"] == node.compute().id(): - selected_images.append(image) - - if not selected_images: - raise ModuleError("No Docker VM on server {}".format( - node.server().url())) - elif len(selected_images) > 1: - from gns3.main_window import MainWindow - mainwindow = MainWindow.instance() - - (selection, ok) = QtWidgets.QInputDialog.getItem( - mainwindow, "Docker Image", "Please choose an image", - selected_images, 0, False) - if ok: - image = selection - else: - raise ModuleError("Please select a Docker Image") - else: - image = selected_images[0] - - image_settings = {} - for setting_name, value in self._docker_containers[image].items(): - if setting_name in node.settings() and value != "" and value is not None: - if setting_name not in ['name', 'image']: - image_settings[setting_name] = value - - default_name_format = DOCKER_CONTAINER_SETTINGS["default_name_format"] - if self._docker_containers[image]["default_name_format"]: - default_name_format = self._docker_containers[image]["default_name_format"] - - image = self._docker_containers[image]["image"] - node.create(image, base_name=node_name, additional_settings=image_settings, default_name_format=default_name_format) - def reset(self): """Resets the servers.""" self._nodes.clear() diff --git a/gns3/modules/dynamips/__init__.py b/gns3/modules/dynamips/__init__.py index d6f547e9..c24a14e0 100644 --- a/gns3/modules/dynamips/__init__.py +++ b/gns3/modules/dynamips/__init__.py @@ -248,50 +248,6 @@ class Dynamips(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - 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 - - default_name_format = IOS_ROUTER_SETTINGS["default_name_format"] - if ios_router["default_name_format"]: - default_name_format = ios_router["default_name_format"] - - # 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.create(image, ram, additional_settings=vm_settings, default_name_format=default_name_format) - else: - node.create() - def updateImageIdlepc(self, image_path, idlepc): """ Updates the Idle-PC for an IOS image. diff --git a/gns3/modules/iou/__init__.py b/gns3/modules/iou/__init__.py index bd901e62..c36c50fa 100644 --- a/gns3/modules/iou/__init__.py +++ b/gns3/modules/iou/__init__.py @@ -177,62 +177,6 @@ class IOU(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - iouimage = None - if node_name: - for iou_key, info in self._iou_devices.items(): - if node_name == info["name"]: - iouimage = iou_key - - if not iouimage: - selected_images = [] - for image, info in self._iou_devices.items(): - if info["server"] == node.compute().id(): - selected_images.append(image) - - if not selected_images: - raise ModuleError("No IOU image found for this device") - elif len(selected_images) > 1: - - from gns3.main_window import MainWindow - mainwindow = MainWindow.instance() - - (selection, ok) = QtWidgets.QInputDialog.getItem(mainwindow, "IOU image", "Please choose an image", selected_images, 0, False) - if ok: - iouimage = selection - else: - raise ModuleError("Please select an IOU image") - - else: - iouimage = selected_images[0] - - vm_settings = {} - for setting_name, value in self._iou_devices[iouimage].items(): - if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None: - vm_settings[setting_name] = value - - default_name_format = IOU_DEVICE_SETTINGS["default_name_format"] - if self._iou_devices[iouimage]["default_name_format"]: - default_name_format = self._iou_devices[iouimage]["default_name_format"] - - if vm_settings["use_default_iou_values"]: - del vm_settings["ram"] - del vm_settings["nvram"] - - if "console" in vm_settings: - # Older GNS3 versions may have a console setting in the VM template - del vm_settings["console"] - - iou_path = vm_settings.pop("path") - node.create(iou_path, additional_settings=vm_settings, default_name_format=default_name_format) - def reset(self): """ Resets the servers. diff --git a/gns3/modules/iou/iou_device.py b/gns3/modules/iou/iou_device.py index a4bf6bde..3d8c3344 100644 --- a/gns3/modules/iou/iou_device.py +++ b/gns3/modules/iou/iou_device.py @@ -61,19 +61,6 @@ class IOUDevice(Node): self.settings().update(iou_device_settings) - def create(self, iou_path, name=None, node_id=None, additional_settings={}, default_name_format="IOU{0}"): - """ - Creates this IOU device. - - :param iou_path: path to an IOU image - :param name: optional name - :param console: optional TCP console port - """ - - params = {"path": iou_path} - params.update(additional_settings) - self._create(name, node_id, params, default_name_format) - def _createCallback(self, result): """ Callback for create. diff --git a/gns3/modules/qemu/__init__.py b/gns3/modules/qemu/__init__.py index fb98af24..991279d8 100644 --- a/gns3/modules/qemu/__init__.py +++ b/gns3/modules/qemu/__init__.py @@ -177,67 +177,6 @@ class Qemu(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - vm = None - if node_name: - for vm_key, info in self._qemu_vms.items(): - if node_name == info["name"]: - vm = vm_key - - if not vm: - selected_vms = [] - for vm, info in self._qemu_vms.items(): - if info["server"] == node.compute().id(): - selected_vms.append(vm) - - if not selected_vms: - raise ModuleError("No QEMU VM on server {}".format(node.server().host())) - elif len(selected_vms) > 1: - - from gns3.main_window import MainWindow - mainwindow = MainWindow.instance() - - (selection, ok) = QtWidgets.QInputDialog.getItem(mainwindow, "QEMU VM", "Please choose a VM", selected_vms, 0, False) - if ok: - vm = selection - else: - raise ModuleError("Please select a QEMU VM") - else: - vm = selected_vms[0] - - vm_settings = {} - for setting_name, value in self._qemu_vms[vm].items(): - if setting_name in node.settings() and value != "" and value is not None: - vm_settings[setting_name] = value - - qemu_path = vm_settings.pop("qemu_path") - name = vm_settings.pop("name") - port_name_format = self._qemu_vms[vm]["port_name_format"] - port_segment_size = self._qemu_vms[vm]["port_segment_size"] - first_port_name = self._qemu_vms[vm]["first_port_name"] - - default_name_format = QEMU_VM_SETTINGS["default_name_format"] - if self._qemu_vms[vm]["default_name_format"]: - default_name_format = self._qemu_vms[vm]["default_name_format"] - if self._qemu_vms[vm]["linked_base"]: - name = default_name_format.replace('{name}', name) - - node.create(qemu_path, - name=name, - port_name_format=port_name_format, - port_segment_size=port_segment_size, - first_port_name=first_port_name, - linked_clone=self._qemu_vms[vm]["linked_base"], - additional_settings=vm_settings, - default_name_format=default_name_format) - def reset(self): """ Resets the servers. diff --git a/gns3/modules/virtualbox/__init__.py b/gns3/modules/virtualbox/__init__.py index 58b95db2..77598cf9 100644 --- a/gns3/modules/virtualbox/__init__.py +++ b/gns3/modules/virtualbox/__init__.py @@ -219,68 +219,6 @@ class VirtualBox(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - vm = None - if node_name: - for vm_key, info in self._virtualbox_vms.items(): - if node_name == info["name"]: - vm = vm_key - - if not vm: - selected_vms = [] - for vm, info in self._virtualbox_vms.items(): - if info["server"] == node.compute().id(): - selected_vms.append(vm) - - if not selected_vms: - raise ModuleError("No VirtualBox VM on server {}".format(node.server().url())) - elif len(selected_vms) > 1: - - from gns3.main_window import MainWindow - mainwindow = MainWindow.instance() - - (selection, ok) = QtWidgets.QInputDialog.getItem(mainwindow, "VirtualBox VM", "Please choose a VM", selected_vms, 0, False) - if ok: - vm = selection - else: - raise ModuleError("Please select a VirtualBox VM") - - else: - vm = selected_vms[0] - - vm_settings = {} - for setting_name, value in self._virtualbox_vms[vm].items(): - if setting_name != "name" and setting_name in node.settings() and value != "" and value is not None: - vm_settings[setting_name] = value - - name = self._virtualbox_vms[vm]["name"] - vmname = self._virtualbox_vms[vm]["vmname"] - port_name_format = self._virtualbox_vms[vm]["port_name_format"] - port_segment_size = self._virtualbox_vms[vm]["port_segment_size"] - first_port_name = self._virtualbox_vms[vm]["first_port_name"] - - default_name_format = VBOX_VM_SETTINGS["default_name_format"] - if self._virtualbox_vms[vm]["default_name_format"]: - default_name_format = self._virtualbox_vms[vm]["default_name_format"] - if self._virtualbox_vms[vm]["linked_base"]: - name = default_name_format.replace('{name}', name) - - node.create(vmname, - name=name, - port_name_format=port_name_format, - port_segment_size=port_segment_size, - first_port_name=first_port_name, - linked_clone=self._virtualbox_vms[vm]["linked_base"], - additional_settings=vm_settings, - default_name_format=default_name_format) - def reset(self): """ Resets the module. diff --git a/gns3/modules/vmware/__init__.py b/gns3/modules/vmware/__init__.py index 5c665cc6..8df49f47 100644 --- a/gns3/modules/vmware/__init__.py +++ b/gns3/modules/vmware/__init__.py @@ -286,68 +286,6 @@ class VMware(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - vm = None - if node_name: - for vm_key, info in self._vmware_vms.items(): - if node_name == info["name"]: - vm = vm_key - - if not vm: - selected_vms = [] - for vm, info in self._vmware_vms.items(): - if info["server"] == node.compute().id(): - selected_vms.append(vm) - - if not selected_vms: - raise ModuleError("No VMware VM on server {}".format(node.server().url())) - elif len(selected_vms) > 1: - - from gns3.main_window import MainWindow - mainwindow = MainWindow.instance() - - (selection, ok) = QtWidgets.QInputDialog.getItem(mainwindow, "VMware VM", "Please choose a VM", selected_vms, 0, False) - if ok: - vm = selection - else: - raise ModuleError("Please select a VMware VM") - else: - vm = selected_vms[0] - - linked_base = self._vmware_vms[vm]["linked_base"] - vm_settings = {} - for setting_name, value in self._vmware_vms[vm].items(): - if setting_name in node.settings(): - vm_settings[setting_name] = value - - vmx_path = vm_settings.pop("vmx_path") - name = vm_settings.pop("name") - port_name_format = self._vmware_vms[vm]["port_name_format"] - port_segment_size = self._vmware_vms[vm]["port_segment_size"] - first_port_name = self._vmware_vms[vm]["first_port_name"] - - default_name_format = VMWARE_VM_SETTINGS["default_name_format"] - if self._vmware_vms[vm]["default_name_format"]: - default_name_format = self._vmware_vms[vm]["default_name_format"] - if linked_base: - name = default_name_format.replace('{name}', name) - - node.create(vmx_path, - name=name, - port_name_format=port_name_format, - port_segment_size=port_segment_size, - first_port_name=first_port_name, - linked_clone=linked_base, - additional_settings=vm_settings, - default_name_format=default_name_format) - def reset(self): """ Resets the module. diff --git a/gns3/modules/vpcs/__init__.py b/gns3/modules/vpcs/__init__.py index 9a142128..4a664926 100644 --- a/gns3/modules/vpcs/__init__.py +++ b/gns3/modules/vpcs/__init__.py @@ -160,33 +160,6 @@ class VPCS(Module): # create an instance of the node class return node_class(self, server, project) - def createNode(self, node, node_name): - """ - Creates a node. - - :param node: Node instance - :param node_name: Node name - """ - - log.info("creating node {}".format(node)) - - vm_settings = { - "base_script_file": "vpcs_base_config.txt" - } - if node_name: - for node_key, info in self._vpcs_nodes.items(): - if node_name == info["name"]: - - for setting_name, value in self._vpcs_nodes[node_key].items(): - - if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None: - vm_settings[setting_name] = value - - node.create(default_name_format=info["default_name_format"], additional_settings=vm_settings) - return - - node.create(additional_settings=vm_settings) - def reset(self): """ Resets the module. @@ -194,18 +167,6 @@ class VPCS(Module): self._nodes.clear() - @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 getNodeType(name, platform=None): if name == "vpcs": @@ -262,16 +223,6 @@ class VPCS(Module): "builtin": True } ) - - for node in self._vpcs_nodes.values(): - nodes.append( - {"class": VPCSNode.__name__, - "name": node["name"], - "server": node["server"], - "symbol": node["symbol"], - "categories": [node["category"]] - } - ) return nodes @staticmethod diff --git a/gns3/node.py b/gns3/node.py index de930d41..2596da8c 100644 --- a/gns3/node.py +++ b/gns3/node.py @@ -69,9 +69,6 @@ class Node(BaseNode): with open(context["path"], "wb+") as f: f.write(raw_body) - def creator(self): - return self._creator - def settings(self): return self._settings @@ -220,66 +217,6 @@ class Node(BaseNode): return body - def _create(self, name=None, node_id=None, params=None, default_name_format="Node{0}", timeout=120): - """ - Create the node on the controller - """ - - self._creator = True - if params is None: - params = {} - - if "symbol" in self._settings: - params["symbol"] = self._settings["symbol"] - params["x"] = self._settings["x"] - params["y"] = self._settings["y"] - if "label" in self._settings: - params["label"] = self._settings["label"] - - if not name: - # use the default name format if no name is provided - name = default_name_format - - params["name"] = name - if node_id is not None: - self._node_id = node_id - - body = self._prepareBody(params) - self.controllerHttpPost("/nodes", self.createNodeCallback, body=body, timeout=timeout) - - def createNodeCallback(self, result, error=False, **kwargs): - """ - Callback for create. - - :param result: server response - :param error: indicates an error (boolean) - :returns: Boolean success or not - """ - if error: - self.server_error_signal.emit(self.id(), "Error while setting up node: {}".format(result["message"])) - self.deleted_signal.emit() - self._module.removeNode(self) - return False - - result = self._parseResponse(result) - self._created = True - self._createCallback(result) - - if self._loading: - self.loaded_signal.emit() - else: - self.setInitialized(True) - log.info("Node instance {} has been created".format(self.name())) - self.created_signal.emit(self.id()) - self._module.addNode(self) - - def _createCallback(self, result): - """ - Create callback compatible with the compute api. - """ - - pass - def _update(self, params, timeout=60): """ Update the node on the controller @@ -380,6 +317,38 @@ class Node(BaseNode): new_port.setStatus(self.status()) self._ports.append(new_port) + def createNodeCallback(self, result, error=False, **kwargs): + """ + Callback for create. + + :param result: server response + :param error: indicates an error (boolean) + :returns: Boolean success or not + """ + if error: + self.server_error_signal.emit(self.id(), "Error while setting up node: {}".format(result["message"])) + self.deleted_signal.emit() + self._module.removeNode(self) + return False + + result = self._parseResponse(result) + self._created = True + self._createCallback(result) + + if self._loading: + self.loaded_signal.emit() + else: + self.setInitialized(True) + log.info("Node instance {} has been created".format(self.name())) + self.created_signal.emit(self.id()) + self._module.addNode(self) + + def _createCallback(self, result): + """ + Create callback compatible with the compute api. + """ + pass + def _updateCallback(self, result): """ Update callback compatible with the compute api. diff --git a/gns3/nodes_view.py b/gns3/nodes_view.py index 32df0499..188cca41 100644 --- a/gns3/nodes_view.py +++ b/gns3/nodes_view.py @@ -21,9 +21,9 @@ on the QGraphics scene. """ import tempfile -import pickle import json import sip +import os from .qt import QtCore, QtGui, QtWidgets, qpartial from .modules import MODULES @@ -34,6 +34,14 @@ from .dialogs.configuration_dialog import ConfigurationDialog from .local_config import LocalConfig +CATEGORY_TO_ID = { + "firewall": 3, + "guest": 2, + "switch": 1, + "multilayer_switch": 1, + "router": 0 +} + CATEGORY_TO_ID = { "firewall": 3, "guest": 2, @@ -62,7 +70,7 @@ class NodesView(QtWidgets.QTreeWidget): # enables the possibility to drag items. self.setDragEnabled(True) - Controller.instance().connected_signal.connect(self.refresh) + ApplianceManager.instance().appliances_changed_signal.connect(self.refresh) def setCurrentSearch(self, search): self._current_search = search @@ -94,27 +102,40 @@ class NodesView(QtWidgets.QTreeWidget): display_appliances = set() - if self._show_installed_appliances: - for module in MODULES: - for node in module.instance().nodes(): - if category is not None and category not in node["categories"]: - continue - if search != "" and search not in node["name"].lower(): - continue + for appliance in ApplianceManager.instance().appliances(): + if category is not None and category != CATEGORY_TO_ID[appliance["category"]]: + continue + if search != "" and search.lower() not in appliance["name"].lower(): + continue + display_appliances.add(appliance["name"]) + item = QtWidgets.QTreeWidgetItem(self) + item.setText(0, appliance["name"]) + item.setData(0, QtCore.Qt.UserRole, appliance) + item.setData(1, QtCore.Qt.UserRole, "node") + item.setSizeHint(0, QtCore.QSize(32, 32)) + Controller.instance().getSymbolIcon(appliance["symbol"], qpartial(self._setItemIcon, item)) - display_appliances.add(node["name"]) - item = QtWidgets.QTreeWidgetItem(self) - item.setText(0, node["name"]) - item.setData(0, QtCore.Qt.UserRole, node) - item.setData(1, QtCore.Qt.UserRole, "node") - item.setSizeHint(0, QtCore.QSize(32, 32)) - Controller.instance().getSymbolIcon(node["symbol"], qpartial(self._setItemIcon, item)) + if self._show_installed_appliances: + for appliance in ApplianceManager.instance().appliances(): + if category is not None and category != CATEGORY_TO_ID[appliance["category"]]: + continue + if search != "" and search.lower() not in appliance["name"].lower(): + continue + if appliance["name"] in display_appliances: + continue + + item = QtWidgets.QTreeWidgetItem(self) + item.setText(0, appliance["name"]) + item.setData(0, QtCore.Qt.UserRole, appliance["appliance_id"]) + item.setData(1, QtCore.Qt.UserRole, "appliance") + item.setSizeHint(0, QtCore.QSize(32, 32)) + Controller.instance().getSymbolIcon(appliance.get("symbol"), qpartial(self._setItemIcon, item), fallback=":/symbols/" + appliance["category"] + ".svg") if self._show_available_appliances: for appliance in ApplianceManager.instance().appliance_templates(): if category is not None and category != CATEGORY_TO_ID[appliance["category"]]: continue - if search != "" and search not in appliance["name"].lower(): + if search != "" and search.lower() not in appliance["name"].lower(): continue if appliance["name"] in display_appliances: continue @@ -123,7 +144,7 @@ class NodesView(QtWidgets.QTreeWidget): item.setForeground(0, QtGui.QBrush(QtGui.QColor("gray"))) item.setText(0, appliance["name"]) item.setData(0, QtCore.Qt.UserRole, appliance) - item.setData(1, QtCore.Qt.UserRole, "appliance") + item.setData(1, QtCore.Qt.UserRole, "appliance_template") item.setSizeHint(0, QtCore.QSize(32, 32)) Controller.instance().getSymbolIcon(appliance.get("symbol"), qpartial(self._setItemIcon, item), fallback=":/symbols/" + appliance["category"] + ".svg") @@ -160,7 +181,7 @@ class NodesView(QtWidgets.QTreeWidget): item = self.currentItem() # retrieve the node class from the item data - if item.data(1, QtCore.Qt.UserRole) == "appliance": + if item.data(1, QtCore.Qt.UserRole) == "appliance_template": f = tempfile.NamedTemporaryFile(mode="w+", suffix=".gns3a", delete=False) json.dump(item.data(0, QtCore.Qt.UserRole), f) f.close() @@ -168,13 +189,12 @@ class NodesView(QtWidgets.QTreeWidget): return icon = item.icon(0) - node = item.data(0, QtCore.Qt.UserRole) mimedata = QtCore.QMimeData() - # pickle the node class, set the Mime type and data - # and start dragging the item. - data = pickle.dumps(node) - mimedata.setData("application/x-gns3-node", data) + if item.data(1, QtCore.Qt.UserRole) == "appliance": + appliance_id = item.data(0, QtCore.Qt.UserRole) + mimedata.setData("application/x-gns3-appliance", appliance_id.encode()) + drag = QtGui.QDrag(self) drag.setMimeData(mimedata) drag.setPixmap(icon.pixmap(self.iconSize())) @@ -184,17 +204,17 @@ class NodesView(QtWidgets.QTreeWidget): def _showContextualMenu(self): item = self.currentItem() - node = item.data(0, QtCore.Qt.UserRole) - if "class" not in node: + node = ApplianceManager.instance().getAppliance(item.data(0, QtCore.Qt.UserRole)) + if not node: return for module in MODULES: - node_class = module.getNodeClass(node["class"]) + node_class = module.getNodeType(node["node_type"]) if node_class: break # We can not edit stuff like EthernetSwitch # or without config template like VPCS - if "builtin" not in node and hasattr(module, "vmConfigurationPage"): + if not node["builtin"] and hasattr(module, "vmConfigurationPage"): for vm_key, vm in module.instance().VMs().items(): if vm["name"] == node["name"]: break diff --git a/gns3/project.py b/gns3/project.py index 8bb8d15b..4bd85612 100644 --- a/gns3/project.py +++ b/gns3/project.py @@ -16,8 +16,6 @@ # along with this program. If not, see . import os -import sys -import traceback from .qt import QtCore, qpartial, QtWidgets, QtNetwork from gns3.controller import Controller @@ -25,7 +23,7 @@ from gns3.compute_manager import ComputeManager from gns3.topology import Topology from gns3.local_config import LocalConfig from gns3.settings import GRAPHICS_VIEW_SETTINGS - +from gns3.appliance_manager import ApplianceManager import logging log = logging.getLogger(__name__) @@ -516,5 +514,6 @@ class Project(QtCore.QObject): cm.computeDataReceivedCallback(result["event"]) elif result["action"] == "settings.updated": LocalConfig.instance().refreshConfigFromController() + ApplianceManager.instance().refresh() elif result["action"] == "ping": pass diff --git a/tests/modules/docker/test_docker_vm.py b/tests/modules/docker/test_docker_vm.py index 81ffc15c..8640d956 100644 --- a/tests/modules/docker/test_docker_vm.py +++ b/tests/modules/docker/test_docker_vm.py @@ -26,27 +26,6 @@ def test_docker_vm_init(local_server, project): vm = DockerVM(Docker(), local_server, project) -def test_docker_vm_create(project, local_server): - - docker_vm = DockerVM(Docker(), local_server, project) - with patch('gns3.project.Project.post') as mock: - docker_vm.create("ubuntu", base_name="ubuntu") - mock.assert_called_with("/nodes", - docker_vm.createNodeCallback, - body={ - "node_id": docker_vm._node_id, - "compute_id": "local", - "node_type": "docker", - "properties": { - "adapters": 1, - "image": "ubuntu", - }, - "name": "ubuntu-{0}" - }, - context={}, - timeout=None) - - def test_createCallback(project, local_server): docker_vm = DockerVM(Docker(), local_server, project) diff --git a/tests/modules/iou/test_iou_device.py b/tests/modules/iou/test_iou_device.py index be96750d..f57e6310 100644 --- a/tests/modules/iou/test_iou_device.py +++ b/tests/modules/iou/test_iou_device.py @@ -40,72 +40,6 @@ def test_iou_device_init(local_server, project): iou_device = IOUDevice(None, local_server, project) -def test_iou_device_create(iou_device, project): - - with patch('gns3.project.Project.post') as mock: - iou_device.create("/tmp/iou.bin", name="PC 1") - mock.assert_called_with("/nodes", - iou_device.createNodeCallback, - body={ - 'node_id': iou_device._node_id, - 'name': 'PC 1', - 'properties': { - 'path': '/tmp/iou.bin', - }, - 'node_type': 'iou', - 'compute_id': 'local' - }, - context={}, - timeout=120) - - # Callback - params = { - "console": 2000, - "name": "PC1", - "project_id": "f91bd115-3b5c-402e-b411-e5919723cf4b", - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c", - "path": "iou.bin", - "md5sum": "0cc175b9c0f1b6a831c399e269772661" - } - iou_device.createNodeCallback(params) - - assert iou_device.node_id() == "aec7a00c-e71c-45a6-8c04-29e40732883c" - - -def test_iou_device_setup_with_uuid(iou_device, project): - """ - If we have an ID that mean the VM already exits and we should not send startup_script - """ - - with patch('gns3.project.Project.post') as mock: - iou_device.create("/tmp/iou.bin", name="PC 1", node_id="aec7a00c-e71c-45a6-8c04-29e40732883c") - mock.assert_called_with("/nodes", - iou_device.createNodeCallback, - body={'name': 'PC 1', - 'properties': { - 'path': '/tmp/iou.bin', - }, - 'node_type': 'iou', - 'node_id': 'aec7a00c-e71c-45a6-8c04-29e40732883c', - 'compute_id': 'local' - }, - context={}, - timeout=120) - - # Callback - params = { - "console": 2000, - "name": "PC1", - "project_id": "f91bd115-3b5c-402e-b411-e5919723cf4b", - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c", - "path": "iou.bin", - "md5sum": "0cc175b9c0f1b6a831c399e269772661" - } - iou_device.createNodeCallback(params) - - assert iou_device.node_id() == "aec7a00c-e71c-45a6-8c04-29e40732883c" - - def test_update(iou_device): new_settings = { diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py index 5b75d27b..3509180f 100644 --- a/tests/modules/qemu/test_qemu_vm.py +++ b/tests/modules/qemu/test_qemu_vm.py @@ -27,79 +27,6 @@ def test_qemu_vm_init(local_server, project): vm = QemuVM(None, local_server, project) -def test_qemu_vm_create(qemu_vm, project): - - with patch('gns3.project.Project.post') as mock: - qemu_vm.create("/bin/fake", name="VMNAME") - mock.assert_called_with("/nodes", - qemu_vm.createNodeCallback, - body={ - 'node_id': qemu_vm._node_id, - 'name': 'VMNAME', - 'properties': { - 'linked_clone': True, - 'qemu_path': '/bin/fake' - }, - 'port_name_format': 'Ethernet{0}', - 'first_port_name': '', - 'port_segment_size': 0, - 'compute_id': 'local', - 'node_type': 'qemu' - }, - context={}, - timeout=120) - - # Callback - params = { - "name": "QEMU1", - "vmname": "VMNAME", - "project_id": "f91bd115-3b5c-402e-b411-e5919723cf4b", - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c", - "node_directory": "/tmp/test", - "hda_disk_image": "0cc175b9c0f1b6a831c399e269772661" - } - qemu_vm.createNodeCallback(params) - assert qemu_vm.node_id() == "aec7a00c-e71c-45a6-8c04-29e40732883c" - assert qemu_vm.nodeDir() == "/tmp/test" - - -def test_qemu_vm_setup_command_line(qemu_vm, project): - - with patch('gns3.project.Project.post') as mock: - qemu_vm.create("/bin/fake", name="VMNAME") - mock.assert_called_with("/nodes", - qemu_vm.createNodeCallback, - body={ - 'node_id': qemu_vm._node_id, - 'name': 'VMNAME', - 'properties': { - 'linked_clone': True, - 'qemu_path': '/bin/fake' - }, - 'port_name_format': 'Ethernet{0}', - 'first_port_name': '', - 'port_segment_size': 0, - 'compute_id': 'local', - 'node_type': 'qemu' - }, - context={}, - timeout=120) - - # Callback - params = { - "name": "QEMU1", - "vmname": "VMNAME", - "project_id": "f91bd115-3b5c-402e-b411-e5919723cf4b", - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c", - "vm_directory": "/tmp/test", - "hda_disk_image": "0cc175b9c0f1b6a831c399e269772661", - "command_line": "/bin/fake" - } - qemu_vm.createNodeCallback(params) - assert qemu_vm.node_id() == "aec7a00c-e71c-45a6-8c04-29e40732883c" - assert qemu_vm.commandLine() == "/bin/fake" - - def test_update(qemu_vm): new_settings = { diff --git a/tests/modules/virtualbox/test_virtualbox_vm.py b/tests/modules/virtualbox/test_virtualbox_vm.py index 9a5316a5..e00e77aa 100644 --- a/tests/modules/virtualbox/test_virtualbox_vm.py +++ b/tests/modules/virtualbox/test_virtualbox_vm.py @@ -27,41 +27,6 @@ def test_virtualbox_vm_init(local_server, project): vm = VirtualBoxVM(None, local_server, project) -def test_virtualbox_vm_create(virtualbox_vm, project): - - with patch('gns3.project.Project.post') as mock: - virtualbox_vm.create("VMNAME") - mock.assert_called_with("/nodes", - virtualbox_vm.createNodeCallback, - body={ - 'node_id': virtualbox_vm._node_id, - 'name': 'VMNAME', - 'compute_id': 'local', - 'node_type': 'virtualbox', - 'properties': { - 'linked_clone': False, - 'vmname': 'VMNAME' - }, - 'port_name_format': 'Ethernet{0}', - 'port_segment_size': 0, - 'first_port_name': '' - }, - context={}, - timeout=120) - - # Callback - params = { - "name": "VBOX1", - "vmname": "VMNAME", - "linked_clone": False, - "adapters": 0, - "project_id": "f91bd115-3b5c-402e-b411-e5919723cf4b", - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c" - } - virtualbox_vm.createNodeCallback(params) - assert virtualbox_vm.node_id() == "aec7a00c-e71c-45a6-8c04-29e40732883c" - - def test_update(virtualbox_vm): new_settings = { diff --git a/tests/modules/vpcs/test_vpcs_device.py b/tests/modules/vpcs/test_vpcs_device.py index 6ea1cc0e..bd1eabf2 100644 --- a/tests/modules/vpcs/test_vpcs_device.py +++ b/tests/modules/vpcs/test_vpcs_device.py @@ -29,68 +29,6 @@ def test_vpcs_device_init(local_server, project): vpcs_device = VPCSNode(None, local_server, project) -def test_vpcs_device_create(vpcs_device, project, local_server): - - with patch('gns3.base_node.BaseNode.controllerHttpPost') as mock: - vpcs_device.create(name="PC 1", additional_settings={}) - assert mock.called - args, kwargs = mock.call_args - assert args[0] == "/nodes" - assert kwargs["body"] == { - "node_id": vpcs_device._node_id, - "name": "PC 1", - "compute_id": local_server.id(), - "node_type": "vpcs", - "properties": { - } - } - - # Callback - params = { - "console": 2000, - "name": "PC1", - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c", - "project_id": "f91bd115-3b5c-402e-b411-e5919723cf4b", - "properties": { - } - } - vpcs_device.createNodeCallback(params) - - assert vpcs_device.node_id() == "aec7a00c-e71c-45a6-8c04-29e40732883c" - - -def test_vpcs_device_setup_with_uuid(vpcs_device, project, local_server): - """ - If we have an ID that mean the VM already exits and we should not send startup_script - """ - - with patch('gns3.base_node.BaseNode.controllerHttpPost') as mock: - vpcs_device.create(name="PC 1", node_id="aec7a00c-e71c-45a6-8c04-29e40732883c", additional_settings={}) - assert mock.called - args, kwargs = mock.call_args - assert args[0] == "/nodes" - assert kwargs["body"] == { - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c", - "name": "PC 1", - "compute_id": local_server.id(), - "node_type": "vpcs", - "properties": {} - } - - # Callback - params = { - "console": 2000, - "name": "PC1", - "project_id": "f91bd115-3b5c-402e-b411-e5919723cf4b", - "node_id": "aec7a00c-e71c-45a6-8c04-29e40732883c", - "properties": { - } - } - vpcs_device.createNodeCallback(params) - - assert vpcs_device.node_id() == "aec7a00c-e71c-45a6-8c04-29e40732883c" - - def test_update(vpcs_device): new_settings = { diff --git a/tests/test_node.py b/tests/test_node.py index b36995c7..954a8ed6 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -26,23 +26,6 @@ from gns3.ports.ethernet_port import EthernetPort from gns3.ports.serial_port import SerialPort -def test_create(vpcs_device, local_server): - with patch('gns3.base_node.BaseNode.controllerHttpPost') as mock: - vpcs_device._create(name="PC 1", params={"startup_script": "echo TEST"}) - assert mock.called - args, kwargs = mock.call_args - assert args[0] == "/nodes" - assert kwargs["body"] == { - "name": "PC 1", - "node_id": vpcs_device._node_id, - "compute_id": local_server.id(), - "node_type": "vpcs", - "properties": { - "startup_script": "echo TEST" - } - } - - def test_setupVMCallback(vpcs_device): node_id = str(uuid.uuid4()) vpcs_device._createCallback = MagicMock()