Compare commits

...
83 Commits
Author SHA1 Message Date
Julien Duponchelle 38745be5fe 1.3.13 2015-12-11 09:21:20 +01:00
Julien Duponchelle a827f6b2de 1.3.12 2015-12-11 09:18:27 +01:00
grossmj 55034dd24f Update links for new website. 2015-12-10 15:00:16 -07:00
Julien Duponchelle 02cabfbffa Contributing instructions 2015-11-30 14:15:54 +01:00
grossmj 9ca72f3bbc Correctly display log messages. 2015-11-08 12:01:25 -07:00
grossmj e4735308f8 Merge remote-tracking branch 'origin/master' 2015-11-07 12:18:05 -07:00
grossmj 13a3e8f324 Tentative fix for "WinError 64 The specified network name is no longer available" issues. 2015-11-07 12:17:53 -07:00
Julien Duponchelle bd54c55ae1 Fix minor errors reported by codacy.com 2015-11-03 12:34:22 +01:00
Julien Duponchelle a961387af9 Add doc on how to got code coverage 2015-10-31 21:27:10 +01:00
Julien Duponchelle 42c8fb7c29 Raise an error when you use a port outside the ranges
Fix #739
2015-10-30 15:15:28 +01:00
Julien Duponchelle 0a536278fc Fix asyncio error when closing the app
Fix #310, https://github.com/GNS3/gns3-gui/issues/705
2015-10-12 16:16:44 +02:00
grossmj a6286db7b1 Release UDP ports when closing a Qemu VM. Fixes #323. 2015-10-07 20:48:36 -06:00
Julien Duponchelle 6cd790880c 1.3.12dev1 2015-10-07 18:26:18 +02:00
Julien Duponchelle b19bfa0270 1.3.11 release 2015-10-07 18:24:03 +02:00
Julien Duponchelle 98ac295e2e Escape other usage of glob
Fix #332
2015-10-07 16:42:34 +02:00
Julien Duponchelle acd5c992ba Fix Dynamips identifier is already used by another router
Fix #327
2015-10-07 13:50:40 +02:00
Julien Duponchelle 4232ea8a00 Protect dynamips against bad glob
Fix #332
2015-10-07 10:26:37 +02:00
Julien Duponchelle bfe9c117ba Fix tests 2015-10-05 09:14:55 +02:00
grossmj fc5afa6676 Catch ProcessLookupError in Qemu VM. 2015-10-04 14:20:44 -06:00
grossmj 3f86df5169 Fixes uncalled coroutine. 2015-10-04 07:00:47 -06:00
grossmj 970f22a83e Use the correct UDP tunnel Qemu syntax for version > 1.1.0 when legacy networking is enabled. 2015-10-04 06:41:39 -06:00
grossmj 80d99ec395 Fixes some minor issues. 2015-09-29 14:15:01 -06:00
grossmj 8560521028 Merge remote-tracking branch 'origin/master' 2015-09-29 06:58:33 -06:00
grossmj e63e3280a1 Prevent launching a packet capture with a non-ASCII path when using Dynamips. 2015-09-29 06:56:01 -06:00
grossmj 09aa31fb43 Do not automatically delete Dynamips bootflash file because they are necessary to restore VLANs on the c3600 platform. 2015-09-23 13:42:37 +02:00
Julien Duponchelle bfe3b12342 Fix dynamips configuration lost when you delete a node
Fix #316
2015-09-22 14:39:21 +02:00
Julien Duponchelle c43960fbfd Clarify error message when we got UTF-8 chars in the iourc file
Fix #307
2015-09-22 13:45:10 +02:00
grossmj 2a0f1586c8 Merge remote-tracking branch 'origin/master' 2015-09-08 02:08:32 -06:00
grossmj 2c5b2a19f6 Check for valid FR or ATM switch mappings. Fixes #300. 2015-09-08 02:07:06 -06:00
Julien Duponchelle 5fa5e7227d Prepare next release 2015-09-04 21:33:48 +02:00
Julien Duponchelle 2689b6b7bb Prepare 1.3.10 2015-09-04 18:22:59 +02:00
grossmj fda33c56de Catch exception when a process cannot be killed. Fixes #296. 2015-09-03 16:18:27 -06:00
grossmj eb5f9c48f2 Backport: fixes NAT NIO for Qemu VMs (do not launch any legacy scripts) 2015-08-14 22:16:02 -06:00
grossmj 83a7e25389 Fixes Unicode error. Fixes #290. 2015-08-13 20:15:50 -06:00
grossmj 2ce01fafc0 Don't delete Dynamips ROM files. They are used to restore the nvram. 2015-08-10 21:22:21 -06:00
Julien Duponchelle 39c1d2d2dd Prepare 1.3.10 2015-08-03 19:25:56 +02:00
Julien Duponchelle d942f52f7d 1.3.9 2015-08-03 18:52:22 +02:00
grossmj 06a6287dff Backport: removes code that deletes IOS router instance files. 2015-07-31 14:37:23 -06:00
Julien Duponchelle 003c2f5f0b New crash report key 2015-07-28 16:48:22 +02:00
Julien Duponchelle c2ba3f923e 1.3.9dev1 2015-07-27 20:11:49 +02:00
Jeremy bdb23d6d3b 1.3.8 2015-07-27 12:10:01 -06:00
Jeremy 5986849b67 Catch ProcessLookupError when updating iouyap config. Fixes #255. 2015-07-27 11:40:01 -06:00
Jeremy 76fe0cb316 Fixes IOS adapters and WICS cannot be removed. Fixes #282. 2015-07-26 15:51:55 -06:00
Jeremy 4aeb451ffd Makes sure the loop is running when closing the app. 2015-07-26 15:28:14 -06:00
Jeremy 7b001e4d36 Catch GeneratorExit exception. Fixes #231. 2015-07-25 16:46:23 -06:00
Jeremy b1b602db3b Merge remote-tracking branch 'origin/master' 2015-07-25 16:39:45 -06:00
Jeremy cf5af8a6f7 Fixes missing chipset info for VirtualBox VM (maybe some older VirtualBox version don't have it). Fixes #254. 2015-07-25 16:39:38 -06:00
Julien Duponchelle 4814bc6c65 Fix IOURC upload 2015-07-23 11:36:11 +02:00
Jeremy 2655d426c2 Backport: option to drop nvram & disk files for IOS routers in order to save disk space. 2015-07-22 16:02:08 -06:00
Jeremy f1bbacecd6 Backport: Remove timeout to wait for connections to finish. 2015-07-21 18:00:31 -06:00
Jeremy a8142a04f8 Merge remote-tracking branch 'origin/master' 2015-07-21 16:02:52 -06:00
Jeremy 19425b1e9c Backport: Fixes RuntimeError: Event loop is closed. 2015-07-21 16:02:44 -06:00
Jeremy Grossmann 1b066bef92 Merge pull request #272 from GNS3/backup_upload_images_projects
Backup upload images projects
2015-07-21 11:21:12 -07:00
Julien Duponchelle 6bf7a6aa38 Fix after jeremy feedback 2015-07-21 20:19:29 +02:00
Julien Duponchelle fc14deee1b Restore images & projects tarballs 2015-07-21 16:14:03 +02:00
Julien Duponchelle 270017d945 Allow user to backup projects and images 2015-07-21 14:20:58 +02:00
Jeremy Grossmann 23a03a24de Merge pull request #271 from lykinsbd/master
Update gns3.conf.upstart
2015-07-20 22:44:40 -07:00
Jeremy 439e0df058 Bind host on 0.0.0.0 when checking for a free UDP port. 2015-07-20 19:27:14 -06:00
Brett Lykins c94dc1ec07 Update gns3.conf.upstart
Fixing start-stop-daemon syntax so that it accepts the aruments
2015-07-20 10:42:34 -05:00
Julien Duponchelle 2ccb30eeb5 Fix Incorrect vboxmanage sudo command
Fix #247
2015-06-26 10:47:52 +02:00
Julien Duponchelle 389788cfdc 1.3.8 dev 2015-06-22 20:19:38 +02:00
Julien Duponchelle 26fc8236c9 1.3.7 2015-06-22 19:06:25 +02:00
Julien Duponchelle 4216c5d2ed Prevent install on Python 2
Fix #236
2015-06-17 14:08:22 +02:00
Julien Duponchelle afa4ba9b55 Crash report key for 1.3.7 2015-06-17 09:30:56 +02:00
grossmj 7980ae9ab1 Update version_info. 2015-06-16 15:00:25 -06:00
grossmj 3495035dbf Bump version to 1.3.7.dev1 2015-06-16 14:39:20 -06:00
Julien Duponchelle a2d4c2427d 1.3.6 2015-06-16 21:56:44 +02:00
Julien Duponchelle 0dae4b6930 1.3.6dev1 2015-06-16 19:32:53 +02:00
Julien Duponchelle 947a732bfb Remove netifaces dependencies due to IOUVM 2015-06-16 19:14:09 +02:00
Julien Duponchelle d88c5648de 1.3.5 2015-06-16 18:56:50 +02:00
Julien Duponchelle 94fbd3fac9 Ignore invalid characters when reading the output of a process
It should happend only when user try to use another binary and
the code of VPCS, dynamips... Will detect it's not the
correct binary.

For example we detect this error after an user used SupperPutty
instead of VPCS.

Fix #235
2015-06-16 15:48:59 +02:00
Julien Duponchelle 07eab6e766 Fix tests 2015-06-11 09:18:02 +02:00
Julien Duponchelle a2833cf276 Turn on / off authentication 2015-06-10 23:14:18 +02:00
Julien Duponchelle 079715bc18 Ensure no colored output on Windows
Fix #228
2015-06-09 10:05:00 +02:00
grossmj 5a32d8a779 Merge remote-tracking branch 'origin/master' 2015-06-08 11:28:58 -06:00
grossmj 9f1705a4f1 Do not stop saving IOS router configs when there is an exception while a project is committed. 2015-06-08 11:28:48 -06:00
Julien Duponchelle 02650fa490 Create a private config file if expected
Fix #217
2015-06-05 16:23:52 +02:00
Julien Duponchelle fff3e1474f Distribute our own version of netifaces working with python 3
Fix #97
2015-06-05 15:59:43 +02:00
Julien Duponchelle d9de1718b7 Fix crash if a private config exist in IOS but no private config file
Fix #217
2015-06-05 15:30:30 +02:00
Julien Duponchelle 78891ae00e Basic Auth support 2015-06-03 15:38:34 +02:00
Julien Duponchelle b344def887 Fix crash when virtualbox list of VMS return an empty line
Fix #206
2015-06-03 11:59:53 +02:00
Julien Duponchelle a1bc815f63 Update crash report key 2015-06-02 20:27:33 +02:00
Julien Duponchelle 668cc3f0a5 1.3.5dev1 2015-06-02 20:05:31 +02:00
45 changed files with 871 additions and 151 deletions

No files matched your search

+76
View File
@@ -1,5 +1,81 @@
# Change Log
## 1.3.13 11/12/2015
* Update links for new website.
## 1.3.12 11/12/2015
* Contributing instructions
* Correctly display log messages.
* Tentative fix for "WinError 64 The specified network name is no longer available" issues.
* Fix minor errors reported by codacy.com
* Add doc on how to got code coverage
* Raise an error when you use a port outside the ranges
* Fix asyncio error when closing the app
* Release UDP ports when closing a Qemu VM. Fixes #323.
## 1.3.11 07/10/2015
* Escape other usage of glob
* Fix Dynamips identifier is already used by another router
* Protect dynamips against bad glob
* Catch ProcessLookupError in Qemu VM.
* Use the correct UDP tunnel Qemu syntax for version > 1.1.0 when legacy networking is enabled.
* Prevent launching a packet capture with a non-ASCII path when using Dynamips.
* Do not automatically delete Dynamips bootflash file because they are necessary to restore VLANs on the c3600 platform.
* Fix dynamips configuration lost when you delete a node
* Clarify error message when we got UTF-8 chars in the iourc file
* Check for valid FR or ATM switch mappings. Fixes #300.
## 1.3.10 04/09/2015
* Catch exception when a process cannot be killed. Fixes #296.
* Backport: fixes NAT NIO for Qemu VMs (do not launch any legacy scripts)
* Fixes Unicode error. Fixes #290.
* Don't delete Dynamips ROM files. They are used to restore the nvram.
## 1.3.9 03/08/2015
* Backport: removes code that deletes IOS router instance files.
## 1.3.8 27/07/2015
* Catch ProcessLookupError when updating iouyap config. Fixes #255.
* Fixes IOS adapters and WICS cannot be removed. Fixes #282.
* Makes sure the loop is running when closing the app.
* Catch GeneratorExit exception. Fixes #231.
* Fixes missing chipset info for VirtualBox VM. Fixes #254.
* Fixes IOURC upload.
* Restore images & projects tarballs
* Allow users to backup projects and images.
* Update gns3.conf.upstart.
* Fix incorrect vboxmanage sudo command.
* Backport from 1.4: option to drop nvram & disk files for IOS routers in order to save disk space.
* Backport from 1.4: Remove timeout to wait for connections to finish.
* Backport from 1.4: Fixes RuntimeError: Event loop is closed.
* Backport from 1.4: Bind host on 0.0.0.0 when checking for a free UDP port.
## 1.3.7 22/06/2015
* Prevent install on Python 2
## 1.3.6 16/06/2015
* Fix an issue with 1.4dev compatibility
## 1.3.5 16/06/15
* Ignore invalid characters when reading the output of a process
* Turn on / off authentication
* Ensure no colored output on Windows
* Do not stop saving IOS router configs when there is an exception while a project is committed.
* Create a private config file if expected
* Distribute our own version of netifaces working with python 3
* Fix crash if a private config exist in IOS but no private config file
* Basic Auth support
* Fix crash when virtualbox list of VMS return an empty line
## 1.3.4 02/06/15
* Drop useless dependencie dateutil
+50
View File
@@ -0,0 +1,50 @@
# Contributing to GNS3
We welcome contributions and bugs reports from everyone.
We are friendly so don't be afraid to ask questions.
## Bug reports
Before reporting an issue:
* check our website over at https://gns3.com
* check if an issue already exists on https://github.com/GNS3/gns3-gui
* check if an issue already exists on https://github.com/GNS3/gns3-server
Please post on our community website if you are unsure you found a bug,
you will get faster support and be able to exchange with more users.
If you are unsure which project you should create an issue for, just do
it on https://github.com/GNS3/gns3-gui we will take care of the triage.
For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm
## Asking for new features
The best is to start a discussion on the community website in order to get feedback
from the whole community.
## Contributing code
We welcome code contribution from everyone including beginners.
Don't be afraid to submit a half finished or mediocre contribution and we will help you.
Don't hesitate to share your plans before starting working on a contribution, we can help
you to find the best approach.
### Contributors License Agreements
We at GNS3 are eager to work with you. For small changes — little bugfixes, correcting typos, and the like — please just submit pull requests to any of our projects. For larger changes, though, we have to ask you to jump through a little hoop.
In particular, in order for us to accept any major patches from you, you will have to electronically sign a statement that indicates two things:
- You are willingly licensing your contributions under the terms of the open source license of the project that you’re contributing to.
- You are legally able to license your contributions as stated.
The reason we do this is to ensure, to the extent possible, that we don’t “taint” the projects we manage with contributions that turn out to be improper. This protects everyone who wants to use the projects, including you!
More information there: https://github.com/GNS3/cla
### Pull requests
Creating a pull request is the easiest way to contribute code. Do not hesitate to create one early when contributing for new feature in order to get our feedback.
+13
View File
@@ -113,4 +113,17 @@ and homebrew: http://brew.sh/.
python3 setup.py install
gns3server
Running tests
*************
Just run:
.. code:: bash
py.test -vv
If you want test coverage:
.. code:: bash
py.test --cov=gns3server
+2 -2
View File
@@ -2,8 +2,8 @@ Welcome to API documentation!
======================================
.. WARNING::
The API is not stable, feel free to send comment on GNS3 Jungle
https://community.gns3.com/
The API is not stable, feel free to post comments on our website
https://gns3.com/
.. toctree::
general
+1 -1
View File
@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://9e6f04df72c74b6894a6dcd2928d069e:2035d1beb1654136b170f1e91f05ee51@app.getsentry.com/38482"
DSN = "sync+https://e47fab7019fe4fb3b699609bb9f4fefc:d1d3008a6c8a443ba34f336434c2da34@app.getsentry.com/38482"
if hasattr(sys, "frozen"):
cacert = os.path.join(os.getcwd(), "cacert.pem")
if os.path.isfile(cacert):
@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import base64
from ...web.route import Route
@@ -317,6 +318,14 @@ class DynamipsVMHandler:
slot_number = int(request.match_info["adapter_number"])
port_number = int(request.match_info["port_number"])
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
if sys.platform.startswith('win'):
#FIXME: Dynamips (Cygwin actually) doesn't like non ascii paths on Windows
try:
pcap_file_path.encode('ascii')
except UnicodeEncodeError:
raise DynamipsError('The capture file path "{}" must only contain ASCII (English) characters'.format(pcap_file_path))
yield from vm.start_capture(slot_number, port_number, pcap_file_path, request.json["data_link_type"])
response.json({"pcap_file_path": pcap_file_path})
+85 -14
View File
@@ -18,6 +18,9 @@
import os
import aiohttp
import stat
import io
import tarfile
import asyncio
from ..config import Config
from ..web.route import Route
@@ -59,29 +62,97 @@ class UploadHandler:
response.redirect("/upload")
return
if data["type"] not in ["IOU", "IOURC", "QEMU", "IOS"]:
raise aiohttp.web.HTTPForbidden("You are not authorized to upload this kind of image {}".format(data["type"]))
if data["type"] not in ["IOU", "IOURC", "QEMU", "IOS", "IMAGES", "PROJECTS"]:
raise aiohttp.web.HTTPForbidden(text="You are not authorized to upload this kind of image {}".format(data["type"]))
if data["type"] == "IOURC":
destination_dir = os.path.expanduser("~/")
destination_path = os.path.join(destination_dir, ".iourc")
else:
destination_dir = os.path.join(UploadHandler.image_directory(), data["type"])
destination_path = os.path.join(destination_dir, data["file"].filename)
try:
os.makedirs(destination_dir, exist_ok=True)
with open(destination_path, "wb+") as f:
chunk = data["file"].file.read()
f.write(chunk)
st = os.stat(destination_path)
os.chmod(destination_path, st.st_mode | stat.S_IXUSR)
if data["type"] == "IMAGES":
UploadHandler._restore_directory(data["file"], UploadHandler.image_directory())
elif data["type"] == "PROJECTS":
UploadHandler._restore_directory(data["file"], UploadHandler.project_directory())
else:
if data["type"] == "IOURC":
destination_dir = os.path.expanduser("~/")
destination_path = os.path.join(destination_dir, ".iourc")
else:
destination_dir = os.path.join(UploadHandler.image_directory(), data["type"])
destination_path = os.path.join(destination_dir, data["file"].filename)
os.makedirs(destination_dir, exist_ok=True)
with open(destination_path, "wb+") as f:
chunk = data["file"].file.read()
f.write(chunk)
st = os.stat(destination_path)
os.chmod(destination_path, st.st_mode | stat.S_IXUSR)
except OSError as e:
response.html("Could not upload file: {}".format(e))
response.set_status(200)
return
response.redirect("/upload")
@classmethod
@Route.get(
r"/backup/images.tar",
description="Backup GNS3 images",
api_version=None
)
def backup_images(request, response):
yield from UploadHandler._backup_directory(request, response, UploadHandler.image_directory())
@classmethod
@Route.get(
r"/backup/projects.tar",
description="Backup GNS3 projects",
api_version=None
)
def backup_images(request, response):
yield from UploadHandler._backup_directory(request, response, UploadHandler.project_directory())
@staticmethod
def _restore_directory(file, directory):
"""
Extract from HTTP stream the content of a tar
"""
destination_path = os.path.join(directory, "archive.tar")
os.makedirs(directory, exist_ok=True)
with open(destination_path, "wb+") as f:
chunk = file.file.read()
f.write(chunk)
t = tarfile.open(destination_path)
t.extractall(directory)
t.close()
os.remove(destination_path)
@staticmethod
@asyncio.coroutine
def _backup_directory(request, response, directory):
"""
Return a tar archive from a directory
"""
response.content_type = 'application/x-gtar'
response.set_status(200)
response.enable_chunked_encoding()
# Very important: do not send a content length otherwise QT close the connection but curl can consume the Feed
response.content_length = None
response.start(request)
buffer = io.BytesIO()
with tarfile.open('arch.tar', 'w', fileobj=buffer) as tar:
for root, dirs, files in os.walk(directory):
for file in files:
path = os.path.join(root, file)
tar.add(os.path.join(root, file), arcname=os.path.relpath(path, directory))
response.write(buffer.getvalue())
yield from response.drain()
buffer.truncate(0)
buffer.seek(0)
yield from response.write_eof()
@staticmethod
def image_directory():
server_config = Config.instance().get_section_config("Server")
return os.path.expanduser(server_config.get("images_path", "~/GNS3/images"))
@staticmethod
def project_directory():
server_config = Config.instance().get_section_config("Server")
return os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))
+4 -3
View File
@@ -113,7 +113,7 @@ class BaseManager:
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not close VM {}".format(e), exc_info=1)
continue
@@ -237,7 +237,7 @@ class BaseManager:
@asyncio.coroutine
def close_vm(self, vm_id):
"""
Delete a VM
Close a VM
:param vm_id: VM identifier
@@ -305,7 +305,8 @@ class BaseManager:
vm = yield from self.close_vm(vm_id)
vm.project.mark_vm_for_destruction(vm)
del self._vms[vm.id]
if vm.id in self._vms:
del self._vms[vm.id]
return vm
@staticmethod
-1
View File
@@ -61,7 +61,6 @@ class BaseVM:
def __del__(self):
self.close()
if self._temporary_directory is not None:
if os.path.exists(self._temporary_directory):
shutil.rmtree(self._temporary_directory, ignore_errors=True)
+60 -11
View File
@@ -27,13 +27,14 @@ import socket
import time
import asyncio
import tempfile
import glob
import logging
import glob
log = logging.getLogger(__name__)
from gns3server.utils.interfaces import get_windows_interfaces
from gns3server.utils.asyncio import wait_run_in_executor
from gns3server.utils.glob import glob_escape
from pkg_resources import parse_version
from uuid import UUID, uuid4
from ..base_manager import BaseManager
@@ -115,6 +116,43 @@ class Dynamips(BaseManager):
self._devices = {}
self._ghost_files = set()
self._dynamips_path = None
self._dynamips_ids = {}
def get_dynamips_id(self, project_id):
"""
:param project_id: UUID of the project
:returns: a free dynamips id
"""
self._dynamips_ids.setdefault(project_id, set())
dynamips_id = 0
for dynamips_id in range(1, 4097):
if dynamips_id not in self._dynamips_ids[project_id]:
self._dynamips_ids[project_id].add(dynamips_id)
return dynamips_id
raise DynamipsError("Maximum number of Dynamips instances reached")
def take_dynamips_id(self, project_id, dynamips_id):
"""
Reserve a dynamips id or raise an error
:param project_id: UUID of the project
:param dynamips_id: Asked id
"""
self._dynamips_ids.setdefault(project_id, set())
if dynamips_id in self._dynamips_ids[project_id]:
raise DynamipsError("Dynamips identifier {} is already used by another router".format(dynamips_id))
self._dynamips_ids[project_id].add(dynamips_id)
def release_dynamips_id(self, project_id, dynamips_id):
"""
A dynamips id can be reused by another vm
:param project_id: UUID of the project
:param dynamips_id: Asked id
"""
self._dynamips_ids.setdefault(project_id, set())
if dynamips_id in self._dynamips_ids[project_id]:
self._dynamips_ids[project_id].remove(dynamips_id)
@asyncio.coroutine
def unload(self):
@@ -130,7 +168,7 @@ class Dynamips(BaseManager):
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not stop device hypervisor {}".format(e), exc_info=1)
continue
@@ -154,7 +192,7 @@ class Dynamips(BaseManager):
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not delete device {}".format(e), exc_info=1)
@asyncio.coroutine
@@ -164,15 +202,14 @@ class Dynamips(BaseManager):
:param project: Project instance
"""
yield from super().project_closed(project)
# delete useless Dynamips files
project_dir = project.module_working_path(self.module_name.lower())
files = glob.glob(os.path.join(project_dir, "*.ghost"))
files += glob.glob(os.path.join(project_dir, "*_lock"))
files += glob.glob(os.path.join(project_dir, "ilt_*"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_*_rommon_vars"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_*_ssa"))
files = glob.glob(os.path.join(glob_escape(project_dir), "*.ghost"))
files += glob.glob(os.path.join(glob_escape(project_dir), "*_lock"))
files += glob.glob(os.path.join(glob_escape(project_dir), "ilt_*"))
files += glob.glob(os.path.join(glob_escape(project_dir), "c[0-9][0-9][0-9][0-9]_i[0-9]*_rommon_vars"))
files += glob.glob(os.path.join(glob_escape(project_dir), "c[0-9][0-9][0-9][0-9]_i[0-9]*_log.txt"))
for file in files:
try:
log.debug("Deleting file {}".format(file))
@@ -183,6 +220,11 @@ class Dynamips(BaseManager):
log.warn("Could not delete file {}: {}".format(file, e))
continue
# Release the dynamips ids if we want to reload the same project
# later
if project.id in self._dynamips_ids:
del self._dynamips_ids[project.id]
@asyncio.coroutine
def project_moved(self, project):
"""
@@ -210,7 +252,11 @@ class Dynamips(BaseManager):
# save the configs when the project is committed
for vm in self._vms.copy().values():
if vm.project.id == project.id:
yield from vm.save_configs()
try:
yield from vm.save_configs()
except DynamipsError as e:
log.warning(e)
continue
@property
def dynamips_path(self):
@@ -353,7 +399,10 @@ class Dynamips(BaseManager):
ghost_ios_support = self.config.get_section_config("Dynamips").getboolean("ghost_ios_support", True)
if ghost_ios_support:
with (yield from Dynamips._ghost_ios_lock):
yield from self._set_ghost_ios(vm)
try:
yield from self._set_ghost_ios(vm)
except GeneratorExit:
log.warning("Could not create ghost IOS image {} (GeneratorExit)".format(vm.name))
@asyncio.coroutine
def create_nio(self, node, nio_settings):
@@ -267,8 +267,8 @@ class DynamipsHypervisor:
self._writer.write(command.encode())
yield from self._writer.drain()
except OSError as e:
raise DynamipsError("Lost communication with {host}:{port} :{error}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, error=e, run=self.is_running()))
raise DynamipsError("Could not send Dynamips command '{command}' to {host}:{port}: {error}, process running: {run}"
.format(command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()))
# Now retrieve the result
data = []
@@ -282,13 +282,19 @@ class DynamipsHypervisor:
# task has been canceled but continue to read
# any remaining data sent by the hypervisor
continue
except ConnectionResetError as e:
# Sometimes WinError 64 (ERROR_NETNAME_DELETED) is returned here on Windows.
# These happen if connection reset is received before IOCP could complete
# a previous operation. Ignore and try again....
log.warning("Connection reset received while reading Dynamips response: {}".format(e))
continue
if not chunk:
raise DynamipsError("No data returned from {host}:{port}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
buf += chunk.decode("utf-8")
buf += chunk.decode("utf-8", errors="ignore")
except OSError as e:
raise DynamipsError("Lost communication with {host}:{port} :{error}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, error=e, run=self.is_running()))
raise DynamipsError("Could not read response for '{command}' from {host}:{port}: {error}, process running: {run}"
.format(command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()))
# If the buffer doesn't end in '\n' then we can't be done
try:
+6 -1
View File
@@ -140,7 +140,12 @@ class Hypervisor(DynamipsHypervisor):
except asyncio.TimeoutError:
if self._process.returncode is None:
log.warn("Dynamips process {} is still running... killing it".format(self._process.pid))
self._process.kill()
try:
self._process.kill()
except OSError as e:
log.error("Cannot stop the Dynamips process: {}".format(e))
if self._process.returncode is None:
log.warn('Dynamips hypervisor with PID={} is still running'.format(self._process.pid))
if self._stdout_file and os.access(self._stdout_file, os.W_OK):
try:
@@ -51,10 +51,14 @@ class ATMSwitch(Device):
def __json__(self):
mappings = {}
for source, destination in self._mappings.items():
mappings[str(source)] = str(destination)
return {"name": self.name,
"device_id": self.id,
"project_id": self.project.id,
"mappings": self._mappings}
"mappings": mappings}
@asyncio.coroutine
def create(self):
@@ -199,6 +203,8 @@ class ATMSwitch(Device):
pvc_entry = re.compile(r"""^([0-9]*):([0-9]*):([0-9]*)$""")
for source, destination in mappings.items():
if not isinstance(source, str) or not isinstance(destination, str):
raise DynamipsError("Invalid ATM mappings")
match_source_pvc = pvc_entry.search(source)
match_destination_pvc = pvc_entry.search(destination)
if match_source_pvc and match_destination_pvc:
@@ -50,10 +50,14 @@ class FrameRelaySwitch(Device):
def __json__(self):
mappings = {}
for source, destination in self._mappings.items():
mappings[str(source)] = str(destination)
return {"name": self.name,
"device_id": self.id,
"project_id": self.project.id,
"mappings": self._mappings}
"mappings": mappings}
@asyncio.coroutine
def create(self):
@@ -191,6 +195,8 @@ class FrameRelaySwitch(Device):
"""
for source, destination in mappings.items():
if not isinstance(source, str) or not isinstance(destination, str):
raise DynamipsError("Invalid Frame-Relay mappings")
source_port, source_dlci = map(int, source.split(':'))
destination_port, destination_dlci = map(int, destination.split(':'))
if self.has_port(destination_port):
+72 -21
View File
@@ -34,6 +34,7 @@ log = logging.getLogger(__name__)
from ...base_vm import BaseVM
from ..dynamips_error import DynamipsError
from ..nios.nio_udp import NIOUDP
from ....utils.glob import glob_escape
from gns3server.utils.asyncio import wait_run_in_executor
@@ -53,7 +54,6 @@ class Router(BaseVM):
:param platform: Platform of this router
"""
_dynamips_ids = {}
_status = {0: "inactive",
1: "shutting down",
2: "running",
@@ -87,6 +87,7 @@ class Router(BaseVM):
self._exec_area = 64 # 64 MB on other systems
self._disk0 = 0 # Megabytes
self._disk1 = 0 # Megabytes
self._auto_delete_disks = False
self._aux = aux
self._mac_addr = ""
self._system_id = "FTX0945W0MY" # processor board ID in IOS
@@ -94,20 +95,11 @@ class Router(BaseVM):
self._ghost_flag = ghost_flag
if not ghost_flag:
self._dynamips_ids.setdefault(project.id, list())
if not dynamips_id:
# find a Dynamips ID if none is provided (0 < id <= 4096)
self._dynamips_id = 0
for identifier in range(1, 4097):
if identifier not in self._dynamips_ids[project.id]:
self._dynamips_id = identifier
break
if self._dynamips_id == 0:
raise DynamipsError("Maximum number of Dynamips instances reached")
self._dynamips_id = manager.get_dynamips_id(project.id)
else:
if dynamips_id in self._dynamips_ids[project.id]:
raise DynamipsError("Dynamips identifier {} is already used by another router".format(dynamips_id))
self._dynamips_ids[project.id].append(self._dynamips_id)
self._dynamips_id = dynamips_id
manager.take_dynamips_id(project.id, dynamips_id)
if self._aux is not None:
self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project)
@@ -145,6 +137,7 @@ class Router(BaseVM):
"exec_area": self._exec_area,
"disk0": self._disk0,
"disk1": self._disk1,
"auto_delete_disks": self._auto_delete_disks,
"console": self._console,
"aux": self._aux,
"mac_addr": self._mac_addr,
@@ -158,7 +151,7 @@ class Router(BaseVM):
for slot in self._slots:
if slot:
slot = str(slot)
router_info["slot" + str(slot_number)] = slot
router_info["slot" + str(slot_number)] = slot
slot_number += 1
# add the wics
@@ -166,6 +159,8 @@ class Router(BaseVM):
for wic_slot_number in range(0, len(self._slots[0].wics)):
if self._slots[0].wics[wic_slot_number]:
router_info["wic" + str(wic_slot_number)] = str(self._slots[0].wics[wic_slot_number])
else:
router_info["wic" + str(wic_slot_number)] = None
return router_info
@@ -314,8 +309,6 @@ class Router(BaseVM):
return
log.debug('Router "{name}" [{id}] is closing'.format(name=self._name, id=self._id))
if self._dynamips_id in self._dynamips_ids[self._project.id]:
self._dynamips_ids[self._project.id].remove(self._dynamips_id)
if self._console:
self._manager.port_manager.release_tcp_port(self._console, self._project)
@@ -342,6 +335,23 @@ class Router(BaseVM):
pass
yield from self.hypervisor.stop()
if self._auto_delete_disks:
# delete nvram and disk files
project_dir = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()))
files = glob.glob(os.path.join(glob_escape(project_dir), "{}_i{}_disk[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob_escape(project_dir), "{}_i{}_slot[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob_escape(project_dir), "{}_i{}_nvram".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob_escape(project_dir), "{}_i{}_flash[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob_escape(project_dir), "{}_i{}_rom".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob_escape(project_dir), "{}_i{}_bootflash".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob_escape(project_dir), "{}_i{}_ssa").format(self.platform, self.dynamips_id))
for file in files:
try:
log.debug("Deleting file {}".format(file))
yield from wait_run_in_executor(os.remove, file)
except OSError as e:
log.warn("Could not delete file {}: {}".format(file, e))
continue
self._closed = True
@property
@@ -851,6 +861,30 @@ class Router(BaseVM):
new_disk1=disk1))
self._disk1 = disk1
@property
def auto_delete_disks(self):
"""
Returns True if auto delete disks is enabled on this router.
:returns: boolean either auto delete disks is activated or not
"""
return self._auto_delete_disks
@asyncio.coroutine
def set_auto_delete_disks(self, auto_delete_disks):
"""
Enable/disable use of auto delete disks
:param auto_delete_disks: activate/deactivate auto delete disks (boolean)
"""
if auto_delete_disks:
log.info('Router "{name}" [{id}]: auto delete disks enabled'.format(name=self._name, id=self._id))
else:
log.info('Router "{name}" [{id}]: auto delete disks disabled'.format(name=self._name, id=self._id))
self._auto_delete_disks = auto_delete_disks
@asyncio.coroutine
def set_console(self, console):
"""
@@ -1178,11 +1212,20 @@ class Router(BaseVM):
if not adapter.port_exists(port_number):
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
port_number=port_number))
yield from self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number,
nio=nio))
try:
yield from self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number,
nio=nio))
except DynamipsError:
# in case of error try to remove and add the nio binding
yield from self._hypervisor.send('vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number))
yield from self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number,
nio=nio))
log.info('Router "{name}" [{id}]: NIO {nio_name} bound to port {slot_number}/{port_number}'.format(name=self._name,
id=self._id,
@@ -1499,6 +1542,9 @@ class Router(BaseVM):
module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
startup_config_base64, private_config_base64 = yield from self.extract_config()
if startup_config_base64:
if not self.startup_config:
self._startup_config = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id))
try:
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
config = "!\n" + config.replace("\r", "")
@@ -1510,6 +1556,9 @@ class Router(BaseVM):
raise DynamipsError("Could not save the startup configuration {}: {}".format(config_path, e))
if private_config_base64:
if not self.private_config:
self._private_config = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
try:
config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace")
config = "!\n" + config.replace("\r", "")
@@ -1550,6 +1599,8 @@ class Router(BaseVM):
log.warn("Could not delete file {}: {}".format(file, e))
continue
self.manager.release_dynamips_id(self._project.id, self._dynamips_id)
@asyncio.coroutine
def clean_delete(self):
"""
+20 -8
View File
@@ -41,6 +41,7 @@ from ..nios.nio_udp import NIOUDP
from ..nios.nio_tap import NIOTAP
from ..nios.nio_generic_ethernet import NIOGenericEthernet
from ..base_vm import BaseVM
from ...utils.glob import glob_escape
from .ioucon import start_ioucon
import gns3server.utils.asyncio
@@ -385,7 +386,7 @@ class IOUVM(BaseVM):
except configparser.Error as e:
raise IOUError("Could not parse iourc file {}: {}".format(self.iourc_path, e))
except UnicodeDecodeError as e:
raise IOUError("Invalid iourc file {}: {}".format(self.iourc_path, e))
raise IOUError("Non ascii characters in iourc file {}, please remove them: {}".format(self.iourc_path, e))
if "license" not in config:
raise IOUError("License section not found in iourc file {}".format(self.iourc_path))
hostname = socket.gethostname()
@@ -486,10 +487,10 @@ class IOUVM(BaseVM):
"""
destination = os.path.join(self.working_dir, "nvram_{:05d}".format(self.application_id))
for file_path in glob.glob(os.path.join(self.working_dir, "nvram_*")):
for file_path in glob.glob(os.path.join(glob_escape(self.working_dir), "nvram_*")):
shutil.move(file_path, destination)
destination = os.path.join(self.working_dir, "vlan.dat-{:05d}".format(self.application_id))
for file_path in glob.glob(os.path.join(self.working_dir, "vlan.dat-*")):
for file_path in glob.glob(os.path.join(glob_escape(self.working_dir), "vlan.dat-*")):
shutil.move(file_path, destination)
@asyncio.coroutine
@@ -862,7 +863,10 @@ class IOUVM(BaseVM):
port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
def adapter_remove_nio_binding(self, adapter_number, port_number):
"""
@@ -894,8 +898,10 @@ class IOUVM(BaseVM):
port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
return nio
@property
@@ -1052,7 +1058,10 @@ class IOUVM(BaseVM):
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
@asyncio.coroutine
def stop_capture(self, adapter_number, port_number):
@@ -1085,4 +1094,7 @@ class IOUVM(BaseVM):
port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
+8 -3
View File
@@ -34,8 +34,9 @@ class PortManager:
def __init__(self, host="127.0.0.1"):
self._console_host = host
self._udp_host = host
# UDP host must be 0.0.0.0, reason: https://github.com/GNS3/gns3-server/issues/265
self._udp_host = "0.0.0.0"
self._used_tcp_ports = set()
self._used_udp_ports = set()
@@ -90,7 +91,7 @@ class PortManager:
return self._console_port_range
@console_host.setter
@console_port_range.setter
def console_port_range(self, new_range):
assert isinstance(new_range, tuple)
@@ -111,7 +112,7 @@ class PortManager:
return self._udp_port_range
@udp_host.setter
@udp_port_range.setter
def udp_port_range(self, new_range):
assert isinstance(new_range, tuple)
@@ -202,6 +203,8 @@ class PortManager:
if port in self._used_tcp_ports:
raise HTTPConflict(text="TCP port {} already in use on host".format(port, self._console_host))
if port < self._console_port_range[0] or port > self._console_port_range[1]:
raise HTTPConflict(text="TCP port {} is outside the range {}-{}".format(port, self._console_port_range[0], self._console_port_range[1]))
self._used_tcp_ports.add(port)
project.record_tcp_port(port)
log.debug("TCP port {} has been reserved".format(port))
@@ -248,6 +251,8 @@ class PortManager:
if port in self._used_udp_ports:
raise HTTPConflict(text="UDP port {} already in use on host".format(port, self._console_host))
if port < self._udp_port_range[0] or port > self._udp_port_range[1]:
raise HTTPConflict(text="UDP port {} is outside the range {}-{}".format(port, self._udp_port_range[0], self._udp_port_range[1]))
self._used_udp_ports.add(port)
project.record_udp_port(port)
log.debug("UDP port {} has been reserved".format(port))
+1 -1
View File
@@ -363,7 +363,7 @@ class Project:
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not close VM or device {}".format(e), exc_info=1)
if cleanup and os.path.exists(self.path):
+25 -12
View File
@@ -86,7 +86,7 @@ class Qemu(BaseManager):
os.access(os.path.join(path, f), os.X_OK) and \
os.path.isfile(os.path.join(path, f)):
qemu_path = os.path.join(path, f)
version = yield from Qemu._get_qemu_version(qemu_path)
version = yield from Qemu.get_qemu_version(qemu_path)
qemus.append({"path": qemu_path, "version": version})
except OSError:
continue
@@ -95,7 +95,7 @@ class Qemu(BaseManager):
@staticmethod
@asyncio.coroutine
def _get_qemu_version(qemu_path):
def get_qemu_version(qemu_path):
"""
Gets the Qemu version.
@@ -103,17 +103,30 @@ class Qemu(BaseManager):
"""
if sys.platform.startswith("win"):
# Qemu on Windows doesn't return anything with parameter -version
# look for a version number in version.txt file in the same directory instead
version_file = os.path.join(os.path.dirname(qemu_path), "version.txt")
if os.path.isfile(version_file):
try:
with open(version_file, "rb") as file:
version = file.read().decode("utf-8").strip()
match = re.search("[0-9\.]+", version)
if match:
return version
except (UnicodeDecodeError, OSError) as e:
log.warn("could not read {}: {}".format(version_file, e))
return ""
try:
output = yield from subprocess_check_output(qemu_path, "-version")
match = re.search("version\s+([0-9a-z\-\.]+)", output)
if match:
version = match.group(1)
return version
else:
raise QemuError("Could not determine the Qemu version for {}".format(qemu_path))
except subprocess.SubprocessError as e:
raise QemuError("Error while looking for the Qemu version: {}".format(e))
else:
try:
output = yield from subprocess_check_output(qemu_path, "-version")
match = re.search("version\s+([0-9a-z\-\.]+)", output)
if match:
version = match.group(1)
return version
else:
raise QemuError("Could not determine the Qemu version for {}".format(qemu_path))
except subprocess.SubprocessError as e:
raise QemuError("Error while looking for the Qemu version: {}".format(e))
@staticmethod
def get_legacy_vm_workdir(legacy_vm_id, name):
+46 -14
View File
@@ -28,6 +28,7 @@ import shlex
import asyncio
import socket
from pkg_resources import parse_version
from .qemu_error import QemuError
from ..adapters.ethernet_adapter import EthernetAdapter
from ..nios.nio_udp import NIOUDP
@@ -587,7 +588,7 @@ class QemuVM(BaseVM):
log.error("Could not start QEMU {}: {}\n{}".format(self.qemu_path, e, stdout))
raise QemuError("Could not start QEMU {}: {}\n{}".format(self.qemu_path, e, stdout))
self._set_process_priority()
yield from self._set_process_priority()
if self._cpu_throttling:
self._set_cpu_throttling()
@@ -602,11 +603,16 @@ class QemuVM(BaseVM):
log.info('Stopping QEMU VM "{}" PID={}'.format(self._name, self._process.pid))
try:
self._process.terminate()
self._process.wait()
yield from self._process.wait()
except subprocess.TimeoutExpired:
self._process.kill()
try:
self._process.kill()
except OSError as e:
log.error("Cannot stop the Qemu process: {}".format(e))
if self._process.returncode is None:
log.warn('QEMU VM "{}" PID={} is still running'.format(self._name, self._process.pid))
log.warn('QEMU VM "{}" with PID={} is still running'.format(self._name, self._process.pid))
except ProcessLookupError:
pass
self._process = None
self._started = False
self._stop_cpulimit()
@@ -659,11 +665,19 @@ class QemuVM(BaseVM):
"""
log.debug('QEMU VM "{name}" [{id}] is closing'.format(name=self._name, id=self._id))
yield from self.stop()
if self._console:
self._manager.port_manager.release_tcp_port(self._console, self._project)
self._console = None
for adapter in self._ethernet_adapters:
if adapter is not None:
for nio in adapter.ports.values():
if nio and isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project)
yield from self.stop()
@asyncio.coroutine
def _get_vm_status(self):
"""
@@ -983,10 +997,19 @@ class QemuVM(BaseVM):
return options
@asyncio.coroutine
def _network_options(self):
network_options = []
network_options.extend(["-net", "none"]) # we do not want any user networking back-end if no adapter is connected.
patched_qemu = False
if self._legacy_networking:
version = yield from self.manager.get_qemu_version(self.qemu_path)
if version and parse_version(version) < parse_version("1.1.0"):
# this is a patched Qemu if version is below 1.1.0
patched_qemu = True
for adapter_number, adapter in enumerate(self._ethernet_adapters):
# TODO: let users specify a base mac address
mac = "00:00:ab:%s:%s:%02x" % (self.id[-4:-2], self.id[-2:], adapter_number)
@@ -996,11 +1019,21 @@ class QemuVM(BaseVM):
if nio:
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)])
if isinstance(nio, NIOUDP):
network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number,
adapter_number,
nio.lport,
nio.rport,
nio.rhost)])
if patched_qemu:
# use patched Qemu syntax
network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number,
adapter_number,
nio.lport,
nio.rport,
nio.rhost)])
else:
# use UDP tunnel support added in Qemu 1.1.0
network_options.extend(["-net", "socket,vlan={},name=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
adapter_number,
nio.rhost,
nio.rport,
self._host,
nio.lport)])
elif isinstance(nio, NIOTAP):
network_options.extend(["-net", "tap,name=gns3-{},ifname={}".format(adapter_number, nio.tap_device)])
elif isinstance(nio, NIONAT):
@@ -1019,7 +1052,7 @@ class QemuVM(BaseVM):
self._host,
nio.lport)])
elif isinstance(nio, NIOTAP):
network_options.extend(["-netdev", "tap,id=gns3-{},ifname={}".format(adapter_number, nio.tap_device)])
network_options.extend(["-netdev", "tap,id=gns3-{},ifname={},script=no,downscript=no".format(adapter_number, nio.tap_device)])
elif isinstance(nio, NIONAT):
network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_number)])
else:
@@ -1048,8 +1081,7 @@ class QemuVM(BaseVM):
command = [self.qemu_path]
command.extend(["-name", self._name])
command.extend(["-m", str(self._ram)])
disk_options = yield from self._disk_options()
command.extend(disk_options)
command.extend((yield from self._disk_options()))
command.extend(self._linux_boot_options())
command.extend(self._serial_options())
command.extend(self._monitor_options())
@@ -1059,7 +1091,7 @@ class QemuVM(BaseVM):
command.extend(shlex.split(additional_options))
except ValueError as e:
QemuError("Invalid additional options: {} error {}".format(additional_options, e))
command.extend(self._network_options())
command.extend((yield from self._network_options()))
command.extend(self._graphic())
return command
+2 -2
View File
@@ -97,7 +97,7 @@ class VirtualBox(BaseManager):
vbox_user = self.config.get_section_config("VirtualBox").get("vbox_user")
if vbox_user:
# TODO: test & review this part
sudo_command = "sudo -i -u {}".format(vbox_user) + " ".join(command)
sudo_command = "sudo -i -u {} ".format(vbox_user) + " ".join(command)
process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
else:
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
@@ -168,7 +168,7 @@ class VirtualBox(BaseManager):
vms = []
result = yield from self.execute("list", ["vms"])
for line in result:
if line[0] != '"' or line[-1:] != "}":
if len(line) == 0 or line[0] != '"' or line[-1:] != "}":
continue # Broken output (perhaps a carriage return in VM name
vmname, _ = line.rsplit(' ', 1)
vmname = vmname.strip('"')
@@ -279,7 +279,7 @@ class VirtualBoxVM(BaseVM):
try:
with open(hdd_info_file, "r", encoding="utf-8") as f:
hdd_table = json.load(f)
except OSError as e:
except (ValueError, OSError) as e:
raise VirtualBoxError("Could not read HDD info file: {}".format(e))
for hdd_info in hdd_table:
@@ -574,10 +574,11 @@ class VirtualBoxVM(BaseVM):
# check the maximum number of adapters supported by the VM
vm_info = yield from self._get_vm_info()
chipset = vm_info["chipset"]
maximum_adapters = 8
if chipset == "ich9":
maximum_adapters = int(self._system_properties["Maximum ICH9 Network Adapter count"])
if "chipset" in vm_info:
chipset = vm_info["chipset"]
if chipset == "ich9":
maximum_adapters = int(self._system_properties["Maximum ICH9 Network Adapter count"])
return maximum_adapters
def _get_pipe_name(self):
+3 -2
View File
@@ -253,11 +253,12 @@ class VPCSVM(BaseVM):
yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=3)
except asyncio.TimeoutError:
if self._process.returncode is None:
log.warn("VPCS process {} is still running... killing it".format(self._process.pid))
try:
self._process.kill()
except OSError as e:
raise VPCSError("Can not stop the VPCS process: {}".format(e))
log.error("Cannot stop the VPCS process: {}".format(e))
if self._process.returncode is None:
log.warn('VPCS VM "{}" with PID={} is still running'.format(self._name, self._process.pid))
self._process = None
self._started = False
+12
View File
@@ -120,6 +120,10 @@ VM_CREATE_SCHEMA = {
"description": "disk1 size in MB",
"type": "integer"
},
"auto_delete_disks": {
"description": "automatically delete nvram and disk files",
"type": "boolean"
},
"console": {
"description": "console TCP port",
"type": "integer",
@@ -337,6 +341,10 @@ VM_UPDATE_SCHEMA = {
"description": "disk1 size in MB",
"type": "integer"
},
"auto_delete_disks": {
"description": "automatically delete nvram and disk files",
"type": "boolean"
},
"console": {
"description": "console TCP port",
"type": "integer",
@@ -591,6 +599,10 @@ VM_OBJECT_SCHEMA = {
"description": "disk1 size in MB",
"type": "integer"
},
"auto_delete_disks": {
"description": "automatically delete nvram and disk files",
"type": "boolean"
},
"console": {
"description": "console TCP port",
"type": "integer",
+15 -4
View File
@@ -86,6 +86,7 @@ class Server:
if self._handler:
yield from self._handler.finish_connections()
self._handler = None
for module in MODULES:
log.debug("Unloading module {}".format(module.__name__))
@@ -102,10 +103,9 @@ class Server:
def _signal_handling(self):
@asyncio.coroutine
def signal_handler(signame):
log.warning("Server has got signal {}, exiting...".format(signame))
yield from self.shutdown_server()
asyncio.async(self.shutdown_server())
signals = ["SIGTERM", "SIGINT"]
if sys.platform.startswith("win"):
@@ -114,7 +114,7 @@ class Server:
signals.extend(["SIGHUP", "SIGQUIT"])
for signal_name in signals:
callback = functools.partial(asyncio.async, signal_handler(signal_name))
callback = functools.partial(signal_handler, signal_name)
if sys.platform.startswith("win"):
# add_signal_handler() is not yet supported on Windows
signal.signal(getattr(signal, signal_name), callback)
@@ -175,6 +175,10 @@ class Server:
yield from embed(globals(), locals(), return_asyncio_coroutine=True, patch_stdout=True)
def _exit_handling(self):
"""
Makes sure the asyncio loop is closed.
"""
def close_asyncio_loop():
loop = None
try:
@@ -226,7 +230,8 @@ class Server:
log.info("Starting server on {}:{}".format(self._host, self._port))
self._handler = app.make_handler(handler=RequestHandler)
self._loop.run_until_complete(self._run_application(self._handler, ssl_context))
server = self._run_application(self._handler, ssl_context)
self._loop.run_until_complete(server)
self._signal_handling()
self._exit_handling()
@@ -245,3 +250,9 @@ class Server:
# on Windows when the process gets the SIGBREAK signal
# TypeError: async() takes 1 positional argument but 3 were given
log.warning("TypeError exception in the loop {}".format(e))
finally:
if self._handler and self._loop.is_running():
self._loop.run_until_complete(self._handler.finish_connections())
server.close()
if self._loop.is_running():
self._loop.run_until_complete(app.finish())
+2 -2
View File
@@ -4,8 +4,8 @@
Welcome to GNS 3.
</h3>
<ul>
<li><a href="http://community.gns3.com">Community</a></li>
<li><a href="https://gns3.com">Website</a></li>
<li><a href="http://api.gns3.net">API documentation</a></li>
<li><a href="/upload">Upload images</a></li>
<li><a href="/upload">Upload images & backup</a></li>
</ul>
{% endblock %}
+9
View File
@@ -4,6 +4,15 @@
<title>GNS3 Server</title>
</head>
<body>
<div>
<a href="/">Home</a>
|
<a href="/upload">Upload</a>
|
<a href="/backup/images.tar">Backup images</a>
|
<a href="/backup/projects.tar">Backup projects</a>
</div>
{% block body %}{% endblock %}
</body>
<small>
+2
View File
@@ -8,6 +8,8 @@
<option value="IOURC">IOU licence (iourc)</option>
<option value="IOS">IOS</option>
<option value="QEMU">Qemu</option>
<option value="IMAGES">GNS3 images backup (.tar)</option>
<option value="PROJECTS">GNS3 projects backup (.tar)</option>
</select>
<br />
<br />
+4 -1
View File
@@ -51,7 +51,10 @@ def subprocess_check_output(*args, cwd=None, env=None):
output = yield from proc.stdout.read()
if output is None:
return ""
return output.decode("utf-8")
# If we received garbage we ignore invalid characters
# it should happend only when user try to use another binary
# and the code of VPCS, dynamips... Will detect it's not the correct binary
return output.decode("utf-8", errors="ignore")
@asyncio.coroutine
+45
View File
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 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 sys
import re
import os
import glob
def glob_escape(pathname):
"""
Escape all special chars for glob.
For Python after 3.4 we use the glob.escape method.
:returns: Escaped path
"""
if sys.version_info < (3, 4):
# Extracted from Python 3.4 source code
# Escaping is done by wrapping any of "*?[" between square brackets.
# Metacharacters do not work in the drive part and shouldn't be escaped.
magic_check = re.compile('([*?[])')
magic_check_bytes = re.compile(b'([*?[])')
drive, pathname = os.path.splitdrive(pathname)
if isinstance(pathname, bytes):
pathname = magic_check_bytes.sub(br'[\1]', pathname)
else:
pathname = magic_check.sub(r'[\1]', pathname)
return drive + pathname
else:
return glob.escape(pathname)
+2 -2
View File
@@ -23,5 +23,5 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "1.3.4"
__version_info__ = (1, 3, 4, 0)
__version__ = "1.3.13"
__version_info__ = (1, 3, 13, 0)
+2 -3
View File
@@ -34,7 +34,7 @@ class ColouredFormatter(logging.Formatter):
message = super().format(record)
if not colour:
if not colour or sys.platform.startswith("win"):
return message.replace("#RESET#", "")
level_no = record.levelno
@@ -90,11 +90,10 @@ class WinStreamHandler(logging.StreamHandler):
stream = self.stream
try:
msg = self.formatter.format(record, stream.isatty())
msg = self.formatter.format(record, stream.isatty())
stream.write(msg.encode(stream.encoding, errors="replace").decode(stream.encoding))
stream.write(self.terminator)
self.flush()
pass
except Exception:
self.handleError(record)
+32 -1
View File
@@ -83,6 +83,31 @@ class Route(object):
def delete(cls, path, *args, **kw):
return cls._route('DELETE', path, *args, **kw)
@classmethod
def authenticate(cls, request, route, server_config):
"""
Ask user for authentication
:returns: Response if you need to auth the user otherwise None
"""
if not server_config.getboolean("auth", False):
return
user = server_config.get("user", "").strip()
password = server_config.get("password", "").strip()
if len(user) == 0:
return
if "AUTHORIZATION" in request.headers:
if request.headers["AUTHORIZATION"] == aiohttp.helpers.BasicAuth(user, password).encode():
return
response = Response(request=request, route=route)
response.set_status(401)
response.headers["WWW-Authenticate"] = 'Basic realm="GNS3 server"'
return response
@classmethod
def _route(cls, method, path, *args, **kw):
# This block is executed only the first time
@@ -118,6 +143,13 @@ class Route(object):
def control_schema(request):
# This block is executed at each method call
server_config = Config.instance().get_section_config("Server")
# Authenticate
response = cls.authenticate(request, route, server_config)
if response:
return response
# Non API call
if api_version is None:
response = Response(request=request, route=route, output_schema=output_schema)
@@ -127,7 +159,6 @@ class Route(object):
# API call
try:
request = yield from parse_request(request, input_schema)
server_config = Config.instance().get_section_config("Server")
record_file = server_config.get("record")
if record_file:
try:
+1 -1
View File
@@ -6,7 +6,7 @@ stop on shutdown
script
echo $$ > /var/run/gns3.pid
exec start-stop-daemon --start -c gns3 --exec /usr/local/bin/gns3server --log /var/log/gns3.log
exec start-stop-daemon --start -c gns3 --exec /usr/local/bin/gns3server -- --log /var/log/gns3.log
end script
pre-start script
+1 -1
View File
@@ -1,4 +1,4 @@
netifaces==0.10.4
gns3-netifaces==0.10.4.1
jsonschema==2.4.0
aiohttp==0.14.4
Jinja2==2.7.3
+11 -5
View File
@@ -19,6 +19,10 @@ import sys
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
# we only support Python 3 version >= 3.3
if sys.version_info < (3, 3):
raise SystemExit("Python 3.3 or higher is required")
class PyTest(TestCommand):
@@ -34,12 +38,14 @@ class PyTest(TestCommand):
sys.exit(errcode)
dependencies = ["aiohttp>=0.14.4",
"jsonschema>=2.4.0",
"Jinja2>=2.7.3",
"raven>=5.2.0"]
dependencies = [
# "gns3-netifaces>=0.10.4.1",
"aiohttp>=0.14.4",
"jsonschema>=2.4.0",
"Jinja2>=2.7.3",
"raven>=5.2.0"]
#if not sys.platform.startswith("win"):
# if not sys.platform.startswith("win"):
# dependencies.append("netifaces==0.10.4")
if sys.version_info == (3, 3):
+2 -1
View File
@@ -73,7 +73,7 @@ def _get_unused_port():
return port
@pytest.fixture(scope="session")
@pytest.fixture
def server(request, loop, port_manager, monkeypatch):
"""A GNS3 server"""
@@ -136,6 +136,7 @@ def run_around_tests(monkeypatch):
config = Config.instance()
config.clear()
config.set("Server", "project_directory", tmppath)
config.set("Server", "auth", False)
# Prevent exectuions of the VM if we forgot to mock something
config.set("VirtualBox", "vboxmanage_path", tmppath)
+4 -1
View File
@@ -88,7 +88,10 @@ class Query:
except ValueError:
response.json = None
else:
response.html = response.body.decode("utf-8")
try:
response.html = response.body.decode("utf-8")
except UnicodeDecodeError:
response.html = None
else:
response.json = {}
response.html = ""
+1 -1
View File
@@ -27,5 +27,5 @@ def test_index(server):
response = server.get('/', api_version=None)
assert response.status == 200
html = response.html
assert "Community" in html
assert "Website" in html
assert __version__ in html
+138 -2
View File
@@ -17,10 +17,15 @@
import aiohttp
import asyncio
import os
import tarfile
from unittest.mock import patch
from gns3server.config import Config
def test_index_upload(server):
response = server.get('/upload', api_version=None)
assert response.status == 200
@@ -37,10 +42,141 @@ def test_upload(server, tmpdir):
body.add_field("type", "QEMU")
body.add_field("file", open(str(tmpdir / "test"), "rb"), content_type="application/iou", filename="test2")
with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}):
response = server.post('/upload', api_version=None, body=body, raw=True)
Config.instance().set("Server", "images_path", str(tmpdir))
response = server.post('/upload', api_version=None, body=body, raw=True)
with open(str(tmpdir / "QEMU" / "test2")) as f:
assert f.read() == "TEST"
assert "test2" in response.body.decode("utf-8")
def test_upload_images_backup(server, tmpdir):
Config.instance().set("Server", "images_path", str(tmpdir / 'images'))
os.makedirs(str(tmpdir / 'images' / 'IOU'))
# An old IOU image that we need to replace
with open(str(tmpdir / 'images' / 'IOU' / 'b.img'), 'w+') as f:
f.write('bad')
os.makedirs(str(tmpdir / 'old' / 'QEMU'))
with open(str(tmpdir / 'old' / 'QEMU' / 'a.img'), 'w+') as f:
f.write('hello')
os.makedirs(str(tmpdir / 'old' / 'IOU'))
with open(str(tmpdir / 'old' / 'IOU' / 'b.img'), 'w+') as f:
f.write('world')
os.chdir(str(tmpdir / 'old'))
with tarfile.open(str(tmpdir / 'test.tar'), 'w') as tar:
tar.add('.', recursive=True)
body = aiohttp.FormData()
body.add_field('type', 'IMAGES')
body.add_field('file', open(str(tmpdir / 'test.tar'), 'rb'), content_type='application/x-gtar', filename='test.tar')
response = server.post('/upload', api_version=None, body=body, raw=True)
assert response.status == 200
with open(str(tmpdir / 'images' / 'QEMU' / 'a.img')) as f:
assert f.read() == 'hello'
with open(str(tmpdir / 'images' / 'IOU' / 'b.img')) as f:
assert f.read() == 'world'
assert 'a.img' in response.body.decode('utf-8')
assert 'b.img' in response.body.decode('utf-8')
assert not os.path.exists(str(tmpdir / 'images' / 'archive.tar'))
def test_upload_projects_backup(server, tmpdir):
Config.instance().set("Server", "projects_path", str(tmpdir / 'projects'))
os.makedirs(str(tmpdir / 'projects' / 'b'))
# An old b image that we need to replace
with open(str(tmpdir / 'projects' / 'b' / 'b.img'), 'w+') as f:
f.write('bad')
os.makedirs(str(tmpdir / 'old' / 'a'))
with open(str(tmpdir / 'old' / 'a' / 'a.img'), 'w+') as f:
f.write('hello')
os.makedirs(str(tmpdir / 'old' / 'b'))
with open(str(tmpdir / 'old' / 'b' / 'b.img'), 'w+') as f:
f.write('world')
os.chdir(str(tmpdir / 'old'))
with tarfile.open(str(tmpdir / 'test.tar'), 'w') as tar:
tar.add('.', recursive=True)
body = aiohttp.FormData()
body.add_field('type', 'PROJECTS')
body.add_field('file', open(str(tmpdir / 'test.tar'), 'rb'), content_type='application/x-gtar', filename='test.tar')
response = server.post('/upload', api_version=None, body=body, raw=True)
assert response.status == 200
with open(str(tmpdir / 'projects' / 'a' / 'a.img')) as f:
assert f.read() == 'hello'
with open(str(tmpdir / 'projects' / 'b' / 'b.img')) as f:
assert f.read() == 'world'
assert 'a.img' not in response.body.decode('utf-8')
assert 'b.img' not in response.body.decode('utf-8')
assert not os.path.exists(str(tmpdir / 'projects' / 'archive.tar'))
def test_backup_images(server, tmpdir, loop):
Config.instance().set('Server', 'images_path', str(tmpdir))
os.makedirs(str(tmpdir / 'QEMU'))
with open(str(tmpdir / 'QEMU' / 'a.img'), 'w+') as f:
f.write('hello')
with open(str(tmpdir / 'QEMU' / 'b.img'), 'w+') as f:
f.write('world')
response = server.get('/backup/images.tar', api_version=None, raw=True)
assert response.status == 200
assert response.headers['CONTENT-TYPE'] == 'application/x-gtar'
with open(str(tmpdir / 'images.tar'), 'wb+') as f:
print(len(response.body))
f.write(response.body)
tar = tarfile.open(str(tmpdir / 'images.tar'), 'r')
os.makedirs(str(tmpdir / 'extract'))
os.chdir(str(tmpdir / 'extract'))
# Extract to current working directory
tar.extractall()
tar.close()
assert os.path.exists(os.path.join('QEMU', 'a.img'))
assert open(os.path.join('QEMU', 'a.img')).read() == 'hello'
assert os.path.exists(os.path.join('QEMU', 'b.img'))
assert open(os.path.join('QEMU', 'b.img')).read() == 'world'
def test_backup_projects(server, tmpdir, loop):
Config.instance().set('Server', 'projects_path', str(tmpdir))
os.makedirs(str(tmpdir / 'a'))
with open(str(tmpdir / 'a' / 'a.gns3'), 'w+') as f:
f.write('hello')
os.makedirs(str(tmpdir / 'b'))
with open(str(tmpdir / 'b' / 'b.gns3'), 'w+') as f:
f.write('world')
response = server.get('/backup/projects.tar', api_version=None, raw=True)
assert response.status == 200
assert response.headers['CONTENT-TYPE'] == 'application/x-gtar'
with open(str(tmpdir / 'projects.tar'), 'wb+') as f:
print(len(response.body))
f.write(response.body)
tar = tarfile.open(str(tmpdir / 'projects.tar'), 'r')
os.makedirs(str(tmpdir / 'extract'))
os.chdir(str(tmpdir / 'extract'))
# Extract to current working directory
tar.extractall()
tar.close()
assert os.path.exists(os.path.join('a', 'a.gns3'))
assert open(os.path.join('a', 'a.gns3')).read() == 'hello'
assert os.path.exists(os.path.join('b', 'b.gns3'))
assert open(os.path.join('b', 'b.gns3')).read() == 'world'
@@ -19,6 +19,9 @@
import pytest
import tempfile
import sys
import uuid
import os
import asyncio
from gns3server.modules.dynamips import Dynamips
from gns3server.modules.dynamips.dynamips_error import DynamipsError
@@ -37,9 +40,57 @@ def test_vm_invalid_dynamips_path(manager):
with pytest.raises(DynamipsError):
manager.find_dynamips()
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported by Windows")
def test_vm_non_executable_dynamips_path(manager):
tmpfile = tempfile.NamedTemporaryFile()
with patch("gns3server.config.Config.get_section_config", return_value={"dynamips_path": tmpfile.name}):
with pytest.raises(DynamipsError):
manager.find_dynamips()
def test_get_dynamips_id(manager):
project_1 = str(uuid.uuid4())
project_2 = str(uuid.uuid4())
project_3 = str(uuid.uuid4())
assert manager.get_dynamips_id(project_1) == 1
assert manager.get_dynamips_id(project_1) == 2
assert manager.get_dynamips_id(project_2) == 1
with pytest.raises(DynamipsError):
for dynamips_id in range(1, 4098):
manager.get_dynamips_id(project_3)
def test_take_dynamips_id(manager):
project_1 = str(uuid.uuid4())
manager.take_dynamips_id(project_1, 1)
assert manager.get_dynamips_id(project_1) == 2
with pytest.raises(DynamipsError):
manager.take_dynamips_id(project_1, 1)
def test_release_dynamips_id(manager):
project_1 = str(uuid.uuid4())
project_2 = str(uuid.uuid4())
manager.take_dynamips_id(project_1, 1)
manager.release_dynamips_id(project_1, 1)
assert manager.get_dynamips_id(project_1) == 1
# Should not crash for 0 id
manager.release_dynamips_id(project_2, 0)
def test_project_closed(manager, project, loop):
manager._dynamips_ids[project.id] = set([1, 2, 3])
project_dir = project.module_working_path(manager.module_name.lower())
os.makedirs(project_dir)
open(os.path.join(project_dir, "test.ghost"), "w+").close()
loop.run_until_complete(asyncio.async(manager.project_closed(project)))
assert not os.path.exists(os.path.join(project_dir, "test.ghost"))
assert project.id not in manager._dynamips_ids
+1 -1
View File
@@ -27,7 +27,7 @@ from tests.utils import asyncio_patch
def test_get_qemu_version(loop):
with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock:
version = loop.run_until_complete(asyncio.async(Qemu._get_qemu_version("/tmp/qemu-test")))
version = loop.run_until_complete(asyncio.async(Qemu.get_qemu_version("/tmp/qemu-test")))
if sys.platform.startswith("win"):
assert version == ""
else:
+21 -7
View File
@@ -26,25 +26,39 @@ from gns3server.modules.project import Project
def test_reserve_tcp_port():
pm = PortManager()
project = Project()
pm.reserve_tcp_port(4242, project)
pm.reserve_tcp_port(2001, project)
with pytest.raises(aiohttp.web.HTTPConflict):
pm.reserve_tcp_port(4242, project)
pm.reserve_tcp_port(2001, project)
def test_reserve_tcp_port_outside_range():
pm = PortManager()
project = Project()
with pytest.raises(aiohttp.web.HTTPConflict):
pm.reserve_tcp_port(80, project)
def test_reserve_udp_port():
pm = PortManager()
project = Project()
pm.reserve_udp_port(4242, project)
pm.reserve_udp_port(10000, project)
with pytest.raises(aiohttp.web.HTTPConflict):
pm.reserve_udp_port(4242, project)
pm.reserve_udp_port(10000, project)
def test_reserve_udp_port_outside_range():
pm = PortManager()
project = Project()
with pytest.raises(aiohttp.web.HTTPConflict):
pm.reserve_udp_port(80, project)
def test_release_udp_port():
pm = PortManager()
project = Project()
pm.reserve_udp_port(4242, project)
pm.release_udp_port(4242, project)
pm.reserve_udp_port(4242, project)
pm.reserve_udp_port(10000, project)
pm.release_udp_port(10000, project)
pm.reserve_udp_port(10000, project)
def test_find_unused_port():
@@ -75,9 +75,9 @@ def test_get_list(manager, loop):
vm_list = ['"Windows 8.1" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}',
'"Carriage',
'Return" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}',
'',
'"<inaccessible>" {42b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}',
'"Linux Microcore 4.7.1" {ccd8c50b-c172-457d-99fa-dd69371ede0e}'
]
'"Linux Microcore 4.7.1" {ccd8c50b-c172-457d-99fa-dd69371ede0e}']
@asyncio.coroutine
def execute_mock(cmd, args):
-9
View File
@@ -171,15 +171,6 @@ def test_update_startup_script(vm):
assert f.read() == content
def test_update_startup_script(vm):
content = "echo GNS3 VPCS\nip 192.168.1.2\n"
vm.startup_script = content
filepath = os.path.join(vm.working_dir, 'startup.vpc')
assert os.path.exists(filepath)
with open(filepath) as f:
assert f.read() == content
def test_update_startup_script_h(vm):
content = "setname %h\n"
vm.name = "pc1"