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",