From 7d055968e5faadd0cdfd38c8e89cc542d0bcac36 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 22 May 2016 19:24:14 -0600 Subject: [PATCH] First step towards the cloud node re-factoring. --- .../compute/builtin/builtin_node_factory.py | 4 +- gns3server/compute/builtin/nodes/cloud.py | 108 +++++++++ .../compute/builtin/nodes/ethernet_hub.py | 7 +- .../compute/builtin/nodes/ethernet_switch.py | 7 +- gns3server/handlers/api/compute/__init__.py | 1 + .../handlers/api/compute/cloud_handler.py | 215 ++++++++++++++++++ gns3server/schemas/cloud.py | 133 +++++++++++ gns3server/schemas/node.py | 3 +- 8 files changed, 468 insertions(+), 10 deletions(-) create mode 100644 gns3server/compute/builtin/nodes/cloud.py create mode 100644 gns3server/handlers/api/compute/cloud_handler.py create mode 100644 gns3server/schemas/cloud.py diff --git a/gns3server/compute/builtin/builtin_node_factory.py b/gns3server/compute/builtin/builtin_node_factory.py index d7f234f4..41d0a91c 100644 --- a/gns3server/compute/builtin/builtin_node_factory.py +++ b/gns3server/compute/builtin/builtin_node_factory.py @@ -17,13 +17,15 @@ from ..node_error import NodeError +from .nodes.cloud import Cloud 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 = {'cloud': Cloud, + 'ethernet_hub': EthernetHub, 'ethernet_switch': EthernetSwitch} diff --git a/gns3server/compute/builtin/nodes/cloud.py b/gns3server/compute/builtin/nodes/cloud.py new file mode 100644 index 00000000..ff6b1592 --- /dev/null +++ b/gns3server/compute/builtin/nodes/cloud.py @@ -0,0 +1,108 @@ +# -*- 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 . + +import asyncio + +from ...node_error import NodeError +from ...base_node import BaseNode + +import logging +log = logging.getLogger(__name__) + + +class Cloud(BaseNode): + + """ + Cloud. + + :param name: name for this cloud + :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): + """ + Creates this cloud. + """ + + super().create() + log.info('Cloud "{name}" [{id}] has been created'.format(name=self._name, id=self._id)) + + @asyncio.coroutine + def delete(self): + """ + Deletes this cloud. + """ + + raise NotImplementedError() + + @asyncio.coroutine + def add_nio(self, nio, port_number): + """ + Adds a NIO as new port on this cloud. + + :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 cloud. + + :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/builtin/nodes/ethernet_hub.py b/gns3server/compute/builtin/nodes/ethernet_hub.py index fbc5f5b1..5c167d52 100644 --- a/gns3server/compute/builtin/nodes/ethernet_hub.py +++ b/gns3server/compute/builtin/nodes/ethernet_hub.py @@ -15,10 +15,6 @@ # 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 @@ -51,6 +47,9 @@ class EthernetHub(BaseNode): @asyncio.coroutine def create(self): + """ + Creates this hub. + """ super().create() log.info('Ethernet hub "{name}" [{id}] has been created'.format(name=self._name, id=self._id)) diff --git a/gns3server/compute/builtin/nodes/ethernet_switch.py b/gns3server/compute/builtin/nodes/ethernet_switch.py index 16afbc10..bb7a2fac 100644 --- a/gns3server/compute/builtin/nodes/ethernet_switch.py +++ b/gns3server/compute/builtin/nodes/ethernet_switch.py @@ -15,10 +15,6 @@ # 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 @@ -51,6 +47,9 @@ class EthernetSwitch(BaseNode): @asyncio.coroutine def create(self): + """ + Creates this switch. + """ super().create() log.info('Ethernet switch "{name}" [{id}] has been created'.format(name=self._name, id=self._id)) diff --git a/gns3server/handlers/api/compute/__init__.py b/gns3server/handlers/api/compute/__init__.py index 965805b8..37ee8178 100644 --- a/gns3server/handlers/api/compute/__init__.py +++ b/gns3server/handlers/api/compute/__init__.py @@ -28,6 +28,7 @@ from .vmware_handler import VMwareHandler from .config_handler import ConfigHandler from .version_handler import VersionHandler from .notification_handler import NotificationHandler +from .cloud_handler import CloudHandler from .ethernet_hub_handler import EthernetHubHandler from .ethernet_switch_handler import EthernetSwitchHandler from .frame_relay_switch_handler import FrameRelaySwitchHandler diff --git a/gns3server/handlers/api/compute/cloud_handler.py b/gns3server/handlers/api/compute/cloud_handler.py new file mode 100644 index 00000000..552045d5 --- /dev/null +++ b/gns3server/handlers/api/compute/cloud_handler.py @@ -0,0 +1,215 @@ +# -*- 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 . + +from gns3server.web.route import Route +from gns3server.schemas.nio import NIO_SCHEMA +from gns3server.compute.builtin import Builtin + +from gns3server.schemas.cloud import ( + CLOUD_CREATE_SCHEMA, + CLOUD_OBJECT_SCHEMA, + CLOUD_UPDATE_SCHEMA +) + + +class CloudHandler: + + """ + API entry points for cloud + """ + + @Route.post( + r"/projects/{project_id}/cloud/nodes", + parameters={ + "project_id": "Project UUID" + }, + status_codes={ + 201: "Instance created", + 400: "Invalid request", + 409: "Conflict" + }, + description="Create a new cloud instance", + input=CLOUD_CREATE_SCHEMA, + output=CLOUD_OBJECT_SCHEMA) + def create(request, response): + + 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="cloud") + response.set_status(201) + response.json(node) + + @Route.get( + r"/projects/{project_id}/cloud/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 a cloud instance", + output=CLOUD_OBJECT_SCHEMA) + def show(request, response): + + 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}/cloud/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 a cloud instance", + input=CLOUD_UPDATE_SCHEMA, + output=CLOUD_OBJECT_SCHEMA) + def update(request, response): + + 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}/cloud/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 a cloud instance") + def delete(request, response): + + 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}/cloud/nodes/{node_id}/start", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 204: "Instance started", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Start a cloud") + def start(request, response): + + Builtin.instance().get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + response.set_status(204) + + @Route.post( + r"/projects/{project_id}/cloud/nodes/{node_id}/stop", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 204: "Instance stopped", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Stop a cloud") + def stop(request, response): + + Builtin.instance().get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + response.set_status(204) + + @Route.post( + r"/projects/{project_id}/cloud/nodes/{node_id}/suspend", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 204: "Instance suspended", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Suspend a cloud") + def suspend(request, response): + + Builtin.instance().get_device(request.match_info["node_id"], project_id=request.match_info["project_id"]) + response.set_status(204) + + @Route.post( + r"/projects/{project_id}/cloud/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 cloud (always 0)", + "port_number": "Port on the cloud" + }, + status_codes={ + 201: "NIO created", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Add a NIO to a cloud instance", + input=NIO_SCHEMA, + output=NIO_SCHEMA) + def create_nio(request, response): + + 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"]) + port_number = int(request.match_info["port_number"]) + yield from node.add_nio(nio, port_number) + response.set_status(201) + response.json(nio) + + @Route.delete( + r"/projects/{project_id}/cloud/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 cloud (always 0)", + "port_number": "Port on the cloud" + }, + status_codes={ + 204: "NIO deleted", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Remove a NIO from a cloud instance") + def delete_nio(request, response): + + 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) diff --git a/gns3server/schemas/cloud.py b/gns3server/schemas/cloud.py new file mode 100644 index 00000000..82199e8b --- /dev/null +++ b/gns3server/schemas/cloud.py @@ -0,0 +1,133 @@ +# -*- 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 . + + +CLOUD_CREATE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to create a new cloud 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": "Cloud 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/EthernetHubPort"} + # ]}, + # ] + # }, + }, + "additionalProperties": False, + "required": ["name"] +} + +CLOUD_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Cloud 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": "Cloud 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/EthernetHubPort"} + # ]}, + # ] + # }, + "status": { + "description": "Node status", + "enum": ["started", "stopped", "suspended"] + }, + }, + "additionalProperties": False, + "required": ["name", "node_id", "project_id"] #, "ports"] +} + +CLOUD_UPDATE_SCHEMA = CLOUD_OBJECT_SCHEMA +del CLOUD_UPDATE_SCHEMA["required"] diff --git a/gns3server/schemas/node.py b/gns3server/schemas/node.py index 24dc4708..bbb2c103 100644 --- a/gns3server/schemas/node.py +++ b/gns3server/schemas/node.py @@ -88,7 +88,8 @@ NODE_OBJECT_SCHEMA = { }, "node_type": { "description": "Type of node", - "enum": ["ethernet_hub", + "enum": ["cloud", + "ethernet_hub", "ethernet_switch", "frame_relay_switch", "atm_switch",