diff --git a/docs/api/examples/post_vpcsvpcsidportsportidnio.txt b/docs/api/examples/post_vpcsvpcsidportsportidnio.txt new file mode 100644 index 00000000..cdcb7a4e --- /dev/null +++ b/docs/api/examples/post_vpcsvpcsidportsportidnio.txt @@ -0,0 +1,22 @@ +curl -i -xPOST 'http://localhost:8000/vpcs/{vpcs_id}/ports/{port_id}/nio' -d '{"local_file": "/tmp/test", "remote_file": "/tmp/remote", "type": "nio_unix"}' + +POST /vpcs/{vpcs_id}/ports/{port_id}/nio HTTP/1.1 +{ + "local_file": "/tmp/test", + "remote_file": "/tmp/remote", + "type": "nio_unix" +} + + +HTTP/1.1 404 +CONNECTION: close +CONTENT-LENGTH: 59 +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 + +{ + "message": "ID 42 doesn't exist", + "status": 404 +} diff --git a/gns3server/handlers/vpcs_handler.py b/gns3server/handlers/vpcs_handler.py index 5213c27e..6fc3495e 100644 --- a/gns3server/handlers/vpcs_handler.py +++ b/gns3server/handlers/vpcs_handler.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . + from ..web.route import Route from ..schemas.vpcs import VPCS_CREATE_SCHEMA from ..schemas.vpcs import VPCS_OBJECT_SCHEMA @@ -72,32 +73,9 @@ class VPCSHandler(object): vm = yield from vpcs_manager.stop_vm(int(request.match_info['vpcs_id'])) response.json({}) - @classmethod - @Route.get( - r"/vpcs/{vpcs_id}", - parameters={ - "vpcs_id": "Id of VPCS instance" - }, - description="Get information about a VPCS", - output=VPCS_OBJECT_SCHEMA) - def show(request, response): - response.json({'name': "PC 1", "vpcs_id": 42, "console": 4242}) - - @classmethod - @Route.put( - r"/vpcs/{vpcs_id}", - parameters={ - "vpcs_id": "Id of VPCS instance" - }, - description="Update VPCS information", - input=VPCS_OBJECT_SCHEMA, - output=VPCS_OBJECT_SCHEMA) - def update(request, response): - response.json({'name': "PC 1", "vpcs_id": 42, "console": 4242}) - @classmethod @Route.post( - r"/vpcs/{vpcs_id}/nio", + r"/vpcs/{vpcs_id}/ports/{port_id}/nio", parameters={ "vpcs_id": "Id of VPCS instance" }, @@ -108,5 +86,12 @@ class VPCSHandler(object): description="ADD NIO to a VPCS", input=VPCS_ADD_NIO_SCHEMA) def create_nio(request, response): - # TODO: raise 404 if VPCS not found + # TODO: raise 404 if VPCS not found GET VM can raise an exeption + # TODO: response with nio + vpcs_manager = VPCS.instance() + vm = vpcs_manager.get_vm(int(request.match_info['vpcs_id'])) + vm.port_add_nio_binding(int(request.match_info['port_id']), request.json) + response.json({'name': "PC 2", "vpcs_id": 42, "console": 4242}) + + diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 923576b5..ab07f427 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -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/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 61c896a0..5384a985 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -27,12 +27,14 @@ import signal import shutil import re import asyncio +import socket 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 @@ -168,8 +170,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 +239,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 +251,34 @@ 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"] + print(has_privileged_access) + 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 +342,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 7d205391..dc5ca6dd 100644 --- a/gns3server/schemas/vpcs.py +++ b/gns3server/schemas/vpcs.py @@ -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", "vpcs_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/tests/api/test_vpcs.py b/tests/api/test_vpcs.py index ccecd96f..eb65610f 100644 --- a/tests/api/test_vpcs.py +++ b/tests/api/test_vpcs.py @@ -15,7 +15,7 @@ # 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.api.base import server, loop, port_manager from tests.utils import asyncio_patch from gns3server import modules @@ -30,16 +30,12 @@ def test_vpcs_create(server): def test_vpcs_nio_create(server): - response = server.post('/vpcs/42/nio', { - 'id': 42, - 'nio': { + response = server.post('/vpcs/42/ports/0/nio', { 'type': 'nio_unix', 'local_file': '/tmp/test', 'remote_file': '/tmp/remote' }, - 'port': 0, - 'port_id': 0}, example=True) assert response.status == 200 - assert response.route == '/vpcs/{vpcs_id}/nio' + assert response.route == '/vpcs/{vpcs_id}/ports/{port_id}/nio' assert response.json['name'] == 'PC 2' diff --git a/tests/modules/vpcs/test_vpcs_device.py b/tests/modules/vpcs/test_vpcs_device.py index 7eeb8e82..b56ace3f 100644 --- a/tests/modules/vpcs/test_vpcs_device.py +++ b/tests/modules/vpcs/test_vpcs_device.py @@ -61,3 +61,19 @@ def test_stop(tmpdir, loop, port_manager): 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), path="/bin/test") + 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), path="/bin/test") + 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), path="/bin/test") + 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"})