From f99e834c376c7a4eabf895a2a55d7e9111d5aa13 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 15 Feb 2015 12:18:12 -0700 Subject: [PATCH] Dynamips devices support (packet capture to complete). --- gns3server/handlers/__init__.py | 3 +- .../handlers/dynamips_device_handler.py | 234 ++++++++++++ ...mips_handler.py => dynamips_vm_handler.py} | 15 +- gns3server/modules/adapters/serial_adapter.py | 2 +- gns3server/modules/base_manager.py | 3 +- gns3server/modules/dynamips/__init__.py | 84 ++++- .../modules/dynamips/dynamips_device.py | 14 +- .../modules/dynamips/dynamips_hypervisor.py | 171 +-------- .../modules/dynamips/nodes/atm_switch.py | 72 +++- gns3server/modules/dynamips/nodes/bridge.py | 2 +- gns3server/modules/dynamips/nodes/device.py | 75 +++- .../modules/dynamips/nodes/ethernet_hub.py | 52 +-- .../modules/dynamips/nodes/ethernet_switch.py | 67 +++- .../dynamips/nodes/frame_relay_switch.py | 53 ++- gns3server/modules/dynamips/nodes/router.py | 8 +- gns3server/modules/project.py | 36 +- gns3server/schemas/dynamips_device.py | 341 ++++++++++++++++++ .../schemas/{dynamips.py => dynamips_vm.py} | 278 -------------- 18 files changed, 971 insertions(+), 539 deletions(-) create mode 100644 gns3server/handlers/dynamips_device_handler.py rename gns3server/handlers/{dynamips_handler.py => dynamips_vm_handler.py} (97%) create mode 100644 gns3server/schemas/dynamips_device.py rename gns3server/schemas/{dynamips.py => dynamips_vm.py} (74%) diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py index 78bb3ca6..03e6dcab 100644 --- a/gns3server/handlers/__init__.py +++ b/gns3server/handlers/__init__.py @@ -3,5 +3,6 @@ __all__ = ["version_handler", "vpcs_handler", "project_handler", "virtualbox_handler", - "dynamips_handler", + "dynamips_vm_handler", + "dynamips_device_handler", "iou_handler"] diff --git a/gns3server/handlers/dynamips_device_handler.py b/gns3server/handlers/dynamips_device_handler.py new file mode 100644 index 00000000..fb5f284e --- /dev/null +++ b/gns3server/handlers/dynamips_device_handler.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 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 . + + +import asyncio +from ..web.route import Route +from ..schemas.dynamips_device import DEVICE_CREATE_SCHEMA +from ..schemas.dynamips_device import DEVICE_UPDATE_SCHEMA +from ..schemas.dynamips_device import DEVICE_CAPTURE_SCHEMA +from ..schemas.dynamips_device import DEVICE_OBJECT_SCHEMA +from ..schemas.dynamips_device import DEVICE_NIO_SCHEMA +from ..modules.dynamips import Dynamips + + +class DynamipsDeviceHandler: + + """ + API entry points for Dynamips devices. + """ + + @classmethod + @Route.post( + r"/projects/{project_id}/dynamips/devices", + parameters={ + "project_id": "UUID for the project" + }, + status_codes={ + 201: "Instance created", + 400: "Invalid request", + 409: "Conflict" + }, + description="Create a new Dynamips device instance", + input=DEVICE_CREATE_SCHEMA, + output=DEVICE_OBJECT_SCHEMA) + def create(request, response): + + dynamips_manager = Dynamips.instance() + device = yield from dynamips_manager.create_device(request.json.pop("name"), + request.match_info["project_id"], + request.json.get("device_id"), + request.json.get("device_type")) + + response.set_status(201) + response.json(device) + + @classmethod + @Route.get( + r"/projects/{project_id}/dynamips/devices/{device_id}", + parameters={ + "project_id": "UUID for the project", + "device_id": "UUID for the instance" + }, + status_codes={ + 200: "Success", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Get a Dynamips device instance", + output=DEVICE_OBJECT_SCHEMA) + def show(request, response): + + dynamips_manager = Dynamips.instance() + device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"]) + response.json(device) + + @classmethod + @Route.put( + r"/projects/{project_id}/dynamips/devices/{device_id}", + parameters={ + "project_id": "UUID for the project", + "device_id": "UUID for the instance" + }, + status_codes={ + 200: "Instance updated", + 400: "Invalid request", + 404: "Instance doesn't exist", + 409: "Conflict" + }, + description="Update a Dynamips device instance", + input=DEVICE_UPDATE_SCHEMA, + output=DEVICE_OBJECT_SCHEMA) + def update(request, response): + + dynamips_manager = Dynamips.instance() + device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"]) + + if "name" in request.json: + yield from device.set_name(request.json["name"]) + + if "ports" in request.json: + for port in request.json["ports"]: + yield from device.set_port_settings(port["port"], port) + + response.json(device) + + @classmethod + @Route.delete( + r"/projects/{project_id}/dynamips/devices/{device_id}", + parameters={ + "project_id": "UUID for the project", + "device_id": "UUID for the instance" + }, + status_codes={ + 204: "Instance deleted", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Delete a Dynamips device instance") + def delete(request, response): + + dynamips_manager = Dynamips.instance() + yield from dynamips_manager.delete_device(request.match_info["device_id"]) + response.set_status(204) + + @Route.post( + r"/projects/{project_id}/dynamips/devices/{device_id}/ports/{port_number:\d+}/nio", + parameters={ + "project_id": "UUID for the project", + "device_id": "UUID for the instance", + "port_number": "Port on the device" + }, + status_codes={ + 201: "NIO created", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Add a NIO to a Dynamips device instance", + input=DEVICE_NIO_SCHEMA) + def create_nio(request, response): + + dynamips_manager = Dynamips.instance() + device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"]) + nio = yield from dynamips_manager.create_nio(device, request.json["nio"]) + port_number = int(request.match_info["port_number"]) + port_settings = request.json.get("port_settings") + mappings = request.json.get("mappings") + + if asyncio.iscoroutinefunction(device.add_nio): + yield from device.add_nio(nio, port_number) + else: + device.add_nio(nio, port_number) + + if port_settings: + yield from device.set_port_settings(port_number, port_settings) + elif mappings: + yield from device.set_mappings(mappings) + + response.set_status(201) + response.json(nio) + + @classmethod + @Route.delete( + r"/projects/{project_id}/dynamips/devices/{device_id}/ports/{port_number:\d+}/nio", + parameters={ + "project_id": "UUID for the project", + "device_id": "UUID for the instance", + "port_number": "Port on the device" + }, + status_codes={ + 204: "NIO deleted", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Remove a NIO from a Dynamips device instance") + def delete_nio(request, response): + + dynamips_manager = Dynamips.instance() + device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + yield from device.remove_nio(port_number) + response.set_status(204) + + # @Route.post( + # r"/projects/{project_id}/dynamips/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture", + # parameters={ + # "project_id": "UUID for the project", + # "vm_id": "UUID for the instance", + # "adapter_number": "Adapter to start a packet capture", + # "port_number": "Port on the adapter" + # }, + # status_codes={ + # 200: "Capture started", + # 400: "Invalid request", + # 404: "Instance doesn't exist" + # }, + # description="Start a packet capture on a Dynamips VM instance", + # input=VM_CAPTURE_SCHEMA) + # def start_capture(request, response): + # + # dynamips_manager = Dynamips.instance() + # vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + # 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"]) + # 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}) + # + # @Route.post( + # r"/projects/{project_id}/dynamips/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture", + # parameters={ + # "project_id": "UUID for the project", + # "vm_id": "UUID for the instance", + # "adapter_number": "Adapter to stop a packet capture", + # "port_number": "Port on the adapter (always 0)" + # }, + # status_codes={ + # 204: "Capture stopped", + # 400: "Invalid request", + # 404: "Instance doesn't exist" + # }, + # description="Stop a packet capture on a Dynamips VM instance") + # def start_capture(request, response): + # + # dynamips_manager = Dynamips.instance() + # vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + # slot_number = int(request.match_info["adapter_number"]) + # port_number = int(request.match_info["port_number"]) + # yield from vm.stop_capture(slot_number, port_number) + # response.set_status(204) + diff --git a/gns3server/handlers/dynamips_handler.py b/gns3server/handlers/dynamips_vm_handler.py similarity index 97% rename from gns3server/handlers/dynamips_handler.py rename to gns3server/handlers/dynamips_vm_handler.py index 3ff92b43..5981db43 100644 --- a/gns3server/handlers/dynamips_handler.py +++ b/gns3server/handlers/dynamips_vm_handler.py @@ -19,19 +19,19 @@ import os import asyncio from ..web.route import Route -from ..schemas.dynamips import VM_CREATE_SCHEMA -from ..schemas.dynamips import VM_UPDATE_SCHEMA -from ..schemas.dynamips import VM_NIO_SCHEMA -from ..schemas.dynamips import VM_CAPTURE_SCHEMA -from ..schemas.dynamips import VM_OBJECT_SCHEMA +from ..schemas.dynamips_vm import VM_CREATE_SCHEMA +from ..schemas.dynamips_vm import VM_UPDATE_SCHEMA +from ..schemas.dynamips_vm import VM_CAPTURE_SCHEMA +from ..schemas.dynamips_vm import VM_OBJECT_SCHEMA +from ..schemas.dynamips_vm import VM_NIO_SCHEMA from ..modules.dynamips import Dynamips from ..modules.project_manager import ProjectManager -class DynamipsHandler: +class DynamipsVMHandler: """ - API entry points for Dynamips. + API entry points for Dynamips VMs. """ @classmethod @@ -340,3 +340,4 @@ class DynamipsHandler: port_number = int(request.match_info["port_number"]) yield from vm.stop_capture(slot_number, port_number) response.set_status(204) + diff --git a/gns3server/modules/adapters/serial_adapter.py b/gns3server/modules/adapters/serial_adapter.py index 1ac39ce1..6a674c21 100644 --- a/gns3server/modules/adapters/serial_adapter.py +++ b/gns3server/modules/adapters/serial_adapter.py @@ -21,7 +21,7 @@ from .adapter import Adapter class SerialAdapter(Adapter): """ - Ethernet adapter. + Serial adapter. """ def __init__(self, interfaces=1): diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index a93cf1fd..fa633f8d 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -140,12 +140,11 @@ class BaseManager: raise aiohttp.web.HTTPNotFound(text="VM ID {} doesn't exist".format(vm_id)) vm = self._vms[vm_id] - if project_id: if vm.project.id != project.id: raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't belong to VM {}".format(project_id, vm.name)) - return self._vms[vm_id] + return vm @asyncio.coroutine def create_vm(self, name, project_id, vm_id, *args, **kwargs): diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 07af76e4..55fc0597 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -32,11 +32,14 @@ log = logging.getLogger(__name__) from gns3server.utils.interfaces import get_windows_interfaces from pkg_resources import parse_version +from uuid import UUID, uuid4 from ..base_manager import BaseManager +from ..project_manager import ProjectManager from .dynamips_error import DynamipsError from .hypervisor import Hypervisor from .nodes.router import Router from .dynamips_vm import DynamipsVM +from .dynamips_device import DynamipsDevice # NIOs from .nios.nio_udp import NIOUDP @@ -54,10 +57,12 @@ from .nios.nio_null import NIONull class Dynamips(BaseManager): _VM_CLASS = DynamipsVM + _DEVICE_CLASS = DynamipsDevice def __init__(self): super().__init__() + self._devices = {} self._dynamips_path = None # FIXME: temporary @@ -67,7 +72,19 @@ class Dynamips(BaseManager): def unload(self): yield from BaseManager.unload(self) - Router.reset() + + tasks = [] + for device in self._devices.values(): + tasks.append(asyncio.async(device.hypervisor.stop())) + + if tasks: + done, _ = yield from asyncio.wait(tasks) + for future in done: + try: + future.result() + except Exception as e: + log.error("Could not stop device hypervisor {}".format(e), exc_info=1) + continue # files = glob.glob(os.path.join(self._working_dir, "dynamips", "*.ghost")) # files += glob.glob(os.path.join(self._working_dir, "dynamips", "*_lock")) @@ -92,6 +109,71 @@ class Dynamips(BaseManager): return self._dynamips_path + @asyncio.coroutine + def create_device(self, name, project_id, device_id, device_type, *args, **kwargs): + """ + Create a new Dynamips device. + + :param name: Device name + :param project_id: Project identifier + :param vm_id: restore a VM identifier + """ + + project = ProjectManager.instance().get_project(project_id) + if not device_id: + device_id = str(uuid4()) + + device = self._DEVICE_CLASS(name, device_id, project, self, device_type, *args, **kwargs) + yield from device.create() + self._devices[device.id] = device + project.add_device(device) + return device + + def get_device(self, device_id, project_id=None): + """ + Returns a device instance. + + :param device_id: Device identifier + :param project_id: Project identifier + + :returns: Device instance + """ + + if project_id: + # check the project_id exists + project = ProjectManager.instance().get_project(project_id) + + try: + UUID(device_id, version=4) + except ValueError: + raise aiohttp.web.HTTPBadRequest(text="Device ID} is not a valid UUID".format(device_id)) + + if device_id not in self._devices: + raise aiohttp.web.HTTPNotFound(text="Device ID {} doesn't exist".format(device_id)) + + device = self._devices[device_id] + if project_id: + if device.project.id != project.id: + raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't belong to device {}".format(project_id, device.name)) + + return device + + @asyncio.coroutine + def delete_device(self, device_id): + """ + Delete a device + + :param device_id: Device identifier + + :returns: Device instance + """ + + device = self.get_device(device_id) + yield from device.delete() + device.project.remove_device(device) + del self._devices[device.id] + return device + def find_dynamips(self): # look for Dynamips diff --git a/gns3server/modules/dynamips/dynamips_device.py b/gns3server/modules/dynamips/dynamips_device.py index bf5012d1..e8398c94 100644 --- a/gns3server/modules/dynamips/dynamips_device.py +++ b/gns3server/modules/dynamips/dynamips_device.py @@ -25,10 +25,10 @@ from .nodes.frame_relay_switch import FrameRelaySwitch import logging log = logging.getLogger(__name__) -DEVICES = {'atmsw': ATMSwitch, - 'frsw': FrameRelaySwitch, - 'ethsw': EthernetSwitch, - 'ethhub': EthernetHub} +DEVICES = {'atm_switch': ATMSwitch, + 'frame_relay_switch': FrameRelaySwitch, + 'ethernet_switch': EthernetSwitch, + 'ethernet_hub': EthernetHub} class DynamipsDevice: @@ -37,9 +37,9 @@ class DynamipsDevice: Factory to create an Device object based on the type """ - def __new__(cls, name, vm_id, project, manager, device_type, **kwargs): + def __new__(cls, name, device_id, project, manager, device_type, **kwargs): - if type not in DEVICES: + if device_type not in DEVICES: raise DynamipsError("Unknown device type: {}".format(device_type)) - return DEVICES[device_type](name, vm_id, project, manager, **kwargs) + return DEVICES[device_type](name, device_id, project, manager, **kwargs) diff --git a/gns3server/modules/dynamips/dynamips_hypervisor.py b/gns3server/modules/dynamips/dynamips_hypervisor.py index 071019f5..e4db5623 100644 --- a/gns3server/modules/dynamips/dynamips_hypervisor.py +++ b/gns3server/modules/dynamips/dynamips_hypervisor.py @@ -128,9 +128,16 @@ class DynamipsHypervisor: Stops this hypervisor (will no longer run). """ - yield from self.send("hypervisor stop") - yield from self._writer.drain() - self._writer.close() + try: + # try to properly stop the hypervisor + yield from self.send("hypervisor stop") + except DynamipsError: + pass + try: + yield from self._writer.drain() + self._writer.close() + except OSError as e: + log.debug("Stopping hypervisor {}:{} {}".format(self._host, self._port, e)) self._reader = self._writer = None self._nio_udp_auto_instances.clear() @@ -186,137 +193,6 @@ class DynamipsHypervisor: return self._devices - # @devices.setter - # def devices(self, devices): - # """ - # Sets the list of devices managed by this hypervisor instance. - # This method is for internal use. - # - # :param devices: a list of device objects - # """ - # - # self._devices = devices - - # @property - # def console_start_port_range(self): - # """ - # Returns the console start port range value - # - # :returns: console start port range value (integer) - # """ - # - # return self._console_start_port_range - - # @console_start_port_range.setter - # def console_start_port_range(self, console_start_port_range): - # """ - # Set a new console start port range value - # - # :param console_start_port_range: console start port range value (integer) - # """ - # - # self._console_start_port_range = console_start_port_range - # - # @property - # def console_end_port_range(self): - # """ - # Returns the console end port range value - # - # :returns: console end port range value (integer) - # """ - # - # return self._console_end_port_range - # - # @console_end_port_range.setter - # def console_end_port_range(self, console_end_port_range): - # """ - # Set a new console end port range value - # - # :param console_end_port_range: console end port range value (integer) - # """ - # - # self._console_end_port_range = console_end_port_range - - # @property - # def aux_start_port_range(self): - # """ - # Returns the auxiliary console start port range value - # - # :returns: auxiliary console start port range value (integer) - # """ - # - # return self._aux_start_port_range - # - # @aux_start_port_range.setter - # def aux_start_port_range(self, aux_start_port_range): - # """ - # Sets a new auxiliary console start port range value - # - # :param aux_start_port_range: auxiliary console start port range value (integer) - # """ - # - # self._aux_start_port_range = aux_start_port_range - # - # @property - # def aux_end_port_range(self): - # """ - # Returns the auxiliary console end port range value - # - # :returns: auxiliary console end port range value (integer) - # """ - # - # return self._aux_end_port_range - # - # @aux_end_port_range.setter - # def aux_end_port_range(self, aux_end_port_range): - # """ - # Sets a new auxiliary console end port range value - # - # :param aux_end_port_range: auxiliary console end port range value (integer) - # """ - # - # self._aux_end_port_range = aux_end_port_range - - # @property - # def udp_start_port_range(self): - # """ - # Returns the UDP start port range value - # - # :returns: UDP start port range value (integer) - # """ - # - # return self._udp_start_port_range - # - # @udp_start_port_range.setter - # def udp_start_port_range(self, udp_start_port_range): - # """ - # Sets a new UDP start port range value - # - # :param udp_start_port_range: UDP start port range value (integer) - # """ - # - # self._udp_start_port_range = udp_start_port_range - # - # @property - # def udp_end_port_range(self): - # """ - # Returns the UDP end port range value - # - # :returns: UDP end port range value (integer) - # """ - # - # return self._udp_end_port_range - # - # @udp_end_port_range.setter - # def udp_end_port_range(self, udp_end_port_range): - # """ - # Sets an new UDP end port range value - # - # :param udp_end_port_range: UDP end port range value (integer) - # """ - # - # self._udp_end_port_range = udp_end_port_range - @property def ghosts(self): """ @@ -337,26 +213,6 @@ class DynamipsHypervisor: self._ghosts[image_name] = router - @property - def jitsharing_groups(self): - """ - Returns a list of the JIT sharing groups hosted by this hypervisor. - - :returns: JIT sharing groups dict (image_name -> group number) - """ - - return self._jitsharing_groups - - def add_jitsharing_group(self, image_name, group_number): - """ - Adds a JIT blocks sharing group name to the list of groups created on this hypervisor. - - :param image_name: name of the ghost image - :param group_number: group (integer) - """ - - self._jitsharing_groups[image_name] = group_number - @property def port(self): """ @@ -462,6 +318,9 @@ class DynamipsHypervisor: while True: try: chunk = yield from self._reader.read(1024) # match to Dynamips' buffer size + 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() except OSError as e: raise DynamipsError("Communication timed out with {host}:{port} :{error}, Dynamips process running: {run}" @@ -480,10 +339,6 @@ class DynamipsHypervisor: data.pop() buf = '' - if len(data) == 0: - raise DynamipsError("no data returned from {host}:{port}, Dynamips process running: {run}" - .format(host=self._host, port=self._port, run=self.is_running())) - # Does it contain an error code? if self.error_re.search(data[-1]): raise DynamipsError(data[-1][4:]) diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index b2186484..07094479 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -21,6 +21,7 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L593 """ import asyncio +import re from .device import Device from ..dynamips_error import DynamipsError @@ -34,17 +35,24 @@ class ATMSwitch(Device): Dynamips ATM switch. :param name: name for this switch - :param node_id: Node instance identifier + :param device_id: Device instance identifier :param project: Project instance :param manager: Parent VM Manager :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, node_id, project, manager, hypervisor=None): + def __init__(self, name, device_id, project, manager, hypervisor=None): - super().__init__(name, node_id, project, manager) + super().__init__(name, device_id, project, manager, hypervisor) self._nios = {} - self._mapping = {} + self._mappings = {} + + def __json__(self): + + return {"name": self.name, + "device_id": self.id, + "project_id": self.project.id, + "mappings": self._mappings} @asyncio.coroutine def create(self): @@ -82,14 +90,14 @@ class ATMSwitch(Device): return self._nios @property - def mapping(self): + def mappings(self): """ - Returns port mapping + Returns port mappings - :returns: mapping list + :returns: mappings list """ - return self._mapping + return self._mappings @asyncio.coroutine def delete(self): @@ -97,10 +105,14 @@ class ATMSwitch(Device): Deletes this ATM switch. """ - yield from self._hypervisor.send('atmsw delete "{}"'.format(self._name)) - log.info('ATM switch "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) + try: + yield from self._hypervisor.send('atmsw delete "{}"'.format(self._name)) + log.info('ATM switch "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) + except DynamipsError: + log.debug("Could not properly delete ATM switch {}".format(self._name)) self._hypervisor.devices.remove(self) - self._instances.remove(self._id) + if self._hypervisor and not self._hypervisor.devices: + yield from self.hypervisor.stop() def has_port(self, port): """ @@ -150,6 +162,36 @@ class ATMSwitch(Device): del self._nios[port_number] return nio + @asyncio.coroutine + def set_mappings(self, mappings): + """ + Applies VC mappings + + :param mappings: mappings (dict) + """ + + pvc_entry = re.compile(r"""^([0-9]*):([0-9]*):([0-9]*)$""") + for source, destination in mappings.items(): + match_source_pvc = pvc_entry.search(source) + match_destination_pvc = pvc_entry.search(destination) + if match_source_pvc and match_destination_pvc: + # add the virtual channels + source_port, source_vpi, source_vci = map(int, match_source_pvc.group(1, 2, 3)) + destination_port, destination_vpi, destination_vci = map(int, match_destination_pvc.group(1, 2, 3)) + if self.has_port(destination_port): + if (source_port, source_vpi, source_vci) not in self.mapping and \ + (destination_port, destination_vpi, destination_vci) not in self.mappings: + yield from self.map_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci) + yield from self.map_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci) + else: + # add the virtual paths + source_port, source_vpi = map(int, source.split(':')) + destination_port, destination_vpi = map(int, destination.split(':')) + if self.has_port(destination_port): + if (source_port, source_vpi) not in self.mappings and (destination_port, destination_vpi) not in self.mappings: + yield from self.map_vp(source_port, source_vpi, destination_port, destination_vpi) + yield from self.map_vp(destination_port, destination_vpi, source_port, source_vpi) + @asyncio.coroutine def map_vp(self, port1, vpi1, port2, vpi2): """ @@ -183,7 +225,7 @@ class ATMSwitch(Device): port2=port2, vpi2=vpi2)) - self._mapping[(port1, vpi1)] = (port2, vpi2) + self._mappings[(port1, vpi1)] = (port2, vpi2) @asyncio.coroutine def unmap_vp(self, port1, vpi1, port2, vpi2): @@ -218,7 +260,7 @@ class ATMSwitch(Device): port2=port2, vpi2=vpi2)) - del self._mapping[(port1, vpi1)] + del self._mappings[(port1, vpi1)] @asyncio.coroutine def map_pvc(self, port1, vpi1, vci1, port2, vpi2, vci2): @@ -259,7 +301,7 @@ class ATMSwitch(Device): vpi2=vpi2, vci2=vci2)) - self._mapping[(port1, vpi1, vci1)] = (port2, vpi2, vci2) + self._mappings[(port1, vpi1, vci1)] = (port2, vpi2, vci2) @asyncio.coroutine def unmap_pvc(self, port1, vpi1, vci1, port2, vpi2, vci2): @@ -299,7 +341,7 @@ class ATMSwitch(Device): port2=port2, vpi2=vpi2, vci2=vci2)) - del self._mapping[(port1, vpi1, vci1)] + del self._mappings[(port1, vpi1, vci1)] @asyncio.coroutine def start_capture(self, port_number, output_file, data_link_type="DLT_ATM_RFC1483"): diff --git a/gns3server/modules/dynamips/nodes/bridge.py b/gns3server/modules/dynamips/nodes/bridge.py index 0f00137c..f5c410d4 100644 --- a/gns3server/modules/dynamips/nodes/bridge.py +++ b/gns3server/modules/dynamips/nodes/bridge.py @@ -78,8 +78,8 @@ class Bridge(Device): Deletes this bridge. """ - yield from self._hypervisor.send('nio_bridge delete "{}"'.format(self._name)) self._hypervisor.devices.remove(self) + yield from self._hypervisor.send('nio_bridge delete "{}"'.format(self._name)) @asyncio.coroutine def add_nio(self, nio): diff --git a/gns3server/modules/dynamips/nodes/device.py b/gns3server/modules/dynamips/nodes/device.py index f5b7ee75..cbf755bd 100644 --- a/gns3server/modules/dynamips/nodes/device.py +++ b/gns3server/modules/dynamips/nodes/device.py @@ -16,23 +16,23 @@ # along with this program. If not, see . -from ...base_vm import BaseVM - - -class Device(BaseVM): +class Device: """ Base device for switches and hubs - :param name: name for this bridge - :param vm_id: Node instance identifier + :param name: name for this device + :param device_id: Device instance identifier :param project: Project instance - :param manager: Parent VM Manager + :param manager: Parent manager :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, node_id, project, manager, hypervisor=None): + def __init__(self, name, device_id, project, manager, hypervisor=None): - super().__init__(name, node_id, project, manager) + self._name = name + self._id = device_id + self._project = project + self._manager = manager self._hypervisor = hypervisor @property @@ -45,10 +45,59 @@ class Device(BaseVM): return self._hypervisor - def start(self): + @property + def project(self): + """ + Returns the device current project. + + :returns: Project instance. + """ + + return self._project + + @property + def name(self): + """ + Returns the name for this device. + + :returns: name + """ - pass # Dynamips switches and hubs are always on + return self._name + + @name.setter + def name(self, new_name): + """ + Sets the name of this VM. + + :param new_name: name + """ - def stop(self): + self._name = new_name + + @property + def id(self): + """ + Returns the ID for this device. + + :returns: device identifier (string) + """ + + return self._id + + @property + def manager(self): + """ + Returns the manager for this device. + + :returns: instance of manager + """ + + return self._manager + + def create(self): + """ + Creates the device. + """ - pass # Dynamips switches and hubs are always on + raise NotImplementedError diff --git a/gns3server/modules/dynamips/nodes/ethernet_hub.py b/gns3server/modules/dynamips/nodes/ethernet_hub.py index 5f7228c7..806b8d3f 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_hub.py +++ b/gns3server/modules/dynamips/nodes/ethernet_hub.py @@ -33,32 +33,38 @@ class EthernetHub(Bridge): Dynamips Ethernet hub (based on Bridge) :param name: name for this hub - :param node_id: Node instance identifier + :param device_id: Device instance identifier :param project: Project instance :param manager: Parent VM Manager :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, node_id, project, manager, hypervisor=None): + def __init__(self, name, device_id, project, manager, hypervisor=None): - Bridge.__init__(self, name, node_id, project, manager, hypervisor) - self._mapping = {} + Bridge.__init__(self, name, device_id, project, manager, hypervisor) + self._mappings = {} + + def __json__(self): + + return {"name": self.name, + "device_id": self.id, + "project_id": self.project.id} @asyncio.coroutine def create(self): - yield from Bridge.create() + yield from Bridge.create(self) log.info('Ethernet hub "{name}" [{id}] has been created'.format(name=self._name, id=self._id)) @property - def mapping(self): + def mappings(self): """ - Returns port mapping + Returns port mappings - :returns: mapping list + :returns: mappings list """ - return self._mapping + return self._mappings @asyncio.coroutine def delete(self): @@ -66,9 +72,13 @@ class EthernetHub(Bridge): Deletes this hub. """ - yield from Bridge.delete(self) - log.info('Ethernet hub "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) - self._instances.remove(self._id) + try: + yield from Bridge.delete(self) + log.info('Ethernet hub "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) + except DynamipsError: + log.debug("Could not properly delete Ethernet hub {}".format(self._name)) + if self._hypervisor and not self._hypervisor.devices: + yield from self.hypervisor.stop() @asyncio.coroutine def add_nio(self, nio, port_number): @@ -79,7 +89,7 @@ class EthernetHub(Bridge): :param port_number: port to allocate for the NIO """ - if port_number in self._mapping: + if port_number in self._mappings: raise DynamipsError("Port {} isn't free".format(port_number)) yield from Bridge.add_nio(self, nio) @@ -88,7 +98,7 @@ class EthernetHub(Bridge): id=self._id, nio=nio, port=port_number)) - self._mapping[port_number] = nio + self._mappings[port_number] = nio @asyncio.coroutine def remove_nio(self, port_number): @@ -100,10 +110,10 @@ class EthernetHub(Bridge): :returns: the NIO that was bound to the allocated port """ - if port_number not in self._mapping: + if port_number not in self._mappings: raise DynamipsError("Port {} is not allocated".format(port_number)) - nio = self._mapping[port_number] + nio = self._mappings[port_number] yield from Bridge.remove_nio(self, nio) log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name, @@ -111,7 +121,7 @@ class EthernetHub(Bridge): nio=nio, port=port_number)) - del self._mapping[port_number] + del self._mappings[port_number] return nio @asyncio.coroutine @@ -124,10 +134,10 @@ class EthernetHub(Bridge): :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB """ - if port_number not in self._mapping: + if port_number not in self._mappings: raise DynamipsError("Port {} is not allocated".format(port_number)) - nio = self._mapping[port_number] + nio = self._mappings[port_number] data_link_type = data_link_type.lower() if data_link_type.startswith("dlt_"): @@ -151,10 +161,10 @@ class EthernetHub(Bridge): :param port_number: allocated port number """ - if port_number not in self._mapping: + if port_number not in self._mappings: raise DynamipsError("Port {} is not allocated".format(port_number)) - nio = self._mapping[port_number] + nio = self._mappings[port_number] yield from nio.unbind_filter("both") log.info('Ethernet hub "{name}" [{id}]: stopping packet capture on {port}'.format(name=self._name, id=self._id, diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index 24fe3020..bf919068 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -35,17 +35,32 @@ class EthernetSwitch(Device): Dynamips Ethernet switch. :param name: name for this switch - :param node_id: Node instance identifier + :param device_id: Device instance identifier :param project: Project instance :param manager: Parent VM Manager :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, node_id, project, manager, hypervisor=None): + def __init__(self, name, device_id, project, manager, hypervisor=None): - super().__init__(name, node_id, project, manager, hypervisor) + super().__init__(name, device_id, project, manager, hypervisor) self._nios = {} - self._mapping = {} + self._mappings = {} + + def __json__(self): + + ethernet_switch_info = {"name": self.name, + "device_id": self.id, + "project_id": self.project.id} + + ports = [] + for port_number, settings in self._mappings.items(): + ports.append({"port": port_number, + "type": settings[0], + "vlan": settings[1]}) + + ethernet_switch_info["ports"] = ports + return ethernet_switch_info @asyncio.coroutine def create(self): @@ -82,14 +97,14 @@ class EthernetSwitch(Device): return self._nios @property - def mapping(self): + def mappings(self): """ - Returns port mapping + Returns port mappings - :returns: mapping list + :returns: mappings list """ - return self._mapping + return self._mappings @asyncio.coroutine def delete(self): @@ -97,10 +112,14 @@ class EthernetSwitch(Device): Deletes this Ethernet switch. """ - yield from self._hypervisor.send('ethsw delete "{}"'.format(self._name)) - log.info('Ethernet switch "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) + try: + yield from self._hypervisor.send('ethsw delete "{}"'.format(self._name)) + log.info('Ethernet switch "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) + except DynamipsError: + log.debug("Could not properly delete Ethernet switch {}".format(self._name)) self._hypervisor.devices.remove(self) - self._instances.remove(self._id) + if self._hypervisor and not self._hypervisor.devices: + yield from self.hypervisor.stop() @asyncio.coroutine def add_nio(self, nio, port_number): @@ -144,11 +163,27 @@ class EthernetSwitch(Device): port=port_number)) del self._nios[port_number] - if port_number in self._mapping: - del self._mapping[port_number] + if port_number in self._mappings: + del self._mappings[port_number] return nio + @asyncio.coroutine + def set_port_settings(self, port_number, settings): + """ + Applies port settings to a specific port. + + :param port_number: port number to set the settings + :param settings: port settings + """ + + if settings["type"] == "access": + yield from self.set_access_port(port_number, settings["vlan"]) + elif settings["type"] == "dot1q": + yield from self.set_dot1q_port(port_number, settings["vlan"]) + elif settings["type"] == "qinq": + yield from self.set_qinq_port(port_number, settings["vlan"]) + @asyncio.coroutine def set_access_port(self, port_number, vlan_id): """ @@ -170,7 +205,7 @@ class EthernetSwitch(Device): id=self._id, port=port_number, vlan_id=vlan_id)) - self._mapping[port_number] = ("access", vlan_id) + self._mappings[port_number] = ("access", vlan_id) @asyncio.coroutine def set_dot1q_port(self, port_number, native_vlan): @@ -194,7 +229,7 @@ class EthernetSwitch(Device): port=port_number, vlan_id=native_vlan)) - self._mapping[port_number] = ("dot1q", native_vlan) + self._mappings[port_number] = ("dot1q", native_vlan) @asyncio.coroutine def set_qinq_port(self, port_number, outer_vlan): @@ -217,7 +252,7 @@ class EthernetSwitch(Device): id=self._id, port=port_number, vlan_id=outer_vlan)) - self._mapping[port_number] = ("qinq", outer_vlan) + self._mappings[port_number] = ("qinq", outer_vlan) @asyncio.coroutine def get_mac_addr_table(self): diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index cdbeb997..301bd732 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -34,17 +34,24 @@ class FrameRelaySwitch(Device): Dynamips Frame Relay switch. :param name: name for this switch - :param node_id: Node instance identifier + :param device_id: Device instance identifier :param project: Project instance :param manager: Parent VM Manager :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, node_id, project, manager, hypervisor=None): + def __init__(self, name, device_id, project, manager, hypervisor=None): - super().__init__(name, node_id, project, manager, hypervisor) + super().__init__(name, device_id, project, manager, hypervisor) self._nios = {} - self._mapping = {} + self._mappings = {} + + def __json__(self): + + return {"name": self.name, + "device_id": self.id, + "project_id": self.project.id, + "mappings": self._mappings} @asyncio.coroutine def create(self): @@ -81,14 +88,14 @@ class FrameRelaySwitch(Device): return self._nios @property - def mapping(self): + def mappings(self): """ - Returns port mapping + Returns port mappings - :returns: mapping list + :returns: mappings list """ - return self._mapping + return self._mappings @asyncio.coroutine def delete(self): @@ -96,10 +103,14 @@ class FrameRelaySwitch(Device): Deletes this Frame Relay switch. """ - yield from self._hypervisor.send('frsw delete "{}"'.format(self._name)) - log.info('Frame Relay switch "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) + try: + yield from self._hypervisor.send('frsw delete "{}"'.format(self._name)) + log.info('Frame Relay switch "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id)) + except DynamipsError: + log.debug("Could not properly delete Frame relay switch {}".format(self._name)) self._hypervisor.devices.remove(self) - self._instances.remove(self._id) + if self._hypervisor and not self._hypervisor.devices: + yield from self.hypervisor.stop() def has_port(self, port): """ @@ -151,6 +162,22 @@ class FrameRelaySwitch(Device): del self._nios[port_number] return nio + @asyncio.coroutine + def set_mappings(self, mappings): + """ + Applies VC mappings + + :param mappings: mappings (dict) + """ + + for source, destination in mappings.items(): + source_port, source_dlci = map(int, source.split(':')) + destination_port, destination_dlci = map(int, destination.split(':')) + if self.has_port(destination_port): + if (source_port, source_dlci) not in self.mappings and (destination_port, destination_dlci) not in self.mappings: + yield from self.map_vc(source_port, source_dlci, destination_port, destination_dlci) + yield from self.map_vc(destination_port, destination_dlci, source_port, source_dlci) + @asyncio.coroutine def map_vc(self, port1, dlci1, port2, dlci2): """ @@ -184,7 +211,7 @@ class FrameRelaySwitch(Device): port2=port2, dlci2=dlci2)) - self._mapping[(port1, dlci1)] = (port2, dlci2) + self._mappings[(port1, dlci1)] = (port2, dlci2) @asyncio.coroutine def unmap_vc(self, port1, dlci1, port2, dlci2): @@ -218,7 +245,7 @@ class FrameRelaySwitch(Device): dlci1=dlci1, port2=port2, dlci2=dlci2)) - del self._mapping[(port1, dlci1)] + del self._mappings[(port1, dlci1)] @asyncio.coroutine def start_capture(self, port_number, output_file, data_link_type="DLT_FRELAY"): diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 34e28ac5..2b4b3d7d 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -294,8 +294,12 @@ class Router(BaseVM): # router is already closed return - if self._hypervisor: - yield from self.stop() + self._hypervisor.devices.remove(self) + if self._hypervisor and not self._hypervisor.devices: + try: + yield from self.stop() + except DynamipsError: + pass yield from self.hypervisor.stop() if self._console: diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 8fa0dc85..1bc9280c 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -59,6 +59,7 @@ class Project: self._vms = set() self._vms_to_destroy = set() + self._devices = set() self.temporary = temporary @@ -128,6 +129,11 @@ class Project: return self._vms + @property + def devices(self): + + return self._devices + @property def temporary(self): @@ -213,7 +219,7 @@ class Project: Add a VM to the project. In theory this should be called by the VM manager. - :param vm: A VM instance + :param vm: VM instance """ self._vms.add(vm) @@ -223,12 +229,33 @@ class Project: Remove a VM from the project. In theory this should be called by the VM manager. - :param vm: A VM instance + :param vm: VM instance """ if vm in self._vms: self._vms.remove(vm) + def add_device(self, device): + """ + Add a device to the project. + In theory this should be called by the VM manager. + + :param device: Device instance + """ + + self._devices.add(device) + + def remove_device(self, device): + """ + Remove a device from the project. + In theory this should be called by the VM manager. + + :param device: Device instance + """ + + if device in self._devices: + self._devices.remove(device) + @asyncio.coroutine def close(self): """Close the project, but keep information on disk""" @@ -250,13 +277,16 @@ class Project: else: vm.close() + for device in self._devices: + tasks.append(asyncio.async(device.delete())) + if tasks: done, _ = yield from asyncio.wait(tasks) for future in done: try: future.result() except Exception as e: - log.error("Could not close VM {}".format(e), exc_info=1) + log.error("Could not close VM or device {}".format(e), exc_info=1) if cleanup and os.path.exists(self.path): try: diff --git a/gns3server/schemas/dynamips_device.py b/gns3server/schemas/dynamips_device.py new file mode 100644 index 00000000..4e44afb4 --- /dev/null +++ b/gns3server/schemas/dynamips_device.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 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 . + + +DEVICE_CREATE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to create a new Dynamips device instance", + "type": "object", + "properties": { + "name": { + "description": "Dynamips device name", + "type": "string", + "minLength": 1, + }, + "device_id": { + "description": "Dynamips device instance identifier", + "type": "string", + "minLength": 36, + "maxLength": 36, + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "device_type": { + "description": "Dynamips device type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["name", "device_type"] +} + +DEVICE_UPDATE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Dynamips device instance", + "type": "object", + "definitions": { + "EthernetSwitchPort": { + "description": "Ethernet switch port", + "properties": { + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1 + }, + "type": { + "description": "Port type", + "enum": ["access", "dot1q", "qinq"], + }, + "vlan": {"description": "VLAN number", + "type": "integer", + "minimum": 1 + }, + }, + "required": ["port", "type", "vlan"], + "additionalProperties": False + }, + }, + "properties": { + "name": { + "description": "Dynamips device instance name", + "type": "string", + "minLength": 1, + }, + "ports": { + "type": "array", + "items": [ + {"type": "object", + "oneOf": [ + {"$ref": "#/definitions/EthernetSwitchPort"} + ]}, + ] + } + }, + "additionalProperties": False, +} + +DEVICE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Dynamips device instance", + "type": "object", + "definitions": { + "EthernetSwitchPort": { + "description": "Ethernet switch port", + "properties": { + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1 + }, + "type": { + "description": "Port type", + "enum": ["access", "dot1q", "qinq"], + }, + "vlan": {"description": "VLAN number", + "type": "integer", + "minimum": 1 + }, + }, + "required": ["port", "type", "vlan"], + "additionalProperties": False + }, + }, + "properties": { + "device_id": { + "description": "Dynamips router instance UUID", + "type": "string", + "minLength": 36, + "maxLength": 36, + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "project_id": { + "description": "Project UUID", + "type": "string", + "minLength": 36, + "maxLength": 36, + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "name": { + "description": "Dynamips device instance name", + "type": "string", + "minLength": 1, + }, + "ports": { + # only Ethernet switches have ports + "type": "array", + "items": [ + {"type": "object", + "oneOf": [ + {"$ref": "#/definitions/EthernetSwitchPort"} + ]}, + ] + }, + "mappings": { + # only Frame-Relay and ATM switches have mappings + "type": "object", + } + }, + "additionalProperties": False, + "required": ["name", "device_id", "project_id"] +} + +DEVICE_NIO_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to add a NIO for a Dynamips device 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 + }, + "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": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "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": { + "nio": { + "type": "object", + "oneOf": [ + {"$ref": "#/definitions/UDP"}, + {"$ref": "#/definitions/Ethernet"}, + {"$ref": "#/definitions/LinuxEthernet"}, + {"$ref": "#/definitions/TAP"}, + {"$ref": "#/definitions/UNIX"}, + {"$ref": "#/definitions/VDE"}, + {"$ref": "#/definitions/NULL"}, + ] + }, + "port_settings": { + # only Ethernet switches have port settings + "type": "object", + "description": "Ethernet switch", + "properties": { + "type": { + "description": "Port type", + "enum": ["access", "dot1q", "qinq"], + }, + "vlan": {"description": "VLAN number", + "type": "integer", + "minimum": 1 + }, + }, + "required": ["type", "vlan"], + "additionalProperties": False + }, + "mappings": { + # only Frame-Relay and ATM switches have mappings + "type": "object", + } + }, + "additionalProperties": False, + "required": ["nio"] +} + +DEVICE_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on an Device instance port", + "type": "object", + "properties": { + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + "data_link_type": { + "description": "PCAP data link type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["capture_file_name"] +} diff --git a/gns3server/schemas/dynamips.py b/gns3server/schemas/dynamips_vm.py similarity index 74% rename from gns3server/schemas/dynamips.py rename to gns3server/schemas/dynamips_vm.py index 2c4f755b..c30d2920 100644 --- a/gns3server/schemas/dynamips.py +++ b/gns3server/schemas/dynamips_vm.py @@ -880,281 +880,3 @@ VM_OBJECT_SCHEMA = { "additionalProperties": False, "required": ["name", "vm_id", "project_id", "dynamips_id"] } - -DEVICE_CREATE_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to create a new Dynamips device instance", - "type": "object", - "properties": { - "name": { - "description": "Dynamips device name", - "type": "string", - "minLength": 1, - }, - "vm_id": { - "description": "Dynamips device instance identifier", - "type": "string", - "minLength": 36, - "maxLength": 36, - "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" - }, - }, - "additionalProperties": False, - "required": ["name"] -} - -ETHHUB_UPDATE_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to update an Ethernet hub instance", - "type": "object", - "properties": { - "id": { - "description": "Ethernet hub instance ID", - "type": "integer" - }, - "name": { - "description": "Ethernet hub name", - "type": "string", - "minLength": 1, - }, - }, - "additionalProperties": False, - "required": ["id"] -} - -ETHHUB_NIO_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to add a NIO for an Ethernet hub 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 - }, - "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": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "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": "Ethernet hub instance ID", - "type": "integer" - }, - "port_id": { - "description": "Unique port identifier for the Ethernet hub instance", - "type": "integer" - }, - "port": { - "description": "Port number", - "type": "integer", - "minimum": 1, - }, - "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"] -} - -ETHHUB_DELETE_NIO_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to delete a NIO for an Ethernet hub instance", - "type": "object", - "properties": { - "id": { - "description": "Ethernet hub instance ID", - "type": "integer" - }, - "port": { - "description": "Port number", - "type": "integer", - "minimum": 1, - }, - }, - "additionalProperties": False, - "required": ["id", "port"] -} - -ETHHUB_START_CAPTURE_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to start a packet capture on an Ethernet hub instance port", - "type": "object", - "properties": { - "id": { - "description": "Ethernet hub instance ID", - "type": "integer" - }, - "port_id": { - "description": "Unique port identifier for the Ethernet hub instance", - "type": "integer" - }, - "port": { - "description": "Port number", - "type": "integer", - "minimum": 1, - }, - "capture_file_name": { - "description": "Capture file name", - "type": "string", - "minLength": 1, - }, - "data_link_type": { - "description": "PCAP data link type", - "type": "string", - "minLength": 1, - }, - }, - "additionalProperties": False, - "required": ["id", "port_id", "port", "capture_file_name"] -} - -ETHHUB_STOP_CAPTURE_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to stop a packet capture on an Ethernet hub instance port", - "type": "object", - "properties": { - "id": { - "description": "Ethernet hub instance ID", - "type": "integer" - }, - "port_id": { - "description": "Unique port identifier for the Ethernet hub instance", - "type": "integer" - }, - "port": { - "description": "Port number", - "type": "integer", - "minimum": 1, - }, - }, - "additionalProperties": False, - "required": ["id", "port_id", "port"] -}