Compare commits

..

39 Commits

Author SHA1 Message Date
41af46cdc5 2.0.3 release 2017-06-13 10:35:50 +02:00
64a852f38f Fixes #1068 - handle zipfile encoding issues at project duplication 2017-06-09 15:32:25 +02:00
5c0d955156 Fix: #1066 - Catching parsing errors at linked vbox file 2017-06-09 10:41:34 +02:00
c1c3a9ed80 Drop debug 2017-06-09 09:31:18 +02:00
0dd35e1cbe Ignoring virtualenv directory at gitignore 2017-06-09 09:14:25 +02:00
7235a312ab Escaping VPCS name in regex #1067 2017-06-09 09:14:25 +02:00
40cc5d76b3 Fix racecondition when listing interface
Fix #1060
2017-06-07 15:57:05 +02:00
eb6068c3d3 Fix Qemu disk creation with unicode characters not supported by local filesystem #1058 (#1063) 2017-06-07 12:35:41 +02:00
e0b519eced Fix when config file doesn't have computes section (#1062) 2017-06-07 10:50:40 +02:00
88cd804274 Update readme 2017-06-02 11:40:39 +02:00
e74e66b203 Typo in error message 2017-05-31 17:04:14 +02:00
6fa27c6885 Check aiohttp version 2017-05-31 16:56:46 +02:00
07049562a4 Update pytest from 3.1.0 to 3.1.1 (#1056) 2017-05-31 15:34:36 +02:00
7913646ce3 2.0.3dev1 2017-05-30 08:58:05 +02:00
78e1321bae 2.0.2 2017-05-30 08:57:07 +02:00
c46a9da769 Update sphinx from 1.6.1 to 1.6.2 (#1049) 2017-05-29 09:01:12 +02:00
ef5c013561 Merge branch 'master' into 2.0 2017-05-26 10:47:57 +02:00
8bbe7e8041 Set correct permission on ubridge when doing a remote installation 2017-05-26 10:47:25 +02:00
777361c23d Merge branch 'master' into 2.0 2017-05-26 10:32:26 +02:00
c3d5abd104 Remote install script should be totally non interactive 2017-05-26 10:29:33 +02:00
39879ca92b Duplicate project on remote server use UUID
Ref https://github.com/GNS3/gns3-server/issues/1040
2017-05-23 17:20:51 +02:00
5a86277a9f Test that import erase the auto_open and auto_start setting
Ref #1041
2017-05-23 16:08:30 +02:00
b87692acce Fix import of some old topologies from 1.3 2017-05-23 14:05:47 +02:00
d765d02f06 Fix error in logging of error during starting GNS3 VM 2017-05-23 13:39:41 +02:00
5bb6c51698 Fix an error when logging Docker container fail to start 2017-05-23 13:31:04 +02:00
59488e8064 Fix tests on python 3.4 (#1046) 2017-05-23 12:15:04 +02:00
54e80d68f8 Fix test failed on Python 3.4
Fix #1045
2017-05-23 11:11:13 +02:00
2a8911d19e Keep DOCKER_MINIMUM_API_VERSION and DOCKER_MINIMUM_VERSION consistent 2017-05-23 11:00:15 +02:00
8b7209ea78 Use docker version in error message of outdated docker installation 2017-05-23 10:41:42 +02:00
7d48eba01b Use versioned docker API
Since docker v1.13 the unversioned docker API is deprecated.
Backport of gns3-server 2.0, commit effbe59
2017-05-23 10:37:47 +02:00
1e598d1890 Test for support of docker commit images
Ref #1039
2017-05-23 10:23:12 +02:00
2191c487b6 Support images created by "docker commit". Fixes #1039 2017-05-23 10:23:11 +02:00
3ed49bbdd5 Update pytest from 3.0.7 to 3.1.0 (#1044) 2017-05-23 08:29:36 +02:00
7d30da5d4e Do not wait auto start to finish in order to complete project opening
Fix https://github.com/GNS3/gns3-gui/issues/2074
2017-05-22 20:18:07 +02:00
4dc55a5af4 Improve logging for remote server connection lost
Ref https://github.com/GNS3/gns3-gui/issues/2074
2017-05-22 13:57:32 +02:00
51d6152a5c Fix support for some old topologies
Fix #1042
2017-05-19 18:27:20 +02:00
bdcadcc335 Fix support from some old topologies
Fix #1038
2017-05-19 17:49:39 +02:00
812ff6b5d8 Update sphinx from 1.5.6 to 1.6.1 (#1035) 2017-05-17 08:30:16 +02:00
e24bfd5913 2.0.2dev1 2017-05-16 09:16:35 +02:00
33 changed files with 314 additions and 34 deletions

3
.gitignore vendored
View File

@ -53,3 +53,6 @@ docs/_build
vpcs.hist
startup.vpcs
.gns3_shell_history
# Virtualenv
env

View File

@ -1,5 +1,29 @@
# Change Log
## 2.0.3 13/06/2017
* Fixes #1068 - handle zipfile encoding issues at project duplication
* Fix: #1066 - Catching parsing errors at linked vbox file
* Ignoring virtualenv directory at gitignore
* Escaping VPCS name in regex #1067
* Fix racecondition when listing interface
* Fix Qemu disk creation with unicode characters not supported by local filesystem #1058 (#1063)
* Fix when config file doesn't have computes section (#1062)
* Check aiohttp version
## 2.0.2 30/05/2017
* Set correct permission on ubridge when doing a remote installation
* Remote install script should be totally non interactive
* Duplicate project on remote server use UUID
* Fix import of some old topologies from 1.3
* Fix error in logging of error during starting GNS3 VM
* Fix an error when logging Docker container fail to start
* Use docker version in error message of outdated docker installation
* Support images created by "docker commit". Fixes #1039
* Do not wait auto start to finish in order to complete project opening
* Improve logging for remote server connection lost
## 2.0.1 16/05/2017
* Handle HTTP 504 errors from compute node

View File

@ -22,7 +22,7 @@ master
master is the next stable release, you can test it in your day to day activities.
Bug fixes or small improvements pull requests go here.
1.x (1.4 for example)
2.x (2.1 for example)
********
Next major release

View File

@ -1,7 +1,7 @@
-rrequirements.txt
sphinx==1.5.6
pytest==3.0.7
sphinx==1.6.2
pytest==3.1.1
pep8==1.7.0
pytest-catchlog==1.2.2
pytest-timeout==1.2.0

View File

@ -33,7 +33,9 @@ from gns3server.compute.docker.docker_error import DockerError, DockerHttp304Err
log = logging.getLogger(__name__)
# Be carefull to keep it consistent
DOCKER_MINIMUM_API_VERSION = "1.25"
DOCKER_MINIMUM_VERSION = "1.13"
class Docker(BaseManager):
@ -60,7 +62,7 @@ class Docker(BaseManager):
self._connected = False
raise DockerError("Can't connect to docker daemon")
if parse_version(version["ApiVersion"]) < parse_version(DOCKER_MINIMUM_API_VERSION):
raise DockerError("Docker API version is {}. GNS3 requires a minimum API version of {}".format(version["ApiVersion"], DOCKER_MINIMUM_API_VERSION))
raise DockerError("Docker version is {}. GNS3 requires a minimum version of {}".format(version["Version"], DOCKER_MINIMUM_VERSION))
def connector(self):
if self._connector is None or self._connector.closed:
@ -113,11 +115,13 @@ class Docker(BaseManager):
:returns: HTTP response
"""
data = json.dumps(data)
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path
if timeout is None:
timeout = 60 * 60 * 24 * 31 # One month timeout
if path == 'version':
url = "http://docker/v1.12/" + path # API of docker v1.0
else:
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path
try:
if path != "version": # version is use by check connection
yield from self._check_connection()

View File

@ -213,7 +213,7 @@ class DockerVM(BaseNode):
self._volumes = ["/etc/network"]
volumes = image_infos.get("ContainerConfig", {}).get("Volumes")
volumes = image_infos.get("Config", {}).get("Volumes")
if volumes is None:
return binds
for volume in volumes.keys():
@ -361,7 +361,7 @@ class DockerVM(BaseNode):
try:
yield from self._add_ubridge_connection(nio, adapter_number)
except UbridgeNamespaceError:
log.error("Container {} failed to start", self.name)
log.error("Container %s failed to start", self.name)
yield from self.stop()
# The container can crash soon after the start, this means we can not move the interface to the container namespace

View File

@ -247,8 +247,13 @@ class Qemu(BaseManager):
directory = self.get_images_directory()
os.makedirs(directory, exist_ok=True)
path = os.path.join(directory, os.path.basename(path))
if os.path.exists(path):
raise QemuError("Could not create disk image {} already exist".format(path))
try:
if os.path.exists(path):
raise QemuError("Could not create disk image {} already exist".format(path))
except UnicodeEncodeError:
raise QemuError("Could not create disk image {}, "
"path contains characters not supported by filesystem".format(path))
command = [qemu_img, "create", "-f", img_format]
for option in sorted(options.keys()):

View File

@ -209,7 +209,12 @@ class VirtualBoxVM(BaseNode):
Fix the VM uuid in the case of linked clone
"""
if os.path.exists(self._linked_vbox_file()):
tree = ET.parse(self._linked_vbox_file())
try:
tree = ET.parse(self._linked_vbox_file())
except ET.ParseError:
raise VirtualBoxError("Cannot modify VirtualBox linked nodes file. "
"File {} is corrupted.".format(self._linked_vbox_file()))
machine = tree.getroot().find("{http://www.virtualbox.org/}Machine")
if machine is not None and machine.get("uuid") != "{" + self.id + "}":

View File

@ -171,7 +171,8 @@ 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)
escaped_name = re.escape(new_name)
content = re.sub(r"^set pcname .+$", "set pcname " + escaped_name, content, flags=re.MULTILINE)
self.startup_script = content
super(VPCSVM, VPCSVM).name.__set__(self, new_name)

View File

@ -82,7 +82,7 @@ class Controller:
password=server_config.get("password", ""),
force=True)
except aiohttp.web_exceptions.HTTPConflict as e:
log.fatal("Can't acces to the local server, make sure anything else is not running on the same port")
log.fatal("Can't access to the local server, make sure anything else is not running on the same port")
sys.exit(1)
for c in computes:
try:
@ -176,7 +176,7 @@ class Controller:
if "gns3vm" in data:
self.gns3vm.settings = data["gns3vm"]
return data["computes"]
return data.get("computes", [])
@asyncio.coroutine
def load_projects(self):

View File

@ -580,8 +580,10 @@ class Compute:
Forward a call to the emulator on compute
"""
try:
res = yield from self.http_query(method, "/{}/{}".format(type, path), data=data, timeout=None)
action = "/{}/{}".format(type, path)
res = yield from self.http_query(method, action, data=data, timeout=None)
except aiohttp.errors.DisconnectedError:
log.error("Connection lost to %s during %s %s", self._id, method, action)
raise aiohttp.web.HTTPGatewayTimeout()
return res.json

View File

@ -250,7 +250,7 @@ class GNS3VM:
force=True)
except aiohttp.web.HTTPConflict:
pass
log.error("Can't start the GNS3 VM: {}", str(e))
log.error("Can't start the GNS3 VM: %s", str(e))
@asyncio.coroutine
def exit_vm(self):
@ -287,7 +287,7 @@ class GNS3VM:
yield from engine.start()
except Exception as e:
yield from self._controller.delete_compute("vm")
log.error("Can't start the GNS3 VM: {}", str(e))
log.error("Can't start the GNS3 VM: {}".format(str(e)))
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname))
raise e
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname),

View File

@ -24,7 +24,6 @@ import asyncio
import zipfile
import aiohttp
from ..config import Config
from .topology import load_topology
@ -74,7 +73,7 @@ def import_project(controller, project_id, stream, location=None, name=None, kee
path = location
else:
projects_path = controller.projects_directory()
path = os.path.join(projects_path, project_name)
path = os.path.join(projects_path, project_id)
try:
os.makedirs(path, exist_ok=True)
except UnicodeEncodeError as e:

View File

@ -675,7 +675,10 @@ class Project:
self._loading = False
# Should we start the nodes when project is open
if self._auto_start:
yield from self.start_all()
# Start all in the background without waiting for completion
# we ignore errors because we want to let the user open
# their project and fix it
asyncio.async(self.start_all())
@asyncio.coroutine
def wait_loaded(self):
@ -711,7 +714,7 @@ class Project:
f.write(data)
with open(os.path.join(tmpdir, "project.gns3p"), "rb") as f:
project = yield from import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True)
except OSError as e:
except (OSError, UnicodeEncodeError) as e:
raise aiohttp.web.HTTPConflict(text="Can not duplicate project: {}".format(str(e)))
if previous_status == "closed":

View File

@ -289,6 +289,10 @@ def _convert_1_3_later(topo, topo_path):
node["properties"] = {}
# Some old dynamips node don't have type
if "type" not in old_node:
old_node["type"] = old_node["properties"]["platform"].upper()
if old_node["type"] == "VPCSDevice":
node["node_type"] = "vpcs"
elif old_node["type"] == "QemuVM":
@ -316,7 +320,7 @@ def _convert_1_3_later(topo, topo_path):
node["symbol"] = ":/symbols/ethernet_switch.svg"
node["console_type"] = None
node["properties"]["ports_mapping"] = []
for port in old_node["ports"]:
for port in old_node.get("ports", []):
node["properties"]["ports_mapping"].append({
"name": "Ethernet{}".format(port["port_number"] - 1),
"port_number": port["port_number"] - 1,
@ -327,12 +331,12 @@ def _convert_1_3_later(topo, topo_path):
node["node_type"] = "frame_relay_switch"
node["symbol"] = ":/symbols/frame_relay_switch.svg"
node["console_type"] = None
elif old_node["type"] in ["C1700", "C2600", "C2691", "C3600", "C3620", "C3640", "C3660", "C3725", "C3745", "C7200", "EtherSwitchRouter"]:
elif old_node["type"].upper() in ["C1700", "C2600", "C2691", "C3600", "C3620", "C3640", "C3660", "C3725", "C3745", "C7200", "EtherSwitchRouter"]:
if node["symbol"] is None:
node["symbol"] = ":/symbols/router.svg"
node["node_type"] = "dynamips"
node["properties"]["dynamips_id"] = old_node.get("dynamips_id")
if "platform" not in node["properties"] and old_node["type"].startswith("C"):
if "platform" not in node["properties"] and old_node["type"].upper().startswith("C"):
node["properties"]["platform"] = old_node["type"].lower()
if node["properties"]["platform"].startswith("c36"):
node["properties"]["platform"] = "c3600"

View File

@ -54,7 +54,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://9b1156a90ee943eba20e032cf007297d:024b75c7b11844a58df147a4fb059774@sentry.io/38482"
DSN = "sync+https://9b15627f2ddf4e21a9880536354bfcb5:b31dee5d3abf4c74844895432193d0ac@sentry.io/38482"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):

View File

@ -23,7 +23,6 @@ import tempfile
from gns3server.web.route import Route
from gns3server.controller import Controller
from gns3server.controller.project import Project
from gns3server.controller.import_project import import_project
from gns3server.controller.export_project import export_project
from gns3server.config import Config

View File

@ -33,7 +33,7 @@ VM_CREATE_SCHEMA = {
},
"dynamips_id": {
"description": "Dynamips ID",
"type": "integer"
"type": ["integer", "null"]
},
"name": {
"description": "Dynamips VM instance name",

View File

@ -198,12 +198,13 @@ def interfaces():
results = []
if not sys.platform.startswith("win"):
for interface in sorted(psutil.net_if_addrs().keys()):
net_if_addrs = psutil.net_if_addrs()
for interface in sorted(net_if_addrs.keys()):
ip_address = ""
mac_address = ""
netmask = ""
interface_type = "ethernet"
for addr in psutil.net_if_addrs()[interface]:
for addr in net_if_addrs[interface]:
# get the first available IPv4 address only
if addr.family == socket.AF_INET:
ip_address = addr.address

View File

@ -23,7 +23,7 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "2.0.1"
__version__ = "2.0.3"
# If it's a git checkout try to add the commit
if "dev" in __version__:
@ -36,4 +36,4 @@ if "dev" in __version__:
except Exception as e:
print(e)
__version_info__ = (2, 0, 1, 0)
__version_info__ = (2, 0, 3, 0)

View File

@ -43,6 +43,9 @@ import gns3server.handlers
import logging
log = logging.getLogger(__name__)
if not aiohttp.__version__.startswith("1.3"):
raise RuntimeError("You need aiohttp 1.3 for running GNS3")
class WebServer:

View File

@ -152,7 +152,7 @@ log "Update system packages"
apt-get update
log "Upgrade packages"
apt-get upgrade -y
apt-get upgrade --yes --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"
log " Install GNS3 packages"
apt-get install -y gns3-server
@ -163,6 +163,10 @@ then
useradd -d /opt/gns3/ -m gns3
fi
log "Add GNS3 to the ubridge group"
usermod -aG ubridge gns3
log "Install docker"
if [ ! -f "/usr/bin/docker" ]
then

View File

@ -840,7 +840,7 @@ def test_get_image_informations(project, manager, loop):
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_mount_binds(vm, tmpdir):
image_infos = {
"ContainerConfig": {
"Config": {
"Volumes": {
"/test/experimental": {}
}

View File

@ -178,6 +178,26 @@ def test_create_image_exist(loop, tmpdir, fake_qemu_img_binary):
assert not process.called
def test_create_image_with_not_supported_characters_by_filesystem(loop, tmpdir, fake_qemu_img_binary):
open(str(tmpdir / "hda.qcow2"), "w+").close()
options = {
"format": "raw",
"size": 100
}
# patching os.makedirs is necessary as it depends on already mocked os.path.exists
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process, \
patch("gns3server.compute.qemu.Qemu.get_images_directory", return_value=str(tmpdir)), \
patch("os.path.exists", side_effect=UnicodeEncodeError('error', u"", 1, 2, 'Emulated Unicode Err')),\
patch("os.makedirs"):
with pytest.raises(QemuError):
loop.run_until_complete(asyncio.async(Qemu.instance().create_disk(
fake_qemu_img_binary, "hda.qcow2", options)))
assert not process.called
def test_get_kvm_archs_kvm_ok(loop):
with patch("os.path.exists", return_value=True):

View File

@ -113,3 +113,15 @@ def test_patch_vm_uuid(vm):
with open(vm._linked_vbox_file()) as f:
c = f.read()
assert "{" + vm.id + "}" in c
def test_patch_vm_uuid_with_corrupted_file(vm):
xml = """<?xml version="1.0"?>
<VirtualBox>
"""
os.makedirs(os.path.join(vm.working_dir, vm._vmname), exist_ok=True)
with open(vm._linked_vbox_file(), "w+") as f:
f.write(xml)
vm._linked_clone = True
with pytest.raises(VirtualBoxError):
vm._patch_vm_uuid()

View File

@ -251,6 +251,12 @@ def test_update_startup_script_h(vm):
assert f.read() == "set pcname pc1\n"
def test_update_startup_script_with_escaping_characters_in_name(vm):
vm.startup_script = "set pcname initial-name\n"
vm.name = "test\\"
assert vm.startup_script == "set pcname test\\\n"
def test_get_startup_script(vm):
content = "echo GNS3 VPCS\nip 192.168.1.2"
vm.startup_script = content

View File

@ -65,6 +65,16 @@ def test_load_controller_settings(controller, controller_config_path, async_run)
assert controller.gns3vm.settings["vmname"] == "Test VM"
def test_load_controller_settings_with_no_computes_section(controller, controller_config_path, async_run):
controller.save()
with open(controller_config_path) as f:
data = json.load(f)
del data['computes']
with open(controller_config_path, "w+") as f:
json.dump(data, f)
assert len(async_run(controller._load_controller_settings())) == 0
def test_import_computes_1_x(controller, controller_config_path, async_run):
"""
At first start the server should import the

View File

@ -35,6 +35,8 @@ def test_import_project(async_run, tmpdir, controller):
topology = {
"project_id": str(uuid.uuid4()),
"name": "test",
"auto_open": True,
"auto_start": True,
"topology": {
},
"version": "2.0.0"
@ -67,6 +69,8 @@ def test_import_project(async_run, tmpdir, controller):
# A new project name is generated when you import twice the same name
with open(zip_path, "rb") as f:
project = async_run(import_project(controller, str(uuid.uuid4()), f))
assert project.auto_open is False
assert project.auto_start is False
assert project.name != "test"

View File

@ -23,6 +23,7 @@ import json
import pytest
import aiohttp
import zipfile
import zipstream
from unittest.mock import MagicMock
from tests.utils import AsyncioMagicMock, asyncio_patch
from unittest.mock import patch
@ -439,6 +440,15 @@ def test_duplicate(project, async_run, controller):
assert list(new_project.nodes.values())[1].compute.id == "remote"
def test_duplicate_with_zipfile_encoding_issues(project, async_run, controller):
zf = zipstream.ZipFile()
zf.writestr('test\udcc3', "data")
with asyncio_patch('gns3server.controller.project.export_project', return_value=zf):
with pytest.raises(aiohttp.web.HTTPConflict):
async_run(project.duplicate(name="Hello"))
def test_snapshots(project):
"""
List the snapshots

View File

@ -46,4 +46,3 @@ def test_put_gns3vm(http_controller):
def test_get_gns3vm(http_controller):
response = http_controller.get('/gns3vm', example=True)
assert response.status == 200
print(response.json)

View File

@ -0,0 +1,77 @@
{
"auto_start": false,
"name": "1_3_dynamips",
"project_id": "ba5790e1-2f51-443e-a3cc-1a2eee132888",
"revision": 6,
"topology": {
"computes": [
{
"compute_id": "local",
"host": "127.0.0.1",
"name": "Local",
"port": 8000,
"protocol": "http"
}
],
"drawings": [],
"links": [],
"nodes": [
{
"symbol": ":/symbols/iosv_virl.svg",
"compute_id": "local",
"console": 2001,
"console_type": "telnet",
"label": {
"rotation": 0,
"style": "font-family: TypeWriter;font-size: 10;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
"text": "R1",
"x": 22,
"y": -25
},
"name": "R1",
"node_id": "0bce6ad5-c688-4d4d-a425-f21aaf3927e2",
"node_type": "dynamips",
"port_name_format": "Ethernet{0}",
"port_segment_size": 0,
"first_port_name": null,
"properties": {
"dynamips_id": 1,
"auto_delete_disks": true,
"clock_divisor": 4,
"disk0": 0,
"disk1": 0,
"exec_area": 64,
"idlemax": 500,
"idlesleep": 30,
"image": "c7200-adventerprisek9-mz.124-24.T8.image",
"mac_addr": "ca01.2f39.0000",
"midplane": "vxr",
"mmap": true,
"npe": "npe-400",
"nvram": 512,
"platform": "c7200",
"power_supplies": [
1,
1
],
"ram": 512,
"sensors": [
22,
22,
22,
22
],
"slot0": "C7200-IO-FE",
"sparsemem": true,
"startup_config": "configs/i1_startup-config.cfg",
"system_id": "FTX0945W0MY"
},
"x": -112,
"y": -100,
"z": 1
}
]
},
"type": "topology",
"version": "ANYSTR"
}

View File

@ -0,0 +1,80 @@
{
"auto_start": false,
"name": "1_3_dynamips",
"project_id": "ba5790e1-2f51-443e-a3cc-1a2eee132888",
"revision": 3,
"topology": {
"nodes": [
{
"default_symbol": ":/symbols/iosv_virl.normal.svg",
"description": "Router c7200",
"dynamips_id": 1,
"hover_symbol": ":/symbols/iosv_virl.selected.svg",
"id": 1,
"label": {
"color": "#000000",
"font": "TypeWriter,10,-1,5,75,0,0,0,0,0",
"text": "R1",
"x": 22.6171875,
"y": -25.0
},
"ports": [
{
"adapter_number": 0,
"id": 1,
"name": "FastEthernet0/0",
"port_number": 0
}
],
"properties": {
"auto_delete_disks": true,
"clock_divisor": 4,
"console": 2001,
"disk0": 0,
"disk1": 0,
"exec_area": 64,
"idlemax": 500,
"idlesleep": 30,
"image": "c7200-adventerprisek9-mz.124-24.T8.image",
"mac_addr": "ca01.2f39.0000",
"midplane": "vxr",
"mmap": true,
"name": "R1",
"npe": "npe-400",
"nvram": 512,
"platform": "c7200",
"power_supplies": [
1,
1
],
"ram": 512,
"sensors": [
22,
22,
22,
22
],
"slot0": "C7200-IO-FE",
"sparsemem": true,
"startup_config": "configs/i1_startup-config.cfg",
"system_id": "FTX0945W0MY"
},
"server_id": 1,
"vm_id": "0bce6ad5-c688-4d4d-a425-f21aaf3927e2",
"x": -112.0,
"y": -100.0
}
],
"servers": [
{
"cloud": false,
"host": "127.0.0.1",
"id": 1,
"local": true,
"port": 8000
}
]
},
"type": "topology",
"version": "1.3.13"
}

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import types
import asyncio
import unittest.mock
@ -69,6 +70,10 @@ class AsyncioMagicMock(unittest.mock.MagicMock):
"""
Magic mock returning coroutine
"""
try:
__class__ = types.CoroutineType
except AttributeError: # Not supported with Python 3.4
__class__ = types.GeneratorType
def __init__(self, return_value=None, return_values=None, **kwargs):
"""