Merge branch 'master' into 2.1

This commit is contained in:
Julien Duponchelle
2017-07-11 17:59:12 +02:00
11 changed files with 130 additions and 20 deletions

2
.gitignore vendored
View File

@@ -61,5 +61,5 @@ updates
.cache
__pycache__
#Virtualenv
# Virtualenv
env

View File

@@ -128,6 +128,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.uiInfoTreeWidget.addTopLevelItem(item)
elif self.page(page_id) == self.uiServerWizardPage:
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
is_win = ComputeManager.instance().localPlatform().startswith("win")
self.uiRemoteServersComboBox.clear()
if len(ComputeManager.instance().remoteComputes()) == 0:
self.uiRemoteRadioButton.setEnabled(False)
@@ -141,7 +144,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if ComputeManager.instance().localPlatform() is None:
self.uiLocalRadioButton.setEnabled(False)
elif (ComputeManager.instance().localPlatform().startswith("darwin") or ComputeManager.instance().localPlatform().startswith("win")):
elif is_mac or is_win:
if type == "qemu":
# Qemu has issues on OSX and Windows we disallow usage of the local server
if not LocalConfig.instance().experimental():
@@ -158,6 +161,14 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
else:
self.uiRemoteRadioButton.setChecked(False)
if is_mac or is_win:
if not self.uiRemoteRadioButton.isEnabled() \
and not self.uiVMRadioButton.isEnabled() \
and not self.uiLocalRadioButton.isEnabled():
QtWidgets.QMessageBox.warning(
self, "No GNS3 VM available.",
"GNS3 VM is not available, please configure GNS3 VM before adding new Appliance.")
elif self.page(page_id) == self.uiFilesWizardPage:
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)

View File

@@ -340,6 +340,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Slot called to create a new project.
"""
# prevents race condition
if self._project_dialog is not None:
return
self._project_dialog = ProjectDialog(self)
self._project_dialog.show()
create_new_project = self._project_dialog.exec_()
@@ -348,7 +353,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiNodesDockWidget.setWindowTitle("")
if create_new_project:
Topology.instance().createLoadProject(self._project_dialog.getProjectSettings())
Topology.instance().createLoadProject(
self._project_dialog.getProjectSettings())
self._project_dialog = None
@@ -727,7 +733,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
project.setShowLayers(self.uiShowLayersAction.isChecked())
project.update()
def _resetPortLabelsActionSlot(self):
"""
Slot called to reset the port labels on the scene.
@@ -751,7 +756,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
project.setShowInterfaceLabels(self.uiShowPortNamesAction.isChecked())
project.update()
def _startAllActionSlot(self):
"""
Slot called when starting all the nodes.

View File

@@ -256,9 +256,9 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
QtWidgets.QMessageBox.critical(parent, "IOU image", "Cannot read ELF magic number: {}".format(e))
return
# file must start with the ELF magic number, be 32-bit, little endian and have an ELF version of 1
# normal IOS image are big endian!
if elf_header_start != b'\x7fELF\x01\x01\x01':
# file must start with the ELF magic number, be 32-bit or 64-bit, little endian and have an ELF version of 1
# (normal IOS image are big endian!)
if elf_header_start != b'\x7fELF\x01\x01\x01' and elf_header_start != b'\x7fELF\x02\x01\x01':
QtWidgets.QMessageBox.critical(parent, "IOU image", "Sorry, this is not a valid IOU image!")
return

View File

@@ -161,14 +161,12 @@ if hasattr(sys, '_called_from_test'):
self._instances.add(self)
def connect(self, func, style=None):
log.debug("{caller} connect to signal".format(caller=sys._getframe(1).f_code.co_name))
self._callbacks.add(func)
def disconnect(self, func):
self._callbacks.remove(func)
def emit(self, *args):
log.debug("{caller} emit signal".format(caller=sys._getframe(1).f_code.co_name))
for callback in list(self._callbacks):
callback(*args)

View File

@@ -42,18 +42,20 @@ class QImageSvgRenderer(QtSvg.QSvgRenderer):
def load(self, path_or_data):
try:
if not os.path.exists(path_or_data) and not path_or_data.startswith(":"):
self._svg = path_or_data
path_or_data = path_or_data.encode("utf-8")
return super().load(path_or_data)
except ValueError:
pass # On windows we can get an error because the path is too long (it's the svg data)
path_exists = os.path.exists(path_or_data)
except ValueError: # On windows we can get an error because the path is too long (it's the svg data)
path_exists = False
if not path_exists and not path_or_data.startswith(":"):
self._svg = path_or_data
path_or_data = path_or_data.encode("utf-8")
return super().load(path_or_data)
try:
# We load the SVG with ElementTree before
# because Qt when failing loading send noise to logs
# and their is no way to prevent that
if not path_or_data.startswith(":") and os.path.exists(path_or_data):
if not path_or_data.startswith(":") and path_exists:
ET.parse(path_or_data)
res = super().load(path_or_data)
# If we can't render a SVG we load and base64 the image to create a SVG

View File

@@ -119,7 +119,7 @@ class Appliance(collections.Mapping):
"""
Duplicate a version in order to create a new version
"""
if len(self._appliance["versions"]) == 0:
if 'versions' not in self._appliance.keys() or len(self._appliance["versions"]) == 0:
raise ApplianceError("Your appliance file doesn't contain any versions")
ref = self._appliance["versions"][0]

View File

@@ -54,7 +54,7 @@ class FileCopyWorker(QtCore.QObject):
self._is_running = True
try:
shutil.copyfile(self._source, self._destination)
shutil.copy(self._source, self._destination)
except OSError as e:
log.warning("cannot copy: {}".format(e))
self.error.emit("Could not copy file to {}: {}".format(self._destination, e), False)

View File

@@ -19,6 +19,7 @@
import pytest
import json
import os
import tempfile
from gns3.registry.appliance import Appliance, ApplianceError
from gns3.registry.registry import Registry
@@ -166,8 +167,11 @@ def test_is_version_installable(linux_microcore_img, microcore_appliance):
def test_create_new_version():
appliance_path = os.path.join(
os.path.dirname(__file__), "appliances", "microcore-linux.gns3a")
a = Appliance(registry, appliance_path)
a = Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a"))
a.create_new_version("42.0")
v = a['versions'][-1:][0]
assert v == {
@@ -182,6 +186,21 @@ def test_create_new_version():
'name': '42.0'
}
# tests what happens without versions in file
wrong_appliance_fp, wrong_appliance_file = tempfile.mkstemp()
with open(appliance_path) as f:
appliance = json.loads(f.read())
del appliance['versions']
os.write(wrong_appliance_fp, json.dumps(appliance).encode())
os.close(wrong_appliance_fp)
a = Appliance(registry, wrong_appliance_file)
with pytest.raises(ApplianceError):
a.create_new_version("42.0")
os.remove(wrong_appliance_file)
def test_emulator():
assert Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a")).emulator() == "qemu"

34
tests/test_main_window.py Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python
#
# Copyright (C) 2017 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from unittest.mock import patch
from gns3.main_window import MainWindow
def test_new_project_when_dialog_is_already_available():
main_window = MainWindow()
with patch('gns3.main_window.ProjectDialog') as project_dialog_mock, \
patch('gns3.topology.Topology.createLoadProject') as load_project_mock:
main_window._project_dialog = project_dialog_mock()
project_dialog_mock().exec_.return_value = 1
project_dialog_mock().getProjectSettings.side_effect = AttributeError("Mocked")
main_window._newProjectActionSlot()
# load is not called when we handle the case
assert load_project_mock.called is False

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import stat
import tempfile
from unittest.mock import patch
from gns3.utils.file_copy_worker import FileCopyWorker
def test_file_copy_worker_with_preserve_permissions():
source_fp, source = tempfile.mkstemp()
destination_fp, destination = tempfile.mkstemp()
st = os.stat(source)
os.chmod(source, st.st_mode | stat.S_IEXEC)
assert os.access(source, os.X_OK)
with patch('gns3.utils.file_copy_worker.FileCopyWorker.finished'):
worker = FileCopyWorker(source, destination)
worker.run()
assert os.access(destination, os.X_OK)
os.close(source_fp)
os.close(destination_fp)
os.remove(source)
os.remove(destination)