diff --git a/gns3server/compute/builtin/builtin_node_factory.py b/gns3server/compute/builtin/builtin_node_factory.py index 9f9e2285..d7f234f4 100644 --- a/gns3server/compute/builtin/builtin_node_factory.py +++ b/gns3server/compute/builtin/builtin_node_factory.py @@ -18,11 +18,13 @@ from ..node_error import NodeError from .nodes.ethernet_hub import EthernetHub +from .nodes.ethernet_switch import EthernetSwitch import logging log = logging.getLogger(__name__) -BUILTIN_NODES = {'ethernet_hub': EthernetHub} +BUILTIN_NODES = {'ethernet_hub': EthernetHub, + 'ethernet_switch': EthernetSwitch} class BuiltinNodeFactory: diff --git a/gns3server/compute/builtin/nodes/ethernet_switch.py b/gns3server/compute/builtin/nodes/ethernet_switch.py new file mode 100644 index 00000000..16afbc10 --- /dev/null +++ b/gns3server/compute/builtin/nodes/ethernet_switch.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Hub object that uses the Bridge interface to create a hub with ports. +""" + +import asyncio + +from ...node_error import NodeError +from ...base_node import BaseNode + +import logging +log = logging.getLogger(__name__) + + +class EthernetSwitch(BaseNode): + + """ + Ethernet switch. + + :param name: name for this switch + :param node_id: Node identifier + :param project: Project instance + :param manager: Parent VM Manager + """ + + def __init__(self, name, node_id, project, manager): + + super().__init__(name, node_id, project, manager) + + def __json__(self): + + return {"name": self.name, + "node_id": self.id, + "project_id": self.project.id} + + @asyncio.coroutine + def create(self): + + super().create() + log.info('Ethernet switch "{name}" [{id}] has been created'.format(name=self._name, id=self._id)) + + @asyncio.coroutine + def delete(self): + """ + Deletes this switch. + """ + + raise NotImplementedError() + + @asyncio.coroutine + def add_nio(self, nio, port_number): + """ + Adds a NIO as new port on this switch. + + :param nio: NIO instance to add + :param port_number: port to allocate for the NIO + """ + + raise NotImplementedError() + + @asyncio.coroutine + def remove_nio(self, port_number): + """ + Removes the specified NIO as member of this switch. + + :param port_number: allocated port number + + :returns: the NIO that was bound to the allocated port + """ + + raise NotImplementedError() + + @asyncio.coroutine + def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"): + """ + Starts a packet capture. + + :param port_number: allocated port number + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + """ + + raise NotImplementedError() + + @asyncio.coroutine + def stop_capture(self, port_number): + """ + Stops a packet capture. + + :param port_number: allocated port number + """ + + raise NotImplementedError() diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index 70ac1d1f..27948622 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -266,32 +266,33 @@ class Dynamips(BaseManager): return self._dynamips_path @asyncio.coroutine - def create_device(self, name, project_id, device_id, device_type, *args, **kwargs): + def create_device(self, name, project_id, node_id, device_type, *args, **kwargs): """ Create a new Dynamips device. :param name: Device name :param project_id: Project identifier + :param node_id: Node identifier """ project = ProjectManager.instance().get_project(project_id) - if device_id and isinstance(device_id, int): + if node_id and isinstance(node_id, int): with (yield from BaseManager._convert_lock): - device_id = yield from self.convert_old_project(project, device_id, name) + node_id = yield from self.convert_old_project(project, node_id, name) - if not device_id: - device_id = str(uuid4()) + if not node_id: + node_id = str(uuid4()) - device = self._DEVICE_CLASS(name, device_id, project, self, device_type, *args, **kwargs) + device = self._DEVICE_CLASS(name, node_id, project, self, device_type, *args, **kwargs) yield from device.create() self._devices[device.id] = device return device - def get_device(self, device_id, project_id=None): + def get_device(self, node_id, project_id=None): """ Returns a device instance. - :param device_id: Device identifier + :param node_id: Node identifier :param project_id: Project identifier :returns: Device instance @@ -302,14 +303,14 @@ class Dynamips(BaseManager): project = ProjectManager.instance().get_project(project_id) try: - UUID(device_id, version=4) + UUID(node_id, version=4) except ValueError: - raise aiohttp.web.HTTPBadRequest(text="Device ID} is not a valid UUID".format(device_id)) + raise aiohttp.web.HTTPBadRequest(text="Node ID {} is not a valid UUID".format(node_id)) - if device_id not in self._devices: - raise aiohttp.web.HTTPNotFound(text="Device ID {} doesn't exist".format(device_id)) + if node_id not in self._devices: + raise aiohttp.web.HTTPNotFound(text="Node ID {} doesn't exist".format(node_id)) - device = self._devices[device_id] + device = self._devices[node_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)) @@ -317,16 +318,16 @@ class Dynamips(BaseManager): return device @asyncio.coroutine - def delete_device(self, device_id): + def delete_device(self, node_id): """ Delete a device - :param device_id: Device identifier + :param node_id: Node identifier :returns: Device instance """ - device = self.get_device(device_id) + device = self.get_device(node_id) yield from device.delete() del self._devices[device.id] return device diff --git a/gns3server/compute/dynamips/nodes/device.py b/gns3server/compute/dynamips/nodes/device.py index 373af5f2..9b0577ee 100644 --- a/gns3server/compute/dynamips/nodes/device.py +++ b/gns3server/compute/dynamips/nodes/device.py @@ -28,10 +28,10 @@ class Device: :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, device_id, project, manager, hypervisor=None): + def __init__(self, name, node_id, project, manager, hypervisor=None): self._name = name - self._id = device_id + self._id = node_id self._project = project self._manager = manager self._hypervisor = hypervisor @@ -96,6 +96,12 @@ class Device: return self._manager + def updated(self): + """ + Send a updated event + """ + self.project.emit("node.updated", self) + def create(self): """ Creates the device. diff --git a/gns3server/compute/dynamips/nodes/ethernet_hub.py b/gns3server/compute/dynamips/nodes/ethernet_hub.py index b76c9102..1e135926 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_hub.py +++ b/gns3server/compute/dynamips/nodes/ethernet_hub.py @@ -35,22 +35,53 @@ class EthernetHub(Bridge): Dynamips Ethernet hub (based on Bridge) :param name: name for this hub - :param device_id: Device instance identifier + :param node_id: Node instance identifier :param project: Project instance :param manager: Parent VM Manager + :param ports: initial hub ports :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, device_id, project, manager, hypervisor=None): + def __init__(self, name, node_id, project, manager, ports=None, hypervisor=None): - super().__init__(name, device_id, project, manager, hypervisor) + super().__init__(name, node_id, project, manager, hypervisor) self._mappings = {} + if ports is None: + # create 8 ports by default + self._ports = [] + for port_number in range(1, 9): + self._ports.append({"port_number": port_number, + "name": "Ethernet{}".format(port_number)}) + else: + self._ports = ports def __json__(self): return {"name": self.name, "node_id": self.id, - "project_id": self.project.id} + "project_id": self.project.id, + "ports": self._ports, + "status": "started"} + + @property + def ports(self): + """ + Ports on this hub + + :returns: ports info + """ + + return self._ports + + @ports.setter + def ports(self, ports): + """ + Set the ports on this hub + + :param ports: ports info + """ + + self._ports = ports @asyncio.coroutine def create(self): @@ -95,6 +126,9 @@ class EthernetHub(Bridge): :param port_number: port to allocate for the NIO """ + if port_number not in [port["port_number"] for port in self._ports]: + raise DynamipsError("Port {} doesn't exist".format(port_number)) + if port_number in self._mappings: raise DynamipsError("Port {} isn't free".format(port_number)) diff --git a/gns3server/compute/dynamips/nodes/ethernet_switch.py b/gns3server/compute/dynamips/nodes/ethernet_switch.py index b6e96977..07f0e522 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_switch.py +++ b/gns3server/compute/dynamips/nodes/ethernet_switch.py @@ -37,33 +37,66 @@ class EthernetSwitch(Device): Dynamips Ethernet switch. :param name: name for this switch - :param device_id: Device instance identifier + :param node_id: Node instance identifier :param project: Project instance :param manager: Parent VM Manager + :param ports: initial switch ports :param hypervisor: Dynamips hypervisor instance """ - def __init__(self, name, device_id, project, manager, hypervisor=None): + def __init__(self, name, node_id, project, manager, ports=None, hypervisor=None): - super().__init__(name, device_id, project, manager, hypervisor) + super().__init__(name, node_id, project, manager, hypervisor) self._nios = {} self._mappings = {} + if ports is None: + # create 8 ports by default + self._ports = [] + for port_number in range(1, 9): + self._ports.append({"port_number": port_number, + "name": "Ethernet{}".format(port_number), + "type": "access", + "vlan": 1}) + else: + self._ports = ports def __json__(self): ethernet_switch_info = {"name": self.name, - "device_id": self.id, - "project_id": self.project.id} + "node_id": self.id, + "project_id": self.project.id, + "ports": self._ports, + "status": "started"} + + # ports = [] + # for port_number, settings in self._mappings.items(): + # ports.append({"port": port_number, + # "type": settings[0], + # "vlan": settings[1], + # "ethertype": settings[2] if len(settings) > 2 else ""}) + # + # ethernet_switch_info["ports"] = ports + return ethernet_switch_info + + @property + def ports(self): + """ + Ports on this switch - ports = [] - for port_number, settings in self._mappings.items(): - ports.append({"port": port_number, - "type": settings[0], - "vlan": settings[1], - "ethertype": settings[2] if len(settings) > 2 else ""}) + :returns: ports info + """ - ethernet_switch_info["ports"] = ports - return ethernet_switch_info + return self._ports + + @ports.setter + def ports(self, ports): + """ + Set the ports on this switch + + :param ports: ports info + """ + + self._ports = ports @asyncio.coroutine def create(self): @@ -149,6 +182,10 @@ class EthernetSwitch(Device): nio=nio, port=port_number)) self._nios[port_number] = nio + for port_settings in self._ports: + if port_settings["port_number"] == port_number: + yield from self.set_port_settings(port_number, port_settings) + break @asyncio.coroutine def remove_nio(self, port_number): diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index b2b6bab5..3d37354b 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -105,7 +105,7 @@ class Node: self.parse_node_response(response.json) @asyncio.coroutine - def update(self, name=None, console=None, console_type="telnet", properties={}): + def update(self, name=None, console=None, console_type=None, properties={}): """ Update the node on the compute server diff --git a/gns3server/handlers/api/compute/__init__.py b/gns3server/handlers/api/compute/__init__.py index 64614935..ca5c61f8 100644 --- a/gns3server/handlers/api/compute/__init__.py +++ b/gns3server/handlers/api/compute/__init__.py @@ -30,9 +30,10 @@ from .config_handler import ConfigHandler from .version_handler import VersionHandler from .notification_handler import NotificationHandler from .ethernet_hub_handler import EthernetHubHandler +from .ethernet_switch_handler import EthernetSwitchHandler if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1": - # IOU runs only on Linux but testsuite work on UNIX platform + # IOU runs only on Linux but test suite works on UNIX platform if not sys.platform.startswith("win"): from .iou_handler import IOUHandler from .docker_handler import DockerHandler diff --git a/gns3server/handlers/api/compute/ethernet_hub_handler.py b/gns3server/handlers/api/compute/ethernet_hub_handler.py index 583c4152..b8de5fe1 100644 --- a/gns3server/handlers/api/compute/ethernet_hub_handler.py +++ b/gns3server/handlers/api/compute/ethernet_hub_handler.py @@ -56,7 +56,8 @@ class EthernetHubHandler: node = yield from dynamips_manager.create_device(request.json.pop("name"), request.match_info["project_id"], request.json.get("node_id"), - device_type="ethernet_hub") + device_type="ethernet_hub", + ports=request.json.get("ports")) # On Linux, use the generic hub # builtin_manager = Builtin.instance() @@ -109,6 +110,10 @@ class EthernetHubHandler: dynamips_manager = Dynamips.instance() node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + if "name" in request.json and node.name != request.json["name"]: + yield from node.set_name(request.json["name"]) + if "ports" in request.json: + node.ports = request.json["ports"] # builtin_manager = Builtin.instance() # node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) @@ -157,10 +162,7 @@ class EthernetHubHandler: node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) nio = yield from dynamips_manager.create_nio(node, request.json) port_number = int(request.match_info["port_number"]) - port_settings = request.json.get("port_settings") yield from node.add_nio(nio, port_number) - if port_settings: - yield from node.set_port_settings(port_number, port_settings) #builtin_manager = Builtin.instance() #node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) diff --git a/gns3server/handlers/api/compute/ethernet_switch_handler.py b/gns3server/handlers/api/compute/ethernet_switch_handler.py new file mode 100644 index 00000000..1435b042 --- /dev/null +++ b/gns3server/handlers/api/compute/ethernet_switch_handler.py @@ -0,0 +1,251 @@ +# -*- 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 os + +from gns3server.web.route import Route +from gns3server.schemas.node import NODE_CAPTURE_SCHEMA +from gns3server.schemas.nio import NIO_SCHEMA +from gns3server.compute.builtin import Builtin +from gns3server.compute.dynamips import Dynamips + +from gns3server.schemas.ethernet_switch import ( + ETHERNET_SWITCH_CREATE_SCHEMA, + ETHERNET_SWITCH_UPDATE_SCHEMA, + ETHERNET_SWITCH_OBJECT_SCHEMA +) + + +class EthernetSwitchHandler: + + """ + API entry points for Ethernet switch. + """ + + @Route.post( + r"/projects/{project_id}/ethernet_switch/nodes", + parameters={ + "project_id": "Project UUID" + }, + status_codes={ + 201: "Instance created", + 400: "Invalid request", + 409: "Conflict" + }, + description="Create a new Ethernet switch instance", + input=ETHERNET_SWITCH_CREATE_SCHEMA, + output=ETHERNET_SWITCH_OBJECT_SCHEMA) + def create(request, response): + + # Use the Dynamips Ethernet switch to simulate this node + dynamips_manager = Dynamips.instance() + node = yield from dynamips_manager.create_device(request.json.pop("name"), + request.match_info["project_id"], + request.json.get("node_id"), + device_type="ethernet_switch", + ports=request.json.get("ports")) + + # On Linux, use the generic switch + # builtin_manager = Builtin.instance() + # node = yield from builtin_manager.create_node(request.json.pop("name"), + # request.match_info["project_id"], + # request.json.get("node_id"), + # node_type="ethernet_switch") + + response.set_status(201) + response.json(node) + + @Route.get( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 200: "Success", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Get an Ethernet switch instance", + output=ETHERNET_SWITCH_OBJECT_SCHEMA) + def show(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + + # builtin_manager = Builtin.instance() + # node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + response.json(node) + + @Route.put( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 200: "Instance updated", + 400: "Invalid request", + 404: "Instance doesn't exist", + 409: "Conflict" + }, + description="Update an Ethernet switch instance", + input=ETHERNET_SWITCH_UPDATE_SCHEMA, + output=ETHERNET_SWITCH_OBJECT_SCHEMA) + def update(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + if "name" in request.json and node.name != request.json["name"]: + yield from node.set_name(request.json["name"]) + if "ports" in request.json: + node.ports = request.json["ports"] + + # builtin_manager = Builtin.instance() + # node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + + node.updated() + response.json(node) + + @Route.delete( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 204: "Instance deleted", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Delete an Ethernet switch instance") + def delete(request, response): + + dynamips_manager = Dynamips.instance() + yield from dynamips_manager.delete_device(request.match_info["node_id"]) + # builtin_manager = Builtin.instance() + # yield from builtin_manager.delete_node(request.match_info["node_id"]) + response.set_status(204) + + @Route.post( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter on the switch (always 0)", + "port_number": "Port on the switch" + }, + status_codes={ + 201: "NIO created", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Add a NIO to an Ethernet switch instance", + input=NIO_SCHEMA, + output=NIO_SCHEMA) + def create_nio(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + nio = yield from dynamips_manager.create_nio(node, request.json) + port_number = int(request.match_info["port_number"]) + #port_settings = request.json.get("port_settings") + yield from node.add_nio(nio, port_number) + #if port_settings: + # yield from node.set_port_settings(port_number, port_settings) + + #builtin_manager = Builtin.instance() + #node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + #nio = yield from builtin_manager.create_nio(node, request.json["nio"]) + + response.set_status(201) + response.json(nio) + + @Route.delete( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter on the switch (always 0)", + "port_number": "Port on the switch" + }, + status_codes={ + 204: "NIO deleted", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Remove a NIO from an Ethernet switch instance") + def delete_nio(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + #builtin_manager = Builtin.instance() + #node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + nio = yield from node.remove_nio(port_number) + yield from nio.delete() + response.set_status(204) + + @Route.post( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter on the switch (always 0)", + "port_number": "Port on the switch" + }, + status_codes={ + 200: "Capture started", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Start a packet capture on an Ethernet switch instance", + input=NODE_CAPTURE_SCHEMA) + def start_capture(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + #builtin_manager = Builtin.instance() + #node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + pcap_file_path = os.path.join(node.project.capture_working_directory(), request.json["capture_file_name"]) + yield from node.start_capture(port_number, pcap_file_path, request.json["data_link_type"]) + response.json({"pcap_file_path": pcap_file_path}) + + @Route.post( + r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID", + "adapter_number": "Adapter on the switch (always 0)", + "port_number": "Port on the switch" + }, + status_codes={ + 204: "Capture stopped", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Stop a packet capture on an Ethernet switch instance") + def stop_capture(request, response): + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + #builtin_manager = Builtin.instance() + #node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + port_number = int(request.match_info["port_number"]) + yield from node.stop_capture(port_number) + response.set_status(204) diff --git a/gns3server/schemas/ethernet_hub.py b/gns3server/schemas/ethernet_hub.py index b91725dd..5f0ac71b 100644 --- a/gns3server/schemas/ethernet_hub.py +++ b/gns3server/schemas/ethernet_hub.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2014 GNS3 Technologies Inc. +# Copyright (C) 2016 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 @@ -20,6 +20,25 @@ ETHERNET_HUB_CREATE_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to create a new Ethernet hub instance", "type": "object", + "definitions": { + "EthernetHubPort": { + "description": "Ethernet port", + "properties": { + "name": { + "description": "Port name", + "type": "string", + "minLength": 1, + }, + "port_number": { + "description": "Port number", + "type": "integer", + "minimum": 1 + }, + }, + "required": ["name", "port_number"], + "additionalProperties": False + }, + }, "properties": { "name": { "description": "Ethernet hub name", @@ -34,7 +53,16 @@ ETHERNET_HUB_CREATE_SCHEMA = { "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}$"} ] - } + }, + "ports": { + "type": "array", + "items": [ + {"type": "object", + "oneOf": [ + {"$ref": "#/definitions/EthernetHubPort"} + ]}, + ] + }, }, "additionalProperties": False, "required": ["name"] @@ -45,16 +73,21 @@ ETHERNET_HUB_OBJECT_SCHEMA = { "description": "Ethernet hub instance", "type": "object", "definitions": { - "EthernetPort": { + "EthernetHubPort": { "description": "Ethernet port", "properties": { - "port": { + "name": { + "description": "Port name", + "type": "string", + "minLength": 1, + }, + "port_number": { "description": "Port number", "type": "integer", "minimum": 1 }, }, - "required": ["port"], + "required": ["name", "port_number"], "additionalProperties": False }, }, @@ -83,13 +116,17 @@ ETHERNET_HUB_OBJECT_SCHEMA = { "items": [ {"type": "object", "oneOf": [ - {"$ref": "#/definitions/EthernetPort"} + {"$ref": "#/definitions/EthernetHubPort"} ]}, ] - } + }, + "status": { + "description": "Node status", + "enum": ["started", "stopped", "suspended"] + }, }, "additionalProperties": False, - "required": ["name", "node_id", "project_id"] + "required": ["name", "node_id", "project_id", "ports"] } ETHERNET_HUB_UPDATE_SCHEMA = ETHERNET_HUB_OBJECT_SCHEMA diff --git a/gns3server/schemas/ethernet_switch.py b/gns3server/schemas/ethernet_switch.py new file mode 100644 index 00000000..83554749 --- /dev/null +++ b/gns3server/schemas/ethernet_switch.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2016 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 . + + +ETHERNET_SWITCH_CREATE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to create a new Ethernet switch instance", + "type": "object", + "definitions": { + "EthernetSwitchPort": { + "description": "Ethernet port", + "properties": { + "name": { + "description": "Port name", + "type": "string", + "minLength": 1, + }, + "port_number": { + "description": "Port number", + "type": "integer", + "minimum": 1 + }, + "type": { + "description": "Port type", + "enum": ["access", "dot1q", "qinq"], + }, + "vlan": {"description": "VLAN number", + "type": "integer", + "minimum": 1 + }, + "ethertype": { + "description": "QinQ Ethertype", + "enum": ["", "0x8100", "0x88A8", "0x9100", "0x9200"], + }, + }, + "required": ["name", "port_number", "type"], + "additionalProperties": False + }, + }, + "properties": { + "name": { + "description": "Ethernet switch name", + "type": "string", + "minLength": 1, + }, + "node_id": { + "description": "Node UUID", + "oneOf": [ + {"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}$"} + ] + }, + "ports": { + "type": "array", + "items": [ + {"type": "object", + "oneOf": [ + {"$ref": "#/definitions/EthernetSwitchPort"} + ]}, + ] + }, + }, + "additionalProperties": False, + "required": ["name"] +} + +ETHERNET_SWITCH_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Ethernet switch instance", + "type": "object", + "definitions": { + "EthernetSwitchPort": { + "description": "Ethernet port", + "properties": { + "name": { + "description": "Port name", + "type": "string", + "minLength": 1, + }, + "port_number": { + "description": "Port number", + "type": "integer", + "minimum": 1 + }, + "type": { + "description": "Port type", + "enum": ["access", "dot1q", "qinq"], + }, + "vlan": {"description": "VLAN number", + "type": "integer", + "minimum": 1 + }, + "ethertype": { + "description": "QinQ Ethertype", + "enum": ["", "0x8100", "0x88A8", "0x9100", "0x9200"], + }, + }, + "required": ["name", "port_number", "type"], + "additionalProperties": False + }, + }, + "properties": { + "name": { + "description": "Ethernet switch name", + "type": "string", + "minLength": 1, + }, + "node_id": { + "description": "Node 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}$" + }, + "ports": { + "type": "array", + "items": [ + {"type": "object", + "oneOf": [ + {"$ref": "#/definitions/EthernetSwitchPort"} + ]}, + ] + }, + "status": { + "description": "Node status", + "enum": ["started", "stopped", "suspended"] + }, + }, + "additionalProperties": False, + "required": ["name", "node_id", "project_id"] +} + +ETHERNET_SWITCH_UPDATE_SCHEMA = ETHERNET_SWITCH_OBJECT_SCHEMA +del ETHERNET_SWITCH_UPDATE_SCHEMA["required"] diff --git a/gns3server/schemas/node.py b/gns3server/schemas/node.py index c2edce9e..e48a9c1a 100644 --- a/gns3server/schemas/node.py +++ b/gns3server/schemas/node.py @@ -88,7 +88,15 @@ NODE_OBJECT_SCHEMA = { }, "node_type": { "description": "Type of node", - "enum": ["ethernet_hub", "docker", "dynamips", "vpcs", "virtualbox", "vmware", "iou", "qemu"] + "enum": ["ethernet_hub", + "ethernet_switch", + "docker", + "dynamips", + "vpcs", + "virtualbox", + "vmware", + "iou", + "qemu"] }, "node_directory": { "description": "Working directory of the node. Read only",