Merge branch '2.0' into 2.1

This commit is contained in:
Julien Duponchelle 2017-02-28 14:25:38 +01:00
commit 1ab9ca2333
No known key found for this signature in database
GPG Key ID: CE8B29639E07F5E8
29 changed files with 548 additions and 55 deletions

View File

@ -1,6 +1,8 @@
language: python
python:
- '3.4'
- '3.5'
- '3.6'
sudo: false
cache: pip
install:

View File

@ -1,6 +1,6 @@
-rrequirements.txt
sphinx==1.5.2
sphinx==1.5.3
pytest==3.0.6
pep8==1.7.0
pytest-catchlog==1.2.2

View File

@ -211,6 +211,7 @@ class EthernetSwitch(Device):
nio = self._nios[port_number]
if isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project)
if self._hypervisor:
yield from self._hypervisor.send('ethsw remove_nio "{name}" {nio}'.format(name=self._name, nio=nio))
log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,

View File

@ -24,6 +24,7 @@ import asyncio
import time
import sys
import os
import re
import glob
import shlex
import base64
@ -1474,7 +1475,7 @@ class Router(BaseNode):
try:
with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f:
old_config = f.read()
new_config = old_config.replace(self.name, new_name)
new_config = re.sub(r"^hostname .+$", "hostname " + new_name, old_config, flags=re.MULTILINE)
f.seek(0)
f.write(new_config)
except OSError as e:

View File

@ -301,7 +301,7 @@ class IOUVM(BaseNode):
if self.startup_config_file:
content = self.startup_config_content
content = content.replace(self._name, new_name)
content = re.sub(r"^hostname .+$", "hostname " + new_name, content, flags=re.MULTILINE)
self.startup_config_content = content
super(IOUVM, IOUVM).name.__set__(self, new_name)
@ -1161,7 +1161,7 @@ class IOUVM(BaseNode):
bay=adapter_number,
unit=port_number,
output_file=output_file,
data_link_type=data_link_type))
data_link_type=re.sub("^DLT_", "", data_link_type)))
@asyncio.coroutine
def stop_capture(self, adapter_number, port_number):

View File

@ -303,6 +303,16 @@ class VirtualBoxVM(BaseNode):
if self.acpi_shutdown:
# use ACPI to shutdown the VM
result = yield from self._control_vm("acpipowerbutton")
trial = 0
while True:
vm_state = yield from self._get_vm_state()
if vm_state == "poweroff":
break
yield from asyncio.sleep(1)
trial += 1
if trial >= 120:
yield from self._control_vm("poweroff")
break
self.status = "stopped"
log.debug("ACPI shutdown result: {}".format(result))
else:

View File

@ -104,7 +104,7 @@ class VPCSVM(BaseNode):
Check if VPCS is available with the correct version.
"""
path = self.vpcs_path
path = self._vpcs_path()
if not path:
raise VPCSError("No path to a VPCS executable has been set")
@ -144,8 +144,7 @@ class VPCSVM(BaseNode):
else:
return None
@property
def vpcs_path(self):
def _vpcs_path(self):
"""
Returns the VPCS executable path.
@ -170,6 +169,7 @@ class VPCSVM(BaseNode):
if self.script_file:
content = self.startup_script
content = content.replace(self._name, new_name)
content = re.sub(r"^set pcname .+$", "set pcname " + new_name, content, flags=re.MULTILINE)
self.startup_script = content
super(VPCSVM, VPCSVM).name.__set__(self, new_name)
@ -215,7 +215,7 @@ class VPCSVM(BaseNode):
Checks if the VPCS executable version is >= 0.8b or == 0.6.1.
"""
try:
output = yield from subprocess_check_output(self.vpcs_path, "-v", cwd=self.working_dir)
output = yield from subprocess_check_output(self._vpcs_path(), "-v", cwd=self.working_dir)
match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output)
if match:
version = match.group(1)
@ -223,7 +223,7 @@ class VPCSVM(BaseNode):
if self._vpcs_version < parse_version("0.6.1"):
raise VPCSError("VPCS executable version must be >= 0.6.1 but not a 0.8")
else:
raise VPCSError("Could not determine the VPCS version for {}".format(self.vpcs_path))
raise VPCSError("Could not determine the VPCS version for {}".format(self._vpcs_path()))
except (OSError, subprocess.SubprocessError) as e:
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
@ -268,8 +268,8 @@ class VPCSVM(BaseNode):
self.status = "started"
except (OSError, subprocess.SubprocessError) as e:
vpcs_stdout = self.read_vpcs_stdout()
log.error("Could not start VPCS {}: {}\n{}".format(self.vpcs_path, e, vpcs_stdout))
raise VPCSError("Could not start VPCS {}: {}\n{}".format(self.vpcs_path, e, vpcs_stdout))
log.error("Could not start VPCS {}: {}\n{}".format(self._vpcs_path(), e, vpcs_stdout))
raise VPCSError("Could not start VPCS {}: {}\n{}".format(self._vpcs_path(), e, vpcs_stdout))
def _termination_callback(self, returncode):
"""
@ -512,7 +512,7 @@ class VPCSVM(BaseNode):
"""
command = [self.vpcs_path]
command = [self._vpcs_path()]
command.extend(["-p", str(self._internal_console_port)]) # listen to console port
command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset
command.extend(["-i", "1"]) # option to start only one VPC instance

View File

@ -55,16 +55,21 @@ class Controller:
def start(self):
log.info("Start controller")
self.load_base_files()
yield from self.load()
server_config = Config.instance().get_section_config("Server")
host = server_config.get("host", "localhost")
# If console_host is 0.0.0.0 client will use the ip they use
# to connect to the controller
console_host = host
if host == "0.0.0.0":
host = "127.0.0.1"
name = socket.gethostname()
if name == "gns3vm":
name = "Main server"
yield from self.add_compute(compute_id="local",
name=socket.gethostname(),
name=name,
protocol=server_config.get("protocol", "http"),
host=host,
console_host=console_host,
@ -72,6 +77,7 @@ class Controller:
user=server_config.get("user", ""),
password=server_config.get("password", ""),
force=True)
yield from self._load_controller_settings()
yield from self.load_projects()
yield from self.gns3vm.auto_start_vm()
yield from self._project_auto_open()
@ -118,7 +124,7 @@ class Controller:
json.dump(data, f, indent=4)
@asyncio.coroutine
def load(self):
def _load_controller_settings(self):
"""
Reload the controller configuration from disk
"""
@ -279,7 +285,7 @@ class Controller:
return None
for compute in self._computes.values():
if name and compute.name == name:
if name and compute.name == name and not force:
raise aiohttp.web.HTTPConflict(text='Compute name "{}" already exists'.format(name))
compute = Compute(compute_id=compute_id, controller=self, name=name, **kwargs)

View File

@ -426,7 +426,7 @@ class Compute:
except aiohttp.errors.WSServerHandshakeError:
self._ws = None
break
if response.tp == aiohttp.MsgType.closed or response.tp == aiohttp.MsgType.error:
if response.tp == aiohttp.MsgType.closed or response.tp == aiohttp.MsgType.error or response.data is None:
self._connected = False
break
msg = json.loads(response.data)

View File

@ -43,6 +43,7 @@ class Drawing:
self._id = str(uuid.uuid4())
else:
self._id = drawing_id
self._svg = "<svg></svg>"
self.svg = svg
self._x = x
self._y = y

View File

@ -47,6 +47,9 @@ def export_project(project, temporary_dir, include_images=False, keep_compute_id
if project.is_running():
raise aiohttp.web.HTTPConflict(text="Running topology could not be exported")
# Make sure we save the project
project.dump()
z = zipstream.ZipFile(allowZip64=True)
if not os.path.exists(project._path):

View File

@ -222,8 +222,14 @@ class GNS3VM:
"""
engine = self._get_engine(engine)
vms = []
try:
for vm in (yield from engine.list()):
vms.append({"vmname": vm["vmname"]})
except GNS3VMError as e:
# We raise error only if user activated the GNS3 VM
# otherwise you have noise when VMware is not installed
if self.enable:
raise e
return vms
@asyncio.coroutine
@ -267,6 +273,7 @@ class GNS3VM:
engine.vmname = self._settings["vmname"]
engine.ram = self._settings["ram"]
engine.vpcus = self._settings["vcpus"]
engine.headless = self._settings["headless"]
compute = yield from self._controller.add_compute(compute_id="vm",
name="GNS3 VM is starting ({})".format(engine.vmname),
host=None,
@ -277,6 +284,7 @@ class GNS3VM:
except Exception as e:
yield from self._controller.delete_compute("vm")
log.error("Can't start the GNS3 VM: {}", str(e))
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname))
raise e
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname),
protocol=self.protocol,

View File

@ -221,7 +221,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
second to a GNS3 endpoint in order to get the list of the interfaces and
their IP and after that match it with VirtualBox host only.
"""
remaining_try = 240
remaining_try = 300
while remaining_try > 0:
json_data = None
session = aiohttp.ClientSession()

View File

@ -15,6 +15,8 @@
# 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 aiohttp
from .atm_port import ATMPort
from .frame_relay_port import FrameRelayPort
from .gigabitethernet_port import GigabitEthernetPort
@ -64,11 +66,14 @@ class StandardPortFactory:
port_name = first_port_name
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
else:
try:
port_name = port_name_format.format(
interface_number,
segment_number,
adapter=adapter_number,
**cls._generate_replacement(interface_number, segment_number))
except (ValueError, KeyError) as e:
raise aiohttp.web.HTTPConflict(text="Invalid port name format {}: {}".format(port_name_format, str(e)))
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
interface_number += 1
if port_segment_size:

View File

@ -293,6 +293,8 @@ class Project:
name = base_name.format(number, id=number, name="Node")
except KeyError as e:
raise aiohttp.web.HTTPConflict(text="{" + e.args[0] + "} is not a valid replacement string in the node name")
except ValueError as e:
raise aiohttp.web.HTTPConflict(text="{} is not a valid replacement string in the node name".format(base_name))
if name not in self._allocated_node_names:
self._allocated_node_names.add(name)
return name

View File

@ -39,6 +39,7 @@ class Symbols:
def list(self):
self._symbols_path = {}
symbols = []
if get_resource("symbols"):
for file in os.listdir(get_resource("symbols")):
if file.startswith('.'):
continue

View File

@ -363,6 +363,14 @@ def _convert_1_3_later(topo, topo_path):
node["symbol"] = ":/symbols/vbox_guest.svg"
elif old_node["type"] == "IOUDevice":
node["node_type"] = "iou"
node["port_name_format"] = old_node.get("port_name_format", "Ethernet{segment0}/{port0}")
node["port_segment_size"] = int(old_node.get("port_segment_size", "4"))
if node["symbol"] is None:
if "l2" in node["properties"].get("path", ""):
node["symbol"] = ":/symbols/multilayer_switch.svg"
else:
node["symbol"] = ":/symbols/router.svg"
elif old_node["type"] == "Cloud":
old_node["ports"] = _create_cloud(node, old_node, ":/symbols/cloud.svg")
elif old_node["type"] == "Host":

View File

@ -18,6 +18,7 @@
import os
import sys
import struct
import aiohttp
import platform
@ -94,6 +95,7 @@ class CrashReport:
"os:win_32": " ".join(platform.win32_ver()),
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
"os:linux": " ".join(platform.linux_distribution()),
"aiohttp:version": aiohttp.__version__,
"python:version": "{}.{}.{}".format(sys.version_info[0],
sys.version_info[1],
sys.version_info[2]),

View File

@ -120,7 +120,8 @@ def _svg_convert_size(size):
"pc": 15,
"mm": 3.543307,
"cm": 35.43307,
"in": 90
"in": 90,
"px": 1
}
if len(size) > 3:
if size[-2:] in conversion_table:

View File

@ -32,7 +32,7 @@ if "dev" in __version__:
import os
import subprocess
if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")):
r = subprocess.run(["git", "rev-parse", "--short", "HEAD"], stdout=subprocess.PIPE).stdout.decode().strip("\n")
r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip("\n")
__version__ += "-" + r
except Exception as e:
print(e)

View File

@ -39,6 +39,7 @@ class Response(aiohttp.web.Response):
self._route = route
self._output_schema = output_schema
self._request = request
headers['Connection'] = "close" # Disable keep alive because create trouble with old Qt (5.2, 5.3 and 5.4)
headers['X-Route'] = self._route
headers['Server'] = "Python/{0[0]}.{0[1]} GNS3/{1}".format(sys.version_info, __version__)
super().__init__(headers=headers, **kwargs)

View File

@ -1,7 +1,7 @@
jsonschema>=2.4.0
aiohttp==1.2.0
aiohttp>=1.3.0,<=1.4.0
aiohttp_cors>=0.4.0
yarl>=0.9.6
yarl>=0.9.8
typing>=3.5.3.0 # Otherwise yarl fail with python 3.4
Jinja2>=2.7.3
raven>=5.23.0

View File

@ -298,6 +298,14 @@ def test_change_name(vm, tmpdir):
assert vm.name == "hello"
with open(path) as f:
assert f.read() == "hostname hello"
# support hostname not sync
vm.name = "alpha"
with open(path, 'w+') as f:
f.write("no service password-encryption\nhostname beta\nno ip icmp rate-limit unreachable")
vm.name = "charlie"
assert vm.name == "charlie"
with open(path) as f:
assert f.read() == "no service password-encryption\nhostname charlie\nno ip icmp rate-limit unreachable"
def test_library_check(loop, vm):

View File

@ -75,7 +75,7 @@ def test_vm_invalid_vpcs_version(loop, manager, vm):
def test_vm_invalid_vpcs_path(vm, manager, loop):
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.vpcs_path", return_value="/tmp/fake/path/vpcs"):
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM._vpcs_path", return_value="/tmp/fake/path/vpcs"):
with pytest.raises(VPCSError):
nio = manager.create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
vm.port_add_nio_binding(0, nio)
@ -97,7 +97,7 @@ def test_start(loop, vm, async_run):
nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
async_run(vm.port_add_nio_binding(0, nio))
loop.run_until_complete(asyncio.async(vm.start()))
assert mock_exec.call_args[0] == (vm.vpcs_path,
assert mock_exec.call_args[0] == (vm._vpcs_path(),
'-p',
str(vm._internal_console_port),
'-m', '1',
@ -133,7 +133,7 @@ def test_start_0_6_1(loop, vm, async_run):
nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
async_run(vm.port_add_nio_binding(0, nio))
async_run(vm.start())
assert mock_exec.call_args[0] == (vm.vpcs_path,
assert mock_exec.call_args[0] == (vm._vpcs_path(),
'-p',
str(vm._internal_console_port),
'-m', '1',
@ -243,12 +243,12 @@ def test_update_startup_script(vm):
def test_update_startup_script_h(vm):
content = "setname %h\n"
content = "set pcname %h\n"
vm.name = "pc1"
vm.startup_script = content
assert os.path.exists(vm.script_file)
with open(vm.script_file) as f:
assert f.read() == "setname pc1\n"
assert f.read() == "set pcname pc1\n"
def test_get_startup_script(vm):
@ -275,11 +275,18 @@ def test_change_name(vm, tmpdir):
path = os.path.join(vm.working_dir, 'startup.vpc')
vm.name = "world"
with open(path, 'w+') as f:
f.write("name world")
f.write("set pcname world")
vm.name = "hello"
assert vm.name == "hello"
with open(path) as f:
assert f.read() == "name hello"
assert f.read() == "set pcname hello"
# Support when the name is not sync with config
with open(path, 'w+') as f:
f.write("set pcname alpha")
vm.name = "beta"
assert vm.name == "beta"
with open(path) as f:
assert f.read() == "set pcname beta"
def test_close(vm, port_manager, loop):

View File

@ -42,7 +42,7 @@ def test_save(controller, controller_config_path):
assert data["gns3vm"] == controller.gns3vm.__json__()
def test_load(controller, controller_config_path, async_run):
def test_load_controller_settings(controller, controller_config_path, async_run):
controller.save()
with open(controller_config_path) as f:
data = json.load(f)
@ -60,7 +60,7 @@ def test_load(controller, controller_config_path, async_run):
data["gns3vm"] = {"vmname": "Test VM"}
with open(controller_config_path, "w+") as f:
json.dump(data, f)
async_run(controller.load())
async_run(controller._load_controller_settings())
assert controller.settings["IOU"]
assert controller.computes["test1"].__json__() == {
"compute_id": "test1",
@ -104,7 +104,7 @@ def test_import_computes_1_x(controller, controller_config_path, async_run):
with open(os.path.join(config_dir, "gns3_gui.conf"), "w+") as f:
json.dump(gns3_gui_conf, f)
async_run(controller.load())
async_run(controller._load_controller_settings())
for compute in controller.computes.values():
if compute.id != "local":
assert len(compute.id) == 36
@ -146,7 +146,7 @@ def test_import_gns3vm_1_x(controller, controller_config_path, async_run):
json.dump(gns3_gui_conf, f)
controller.gns3vm.settings["engine"] = None
async_run(controller.load())
async_run(controller._load_controller_settings())
assert controller.gns3vm.settings["engine"] == "vmware"
assert controller.gns3vm.settings["enable"]
assert controller.gns3vm.settings["headless"]
@ -202,7 +202,7 @@ def test_import_remote_gns3vm_1_x(controller, controller_config_path, async_run)
json.dump(gns3_gui_conf, f)
with asyncio_patch("gns3server.controller.compute.Compute.connect"):
async_run(controller.load())
async_run(controller._load_controller_settings())
assert controller.gns3vm.settings["engine"] == "remote"
assert controller.gns3vm.settings["vmname"] == "http://127.0.0.1:3081"

View File

@ -33,7 +33,9 @@ from gns3server.controller.export_project import export_project, _filter_files
@pytest.fixture
def project(controller):
return Project(controller=controller, name="Test")
p = Project(controller=controller, name="Test")
p.dump = MagicMock()
return p
@pytest.fixture

420
tests/resources/firefox.svg Normal file
View File

@ -0,0 +1,420 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="66px" height="70px" viewBox="0 0 66 70" enable-background="new 0 0 66 70" xml:space="preserve"> <image id="image0" width="66" height="70" x="0" y="0"
xlink:href="data:image/png;base64,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" />
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -30,8 +30,8 @@
"name": "IOU1",
"node_id": "aaeb2288-a7d8-42a9-b9d8-c42ab464a390",
"node_type": "iou",
"port_name_format": "Ethernet{0}",
"port_segment_size": 0,
"port_name_format": "Ethernet{segment0}/{port0}",
"port_segment_size": 4,
"first_port_name": null,
"properties": {
"ethernet_adapters": 2,

View File

@ -39,3 +39,7 @@ def test_get_size():
with open("gns3server/symbols/cloud.svg", "rb") as f:
res = get_size(f.read())
assert res == (159, 71, "svg")
# Size with px
with open("tests/resources/firefox.svg", "rb") as f:
res = get_size(f.read())
assert res == (66, 70, "svg")