Display the appliances in the application

Ref #1045
This commit is contained in:
Julien Duponchelle
2017-01-24 19:29:19 +01:00
parent 05ba772715
commit 012bc1e406
20 changed files with 163 additions and 861 deletions

View File

@@ -15,37 +15,100 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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():

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -16,8 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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

View File

@@ -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)

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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()