diff --git a/docs/api/examples/delete_vpcsvpcsidportsportidnio.txt b/docs/api/examples/delete_vpcsvpcsidportsportidnio.txt new file mode 100644 index 00000000..37bc3fda --- /dev/null +++ b/docs/api/examples/delete_vpcsvpcsidportsportidnio.txt @@ -0,0 +1,15 @@ +curl -i -xDELETE 'http://localhost:8000/vpcs/{vpcs_id}/ports/{port_id}/nio' + +DELETE /vpcs/{vpcs_id}/ports/{port_id}/nio HTTP/1.1 + + + +HTTP/1.1 200 +CONNECTION: close +CONTENT-LENGTH: 2 +CONTENT-TYPE: application/json +DATE: Thu, 08 Jan 2015 16:09:15 GMT +SERVER: Python/3.4 aiohttp/0.13.1 +X-ROUTE: /vpcs/{vpcs_id}/ports/{port_id}/nio + +{} diff --git a/docs/api/examples/post_vpcsvpcsidportsportidnio.txt b/docs/api/examples/post_vpcsvpcsidportsportidnio.txt new file mode 100644 index 00000000..06fbc0fb --- /dev/null +++ b/docs/api/examples/post_vpcsvpcsidportsportidnio.txt @@ -0,0 +1,25 @@ +curl -i -xPOST 'http://localhost:8000/vpcs/{vpcs_id}/ports/{port_id}/nio' -d '{"lport": 4242, "rhost": "127.0.0.1", "rport": 4343, "type": "nio_udp"}' + +POST /vpcs/{vpcs_id}/ports/{port_id}/nio HTTP/1.1 +{ + "lport": 4242, + "rhost": "127.0.0.1", + "rport": 4343, + "type": "nio_udp" +} + + +HTTP/1.1 200 +CONNECTION: close +CONTENT-LENGTH: 89 +CONTENT-TYPE: application/json +DATE: Thu, 08 Jan 2015 16:09:15 GMT +SERVER: Python/3.4 aiohttp/0.13.1 +X-ROUTE: /vpcs/{vpcs_id}/ports/{port_id}/nio + +{ + "lport": 4242, + "rhost": "127.0.0.1", + "rport": 4343, + "type": "nio_udp" +} diff --git a/docs/api/sleep.rst b/docs/api/sleep.rst deleted file mode 100644 index fbf7845d..00000000 --- a/docs/api/sleep.rst +++ /dev/null @@ -1,13 +0,0 @@ -/sleep ------------------------------- - -.. contents:: - -GET /sleep -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -Response status codes -************************** -- **200**: OK - diff --git a/docs/api/stream.rst b/docs/api/stream.rst deleted file mode 100644 index 00180c62..00000000 --- a/docs/api/stream.rst +++ /dev/null @@ -1,13 +0,0 @@ -/stream ------------------------------- - -.. contents:: - -GET /stream -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -Response status codes -************************** -- **200**: OK - diff --git a/docs/conf.py b/docs/conf.py index 346e586a..73bef3c9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -90,6 +90,7 @@ exclude_patterns = ['_build'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' + # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -102,6 +103,10 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' +#html_theme = 'nature' + +#If uncommented it's turn off the default read the doc style +html_style = "/default.css" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -162,7 +167,7 @@ html_static_path = ['_static'] # html_split_index = False # If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True +html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True diff --git a/gns3server/handlers/vpcs_handler.py b/gns3server/handlers/vpcs_handler.py index 86d97144..4719f0b0 100644 --- a/gns3server/handlers/vpcs_handler.py +++ b/gns3server/handlers/vpcs_handler.py @@ -18,7 +18,7 @@ from ..web.route import Route from ..schemas.vpcs import VPCS_CREATE_SCHEMA from ..schemas.vpcs import VPCS_OBJECT_SCHEMA -from ..schemas.vpcs import VPCS_ADD_NIO_SCHEMA +from ..schemas.vpcs import VPCS_NIO_SCHEMA from ..modules.vpcs import VPCS @@ -53,6 +53,7 @@ class VPCSHandler(object): }, status_codes={ 204: "VPCS instance started", + 404: "VPCS instance doesn't exist" }, description="Start a VPCS instance") def create(request, response): @@ -68,7 +69,8 @@ class VPCSHandler(object): "id": "VPCS instance ID" }, status_codes={ - 201: "Success of stopping VPCS", + 204: "VPCS instance stopped", + 404: "VPCS instance doesn't exist" }, description="Stop a VPCS instance") def create(request, response): @@ -77,44 +79,42 @@ class VPCSHandler(object): yield from vpcs_manager.stop_vm(int(request.match_info["id"])) response.json({}) - @classmethod - @Route.get( - r"/vpcs/{id:\d+}", - parameters={ - "id": "VPCS instance ID" - }, - description="Get information about a VPCS", - output=VPCS_OBJECT_SCHEMA) - def show(request, response): - - response.json({'name': "PC 1", "id": 42, "console": 4242}) - - @classmethod - @Route.put( - r"/vpcs/{id:\d+}", - parameters={ - "id": "VPCS instance ID" - }, - description="Update VPCS information", - input=VPCS_OBJECT_SCHEMA, - output=VPCS_OBJECT_SCHEMA) - def update(request, response): - - response.json({'name': "PC 1", "id": 42, "console": 4242}) - - @classmethod @Route.post( - r"/vpcs/{id:\d+}/nio", + r"/vpcs/{id:\d+}/ports/{port_id}/nio", parameters={ - "id": "VPCS instance ID" + "id": "VPCS instance ID", + "port_id": "Id of the port where the nio should be add" }, status_codes={ 201: "NIO created", - 409: "Conflict" + 404: "VPCS instance doesn't exist" }, - description="ADD NIO to a VPCS", - input=VPCS_ADD_NIO_SCHEMA) + description="Add a NIO to a VPCS", + input=VPCS_NIO_SCHEMA, + output=VPCS_NIO_SCHEMA) def create_nio(request, response): - # TODO: raise 404 if VPCS not found - response.json({'name': "PC 2", "id": 42, "console": 4242}) + vpcs_manager = VPCS.instance() + vm = vpcs_manager.get_vm(int(request.match_info["id"])) + nio = vm.port_add_nio_binding(int(request.match_info["port_id"]), request.json) + + response.json(nio) + + @classmethod + @Route.delete( + r"/vpcs/{id:\d+}/ports/{port_id}/nio", + parameters={ + "id": "VPCS instance ID", + "port_id": "Id of the port where the nio should be remove" + }, + status_codes={ + 200: "NIO deleted", + 404: "VPCS instance doesn't exist" + }, + description="Remove a NIO from a VPCS") + def delete_nio(request, response): + + vpcs_manager = VPCS.instance() + vm = vpcs_manager.get_vm(int(request.match_info["id"])) + nio = vm.port_remove_nio_binding(int(request.match_info["port_id"])) + response.json({}) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 923576b5..36d031a6 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -39,7 +39,7 @@ class BaseManager: :returns: instance of Manager """ - if not hasattr(cls, "_instance"): + if not hasattr(cls, "_instance") or cls._instance is None: cls._instance = cls() return cls._instance @@ -48,7 +48,7 @@ class BaseManager: def destroy(cls): cls._instance = None - def _get_vm_instance(self, vm_id): + def get_vm(self, vm_id): """ Returns a VM instance. @@ -80,10 +80,10 @@ class BaseManager: @asyncio.coroutine def start_vm(self, vm_id): - vm = self._get_vm_instance(vm_id) + vm = self.get_vm(vm_id) yield from vm.start() @asyncio.coroutine def stop_vm(self, vm_id): - vm = self._get_vm_instance(vm_id) + vm = self.get_vm(vm_id) yield from vm.stop() diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py index 19571831..5fe85b64 100644 --- a/gns3server/modules/base_vm.py +++ b/gns3server/modules/base_vm.py @@ -19,74 +19,28 @@ import asyncio from .vm_error import VMError from .attic import find_unused_port +from ..config import Config import logging log = logging.getLogger(__name__) class BaseVM: - _allocated_console_ports = [] - def __init__(self, name, identifier, port_manager): self._loop = asyncio.get_event_loop() - self._allocate_console() self._queue = asyncio.Queue() self._name = name self._id = identifier self._created = asyncio.Future() self._worker = asyncio.async(self._run()) self._port_manager = port_manager + self._config = Config.instance() log.info("{type} device {name} [id={id}] has been created".format( type=self.__class__.__name__, name=self._name, id=self._id)) - def _allocate_console(self): - - if not self._console: - # allocate a console port - try: - self._console = find_unused_port(self._console_start_port_range, - self._console_end_port_range, - self._console_host, - ignore_ports=self._allocated_console_ports) - except Exception as e: - raise VMError(e) - - if self._console in self._allocated_console_ports: - raise VMError("Console port {} is already used by another VM".format(self._console)) - self._allocated_console_ports.append(self._console) - - - @property - def console(self): - """ - Returns the TCP console port. - - :returns: console port (integer) - """ - - return self._console - - @console.setter - def console(self, console): - """ - Sets the TCP console port. - - :param console: console port (integer) - """ - - if console in self._allocated_console_ports: - raise VMError("Console port {} is already used by another VM".format(console)) - - self._allocated_console_ports.remove(self._console) - self._console = console - self._allocated_console_ports.append(self._console) - log.info("{type} {name} [id={id}]: console port set to {port}".format(type=self.__class__.__name__, - name=self._name, - id=self._id, - port=console)) @property def id(self): """ diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index e50ab9cb..22294edf 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -454,6 +454,8 @@ class VirtualBoxVM(object): """ log.info("VirtualBox VM {name} [id={id}] has set the VM name to {vmname}".format(name=self._name, id=self._id, vmname=vmname)) + if self._linked_clone: + self._modify_vm('--name "{}"'.format(vmname)) self._vmname = vmname @property diff --git a/gns3server/modules/vpcs/nios/nio_tap.py b/gns3server/modules/vpcs/nios/nio_tap.py index 4c3ed6b2..39923a01 100644 --- a/gns3server/modules/vpcs/nios/nio_tap.py +++ b/gns3server/modules/vpcs/nios/nio_tap.py @@ -44,3 +44,6 @@ class NIO_TAP(object): def __str__(self): return "NIO TAP" + + def __json__(self): + return {"type": "nio_tap", "tap_device": self._tap_device} diff --git a/gns3server/modules/vpcs/nios/nio_udp.py b/gns3server/modules/vpcs/nios/nio_udp.py index 0527f675..cca313e7 100644 --- a/gns3server/modules/vpcs/nios/nio_udp.py +++ b/gns3server/modules/vpcs/nios/nio_udp.py @@ -70,3 +70,6 @@ class NIO_UDP(object): def __str__(self): return "NIO UDP" + + def __json__(self): + return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost} diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 61c896a0..d1531eb2 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -27,12 +27,15 @@ import signal import shutil import re import asyncio +import socket +import shutil from pkg_resources import parse_version from .vpcs_error import VPCSError from .adapters.ethernet_adapter import EthernetAdapter from .nios.nio_udp import NIO_UDP from .nios.nio_tap import NIO_TAP +from ..attic import has_privileged_access from ..base_vm import BaseVM @@ -50,17 +53,17 @@ class VPCSDevice(BaseVM): :param console: TCP console port """ def __init__(self, name, vpcs_id, port_manager, - path = None, - working_dir = None, - console=None): + working_dir = None, console = None): + super().__init__(name, vpcs_id, port_manager) #self._path = path #self._working_dir = working_dir # TODO: Hardcodded for testing - self._path = "/usr/local/bin/vpcs" - self._working_dir = "/tmp" + self._path = self._config.get_section_config("VPCS").get("path", "vpcs") + self._working_dir = "/tmp" self._console = console + self._command = [] self._process = None self._vpcs_stdout_file = "" @@ -87,12 +90,15 @@ class VPCSDevice(BaseVM): raise VPCSError(e) self._check_requirements() - super().__init__(name, vpcs_id, port_manager) def _check_requirements(self): """ Check if VPCS is available with the correct version """ + if self._path == "vpcs": + self._path = shutil.which("vpcs") + + if not self._path: raise VPCSError("No path to a VPCS executable has been set") @@ -104,6 +110,16 @@ class VPCSDevice(BaseVM): self._check_vpcs_version() + @property + def console(self): + """ + Returns the console port of this VPCS device. + + :returns: console port + """ + + return self._console + @property def name(self): """ @@ -168,8 +184,8 @@ class VPCSDevice(BaseVM): """ if not self.is_running(): - # if not self._ethernet_adapter.get_nio(0): - # raise VPCSError("This VPCS instance must be connected in order to start") + if not self._ethernet_adapter.get_nio(0): + raise VPCSError("This VPCS instance must be connected in order to start") self._command = self._build_command() try: @@ -237,7 +253,7 @@ class VPCSDevice(BaseVM): return True return False - def port_add_nio_binding(self, port_id, nio): + def port_add_nio_binding(self, port_id, nio_settings): """ Adds a port NIO binding. @@ -249,11 +265,33 @@ class VPCSDevice(BaseVM): raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, port_id=port_id)) + nio = None + if nio_settings["type"] == "nio_udp": + lport = nio_settings["lport"] + rhost = nio_settings["rhost"] + rport = nio_settings["rport"] + try: + #TODO: handle IPv6 + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.connect((rhost, rport)) + except OSError as e: + raise VPCSError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e)) + nio = NIO_UDP(lport, rhost, rport) + elif nio_settings["type"] == "nio_tap": + tap_device = nio_settings["tap_device"] + if not has_privileged_access(self._path): + raise VPCSError("{} has no privileged access to {}.".format(self._path, tap_device)) + nio = NIO_TAP(tap_device) + if not nio: + raise VPCSError("Requested NIO does not exist or is not supported: {}".format(nio_settings["type"])) + + self._ethernet_adapter.add_nio(port_id, nio) log.info("VPCS {name} [id={id}]: {nio} added to port {port_id}".format(name=self._name, id=self._id, nio=nio, port_id=port_id)) + return nio def port_remove_nio_binding(self, port_id): """ @@ -317,6 +355,8 @@ class VPCSDevice(BaseVM): nio = self._ethernet_adapter.get_nio(0) if nio: + print(nio) + print(isinstance(nio, NIO_UDP)) if isinstance(nio, NIO_UDP): # UDP tunnel command.extend(["-s", str(nio.lport)]) # source UDP port diff --git a/gns3server/schemas/vpcs.py b/gns3server/schemas/vpcs.py index 2947efba..a2819471 100644 --- a/gns3server/schemas/vpcs.py +++ b/gns3server/schemas/vpcs.py @@ -42,7 +42,7 @@ VPCS_CREATE_SCHEMA = { } -VPCS_ADD_NIO_SCHEMA = { +VPCS_NIO_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to add a NIO for a VPCS instance", "type": "object", @@ -75,36 +75,6 @@ VPCS_ADD_NIO_SCHEMA = { "required": ["type", "lport", "rhost", "rport"], "additionalProperties": False }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, "TAP": { "description": "TAP Network Input/Output", "properties": { @@ -120,89 +90,14 @@ VPCS_ADD_NIO_SCHEMA = { "required": ["type", "tap_device"], "additionalProperties": False }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False - }, }, - "properties": { - "id": { - "description": "VPCS device instance ID", - "type": "integer" - }, - "port_id": { - "description": "Unique port identifier for the VPCS instance", - "type": "integer" - }, - "port": { - "description": "Port number", - "type": "integer", - "minimum": 0, - "maximum": 0 - }, - "nio": { - "type": "object", - "description": "Network Input/Output", - "oneOf": [ - {"$ref": "#/definitions/UDP"}, - {"$ref": "#/definitions/Ethernet"}, - {"$ref": "#/definitions/LinuxEthernet"}, - {"$ref": "#/definitions/TAP"}, - {"$ref": "#/definitions/UNIX"}, - {"$ref": "#/definitions/VDE"}, - {"$ref": "#/definitions/NULL"}, - ] - }, - }, - "additionalProperties": False, - "required": ["id", "port_id", "port", "nio"] + "oneOf": [ + {"$ref": "#/definitions/UDP"}, + {"$ref": "#/definitions/TAP"}, + ], + "additionalProperties": True, + "required": ['type'] } VPCS_OBJECT_SCHEMA = { @@ -230,41 +125,3 @@ VPCS_OBJECT_SCHEMA = { "required": ["name", "id", "console"] } -VBOX_CREATE_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to create a new VirtualBox VM instance", - "type": "object", - "properties": { - "name": { - "description": "VirtualBox VM instance name", - "type": "string", - "minLength": 1, - }, - "vbox_id": { - "description": "VirtualBox VM instance ID", - "type": "integer" - }, - }, - "additionalProperties": False, - "required": ["name"], -} - - -VBOX_OBJECT_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "VirtualBox instance", - "type": "object", - "properties": { - "name": { - "description": "VirtualBox VM name", - "type": "string", - "minLength": 1, - }, - "vbox_id": { - "description": "VirtualBox VM instance ID", - "type": "integer" - }, - }, - "additionalProperties": False, - "required": ["name", "vbox_id"] -} diff --git a/gns3server/version.py b/gns3server/version.py index 049db3b8..ed60edd9 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -15,5 +15,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# __version__ is a human-readable version number. + +# __version_info__ is a four-tuple for programmatic comparison. The first +# three numbers are the components of the version number. The fourth +# is zero for an official release, positive for a development branch, +# or negative for a release candidate or beta (after the base version +# number has been incremented) + __version__ = "1.3.dev1" __version_info__ = (1, 3, 0, 0) + diff --git a/gns3server/web/response.py b/gns3server/web/response.py index 325455f4..d829e725 100644 --- a/gns3server/web/response.py +++ b/gns3server/web/response.py @@ -18,7 +18,9 @@ import json import jsonschema import aiohttp.web +import logging +log = logging.getLogger(__name__) class Response(aiohttp.web.Response): @@ -29,13 +31,22 @@ class Response(aiohttp.web.Response): headers['X-Route'] = self._route super().__init__(headers=headers, **kwargs) + """ + Set the response content type to application/json and serialize + the content. + + :param anwser The response as a Python object + """ def json(self, answer): """Pass a Python object and return a JSON as answer""" self.content_type = "application/json" + if hasattr(answer, '__json__'): + answer = answer.__json__() if self._output_schema is not None: try: jsonschema.validate(answer, self._output_schema) except jsonschema.ValidationError as e: + log.error("Invalid output schema") raise aiohttp.web.HTTPBadRequest(text="{}".format(e)) self.body = json.dumps(answer, indent=4, sort_keys=True).encode('utf-8') diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 1687cde9..086a6b50 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -19,6 +19,9 @@ import json import jsonschema import asyncio import aiohttp +import logging + +log = logging.getLogger(__name__) from ..modules.vm_error import VMError from .response import Response @@ -37,6 +40,7 @@ def parse_request(request, input_schema): try: jsonschema.validate(request.json, input_schema) except jsonschema.ValidationError as e: + log.error("Invalid input schema") raise aiohttp.web.HTTPBadRequest(text="Request is not {} '{}' in schema: {}".format( e.validator, e.validator_value, @@ -66,6 +70,10 @@ class Route(object): def put(cls, path, *args, **kw): return cls._route('PUT', path, *args, **kw) + @classmethod + def delete(cls, path, *args, **kw): + return cls._route('DELETE', path, *args, **kw) + @classmethod def _route(cls, method, path, *args, **kw): # This block is executed only the first time diff --git a/tests/api/base.py b/tests/api/base.py index a95d36c2..b8f5f395 100644 --- a/tests/api/base.py +++ b/tests/api/base.py @@ -44,6 +44,9 @@ class Query: def get(self, path, **kwargs): return self._fetch("GET", path, **kwargs) + def delete(self, path, **kwargs): + return self._fetch("DELETE", path, **kwargs) + def _get_url(self, path): return "http://{}:{}{}".format(self._host, self._port, path) diff --git a/tests/api/test_version.py b/tests/api/test_version.py index 35d0f5b8..8fb46174 100644 --- a/tests/api/test_version.py +++ b/tests/api/test_version.py @@ -21,6 +21,7 @@ It's also used for unittest the HTTP implementation. """ from tests.utils import asyncio_patch +from tests.api.base import server, loop, port_manager from gns3server.version import __version__ diff --git a/tests/api/test_vpcs.py b/tests/api/test_vpcs.py index 6abb5c3c..365f86a7 100644 --- a/tests/api/test_vpcs.py +++ b/tests/api/test_vpcs.py @@ -15,7 +15,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from tests.api.base import server, loop from tests.utils import asyncio_patch +from gns3server import modules @asyncio_patch('gns3server.modules.VPCS.create_vm', return_value=84) @@ -27,17 +29,41 @@ def test_vpcs_create(server): assert response.json['id'] == 84 -def test_vpcs_nio_create(server): - response = server.post('/vpcs/42/nio', { - 'id': 42, - 'nio': { - 'type': 'nio_unix', - 'local_file': '/tmp/test', - 'remote_file': '/tmp/remote' +def test_vpcs_nio_create_udp(server): + vm = server.post('/vpcs', {'name': 'PC TEST 1'}) + response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), { + 'type': 'nio_udp', + 'lport': 4242, + 'rport': 4343, + 'rhost': '127.0.0.1' }, - 'port': 0, - 'port_id': 0}, example=True) assert response.status == 200 - assert response.route == '/vpcs/{id}/nio' - assert response.json['name'] == 'PC 2' + assert response.route == '/vpcs/{id}/ports/{port_id}/nio' + assert response.json['type'] == 'nio_udp' + +@patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=True) +def test_vpcs_nio_create_tap(mock, server): + vm = server.post('/vpcs', {'name': 'PC TEST 1'}) + response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), { + 'type': 'nio_tap', + 'tap_device': 'test', + }) + assert response.status == 200 + assert response.route == '/vpcs/{vpcs_id}/ports/{port_id}/nio' + assert response.json['type'] == 'nio_tap' + +def test_vpcs_delete_nio(server): + vm = server.post('/vpcs', {'name': 'PC TEST 1'}) + response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), { + 'type': 'nio_udp', + 'lport': 4242, + 'rport': 4343, + 'rhost': '127.0.0.1' + }, + ) + response = server.delete('/vpcs/{}/ports/0/nio'.format(vm.json["vpcs_id"]), example=True) + assert response.status == 200 + assert response.route == '/vpcs/{id}/ports/{port_id}/nio' + + diff --git a/tests/modules/vpcs/test_vpcs_device.py b/tests/modules/vpcs/test_vpcs_device.py index 7eeb8e82..4844e820 100644 --- a/tests/modules/vpcs/test_vpcs_device.py +++ b/tests/modules/vpcs/test_vpcs_device.py @@ -28,36 +28,64 @@ from gns3server.modules.vpcs.vpcs_error import VPCSError @patch("subprocess.check_output", return_value="Welcome to Virtual PC Simulator, version 0.6".encode("utf-8")) def test_vm(tmpdir, port_manager): - vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir), path="/bin/test") + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) assert vm.name == "test" assert vm.id == 42 @patch("subprocess.check_output", return_value="Welcome to Virtual PC Simulator, version 0.1".encode("utf-8")) def test_vm_invalid_vpcs_version(tmpdir, port_manager): with pytest.raises(VPCSError): - vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir), path="/bin/test") + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) assert vm.name == "test" assert vm.id == 42 +@patch("gns3server.config.Config.get_section_config", return_value = {"path": "/bin/test_fake"}) def test_vm_invalid_vpcs_path(tmpdir, port_manager): with pytest.raises(VPCSError): - vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir), path="/bin/test_fake") + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) assert vm.name == "test" assert vm.id == 42 def test_start(tmpdir, loop, port_manager): with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): - vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir), path="/bin/test") + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) + nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) + loop.run_until_complete(asyncio.async(vm.start())) assert vm.is_running() == True def test_stop(tmpdir, loop, port_manager): process = MagicMock() with asyncio_patch("asyncio.create_subprocess_exec", return_value=process): - vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir), path="/bin/test") + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) + nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) + loop.run_until_complete(asyncio.async(vm.start())) assert vm.is_running() == True loop.run_until_complete(asyncio.async(vm.stop())) assert vm.is_running() == False process.terminate.assert_called_with() +def test_add_nio_binding_udp(port_manager, tmpdir): + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) + nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) + assert nio.lport == 4242 + +def test_add_nio_binding_tap(port_manager, tmpdir): + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) + with patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=True): + nio = vm.port_add_nio_binding(0, {"type": "nio_tap", "tap_device": "test"}) + assert nio.tap_device == "test" + +def test_add_nio_binding_tap_no_privileged_access(port_manager, tmpdir): + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) + with patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=False): + with pytest.raises(VPCSError): + vm.port_add_nio_binding(0, {"type": "nio_tap", "tap_device": "test"}) + assert vm._ethernet_adapter.ports[0] is not None + +def test_port_remove_nio_binding(port_manager, tmpdir): + vm = VPCSDevice("test", 42, port_manager, working_dir=str(tmpdir)) + nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) + vm.port_remove_nio_binding(0) + assert vm._ethernet_adapter.ports[0] == None