From f99d8253465115735bbbb1c236324f30ac41bec9 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 12 Feb 2015 22:28:12 +0100 Subject: [PATCH] Support network for IOU --- gns3server/handlers/iou_handler.py | 46 +++++++++++++++++++ gns3server/modules/base_manager.py | 8 ++-- gns3server/modules/iou/iou_vm.py | 74 +++++++++++++++++++++++++++++- gns3server/modules/nios/nio_tap.py | 2 +- gns3server/modules/nios/nio_udp.py | 2 +- gns3server/schemas/iou.py | 72 +++++++++++++++++++++++++++++ gns3server/web/route.py | 6 +-- tests/api/test_iou.py | 30 ++++++++++++ 8 files changed, 230 insertions(+), 10 deletions(-) diff --git a/gns3server/handlers/iou_handler.py b/gns3server/handlers/iou_handler.py index d73a0fb1..eddb85bf 100644 --- a/gns3server/handlers/iou_handler.py +++ b/gns3server/handlers/iou_handler.py @@ -19,6 +19,7 @@ from ..web.route import Route from ..schemas.iou import IOU_CREATE_SCHEMA from ..schemas.iou import IOU_UPDATE_SCHEMA from ..schemas.iou import IOU_OBJECT_SCHEMA +from ..schemas.iou import IOU_NIO_SCHEMA from ..modules.iou import IOU @@ -187,3 +188,48 @@ class IOUHandler: vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) yield from vm.reload() response.set_status(204) + + @Route.post( + r"/projects/{project_id}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the instance", + "port_number": "Port where the nio should be added" + }, + status_codes={ + 201: "NIO created", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Add a NIO to a IOU instance", + input=IOU_NIO_SCHEMA, + output=IOU_NIO_SCHEMA) + def create_nio(request, response): + + iou_manager = IOU.instance() + vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + nio = iou_manager.create_nio(vm.iouyap_path, request.json) + vm.slot_add_nio_binding(0, int(request.match_info["port_number"]), nio) + response.set_status(201) + response.json(nio) + + @classmethod + @Route.delete( + r"/projects/{project_id}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the instance", + "port_number": "Port from where the nio should be removed" + }, + status_codes={ + 204: "NIO deleted", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Remove a NIO from a IOU instance") + def delete_nio(request, response): + + iou_manager = IOU.instance() + vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + vm.slot_remove_nio_binding(0, int(request.match_info["port_number"])) + response.set_status(204) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index ec51f658..f38feacd 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -32,8 +32,8 @@ from ..config import Config from ..utils.asyncio import wait_run_in_executor from .project_manager import ProjectManager -from .nios.nio_udp import NIOUDP -from .nios.nio_tap import NIOTAP +from .nios.nio_udp import NIO_UDP +from .nios.nio_tap import NIO_TAP class BaseManager: @@ -274,11 +274,11 @@ class BaseManager: sock.connect((rhost, rport)) except OSError as e: raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e)) - nio = NIOUDP(lport, rhost, rport) + nio = NIO_UDP(lport, rhost, rport) elif nio_settings["type"] == "nio_tap": tap_device = nio_settings["tap_device"] if not self._has_privileged_access(executable): raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device)) - nio = NIOTAP(tap_device) + nio = NIO_TAP(tap_device) assert nio is not None return nio diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 6fb7db94..d2387c7d 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -35,6 +35,8 @@ from pkg_resources import parse_version from .iou_error import IOUError from ..adapters.ethernet_adapter import EthernetAdapter from ..adapters.serial_adapter import SerialAdapter +from ..nios.nio_udp import NIO_UDP +from ..nios.nio_tap import NIO_TAP from ..base_vm import BaseVM from .ioucon import start_ioucon @@ -86,7 +88,7 @@ class IOUVM(BaseVM): self._ethernet_adapters = [] self._serial_adapters = [] self.ethernet_adapters = 2 if ethernet_adapters is None else ethernet_adapters # one adapter = 4 interfaces - self.serial_adapters = 2 if serial_adapters is None else serial_adapters # one adapter = 4 interfaces + self.serial_adapters = 2 if serial_adapters is None else serial_adapters # one adapter = 4 interfaces self._use_default_iou_values = True # for RAM & NVRAM values self._nvram = 128 if nvram is None else nvram # Kilobytes self._initial_config = "" @@ -529,6 +531,17 @@ class IOUVM(BaseVM): return True return False + def is_iouyap_running(self): + """ + Checks if the IOUYAP process is running + + :returns: True or False + """ + + if self._iouyap_process: + return True + return False + def _create_netmap_config(self): """ Creates the NETMAP file. @@ -687,3 +700,62 @@ class IOUVM(BaseVM): adapters=len(self._serial_adapters))) self._slots = self._ethernet_adapters + self._serial_adapters + + def slot_add_nio_binding(self, slot_id, port_id, nio): + """ + Adds a slot NIO binding. + :param slot_id: slot ID + :param port_id: port ID + :param nio: NIO instance to add to the slot/port + """ + + try: + adapter = self._slots[slot_id] + except IndexError: + raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name, + slot_id=slot_id)) + + if not adapter.port_exists(port_id): + raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + adapter.add_nio(port_id, nio) + log.info("IOU {name} [id={id}]: {nio} added to {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio=nio, + slot_id=slot_id, + port_id=port_id)) + if self.is_iouyap_running(): + self._update_iouyap_config() + os.kill(self._iouyap_process.pid, signal.SIGHUP) + + def slot_remove_nio_binding(self, slot_id, port_id): + """ + Removes a slot NIO binding. + :param slot_id: slot ID + :param port_id: port ID + :returns: NIO instance + """ + + try: + adapter = self._slots[slot_id] + except IndexError: + raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name, + slot_id=slot_id)) + + if not adapter.port_exists(port_id): + raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + nio = adapter.get_nio(port_id) + adapter.remove_nio(port_id) + log.info("IOU {name} [id={id}]: {nio} removed from {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio=nio, + slot_id=slot_id, + port_id=port_id)) + if self.is_iouyap_running(): + self._update_iouyap_config() + os.kill(self._iouyap_process.pid, signal.SIGHUP) + + return nio diff --git a/gns3server/modules/nios/nio_tap.py b/gns3server/modules/nios/nio_tap.py index 9f51ce13..a63a72c3 100644 --- a/gns3server/modules/nios/nio_tap.py +++ b/gns3server/modules/nios/nio_tap.py @@ -22,7 +22,7 @@ Interface for TAP NIOs (UNIX based OSes only). from .nio import NIO -class NIOTAP(NIO): +class NIO_TAP(NIO): """ TAP NIO. diff --git a/gns3server/modules/nios/nio_udp.py b/gns3server/modules/nios/nio_udp.py index a87875fe..4af43cd6 100644 --- a/gns3server/modules/nios/nio_udp.py +++ b/gns3server/modules/nios/nio_udp.py @@ -22,7 +22,7 @@ Interface for UDP NIOs. from .nio import NIO -class NIOUDP(NIO): +class NIO_UDP(NIO): """ UDP NIO. diff --git a/gns3server/schemas/iou.py b/gns3server/schemas/iou.py index 387874cf..6d304a19 100644 --- a/gns3server/schemas/iou.py +++ b/gns3server/schemas/iou.py @@ -173,3 +173,75 @@ IOU_OBJECT_SCHEMA = { "additionalProperties": False, "required": ["name", "vm_id", "console", "project_id", "path", "serial_adapters", "ethernet_adapters", "ram", "nvram"] } + +IOU_NIO_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to add a NIO for a VPCS instance", + "type": "object", + "definitions": { + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } + }, + "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 + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + }, + "oneOf": [ + {"$ref": "#/definitions/UDP"}, + {"$ref": "#/definitions/Ethernet"}, + {"$ref": "#/definitions/TAP"}, + ], + "additionalProperties": True, + "required": ["type"] +} diff --git a/gns3server/web/route.py b/gns3server/web/route.py index d63701fc..4de33da0 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -28,6 +28,7 @@ log = logging.getLogger(__name__) from ..modules.vm_error import VMError from .response import Response + @asyncio.coroutine def parse_request(request, input_schema): """Parse body of request and raise HTTP errors in case of problems""" @@ -42,9 +43,8 @@ def parse_request(request, input_schema): jsonschema.validate(request.json, input_schema) except jsonschema.ValidationError as e: log.error("Invalid input query. JSON schema error: {}".format(e.message)) - raise aiohttp.web.HTTPBadRequest(text="Request is not {} '{}' in schema: {}".format( - e.validator, - e.validator_value, + raise aiohttp.web.HTTPBadRequest(text="Invalid JSON: {} in schema: {}".format( + e.message, json.dumps(e.schema))) return request diff --git a/tests/api/test_iou.py b/tests/api/test_iou.py index 6ae3f715..61837160 100644 --- a/tests/api/test_iou.py +++ b/tests/api/test_iou.py @@ -133,3 +133,33 @@ def test_iou_update(server, vm, tmpdir, free_console_port): assert response.json["serial_adapters"] == 0 assert response.json["ram"] == 512 assert response.json["nvram"] == 2048 + + +def test_iou_nio_create_udp(server, vm): + response = server.post("/projects/{project_id}/iou/vms/{vm_id}/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1"}, + example=True) + assert response.status == 201 + assert response.route == "/projects/{project_id}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio" + assert response.json["type"] == "nio_udp" + + +def test_iou_nio_create_tap(server, vm): + with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=True): + response = server.post("/projects/{project_id}/iou/vms/{vm_id}/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_tap", + "tap_device": "test"}) + assert response.status == 201 + assert response.route == "/projects/{project_id}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio" + assert response.json["type"] == "nio_tap" + + +def test_iou_delete_nio(server, vm): + server.post("/projects/{project_id}/iou/vms/{vm_id}/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1"}) + response = server.delete("/projects/{project_id}/iou/vms/{vm_id}/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True) + assert response.status == 204 + assert response.route == "/projects/{project_id}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio"